Calc(27)ドキュメント内のすべてのシートでクリックを補足する

2017-10-20

旧ブログ

t f B! P L
マクロの起動はシートをダブルクリックして行うことに決めました。Calcドキュメントすべてのシートでダブルクリックしたときに駆動できるようにするためにマウスハンドラを使うことにしました。

前の関連記事:Calc(26)シートイベントにマクロを登録してダブルクリックで起動する


リスナーとハンドラの違い


似たようなことをしているに、名前が違うのはなぜだろうと思ってリスナーとハンドラの違いについて調べました。

リスナーについてはlinuxBean14.04(172)Observerパターン:Subjectを実装しない方法LibreOffice5(52)Javaの例:ConfigExamplesをPythonにする その5ででてきたオブザーバーパターンで学習しました。

私はオブサーバーパターンはオブザーブされる方をパブリッシャ(発行者)(サブジェクト)、オブザーブする方をサブスクライバ(購読者)(リスナー)と命名する方法の方が理解しやすいです。

今回マウスハンドラを扱いますが、マウスリスナーとの違いがよくわからないのでリスナーとハンドラの定義を探してみました。


この定義が私には受け入れやすかったです。

リスナーはイベントにサブスクライブ(購読する)するオブジェクトで、パブリッシャ(発行者)(サブジェクト)、つまりイベントは複数のサブスクライバ(リスナー)を受け付けることができます。

ハンドラはイベントを処理するオブジェクトで、イベントと1対1になっています。

Blogger:ページ番号付ページナビ(9)Paginavi2_Bloggerモジュールの導入ではJavaScriptのハンドラについて、イベントバブリングを学習しました。

イベントに対して傍観者であるリスナーと違って、イベントに対して責任者であるハンドラには次のハンドラに受け取ったイベントを渡すかどうかということを考える必要があります。

ということでリスナーのメソッドには戻り値がないのに対して、ハンドラのメソッドにはブーリアンの戻り値があります。

UNO APIに関しては簡単にはこの戻り値がブーリアンか、そうでないかで判断できそうですね。

EnhancedMouseClickHandlerでマウスのクリックを捕捉してコードを実行させる


GUIでシートイベントにマクロを割り当てる方法はシートごとに設定が必要なので、Calcドキュメントすべてのシートでダブルクリックしたときに駆動するという私の目的には合いませんので、他の方法を考えました。

OOobbs/88 - ...?

OOoBasic/Generic/InputInterception - ...?

これらを読んで、マウスハンドラを使ってドキュメントに対するマウスクリックを捕捉すれば、ドキュメントのシートすべてに対するダブルクリックに対応できることがわかりました。

マウスクリックはGUIに関することなので、シートに対するマウスクリックはLibreOfficeのFCMパラダイムではC(コントローラ)で操作することになります(LibreOffice(32)デベロッパーガイド4:コンポーネントフレームワーク)。

Calc(19)Calcコントローラのサービスとインターフェイス一覧をlistenerで検索するとXEnhancedMouseClickBroadcasterインターフェイスのメソッドで、CalcドキュメントにXEnhancedMouseClickHandlerインターフェイスをもつオブジェクトを割り当てられることがわかりました。

XEnhancedMouseClickHandlerインターフェイスのメソッドの引数はEnhancedMouseEvent Structが渡されます。

ドキュメントのコントローラにはXMouseClickHandlerインターフェイスのオブジェクトでも割り当てできますが、このインターフェイスのメソッドの引数はMouseEvent Structが渡されます。

EnhancedMouseEvent StructはMouseEvent StructにTargetアトリビュートが追加されたものです。
(2018.1.31追記。ただしXMouseClickHandlerのMouseEvent StructのSourceにはコンポーネントウィンドウが入っているのに対して、XEnhancedMouseClickHandlerのEnhancedMouseEvent StructのSourceはNoneでした。)

セルをクリックした場合はそのセルがTargetに入っていました。

今回はマウスハンドラとしてEnhancedMouseClickHandlerを使うことにします。

ドキュメントを開いた時にマクロを起動する


EnhancedMouseClickHandlerをコントローラに割り当てるために、ドキュメントを開いた時にそれをするマクロを実行させることにします。

まずドキュメントを開いた時にマクロを起動する方法について調べます。

ドキュメント開いた時にマクロを実行させるには、そのドキュメントを開いた状態で、ツール→カスタマイズ→イベント、文書を開いた時、を選択して「マクロ」ボタンをクリックします。


「保存先」はドキュメントにしておきます。
def macro(arg):
 doc = XSCRIPTCONTEXT.getDocument()  # ドキュメントを取得。
 sheets = doc.getSheets()  # シートコレクション。
 sheet = sheets[0]  # 最初のシート。
 sheet[0, 0].setString(str(arg))
マクロに渡させる引数の内容がわからないので、引数を文字列にしてA1セルに出力するマクロを割り当てました。

このマクロはAPSOでドキュメント内に保存しました。
<office:document-content>
 <office:scripts>
  <office:event-listeners>
   <script:event-listener script:language="ooo:script"
    script:event-name="dom:load"
    xlink:href="vnd.sun.star.script:onload.py$macro?language=Python&amp;location=document"
    xlink:type="simple" />
  </office:event-listeners>
 </office:scripts>
ドキュメント内のcontent.xmlはこのようになっていました。

onload.pyが今回使っているマクロファイル名です。

ドキュメントを保存して再度開きます。

セキュリティ警告が出てきた場合は「マクロを有効化」をクリックします。

このマクロの引数にはDocumentEvent Structが入っていました。

Calc(5)Calcドキュメントのサービスとインターフェイス一覧を検索して、これはXDocumentEventListenerのdocumentEventOccured()メソッドの引数とわかりました。

DocumentEvent StructのアトリビュートはEventNameにはOnLoad、SupplementとViewContollerにはNoneが入っていました。

Sourceにはドキュメントモデル(XSCRIPTCONTEXT.getDocument()と同じ)が入っていました。

ドキュメントを開いた時に起動するマクロでEnhancedMouseClickHandlerを割り当てる

import unohelper  # オートメーションには必須(必須なのはuno)。
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.awt import MouseButton
def macro(documentevent):
 doc = documentevent.Source  # ドキュメントの取得。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler())  # マウスハンドラをコントローラに設定。
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler):
 def mousePressed(self, enhancedmouseevent):  # マウスボタンをクリックした時。ブーリアンを返さないといけない。
  target = enhancedmouseevent.Target  # ターゲットを取得。
  if target.supportsService("com.sun.star.sheet.SheetCell"):  # ターゲットがセルの時。
   if enhancedmouseevent.Buttons==MouseButton.LEFT:  # 左ボタンのとき
    c = enhancedmouseevent.ClickCount  # クリック数を取得。
    target.setString("ClickCount: {}".format(c))  # セルにクリック数を出力する。
    if c==2:  # ダブルクリックのとき。クリック数が2になったときに実行されるので実質wクリック以上。
     row = target.getCellAddress().Row  # 行インデックスを取得。
     if row>9:  # 行インデックスが9(行10)以上のとき。
      target.setPropertyValue("CellBackColor", 0x8080FF)  # 背景を青紫色にする。
      return False  # セル編集モードにしない。
     else:  # returnしていないので3クリック以上できる。
      target.setPropertyValue("CellBackColor", 0xFFFF80)  # 背景を黄色にする。
  return True  # Falseを返すと右クリックメニューがでてこなくなる。
 def mouseReleased(self, enhancedmouseevent):  # ブーリアンを返さないといけない。
  return True  # Trueでイベントを次のハンドラに渡す。
 def disposing(self, eventobject):
  pass
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。 
このマクロをAPSOでCalcドキュメントに保存し、ツール→カスタマイズ→イベント、文書を開いた時、にこのマクロを登録しました。

CalcExamples - p--qのenhancedmouseclickhandlerexample.odsがそのファイルになります(デバッグのためのコードも含んでいます)。

ドキュメントを保存して、開き直すと、マウスクリックを左クリックした数がセルに表示されます。


右クリックではデフォルトのコンテクストメニューが表示されるだけです。

左ボタンを2クリック以上したときは背景色もつけます。

行1から行10までのセルでダブルクリックしたときは背景色を黄色にします。

行11以上のときは背景を青紫色にするだけでなく、ダブルクリックでもセル編集モードにならないようにしています。

そのためクリックした数だけクリック数が増えていきます。

コピーしたシートでも新規に挿入したシートでも同様に動作します。

(2017.11.16追記。この方法の欠点はCalc(35)コンテクストメニューをカスタマイズする: その4のコンテクストメニューと違って、EnhancedMouseClickHandlerのメソッド内で例外が発生するとマウスハンドラがはずれて再度addEnhancedMouseClickHandler()しないといけません。)

mousePressed()メソッドの引数のEnhancedMouseEvent Structのアトリビュート


シートのセルでマウスの左ボタンをダブルクリックするとXEnhancedMouseClickHandlerインターフェイスのメソッドの引数のEnhancedMouseEvent StructのButtonsは1、ClickCountには2、Targetにはセル、SourceにはNone、Modifiersには0、PopupTriggerはFalse、が入っていました。

Buttonsアトリビュートに入っているのは定数MouseButtonです。

ダブルクリックでセル編集モードにしないための戻り値


シートイベントに割り当てたマクロではなにも戻さないかFalseをreturnするとセル編集モードになりました。

Trueをreturnするとセル編集モードになりませんでした。

これはOOobbs/88 - ...?OOoBasic/Generic/InputInterception - ...?LibreOffice: XMouseClickHandler Interface Referenceに書いてあるように、Trueを返すとそれ以上のハンドラが呼ばれないということに一致します。

しかし今回使ったXEnhancedMouseClickHandlerインターフェイスのメソッドではTrueを返すとセル編集モードになり、Falseを返すとセル編集モードにはなりませんでした。

どうしてXMouseClickHandlerと逆になっているのかは今のところはわかりません。
(2018.1.15追記。シングルクリック時にFalseを返すようにするとクリックしたセルが選択されなくなってしまいました。)

マウスハンドラのメソッドのデバッグ


ハンドラのメソッドにブレークポイントを設定するにはリスナーと同様にリモートデバッグにしないといけません。

Eclipse: PyDevメモ: LibreOfficeのPythonマクロのデバッグのenableRemoteDebuggingデコレーターを使いました。

しかし、ブレークするのはかなり困難でした。
(2017.11.11追記。これはenableRemoteDebuggingでステータスバーのGUIを操作しているのが原因かもしれません。マウスの左ボタンがダブルクリックされたことを場合分けしたあとにimport pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True)を挿入すればそこで問題なくブレークできました。)

mousePressed()の引数のEnhancedMouseEvent StructのTargetにセル以外のものが入るのか確認したかったのですが、シングルクリックした時点でブレークされて、それ以降はハンドラのメソッドが呼ばれませんでした。

ちなみにダブルクリックするとLibreOfficeがクラッシュしました。

mouseReleased()メソッドの場合はシングルクリックでもブレークすることはできず、Eclipseがフリーズするか、マウスハンドラが呼ばれなかったりしてうまくいきませんでした。

とりあえず行名や列名をクリックしてもmousePressed()メソッドは呼ばれないことはわかりました。

EnhancedMouseEvent StructのTargetに入るものはセルだけ?

import unohelper  # オートメーションには必須(必須なのはuno)。
from com.sun.star.awt import XEnhancedMouseClickHandler
def macro(documentevent):
 doc = documentevent.Source  # ドキュメントの取得。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler())  # マウスハンドラをコントローラに設定。
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler):
 def mousePressed(self, enhancedmouseevent):  # マウスボタンをクリックした時。ブーリアンを返さないといけない。
  target = enhancedmouseevent.Target  # ターゲットを取得。
  doc = XSCRIPTCONTEXT.getDocument()
  sheets = doc.getSheets()  # シートコレクション。
  sheet = sheets[0]  # 最初のシート。
  sheet[0, 0].setString(str(target))
  return True  # Falseを返すと右クリックメニューがでてこなくなる。
 def mouseReleased(self, enhancedmouseevent):  # ブーリアンを返さないといけない。
  return True  # Trueでイベントを次のハンドラに渡す。
 def disposing(self, eventobject):
  pass
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。 
デバッガがうまく使えないので、このマクロでTagetに入るものをA1セルで文字列として書き出して調べました。

複数セルを選択して右クリックしたときはセル範囲がTargetに入ると思ったのですが、右クリックで選択状態になっているセルが入っていました。

今のところTargetにセル以外のオブジェクトが入っているパターンは見つけられていません。
(2017.11.11追記。セル注釈をダブルクリックするとCellAnnotationShapeが入っていました。Calc(42)セル注釈のサービスとインターフェイスの一覧参照。)

参考にしたサイト


What's the difference between Event Listeners & Handlers in Java? - Stack Overflow
リスナーとハンドラの違いについてのQ&A。

OOobbs/88 - ...?
MouseClickHandlerのBasicの例。

OOoBasic/Generic/InputInterception - ...?
MouseClickHandlerのBasicの例。

次の関連記事:Calc(28)コンテクストメニューをカスタマイズする: その1

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ