LibreOffice5(64)オプションページを持つ拡張機能の例を作る: その2

2017-07-28

旧ブログ

t f B! P L
1 オプションダイアログに載せるコントロールをランタイムで作成、と2 オプションダイアログの国際化にPythonのgettextモジュールを使う、についての解説です。

前の関連記事:LibreOffice5(63)オプションページを持つ拡張機能の例を作る: その1


1 オプションダイアログに載せるコントロールをランタイムで作成


ダイアログエディタを使わない理由は単に使いにくいからです(LibreOffice5(21)ダイアログエディタでGUIを作成しPythonで利用する:その1とかLibreOffice5(29)ダイアログエディタの言語ツールバーを利用する方法とか)。
def controlCreator(ctx, smgr, dialog):  # コントロールを追加する関数を返す。
 dialogmodel = dialog.getModel()  # ダイアログモデルを取得。
 def addControl(controltype, props, attrs=None):  # props: コントロールモデルのプロパティ、attr: コントロールの属性。
  if "PosSize" in props:  # コントロールモデルのプロパティの辞書にPosSizeキーがあるときはピクセル単位でコントロールに設定をする。
   control = smgr.createInstanceWithContext("com.sun.star.awt.UnoControl{}".format(controltype), ctx)  # コントロールを生成。
   control.setPosSize(props.pop("PositionX"), props.pop("PositionY"), props.pop("Width"), props.pop("Height"), props.pop("PosSize"))  # ピクセルで指定するために位置座標と大きさだけコントロールで設定。
   controlmodel = _createControlModel(controltype, props)  # コントロールモデルの生成。
   control.setModel(controlmodel)  # コントロールにコントロールモデルを設定。
   dialog.addControl(props["Name"], control)  # コントロールをコントロールコンテナに追加。
  else:  # Map AppFont (ma)のときはダイアログモデルにモデルを追加しないと正しくピクセルに変換されない。
   controlmodel = _createControlModel(controltype, props)  # コントロールモデルの生成。
   dialogmodel.insertByName(props["Name"], controlmodel)  # ダイアログモデルにモデルを追加するだけでコントロールも作成される。
  if attrs is not None:  # Dialogに追加したあとでないと各コントロールへの属性は追加できない。
   control = dialog.getControl(props["Name"])  # コントロールコンテナに追加された後のコントロールを取得。
   for key, val in attrs.items():  # メソッドの引数がないときはvalをNoneにしている。
    if val is None:
     getattr(control, key)()
    else:
     getattr(control, key)(val)
 def _createControlModel(controltype, props):  # コントロールモデルの生成。
  if not "Name" in props:
   props["Name"] = _generateSequentialName(controltype)  # Nameがpropsになければ通し番号名を生成。
  controlmodel = dialogmodel.createInstance("com.sun.star.awt.UnoControl{}Model".format(controltype))  # コントロールモデルを生成。UnoControlDialogElementサービスのためにUnoControlDialogModelからの作成が必要。
  if props:
   values = props.values()  # プロパティの値がタプルの時にsetProperties()でエラーが出るのでその対応が必要。
   if any(map(isinstance, values, [tuple]*len(values))):
    [setattr(controlmodel, key, val) for key, val in props.items()]  # valはリストでもタプルでも対応可能。XMultiPropertySetのsetPropertyValues()では[]anyと判断されてタプルも使えない。
   else:
    controlmodel.setPropertyValues(tuple(props.keys()), tuple(values))
  return controlmodel
 def _generateSequentialName(controltype):  # 連番名の作成。
  i = 1
  flg = True
  while flg:
   name = "{}{}".format(controltype, i)
   flg = dialog.getControl(name)  # 同名のコントロールの有無を判断。
   i += 1
  return name
 return addControl  # コントロールコンテナとそのコントロールコンテナにコントロールを追加する関数を返す。
この関数を使ってcallHandlerMethod()メソッドの第一引数で渡されるUnoControlDialogサービスのインスタンス(ダイアログ)にコントロールを追加しています。

このダイアログはつまりOptionsDialog.xcuファイルのprop oor:name="OptionsPage"で呼び出しているxdlファイルで定義しているものです。

この引数で返ってくるダイアログ自体をランタイムにしようと思いましたがそれはできませんでしたので、optionsdialog.xdlで最低限の定義をしています。

xdlファイルでの単位はmaです。

addControl = controlCreator(self.ctx, self.smgr, dialog)  # オプションダイアログdialogにコントロールを追加する関数を取得。
クロージャーにコンポーネントコンテクスト、サービスマネージャー、コントロールダイアログを渡して、渡したコントロールダイアログにコントロールを追加する関数を取得します。
addControl("Button", {"PositionX": 155, "PositionY": 39, "Width": 50, "Height": 15, "Label": _("~Default")}, {"setActionCommand": "width", "addActionListener": buttonlistener})  # ボタン。
返ってきた関数の第一引数にはコントロール名(サービス名のUnoControl~の~の部分)、第二引数にはコントロールモデルの属性をキー、その値を値とした辞書、第三引数にはコントロールのメソッドをキー、その引数を値の辞書、を渡します。

オブジェクトは参照渡しなので関数内でコントロールを追加した時点でダイアログは変更されています。

コントロールモデルの属性の辞書のキーにPosSize、値に定数com.sun.star.awt.PosSizeを渡すとコントロールモデルではなくコントロールのsetPosSize()メソッドでピクセル単位で位置と大きさを設定するようにしています。

そうでないときはma単位で位置と大きさを設定します。

そのためにコントロールモデルはダイアログモデルからインスタンス化しています(106行目)。

callHandlerMethod()メソッドの第二引数は、APIリファレンスではany型でEventObjectStructが返ってくると書いてあるように読めるのですが、デベロッパーガイドによると “ok”(OKボタンがクリックしたとき), “back”(元に戻すボタンがクリックしたとき)、“initialize”(オプションダイアログが呼ばれたとき)のいずれかの文字列しか返ってこないようです(Saving and Reading Data for the Options Page - Apache OpenOffice Wiki)。

callHandlerMethod()メソッドの第三引数には“external_event”という文字列が返ってきます。


これはダイアログエディタでコントロールを選択した時に出てくるイベントタブでマクロを割り当てるときにマクロではなく「コンポーネント」ボタンをクリックするとコンポーネントのメソッドを割り当てることができ、そのメソッド名に該当します(Assigning Component Methods to Control Events - Apache OpenOffice Wiki)。

ダイアログエディタではダイアログ自身にイベントタブがでてこずメソッド名を割り当てることができないため“external_event”というメソッド名が決め打ちで返ってくるようです。

Using Dialogs in Components - Apache OpenOffice Wikiを読むとIDLでコンポーネントにexternal_eventという名前のメソッドを作成するとそのメソッドを呼び出してくれるようです、、、手間なので確認はしていません。

とりあえずダイアログエディタで、コントロールのイベントにコンポーネントのメソッドを割り当てている場合は“external_event”以外のメソッド名が入っていることもありそうなので、メソッド名での振り分けは残しておくことにします。

2 オプションダイアログの国際化にPythonのgettextモジュールを使う


ライタイムダイアログを国際化する方法はLibreOfficeのAPIで見つけられなかったので、Pythonのgettextモジュールを使う方法を採用しました。

gettextモジュールはLibreOfficeのバンドルPythonでも利用可能です(LibreOffice5(34)LibreOffice5.2のバンドルPythonで利用可能なモジュール一覧参照)。

国際化のやり方はlinuxBean14.04(158)Pythonモジュールを多言語対応にするの通りです。

国際化したいのはcomponent.pyなのでこれの多言語化したい部分をデフォルト言語の英語で書いて_("英語")としました。

mkdir -p locale/ja/LC_MESSAGES

component.pyファイルのあるフォルダでこのコマンドを実行してpoファイルを入れるフォルダを作成します。

これで準備完了です。

豆ボタン→プログラミング→Poedit。

ファイル→New Catalog。


保存してあるカタログファイルのプロパティを確認するとなぜかメールアドレスの@が:に変化していますが、この項目はなくてもよいと思います。


Sources pathsタブでは「新しいパス」ボタンをクリックして先ほど作成したLC_MESSAGESフォルダからの相対パス、../../../を入力します。

これでOKボタンをクリックすると保存先を聞かれるので必ず先ほど作成したLC_MESSAGESフォルダに国際化するpyファイルと同じファイル名で保存します(今回はcomponent.po)。


翻訳するべき単語が自動的に読め込まれて表示されるので順番にその行を選択して、その翻訳を下のTranslationの欄に入力します。

ショートカットキーを表示させたくないラベルの先頭には~を付けておきます。

保存するとpoファイルが自動的にmoファイルに変換されます。

oxtファイルに含めるのはpoファイルではなくmoファイルの方ですが、Poeditで編集するのはpoファイルなのでpoファイルも保存しておきます。
def localization(ctx):  # 地域化。moファイルの切替。Linuxでは不要だがWindowsでは設定が必要。
 global _
 readConfig, dummy = createConfigAccessor(ctx, ctx.getServiceManager(), "/org.openoffice.Setup/L10N")  # LibreOfficeの言語設定へのパス。
 lang = readConfig("ooLocale"),  # 現在のロケールを取得。タプルかリストで渡す。
 lodir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "locale")  # このスクリプトと同じファルダにあるlocaleフォルダの絶対パスを取得。
 mo = os.path.splitext(os.path.basename(__file__))[0]  # moファイル名。拡張子なし。このスクリプトファイルと同名と想定。
 t = gettext.translation(mo, lodir, lang, fallback=True)  # Translations インスタンスを取得。moファイルがなくてもエラーはでない。
 _ = t.gettext  # _にt.gettext関数を代入。
国際化したcomponent.pyファイルを地域化するためにこの関数をcomponent.pyで実行しています。

これは/org.openoffice.Setup/L10NのooLocaleの値を読み込んでLibreOfficeの言語設定をgettextに渡しています。

Linuxではこのようなことをしなくても自動的に言語設定を読み込んでくれますが、WindowsではLibreOfficeの言語設定を取得しないとダメでした。

これで、LibreOfficeでツール→オプション→言語設定→言語、でユーザーインターフェイスを英語にすると英語のダイアログが表示されますし、日本語にすると日本語のダイアログが表示されます。

createConfigAccessor()はConfigurationUpdateAccessサービスのインスタンスをクロージャーにもちreadConfig()とwriteConfig()という二つの関数を返します。
(OptionsDialog/component.py at master · p--q/OptionsDialog · GitHub)

localization()ではwriteConfig()は使わないのでdummyという変数を当てています。


未使用でも警告がでない変数名はEclipseのWindow→Preferences、PyDev→Editor→Code AnalysisのUnusedタブ→Don't report unused variable if name starts withで確認できます。

これを機会にUnused parameterもIgnoreをWarningに変更しました。
やっぱりIgnoreにしておかないとIDLのメソッドなど止むを得ず受ける引数でも警告がでてきて面倒です。

参考にしたサイト


Saving and Reading Data for the Options Page - Apache OpenOffice Wiki
オプションダイアログでのcallHandlerMethod()メソッドの引数の解説。

Assigning Component Methods to Control Events - Apache OpenOffice Wiki
ダイアログエディタでコントロールにコンポーネントのメソッドを割り当てる方法。

Using Dialogs in Components - Apache OpenOffice Wiki
コントロールのイベントに割り当てたコンポーネントのメソッドのコンポーネント側の処理方法の解説。

次の解説:LibreOffice5(65)オプションページを持つ拡張機能の例を作る: その3

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ