LibreOffice5(139)リスナーのメソッドの発火ログを取得する

公開日: 2018年01月29日 更新日: 2019年05月11日

旧ブログ

t f B! P L
Calc(49)追加できるリスナー一覧: その1で作成した 関数createLog()を改良して、リスナーのメソッドに1行追加するだけでサブジェクト名、リスナー名、メソッド名をファイル名として出力するようにしました。

前の関連記事:LibreOffice5(138)スクロールバーのサービスとインターフェイスの一覧


ログ取得のためにマクロに追加するコード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os, inspect
from datetime import datetime
C = 100  # カウンターの初期値。
TIMESTAMP = datetime.now().isoformat().split(".")[0].replace("-", "").replace(":", "")  # コピー先ファイル名に使う年月日T時分秒を結合した文字列を取得。
def createLog(source, filename, txt):  # 年月日T時分秒リスナーのインスタンス名_メソッド名(_オプション).logファイルを作成。txtはファイルに書き込むテキスト。dirpathはファイルを書き出すディレクトリ。
    path = XSCRIPTCONTEXT.getDocument().getURL() if __file__.startswith("vnd.sun.star.tdoc:") else __file__  # このスクリプトのパス。fileurlで返ってくる。埋め込みマクロの時は埋め込んだドキュメントのURLで代用する。
    thisscriptpath = unohelper.fileUrlToSystemPath(path)  # fileurlをsystempathに変換。
    dirpath = os.path.dirname(thisscriptpath)  # このスクリプトのあるディレクトリのフルパスを取得。
    name = source.getImplementationName().split(".")[-1]
    global C
    filename = "".join((TIMESTAMP, "_", str(C), "{}_{}".format(name, filename), ".log"))
    C += 1
    with open(os.path.join(dirpath, filename), "w") as f:
        f.write(txt)
ログファイルのパスを取得する部分もcreateLog()に含めてしまいました。

さらにサブジェクト名はサブジェクトの実装サービス名の最後の部分を使用するようにしました。

このコードをマクロのどこかにおきます。
1
createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
あとはこの1行をログを出力したいリスナーのメソッド内に書き込むだけです。

eventobjectはメソッドの引数です。

createLog()の最後の引数はログファイルに書き込む文字列なので、取得する情報を追加したいときはこの引数に追加します。

リスナーの発火ログを取得する例


LibreOffice5(137)グリッドコントロールを使うマクロの例のリスナーのメソッドのログをとることにしました。

gridcontrolLog.ods

ログ取得するようにしたマクロを埋め込んだCalcドキュメントです。

リスナーのメソッドが発火するとドキュメントがあるフォルダにログファイルを出力します。

LibreOffice5(137)グリッドコントロールを使うマクロの例のマクロで使用しているリスナーはドキュメントのコントローラに追加したEnhancedMouseClickHandler、コントロールダイアログ上のグリッドコントロールに追加したMouseListener、ボタンコントロールに追加したActionListenerです。

これら3つのリスナーに対してはdisposing()メソッドにだけログ取得するようにしました。

コントロールダイアログをノンモダルダイアログにしているときだけ、ダイアログのフレームにCloseListenerを追加しています。

ダイアログを閉じた時のリスナーの挙動を知りたかったので、コントロールダイアログにTopWindowListener、ノンモダルダイアログのフレームにFrameActionListenerを追加してそれらのメソッドすべての発火をログ取得するようにしました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import unohelper  # オートメーションには必須(必須なのはuno)。
from datetime import datetime
from com.sun.star.style.VerticalAlignment import MIDDLE
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XMouseListener
from com.sun.star.accessibility import AccessibleRole  # 定数
from com.sun.star.awt import ScrollBarOrientation  # 定数
from com.sun.star.awt import MouseButton  # 定数
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.util import XCloseListener
def macro(documentevent=None):  # 引数は文書のイベント駆動用。import pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True)
    ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
    smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
    doc = XSCRIPTCONTEXT.getDocument()  # マクロを起動した時のドキュメントのモデルを取得。  
    controller = doc.getCurrentController()  # コントローラの取得。
    controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler(controller, ctx, smgr, doc))  # EnhancedMouseClickHandler
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler):
    def __init__(self, subj, ctx, smgr, doc):
        self.subj = subj
        self.args = ctx, smgr, doc
    def mousePressed(self, enhancedmouseevent):
        ctx, smgr, doc = self.args
        target = enhancedmouseevent.Target  # ターゲットのセルを取得。
        if enhancedmouseevent.Buttons==MouseButton.LEFT:  # 左ボタンのとき
            if target.supportsService("com.sun.star.sheet.SheetCell"):  # ターゲットがセルの時。
                if enhancedmouseevent.ClickCount==2# ダブルクリックの時
                    cellbackcolor = target.getPropertyValue("CellBackColor"# セルの背景色を取得。
                    if cellbackcolor==0x8080FF# 背景が青紫色の時。
                        createDialog(ctx, smgr, doc, True# ノンモダルダイアログにする。
                        return False  # セル編集モードにしない。
                    elif cellbackcolor==0xFFFF80# 背景が黄色の時。
                        createDialog(ctx, smgr, doc, False# モダルダイアログにする。 
                        return False  # セル編集モードにしない。
        return True  # セル編集モードにする。
    def mouseReleased(self, enhancedmouseevent):
        return True  # シングルクリックでFalseを返すとセル選択範囲の決定の状態になってどうしようもなくなる。
    def disposing(self, eventobject):  # ドキュメントを閉じる時でも呼ばれない。
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        self.subj.removeEnhancedMouseClickHandler(self)
def createDialog(ctx, smgr, doc, flg):
    frame = doc.getCurrentController().getFrame()  # モデル→コントローラ→フレーム、でドキュメントのフレームを取得。
    containerwindow = frame.getContainerWindow()  # ドキュメントのウィンドウ(コンテナウィンドウ=ピア)を取得。
    toolkit = containerwindow.getToolkit()  # ピアからツールキットを取得。 
    m = 6  # コントロール間の間隔
    grid = {"PositionX": m, "PositionY": m, "Width": 145, "Height": 100, "ShowColumnHeader": True, "ShowRowHeader": True# グリッドコントロールの基本プロパティ。
    label = {"PositionX": m, "Width": 45, "Height": 12, "Label": "Date and time: ", "NoLabel": True, "Align": 2, "VerticalAlign": MIDDLE}  # ラベルフィールドコントロールの基本プロパティ。
    x = label["PositionX"]+label["Width"# ラベルフィールドコントロールの右端。
    textbox = {"PositionX": x, "Width": grid["PositionX"]+grid["Width"]-x, "Height": label["Height"], "VerticalAlign": MIDDLE}  # テクストボックスコントロールの基本プロパティ。
    button = {"PositionX": m, "Width": 30, "Height": label["Height"]+2, "PushButtonType": 0# ボタンの基本プロパティ。PushButtonTypeの値はEnumではエラーになる。
    controldialog =  {"PositionX": 100, "PositionY": 40, "Width": grid["PositionX"]+grid["Width"]+m, "Title": "Grid Example", "Name": "controldialog", "Step": 0, "Moveable": True# コントロールダイアログの基本プロパティ。幅は右端のコントロールから取得。高さは最後に設定する。
    dialog, addControl = dialogCreator(ctx, smgr, controldialog)  # コントロールダイアログの作成。
    mouselister = MouseListener(doc)
    actionlistener = ActionListener()
    grid1 = addControl("Grid", grid, {"addMouseListener": mouselister})  # グリッドコントロールの取得。
    gridmodel = grid1.getModel()  # グリッドコントロールモデルの取得。
    gridcolumn = gridmodel.getPropertyValue("ColumnModel"# DefaultGridColumnModel
    column0 = gridcolumn.createColumn()  # 列の作成。
    column0.Title = "Date"  # 列ヘッダー。
    column0.ColumnWidth = 60  # 列幅。
    gridcolumn.addColumn(column0)  # 列を追加。
    column1 = gridcolumn.createColumn()  # 列の作成。
    column1.Title = "Time"  # 列ヘッダー。
    column1.ColumnWidth = grid["Width"] - column0.ColumnWidth  #  列幅。列の合計がグリッドコントロールの幅に一致するようにする。
    gridcolumn.addColumn(column1)  # 列を追加。
    griddata = gridmodel.getPropertyValue("GridDataModel"# GridDataModel
    now = datetime.now()  # 現在の日時を取得。
    griddata.addRow(0, (now.date().isoformat(), now.time().isoformat()))  # グリッドに行を挿入。
    y = grid["PositionY"] + grid["Height"] + # 下隣のコントロールのY座標を取得。
    label["PositionY"] = textbox["PositionY"] = y
    textbox["Text"] = now.isoformat().replace("T", " ")
    addControl("FixedText", label)
    addControl("Edit", textbox) 
    y = label["PositionY"] + label["Height"] + # 下隣のコントロールのY座標を取得。
    button1, button2 = button.copy(), button.copy()
    button1["PositionY"] = button2["PositionY"= y
    button1["Label"] = "~Now"
    button2["Label"] = "~Close"
    button2["PushButtonType"] = 2  # CANCEL 
    button2["PositionX"] = grid["Width"] - button2["Width"]
    button1["PositionX"] = button2["PositionX"] - m - button1["Width"]
    addControl("Button", button1, {"setActionCommand": "now" ,"addActionListener": actionlistener}) 
    addControl("Button", button2) 
    dialog.getModel().setPropertyValue("Height", button1["PositionY"]+button1["Height"]+m)  # コントロールダイアログの高さを設定。
    dialog.createPeer(toolkit, containerwindow)  # ダイアログを描画。親ウィンドウを渡す。ノンモダルダイアログのときはNone(デスクトップ)ではフリーズする。Stepを使うときはRoadmap以外のコントロールが追加された後にピアを作成しないとStepが重なって表示される。
    dialog.addTopWindowListener(TopWindowListener())  # ログ出力用。
    if flg:  # ノンモダルダイアログにするとき。オートメーションでは動かない。
        dialogframe = showModelessly(ctx, smgr, frame, dialog) 
        dialogframe.addCloseListener(CloseListener(dialog, mouselister, actionlistener))  # CloseListener
        # ログ出力用。
        dialogframe.addFrameActionListener(FrameActionListener())  # FrameActionListener  
    else# モダルダイアログにする。フレームに追加するとエラーになる。
        dialog.execute() 
        dialog.dispose()
class CloseListener(unohelper.Base, XCloseListener):  # ノンモダルダイアログのリスナー削除用。
    def __init__(self, dialog, mouselister, actionlistener):
        self.args = dialog, mouselister, actionlistener
    def queryClosing(self, eventobject, getsownership):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "getsownership: {}\nSource: {}".format(getsownership, eventobject.Source))
        dialog, mouselister, actionlistener = self.args
        dialog.getControl("Grid1").removeMouseListener(mouselister)
        dialog.getControl("Button1").removeActionListener(actionlistener)
    def notifyClosing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def disposing(self, eventobject): 
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        eventobject.Source.removeCloseListener(self)
class MouseListener(unohelper.Base, XMouseListener): 
    def __init__(self, doc):
        self.args = doc
    def mousePressed(self, mouseevent):
        if mouseevent.Buttons==MouseButton.LEFT and mouseevent.ClickCount==2# ダブルクリックの時。
            doc = self.args
            selection = doc.getCurrentSelection()  # シート上で選択しているオブジェクトを取得。
            if selection.supportsService("com.sun.star.sheet.SheetCell"):  # 選択オブジェクトがセルの時。
                source = mouseevent.Source  # グリッドコントロールを取得。
                griddata = source.getModel().getPropertyValue("GridDataModel"# GridDataModelを取得。
                rowdata = griddata.getRowData(source.getCurrentRow())  # グリッドコントロールで選択している行のすべての列をタプルで取得。
                selection.setString(" ".join(rowdata))  # 選択セルに書き込む。
    def mouseReleased(self, mouseevent):
        pass
    def mouseEntered(self, mouseevent):
        pass
    def mouseExited(self, mouseevent):
        pass
    def disposing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        eventobject.Source.removeMouseListener(self)
class ActionListener(unohelper.Base, XActionListener):
    def actionPerformed(self, actionevent):
        cmd = actionevent.ActionCommand
        source = actionevent.Source  # ボタンコントロールが返る。
        context = source.getContext()  # コントロールダイアログが返ってくる。
        if cmd == "now":
            now = datetime.now()  # 現在の日時を取得。
            context.getControl("Edit1").setText(now.isoformat().replace("T", " "))  # テキストボックスコントロールに入力。
            grid1 = context.getControl("Grid1"# グリッドコントロールを取得。
            griddata = grid1.getModel().getPropertyValue("GridDataModel"# グリッドコントロールモデルからDefaultGridDataModelを取得。
            griddata.addRow(griddata.RowCount, (now.date().isoformat(), now.time().isoformat()))  # 新たな行を追加。
            accessiblecontext = grid1.getAccessibleContext()  # グリッドコントロールのAccessibleContextを取得。
            for i in range(accessiblecontext.getAccessibleChildCount()):  # 子要素をのインデックスを走査する。
                child = accessiblecontext.getAccessibleChild(i)  # 子要素を取得。
                if child.getAccessibleContext().getAccessibleRole()==AccessibleRole.SCROLL_BAR:  # スクロールバーの時。
                    if child.getOrientation()==ScrollBarOrientation.VERTICAL:  # 縦スクロールバーの時。
                        child.setValue(child.getMaximum())  # 最大値にスクロールさせる。
                        return
    def disposing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        eventobject.Source.removeActionListener(self)
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。
# ログ出力用。
import os, inspect
from datetime import datetime
C = 100  # カウンターの初期値。
TIMESTAMP = datetime.now().isoformat().split(".")[0].replace("-", "").replace(":", "")  # コピー先ファイル名に使う年月日T時分秒を結合した文字列を取得。
def createLog(source, filename, txt):  # 年月日T時分秒リスナーのインスタンス名_メソッド名(_オプション).logファイルを作成。txtはファイルに書き込むテキスト。dirpathはファイルを書き出すディレクトリ。
    path = XSCRIPTCONTEXT.getDocument().getURL() if __file__.startswith("vnd.sun.star.tdoc:") else __file__  # このスクリプトのパス。fileurlで返ってくる。埋め込みマクロの時は埋め込んだドキュメントのURLで代用する。
    thisscriptpath = unohelper.fileUrlToSystemPath(path)  # fileurlをsystempathに変換。
    dirpath = os.path.dirname(thisscriptpath)  # このスクリプトのあるディレクトリのフルパスを取得。
    name = source.getImplementationName().split(".")[-1]
    global C
    filename = "".join((TIMESTAMP, "_", str(C), "{}_{}".format(name, filename), ".log"))
    C += 1
    with open(os.path.join(dirpath, filename), "w") as f:
        f.write(txt)
from com.sun.star.awt import XTopWindowListener
from com.sun.star.frame import XFrameActionListener
from com.sun.star.frame.FrameAction import COMPONENT_ATTACHED, COMPONENT_DETACHING, COMPONENT_REATTACHED, FRAME_ACTIVATED, FRAME_DEACTIVATING, CONTEXT_CHANGED, FRAME_UI_ACTIVATED, FRAME_UI_DEACTIVATING  # enum
class FrameActionListener(unohelper.Base, XFrameActionListener):
    def __init__(self):
        enums = COMPONENT_ATTACHED, COMPONENT_DETACHING, COMPONENT_REATTACHED, FRAME_ACTIVATED, FRAME_DEACTIVATING, CONTEXT_CHANGED, FRAME_UI_ACTIVATED, FRAME_UI_DEACTIVATING  # enum
        frameactionnames = "COMPONENT_ATTACHED", "COMPONENT_DETACHING", "COMPONENT_REATTACHED", "FRAME_ACTIVATED", "FRAME_DEACTIVATING", "CONTEXT_CHANGED", "FRAME_UI_ACTIVATED", "FRAME_UI_DEACTIVATING"
        self.args = zip(enums, frameactionnames)
    def frameAction(self, frameactionevent):
        frameactions = self.args
        frameaction = frameactionevent.Action
        for enum, frameactionname in frameactions:
            if frameaction==enum:
                createLog(frameactionevent.Source, "{}_{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name, frameactionname), "FrameAction: {}\nSource: {}".format(frameactionname, frameactionevent.Source))
                return
    def disposing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        eventobject.Source.removeFrameActionListener(self)
class TopWindowListener(unohelper.Base, XTopWindowListener):
    def windowOpened(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowClosing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowClosed(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowMinimized(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowNormalized(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowActivated(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def windowDeactivated(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
    def disposing(self, eventobject):
        createLog(eventobject.Source, "{}_{}".format(self.__class__.__name__, inspect.currentframe().f_code.co_name), "Source: {}".format(eventobject.Source))
        eventobject.Source.removeTopWindowListener(self)
showModelessly()とdialogCreator()の表記は略しています。

ハイライトしている行と150行目以降がログ出力のために追加したコードです。

FrameActionListenerについてはイベントとメソッドが1対1に対応していないのでCalc(52)追加できるリスナー一覧: その4のときと同様にしてenum FrameActionをファイル名に出力するようにしています。

次の関連記事:LibreOffice5(140)ノンモダルダイアログとモダルダイアログを閉じるときの違い

ブログ検索 by Blogger

Translate

Created by Calendar Gadget

QooQ