Eclipseのプラグイン開発の自作DMDLエディターで色を設定できるようにする。
|
プラグインは、EclipseのPreference(設定)で設定を変更できるのが普通。
DMDLエディターの色も変更できるようにしてみる。
色を設定するには、FieldEditorPreferencePage用の色設定フィールドとしてColorFieldEditorというクラスがあるので、これを参考にする。
(一行には「ラベル」「色」「BOLD有無」の3種類(3列)を出力したいので、2列しか出力できないFieldEditorPreferencePageは今回は使えない)
修正内容を全部挙げているとけっこう量が多いので、抜粋する。
拡張ポイントのpreferencesとpreferencePagesを追加。
色の管理はColorManagerクラスで行うようになっていたので、PreferenceStoreから色コードを取得するように変更。
import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.StringConverter;
public Color getColor(String key) {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
String s = store.getString(key);
RGB rgb = StringConverter.asRGB(s);
return getColor(rgb); //これは今までにも在ったgetColor()
}
設定ストア(store)から値を取ってくるところは特に不思議なことは無い。
StringConverterクラスにRGBとStringの変換メソッドがあるので、それを使ってRGBクラスに変換する。
TextAttributeを作って色を指定していた箇所は、設定ストアから色を取ってくるColorManager#getColor()を使うように修正。
デフォルトの色を指定するクラスを新設。
public class DMDLEditorPreferenceInitializer extends AbstractPreferenceInitializer { @Override public void initializeDefaultPreferences() { IPreferenceStore store = Activator.getDefault().getPreferenceStore(); String red = StringConverter.asString(new RGB(192, 0, 0)); String green = StringConverter.asString(new RGB(0, 192, 0)); String blue = StringConverter.asString(new RGB(0, 0, 192)); store.setDefault(COLOR_COMMENT, green); store.setDefault(STYLE_COMMENT, SWT.NORMAL); store.setDefault(COLOR_ANNOTATION, red); store.setDefault(STYLE_ANNOTATION, SWT.BOLD); 〜 } }
設定ページを表示するクラスを新設。
public class DMDLEditorPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
public DMDLEditorPreferencePage() { super("DMDLEditorPreferencePage"); setPreferenceStore(Activator.getDefault().getPreferenceStore()); }
@Override protected Control createContents(Composite parent) { setTitle("color preference"); IPreferenceStore store = getPreferenceStore(); Composite composite = new Composite(parent, SWT.NONE); { GridLayout layout = new GridLayout(); layout.numColumns = 3; // 列数 composite.setLayout(layout); } { // ラベル(一番上の行) Label label1 = new Label(composite, SWT.CENTER); label1.setText("column"); Label label2 = new Label(composite, SWT.CENTER); label2.setText("color"); Label label3 = new Label(composite, SWT.LEFT); label3.setText("bold"); } { // コメントの色 Label label = new Label(composite, SWT.NONE); label.setText("comment"); RGB rgb = PreferenceConverter.getColor(store, COLOR_COMMENT); commentColor = new ColorSelector(composite); commentColor.setColorValue(rgb); int style = store.getInt(STYLE_COMMENT); commentCheck = new Button(composite, SWT.CHECK | SWT.CENTER); commentCheck.setSelection((style & SWT.BOLD) != 0); } 〜 return composite; }
PreferenceConverterを使うと、設定ストア(store)からRGBを直接取得できる。(内部ではStringConverterを使っている)
色の設定にはColorSelectorクラスを使用する。画面上はボタンになり、そのボタンを押すと色ダイアログが出て色を指定できる。(このダイアログの具体的な表現方法はOSによって異なる)
チェックボックスにはButtonクラスを使い、スタイルとしてSWT.CHECKを指定する。
// デフォルト値に戻す @Override protected void performDefaults() { IPreferenceStore store = getPreferenceStore(); { // コメントの色 RGB rgb = PreferenceConverter.getDefaultColor(store, COLOR_COMMENT); commentColor.setColorValue(rgb); int style = store.getDefaultInt(STYLE_COMMENT); commentCheck.setSelection((style & SWT.BOLD) != 0); } 〜 }
// 設定を反映させる @Override public boolean performOk() { IPreferenceStore store = getPreferenceStore(); { // コメントの色 RGB rgb = commentColor.getColorValue(); PreferenceConverter.setValue(store, COLOR_COMMENT, rgb); int style = SWT.NORMAL; if (commentCheck.getSelection()) { style |= SWT.BOLD; } store.setValue(STYLE_COMMENT, style); } 〜 return true; }
これで、メニューバーの「ウィンドウ(W)」→「設定(P)」で開く設定ダイアログにDMDLエディターのメニューが出て、色を設定できる。
しかし、色を設定して「OK」ボタンを押しても、すぐには色は反映されない。
現在開いているdmdlファイル(DMDLエディター)を一回閉じて再度開くと反映される。
開いているdmdlファイル(DMDLエディター)を開き直さなくてもOKボタン(あるいは適用(Apply)ボタン)を押したときに色を反映させたい。
これには、ScannerやDamagerRepairerに指定しているTextAttributeを作り直す必要がある。
設定ストアが変更されたときにそれを通知するリスナー機構があるので、それを利用して
通知があったらTextAttributeを作り直すようにしてやる。
その後再描画を行うと新しい色が反映される。
再描画を行うメソッドはDMDLEditor(の親クラスであるTextEditor)にあるので、設定ストアの変更通知を受け取るリスナーはDMDLEditorに実装する。
import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent;
public class DMDLEditor extends TextEditor implements IPropertyChangeListener {
/** * コンストラクター. */ public DMDLEditor() { setDocumentProvider(new DMDLDocumentProvider()); setSourceViewerConfiguration(new DMDLConfiguration(colorManager)); // Preferenceの更新リスナーを登録 IPreferenceStore store = Activator.getDefault().getPreferenceStore(); store.addPropertyChangeListener(this); }
@Override public void dispose() { // Preferenceの更新リスナーを削除 IPreferenceStore store = Activator.getDefault().getPreferenceStore(); store.removePropertyChangeListener(this); colorManager.dispose(); super.dispose(); }
// Preferenceの更新イベント処理
@Override
public void propertyChange(PropertyChangeEvent event) {
// 色をPreferenceから取得し直す
DMDLConfiguration config = (DMDLConfiguration) getSourceViewerConfiguration();
config.updatePreferences(); //←これは今回新設したメソッド
// エディターを再描画する
getSourceViewer().invalidateTextPresentation();
}
}
DMDLConfigurationで各種Scannerの初期設定を行っている。
そこで、設定ストアから設定を取り直す処理を追加する。
public class DMDLConfiguration extends SourceViewerConfiguration { 〜
private PresentationReconciler reconciler; private NonRuleBasedDamagerRepairer commentDamagerPepairer; @Override public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { reconciler = new PresentationReconciler(); { // デフォルトの色の設定 DMDefaultScanner scanner = getDefaultScanner(); DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner); reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); } { // コメントの色の設定 TextAttribute attr = new TextAttribute(colorManager.getCommentColor()); NonRuleBasedDamagerRepairer dr = new NonRuleBasedDamagerRepairer(attr); commentDamagerPepairer = dr; reconciler.setDamager(dr, DMDLPartitionScanner.DMDL_COMMENT); reconciler.setRepairer(dr, DMDLPartitionScanner.DMDL_COMMENT); } return reconciler; }
/** * Preference更新時に呼ばれる処理. * <p> * 色を設定し直す。 * </p> */ public void updatePreferences() { // デフォルトの色の設定 getDefaultScanner().initialize(); //←initialize()は今回新設したメソッド // コメントの色の設定 TextAttribute attr = new TextAttribute(colorManager.getCommentColor()); commentDamagerPepairer.setDefaultTextAttribute(attr); //←setDefaultTextAttribute()は今回新設したメソッド }
private DMDefaultScanner defaultScanner; protected DMDefaultScanner getDefaultScanner() { if (defaultScanner == null) { defaultScanner = new DMDefaultScanner(attrManager); defaultScanner.setDefaultReturnToken(new Token(new TextAttribute(colorManager.getDefaultColor()))); } return defaultScanner; } }
Scannerは最初に作ったインスタンスを使い回すようにする。
で、更新リスナーからupdatePreferences()が呼ばれる度にTextAttributeだけ新しくする。
これは@ITのエディタに設定内容を反映するを参考に作ったのだが、その方法ではDamagerPepairerオブジェクトを作り直している。
その方法を真似ると、テスト実行時には問題なかったのに、実際のEclipseにインストールして動かしてみるとNullPointerExceptionが発生した。
ログを出力して調べてみると、NonRuleBasedDamagerRepairer内のfDocumentフィールドがnullになっていた。ドキュメントの設定タイミングがテストと本番で違うのかなぁ。
そこで、今回はsetDefaultTextAttribute()を新設し、DamagerPepairerオブジェクトを作り直さずにTextAttributeだけ差し替えるようにした。
public class NonRuleBasedDamagerRepairer implements IPresentationDamager, IPresentationRepairer {
public void setDefaultTextAttribute(TextAttribute defaultTextAttribute) { fDefaultTextAttribute = defaultTextAttribute; }
public class DMDefaultScanner extends RuleBasedScanner {
/**
* コンストラクター.
*
* @param colorManager
*/
public DMDefaultScanner(ColorManager colorManager) {
this.colorManager = colorManager;
initialize();
}
public void initialize() { IToken modelToken = new Token(new TextAttribute(colorManager.getModelColor())); IToken annToken = new Token(new TextAttribute(colorManager.getAnnotationColor())); IToken descToken = new Token(new TextAttribute(colorManager.getDescriptionColor())); IRule[] rules = { new DMWordRule(MODEL_TYPE, modelToken), new DMAnnotationRule(annToken), new SingleLineRule("\"", "\"", descToken), }; setRules(rules); }
今までコンストラクター内で行っていたルールの初期化処理を、initialize()メソッドに外出しした。
そして、設定ストアの更新リスナーからinitialize()メソッドを呼び出して色を変更する。
OKボタン(あるいは適用(Apply)ボタン)を押したときにdmdlファイル(DMDLエディター)に色を反映させる方式だと、「変な色になっちゃった!」という時には既に反映済みなので、戻すのが面倒。
やはり色を選択したときに実際にエディター上の色を変えて、雰囲気を見たい。[2013-01-19]
色ダイアログを出すColorSelectorには設定変更時に通知してくれるリスナーがあるので、このイベントを捕捉してエディターを再描画すればよい。
エディターの再描画をどうやって行うかが問題だが、上で“設定ストアを更新すれば自動的に再描画される”ように作ったので、その機構を使う。
つまり、設定ストアに一時的な色を示すキーを追加し、ColorManagerで一時的な色を優先して使うようにする。
設定ストアに余計な情報(本来は永続化する必要のない情報)が保存されてしまうのが気に入らないが…。
また、OK・Cancel時に一時情報を削除する処理を入れる必要がある。
と思ったのだが、逆の発想で、色を変更する度に正式に設定ストアに保存してしまい、Cancel時に元に戻せば余分なキーは作らなくて済む。
従来の方式 | 一時設定方式 | 即時反映方式 | |
---|---|---|---|
初期処理 | 特に無し。 | 特に無し。 | Cancel用に「元の色」を保持しておく。 |
色変更時 | 無処理。 | 設定ストアの一時項目に色を書き込む。 (エディターに反映される) |
設定ストアの正式項目に色を書き込む。 (エディターに反映される) |
Apply時 | OKと同じ。 | OKと同じ。 | Applyは“現在値で確定”という意味なので、 Cancel用の「元の色」を現在値に変更する。 (設定ストアに対する処理は無い。既に反映されているから) |
OK時 | 設定ストアの正式項目に色を書き込む。 (エディターに反映される) |
設定ストアの正式項目に色を書き込む。 設定ストアの一時項目を削除する。 (エディターに反映される) |
無処理。(既に反映されているから) |
Cancel時 | 無処理。 | 設定ストアの一時項目を削除する。 (エディターに反映される) |
設定ストアの正式項目に「元の色」を書き込む。 (エディターに反映される) |
Defaultボタン | 設定ストアからデフォルト値を取得し 設定画面に反映させる。 |
設定ストアからデフォルト値を取得し 設定画面および一時項目に反映させる。 (エディターに反映される) |
設定ストアからデフォルト値を取得し 設定画面および正式項目に反映させる。 (エディターに反映される) |
ColorManager | 正式項目から色を取得する。 | 一時項目があればそれを使う。 無ければ正式項目から色を取得する。 |
正式項目から色を取得する。(従来方式と同じ) |
public class DMDLEditorColorPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
@Override protected Control createContents(Composite parent) { IPreferenceStore store = getPreferenceStore(); 〜 { // コメントの色 Label label = new Label(composite, SWT.NONE); label.setText("comment"); backupColor = PreferenceConverter.getColor(store, COLOR_COMMENT); commentColor = new ColorSelector(composite); commentColor.addListener(new ColorListener(store, COLOR_COMMENT)); commentColor.setColorValue(backupColor); backupStyle = store.getInt(STYLE_COMMENT); commentCheck = new Button(composite, SWT.CHECK | SWT.CENTER); commentCheck.addSelectionListener(new CheckListener(store, STYLE_COMMENT)); commentCheck.setSelection((backupStyle & SWT.BOLD) != 0); } 〜 return composite; }
// デフォルト値に戻す @Override protected void performDefaults() { IPreferenceStore store = getPreferenceStore(); { // コメントの色 RGB rgb = PreferenceConverter.getDefaultColor(store, COLOR_COMMENT); commentColor.setColorValue(rgb); PreferenceConverter.setValue(store, COLOR_COMMENT, rgb); int style = store.getDefaultInt(STYLE_COMMENT); commentCheck.setSelection((style & SWT.BOLD) != 0); store.setValue(STYLE_COMMENT, style); } 〜 }
// 設定を確定させる @Override public void performApply() { IPreferenceStore store = getPreferenceStore(); { // コメントの色 backupColor = commentColor.getColorValue(); int style = SWT.NORMAL; if (commentCheck.getSelection()) { style |= SWT.BOLD; } backupStyle = style; } 〜 }
// キャンセル(元に戻す) @Override protected boolean performCancel() { IPreferenceStore store = getPreferenceStore(); { // コメントの色 PreferenceConverter.setValue(store, COLOR_COMMENT, backupColor); store.setValue(STYLE_COMMENT, backupStyle); } 〜 return true; }
ColorSelectorの色が設定されたときに通知を受け取るリスナーを用意する。
→リスナー登録箇所
import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent;
class ColorListener implements IPropertyChangeListener { protected IPreferenceStore store; protected String colorKey; public ColorListener(IPreferenceStore store, String key) { this.store = store; this.colorKey = key; }
@Override public void propertyChange(PropertyChangeEvent event) { RGB rgb = (RGB) event.getNewValue(); PreferenceConverter.setValue(store, colorKey, rgb); } }
IPropertyChangeListenerでは、色が設定されるとpropertyChange()が呼ばれる。
event.getNewValue()
で新しい設定値が取得できる。(戻り型はObjectなので、RGBにキャストする)
DMDLエディターのPreferenceでは、文字をBOLDにするかどうかをチェックボックス(Buttonクラス)で設定できるようにしている。
Buttonにはいくつかのリスナーが登録できるが、今回は(チェックボックスとして使われている場合に呼ばれる)SelectionListenerを使う。
→リスナー登録箇所
import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener;
class CheckListener implements SelectionListener { protected IPreferenceStore store; protected String styleKey; public CheckListener(IPreferenceStore store, String key) { this.store = store; this.styleKey = key; }
@Override public void widgetSelected(SelectionEvent e) { Button check = (Button) e.widget; int style = SWT.NORMAL; if (check.getSelection()) { style |= SWT.BOLD; } store.setValue(styleKey, style); }
@Override public void widgetDefaultSelected(SelectionEvent e) { } }
チェックが付けられたり外されたりするとSelectionListener#widgetSelected()が呼ばれるのだが、(IPropertyChangeListenerと違って)SelectionEventには“チェックの値”は入っていない。
SelectionEvent#widget(ちなみに、これはフィールド。ゲッターメソッドは用意されていない)でチェックボックス(Button)が取得できるので、そこから値を取り出す。