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)が取得できるので、そこから値を取り出す。