Calc(55)追加できるリスナー一覧: その7

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

旧ブログ

t f B! P L
Calc(52)追加できるリスナー一覧: その4のマクロでは不十分だったので書き換えました。ドキュメントウィンドウを閉じるとフレームが閉じると思っていましたが、そうではなくて、フレームとコンテナウィンドウが使い回しされていることがわかりました。

前の関連記事:Calc(54)追加できるリスナー一覧: その6


listenersforcalc2.pyでリスナーのメソッドの発火をファイル名に記録する

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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import os
import inspect
import platform
from datetime import datetime
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.awt import XKeyHandler
from com.sun.star.awt import XTopWindowListener
from com.sun.star.awt import Key  # 定数
from com.sun.star.view import XSelectionChangeListener
from com.sun.star.view import XPrintJobListener
from com.sun.star.view.PrintableState import JOB_STARTED, JOB_COMPLETED, JOB_SPOOLED, JOB_ABORTED, JOB_FAILED, JOB_SPOOLING_FAILED  # enum
from com.sun.star.util import XCloseListener
from com.sun.star.util import XModifyListener
from com.sun.star.util import XChangesListener
from com.sun.star.frame import XTerminateListener
from com.sun.star.frame import XFrameActionListener
from com.sun.star.frame import XTitleChangeListener
from com.sun.star.frame import XBorderResizeListener
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
from com.sun.star.document import XDocumentEventListener
from com.sun.star.document import XEventListener
from com.sun.star.document import XStorageChangeListener
from com.sun.star.sheet import XActivationEventListener
from com.sun.star.chart import XChartDataChangeEventListener
from com.sun.star.chart.ChartDataChangeType import ALL, DATA_RANGE, COLUMN_INSERTED, ROW_INSERTED, COLUMN_DELETED, ROW_DELETED  # enum
def macro(documentevent=None):  # 引数は文書のイベント駆動用。OnStartAppでもDocumentEventが入るがSourceはNoneになる。# import pydevd; pydevd.settrace(stdoutToServer=True, stderrToServer=True)  # デバッグサーバーを起動していた場合はここでブレークされる。import pydevdは時間がかかる。
    doc = XSCRIPTCONTEXT.getDocument() if documentevent is None else documentevent.Source  # ドキュメントのモデルを取得。
    desktop = XSCRIPTCONTEXT.getDesktop()  # デスクトップの取得。
    path = doc.getURL() if __file__.startswith("vnd.sun.star.tdoc:") else __file__  # このスクリプトのパス。fileurlで返ってくる。埋め込みマクロの時は埋め込んだドキュメントのURLで代用する。
    thisscriptpath = unohelper.fileUrlToSystemPath(path)  # fileurlをsystempathに変換。
    dirpath = os.path.dirname(thisscriptpath)  # このスクリプトのあるディレクトリのフルパスを取得。
    listeners = {}
    listeners["desktop_terminatelistener"] = TerminateListener(dirpath, "desktop_terminatelistener")
    listeners["desktop_frameactionlistener"] = FrameActionListener(dirpath, "desktop_frameactionlistener")
    desktop.addTerminateListener(listeners["desktop_terminatelistener"])  # TerminateListener
    desktop.addFrameActionListener(listeners["desktop_frameactionlistener"])  # FrameActionListener
    controller = doc.getCurrentController()  # コントローラーの取得。
    frame = controller.getFrame()  # フレームの取得。
    listeners["frame_frameactionlistener"] = FrameActionListener(dirpath, "frame_frameactionlistener")
    listeners["frame_closelistener"] = CloseListener(dirpath, "frame_closelistener")
    listeners["frame_titlechangelistener"] = TitleChangeListener(dirpath, "frame_titlechangelistener", frame)
    frame.addFrameActionListener(listeners["frame_frameactionlistener"])  # FrameActionListener
    frame.addCloseListener(listeners["frame_closelistener"])  # CloseListener
    frame.addTitleChangeListener(listeners["frame_titlechangelistener"])  # TitleChangeListener
    containerwindow = frame.getContainerWindow()  # フレームのコンテナウィンドウの取得。
    listeners["containerwindow_topwindowlistener"] = TopWindowListener(dirpath, "containerwindow_topwindowlistener")
    containerwindow.addTopWindowListener(listeners["containerwindow_topwindowlistener"])
    controller.addActivationEventListener(ActivationEventListener(dirpath, "controller_activationeventlistener", controller))  # ActivationEventListener
    controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler(dirpath, "controller_enhancedmouseclickhandler", controller))  # EnhancedMouseClickHandler
    controller.addSelectionChangeListener(SelectionChangeListener(dirpath, "controller_selectionchangelistener", controller))  # SelectionChangeListener
    controller.addBorderResizeListener(BorderResizeListener(dirpath, "controller_borderresizelistener", controller))  # BorderResizeListener
    controller.addTitleChangeListener(TitleChangeListener(dirpath, "controller_titlechangelistener", controller))  # TitleChangeListener
    controller.addKeyHandler(KeyHandler(dirpath, "controller_keyhandler", controller))  # KeyHandler 
    doc.addDocumentEventListener(DocumentEventListener(dirpath, "doc_documenteventlistener", doc, desktop, frame, containerwindow, listeners))  # DocumentEventListener
    doc.addEventListener(EventListener(dirpath, "doc_eventlistener", doc))  # EventListener
    doc.addModifyListener(ModifyListener(dirpath, "doc_modifylistener", doc))  # ModifyListener 
    doc.addPrintJobListener(PrintJobListener(dirpath, "doc_printjoblistener", doc))  # PrintJobListener
    doc.addStorageChangeListener(StorageChangeListener(dirpath, "doc_storagechangelistener", doc))  # StorageChangeListener
    doc.addTitleChangeListener(TitleChangeListener(dirpath, "doc_titlechangelistener", doc))  # TitleChangeListener
    doc.addChangesListener(ChangesListener(dirpath, "doc_changelistener", doc))  # ChangesListener
    sheet = controller.getActiveSheet()  # アクティブシートを取得。
    sheet.addChartDataChangeEventListener(ChartDataChangeEventListener(dirpath, "sheet_chartdatachangeeventlistener", sheet))  # ChartDataChangeEventListener
    sheet.addModifyListener(ModifyListener(dirpath, "sheet_modifylistener", sheet))  # ModifyListener
    cell = sheet["A1"# セルの取得。
    cell.addChartDataChangeEventListener(ChartDataChangeEventListener(dirpath, "cell_chartdatachangeeventlistener", cell))  # ChartDataChangeEventListener 
    cell.addModifyListener(ModifyListener(dirpath, "cell_modifylistener", cell))  # ModifyListener
    cells = sheet["A2:C4"# セル範囲の取得。
    cells.addChartDataChangeEventListener(ChartDataChangeEventListener(dirpath, "cells_chartdatachangeeventlistener", cells))  # ChartDataChangeEventListener
    cells.addModifyListener(ModifyListener(dirpath, "cells_modifylistener", cells))  # ModifyListener
class TopWindowListener(unohelper.Base, XTopWindowListener):
    def __init__(self, dirpath, name):
        self.args = dirpath, name
    def windowOpened(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source)) 
    def windowClosing(self, eventobject):  # 呼ばれない。いつ呼ばれる?
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))  
    def windowClosed(self, eventobject):  # フレームが閉じた後デスクトップが閉じる前に呼ばれる。
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
    def windowMinimized(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source)) 
    def windowNormalized(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
    def windowActivated(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source)) 
    def windowDeactivated(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
class ChartDataChangeEventListener(unohelper.Base, XChartDataChangeEventListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
        enums = ALL, DATA_RANGE, COLUMN_INSERTED, ROW_INSERTED, COLUMN_DELETED, ROW_DELETED  # enum
        chartdatachangetypenames = "ALL", "DATA_RANGE", "COLUMN_INSERTED", "ROW_INSERTED", "COLUMN_DELETED", "ROW_DELETED"
        self.args = dirpath, name, zip(enums, chartdatachangetypenames)
    def chartDataChanged(self, chartdatachangeevent):
        dirpath, name, chartdatachangetypes = self.args
        chartdatachangetype = chartdatachangeevent.Type
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        for enum, chartdatachangetypename in chartdatachangetypes:
            if chartdatachangetype==enum:
                filename = "_".join((name, inspect.currentframe().f_code.co_name, chartdatachangetypename))  # ChartDataChangeType名を追加。
                createLog(dirpath, filename, "ChartDataChangeType: {}".format(chartdatachangetypename))
                return
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeChartDataChangeEventListener(self
class ChangesListener(unohelper.Base, XChangesListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def changesOccurred(self, changesevent):
        dirpath, name = self.args
        base = changesevent.Base
        if base.supportsService("com.sun.star.sheet.SpreadsheetDocument"):  # ドキュメントの時
            basetxt = "Base URL: {}".format(__file__)  # ドキュメントのURLを取得。
        else:
            basetxt = "Base: {}".format(base)
        txts = [basetxt]  # ログファイルに出力する行のリスト。
        changes = changesevent.Changes
        for change in changes:
            txts.append("Accessor: {}".format(change.Accessor))
            for element in change.Element:
                if hasattr(element, "Name") and hasattr(element, "Value"):
                    propertyname, propertyvalue = element.Name, element.Value
                    if "Color" in propertyname:  # 色の時は16進数で出力する。
                        propertyvalue = hex(propertyvalue)
                    txts.append("{}: {}".format(propertyname, propertyvalue))
            replacedelement = getStringAddressFromCellRange(change.ReplacedElement)  # 変更対象オブジェクトから文字列アドレスを取得する。
            replacedelement = replacedelement or change.ReplaceElement  # 文字列アドレスを取得できないオブジェクトの時はオブジェクトをそのまま文字列にする。
            txts.append("ReplacedElement: {}".format(replacedelement))    
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "\n".join(txts))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeChangesListener(self)
class StorageChangeListener(unohelper.Base, XStorageChangeListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def notifyStorageChange(self, document, storage):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Storage: {}".format(storage))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeStorageChangeListener(self
class PrintJobListener(unohelper.Base, XPrintJobListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
        enums = JOB_STARTED, JOB_COMPLETED, JOB_SPOOLED, JOB_ABORTED, JOB_FAILED, JOB_SPOOLING_FAILED  # enum
        printablestatenames = "JOB_STARTED", "JOB_COMPLETED", "JOB_SPOOLED", "JOB_ABORTED", "JOB_FAILED", "JOB_SPOOLING_FAILED"
        self.args = dirpath, name, zip(enums, printablestatenames)
    def printJobEvent(self, printjobevent):
        dirpath, name, printablestates = self.args
        printablestate = printjobevent.State
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        for enum, printablestatename in printablestates:
            if printablestate==enum:
                filename = "_".join((name, inspect.currentframe().f_code.co_name, printablestatename))  # State名も追加。
                createLog(dirpath, filename, "PrintableState: {}, Source: {}".format(printablestatename, printjobevent.Source))
                return
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removePrintJobListener(self)
class ModifyListener(unohelper.Base, XModifyListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def modified(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeModifyListener(self)
class EventListener(unohelper.Base, XEventListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def notifyEvent(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name, eventobject.EventName))  # イベント名も追加。
        createLog(dirpath, filename, "EventName: {}, Source: {}".format(eventobject.EventName, eventobject.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeEventListener(self)
class DocumentEventListener(unohelper.Base, XDocumentEventListener):
    def __init__(self, dirpath, name, subj, desktop, frame, containerwindow, listeners):
        self.subj = subj
        self.args = dirpath, name, desktop, frame, containerwindow, listeners
    def documentEventOccured(self, documentevent):
        dirpath, name, desktop, frame, containerwindow, listeners = self.args
        eventname = documentevent.EventName
        filename = "_".join((name, inspect.currentframe().f_code.co_name, eventname))  # イベント名も追加。
        if eventname=="OnUnload"# ドキュメントを閉じてもdisposeされないデスクトップ、フレーム、コンテナウィンドウにつけたリスナーを除去する。
            desktop.removeTerminateListener(listeners["desktop_terminatelistener"])  # TerminateListener
            desktop.removeFrameActionListener(listeners["desktop_frameactionlistener"])  # FrameActionListener
            frame.removeFrameActionListener(listeners["frame_frameactionlistener"])  # FrameActionListener
            frame.removeCloseListener(listeners["frame_closelistener"])  # CloseListener
            frame.removeTitleChangeListener(listeners["frame_titlechangelistener"])  # TitleChangeListener
            containerwindow.removeTopWindowListener(listeners["containerwindow_topwindowlistener"])  # TopWindowListener
            filename = "_".join((name, inspect.currentframe().f_code.co_name, eventname, "RemoveListeners"))  # イベント名も追加。
        createLog(dirpath, filename, "EventName: {}, Source: {}".format(eventname, documentevent.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeDocumentEventListener(self)
class KeyHandler(unohelper.Base, XKeyHandler):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.keycodes = {
            Key.DOWN: "DOWN",
            Key.UP: "UP",
            Key.LEFT: "LEFT",
            Key.RIGHT: "RIGHT",
            Key.HOME: "HOME",
            Key.END: "END",
            Key.RETURN: "RETURN",
            Key.ESCAPE: "ESCAPE",
            Key.TAB: "TAB"
            Key.BACKSPACE: "BACKSPACE",
            Key.SPACE: "SPACE",
            Key.DELETE: "DELETE"
        # キーは定数。特殊文字を文字列に置換する。
        self.args = dirpath, name
    def keyPressed(self, keyevent):
        keychar = self._keycharToText(keyevent)
        self._createLogFile(keyevent, keychar, inspect.currentframe().f_code.co_name)
        return False
    def keyReleased(self, keyevent):
        keychar = "" if platform.system()=="Windows" else self._keycharToText(keyevent)  # Windowsの時日本語入力ではKeyCharを使うとすべて文字化けするので使わない。
        self._createLogFile(keyevent, keychar, inspect.currentframe().f_code.co_name)
        return False 
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeKeyHandler(self)
    def _keycharToText(self, keyevent):
        keycode = keyevent.KeyCode
        keychar = ""  # KeyCharが特殊文字の場合はその後のテキストが表示されないときがあるので書き込まない。
        if keycode in self.keycodes:  # self.keycodesにキーがある特殊文字は文字列に置換する。
            keychar = self.keycodes[keycode]
        elif 255<keycode<267 or 511<keycode<538# 数値かアルファベットの時
            keychar = keyevent.KeyChar.value
        return keychar
    def _createLogFile(self, keyevent, keychar, methodname):
        dirpath, name = self.args
        if keychar:
            filename = "_".join((name, methodname, keychar))
            txt = "KeyCode: {}, KeyChar: {}, KeyFunc: {}, Modifiers: {}".format(keyevent.KeyCode, keychar, keyevent.KeyFunc, keyevent.Modifiers)
        else:
            filename = "_".join((name, methodname))
            txt = "KeyCode: {}, KeyFunc: {}, Modifiers: {}".format(keyevent.KeyCode, keyevent.KeyFunc, keyevent.Modifiers)  
        createLog(dirpath, filename, txt)
class BorderResizeListener(unohelper.Base, XBorderResizeListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def borderWidthsChanged(self, obj, borderwidths):
        dirpath, name = self.args
        if obj.supportsService("com.sun.star.sheet.SpreadsheetView"):  # objがコントローラーの時。
            cellrangeaddressconversion = obj.getModel().createInstance("com.sun.star.table.CellRangeAddressConversion"# ドキュメントからCellRangeAddressConversionを取得。
            cellrangeaddressconversion.Address = obj.getVisibleRange()  # 表示されているセル範囲のCellRangeAddressを取得。
            txt = "Visible Range: {}".format(cellrangeaddressconversion.PersistentRepresentation)  # 表示されているセル範囲の文字列アドレスの取得。
        else:
            txt = "Top: {}, Left: {}, Right: {}, Bottom: {}, Object: {}".format(borderwidths.Top, borderwidths.Left, borderwidths.Right, borderwidths.Bottom, obj)
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, txt)
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeBorderResizeListener(self
class SelectionChangeListener(unohelper.Base, XSelectionChangeListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def selectionChanged(self, eventobject):
        dirpath, name = self.args
        txt = ""
        source = eventobject.Source
        if source.supportsService("com.sun.star.sheet.SpreadsheetView"):  # sourceがコントローラーのとき
            selection = source.getSelection()  # 選択しているオブジェクトを取得。
            stringaddress = getStringAddressFromCellRange(selection)
            if stringaddress:
                filename = "_".join((name, inspect.currentframe().f_code.co_name, stringaddress.replace(":", "")))
                txt = "Selection: {}".format(stringaddress)
        if not txt:
            txt = "Source: {}".format(source)
        createLog(dirpath, filename, txt)
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeSelectionChangeListener(self)
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def mousePressed(self, enhancedmouseevent):
        self._createLog(enhancedmouseevent, inspect.currentframe().f_code.co_name)
        return True
    def mouseReleased(self, enhancedmouseevent):
        self._createLog(enhancedmouseevent, inspect.currentframe().f_code.co_name)
        return True
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeEnhancedMouseClickHandler(self)
    def _createLog(self, enhancedmouseevent, methodname):
        dirpath, name = self.args
        target = enhancedmouseevent.Target
        target = getStringAddressFromCellRange(target) or target  # sourceがセル範囲の時は選択範囲の文字列アドレスを返す。
        clickcount = enhancedmouseevent.ClickCount
        filename = "_".join((name, methodname, "ClickCount", str(clickcount)))
        createLog(dirpath, filename, "Buttons: {}, ClickCount: {}, PopupTrigger {}, Modifiers: {}, Target: {}".format(enhancedmouseevent.Buttons, clickcount, enhancedmouseevent.PopupTrigger, enhancedmouseevent.Modifiers, target))
class ActivationEventListener(unohelper.Base, XActivationEventListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def activeSpreadsheetChanged(self, activationevent):
        dirpath, name = self.args
        activesheet = activationevent.ActiveSheet
        activesheetname = activesheet.getName()
        txt = ""
        source = activationevent.Source
        if source.supportsService("com.sun.star.sheet.SpreadsheetView"):  # sourceがコントローラーのとき
            selection = source.getSelection()  # 選択しているオブジェクトを取得。
            stringaddress = getStringAddressFromCellRange(selection)
            if stringaddress:
                txt = "Selection: {}".format(stringaddress)
        if not txt:
            txt = "Source: {}".format(source)
        txt = "ActiveSheet: {}, {}".format(activesheetname, txt)  # アクティブシート名を取得。
        filename = "_".join((name, inspect.currentframe().f_code.co_name, activesheetname))
        createLog(dirpath, filename, txt)
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeActivationEventListener(self)
class TitleChangeListener(unohelper.Base, XTitleChangeListener):
    def __init__(self, dirpath, name, subj):
        self.subj = subj
        self.args = dirpath, name
    def titleChanged(self, titlechangedevent):
        dirpath, name = self.args
        title = titlechangedevent.Title
        filename = "_".join((name, inspect.currentframe().f_code.co_name, title))
        createLog(dirpath, filename, "Title: {}, Source: {}".format(title, titlechangedevent.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
        self.subj.removeTitleChangeListener(self)
class CloseListener(unohelper.Base, XCloseListener):
    def __init__(self, dirpath, name):
        self.args = dirpath, name
    def queryClosing(self, eventobject, getsownership):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "getsownership: {}, Source: {}".format(getsownership, eventobject.Source))
    def notifyClosing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
class FrameActionListener(unohelper.Base, XFrameActionListener):
    def __init__(self, dirpath, name):
        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 = dirpath, name, zip(enums, frameactionnames)
    def frameAction(self, frameactionevent):
        dirpath, name, frameactions = self.args
        frameaction = frameactionevent.Action
        for enum, frameactionname in frameactions:
            if frameaction==enum:
                filename = "_".join((name, inspect.currentframe().f_code.co_name, frameactionname))  # Action名も追加。
                createLog(dirpath, filename, "FrameAction: {}, Source: {}".format(frameactionname, frameactionevent.Source))
                return
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
class TerminateListener(unohelper.Base, XTerminateListener):  # TerminateListener
    def __init__(self, dirpath, name):  # 出力先ディレクトリのパス、リスナーのインスタンス名。
        self.args = dirpath, name
    def queryTermination(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))  # このメソッド名を取得。メソッド内で実行する必要がある。
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))  # Sourceを出力。
    def notifyTermination(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))  # このメソッド名を取得。メソッド内で実行する必要がある。
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))  # Sourceを出力。
    def disposing(self, eventobject):
        dirpath, name = self.args
        filename = "_".join((name, inspect.currentframe().f_code.co_name))
        createLog(dirpath, filename, "Source: {}".format(eventobject.Source))
def getStringAddressFromCellRange(source):  # sourceがセル範囲の時は選択範囲の文字列アドレスを返す。文字列アドレスが取得できないオブジェクトの時はオブジェクトの文字列を返す。
    stringaddress = ""
    propertysetinfo = source.getPropertySetInfo()  # PropertySetInfo
    if propertysetinfo.hasPropertyByName("AbsoluteName"):  # AbsoluteNameプロパティがある時。
        absolutename = source.getPropertyValue("AbsoluteName") # セル範囲コレクションは$Sheet1.$A$4:$A$6,$Sheet1.$B$4という形式で返る。
        names = absolutename.replace("$", "").split(",")  # $を削除してセル範囲の文字列アドレスのリストにする。
        stringaddress = ", ".join(names)  # コンマでつなげる。
    return stringaddress
C = 100  # カウンターの初期値。
TIMESTAMP = datetime.now().isoformat().split(".")[0].replace("-", "").replace(":", "")  # コピー先ファイル名に使う年月日T時分秒を結合した文字列を取得。
def createLog(dirpath, filename, txt):  # 年月日T時分秒リスナーのインスタンス名_メソッド名(_オプション).logファイルを作成。txtはファイルに書き込むテキスト。dirpathはファイルを書き出すディレクトリ。
    global C
    filename = "".join((TIMESTAMP, "_", str(C), filename, ".log"))
    C += 1
    with open(os.path.join(dirpath, filename), "w") as f:
        f.write(txt)
g_exportedScripts = macro,  #マクロセレクターに限定表示させる関数をタプルで指定。
変更点はすべてのリスナーのdisposing()メソッドが発火したときもログファイルを出力するようにしたことと、XTopWindowListenerのメソッドを追加したことと、ログファイル名に出力する情報を追加したこと、ログファイルの連番を100から開始にしたことなどです。

ListenersForCalc2.ods

このマクロを埋め込んだCalcドキュメントです。

ドキュメントを開いた時にこのマクロが起動し、ドキュメントと同じフォルダにログファイルを出力します。

ListenersForCalc2-DisabledLine228.ods

228行目のif文をコメントアウトしてデスクトップ、フレーム、コンテナウィンドウのリスナーを削除しないバージョンです。

リスナーを削除するタイミング


リスナーは終了時に削除した方がいいらしいので、disposing()メソッドの最後で除去しています。

リスナーのオブジェクトがすべてのサブジェクトから削除されるとすぐに消えてしまうせいか、XModifyListenerのdisposing()メソッドで除去した後にコードを実行するとLibreOfficeがクラッシュしましたので、リスナーを削除するのは一番最後に実行しています。

ドキュメントを閉じたときにフレームも閉じられると思っていましたが、フレームはウィンドウが増えなければ同じものが使い回しされるようで、フレームにXCloseListenerを付けてもそのメソッドはドキュメントのウィンドウを閉じるだけでは呼び出されませんでした。

コンテナウィンドウに追加したXTopWindowListenerのwindowClosed()ならドキュメントを閉じたときに呼び出されると思いましたが、呼び出されるタイミングはフレームと同じでした。

XTopWindowListenerのwindowClosing()メソッドはいつ呼ばれるのかわかりませんでした(OOoBasic/Window/Listeners - ...?の時と変わっているようです)。

ドキュメントを一つ開いたところから、ファイル→閉じる、を実行するとスタートセンターのウィンドウに切り替わります。

(2018.1.5追記。スタートセンターに切り替わるのは、開いているドキュメントが一つで、ファイル→閉じる、でドキュメントを閉じたときだけでした。ドキュメントのウィンドウが複数開いているときや、ドキュメントが一つしか開いていなくても、ウィンドウの閉じるボタンで閉じたときはスタートセンターに切り替わりませんでした。windowClosing()メソッドの発火も異なりました。Calc(58)追加できるリスナー一覧: その9参照。)


デスクトップ、フレーム、コンテナウィンドウのリスナーを削除しないまま、「閉じる」、ところから呼び出されたリスナーの発火のログはこのようになっていました。

ドキュメントを閉じても6行目でCOMPONENT_DETACHINGしたフレームが、下から3行目でCOMPONENT_REATTACHEDされて、フレームタイトルがドキュメント名からLibreOfficeに変更になって、スタートセンターのウィンドウに使い回しされていることがわかります。

この状態で再度上記のマクロを実行するとフレームとデスクトップ、コンテナウィンドウには同じリスナーが再度追加されて、今度は2回ずつリスナーのメソッドが発火してしまいました。


COMPONENT_REATTACHEDした後フレームとコンテナウィンドウのメソッドが2回ずつ発火していることがわかります。

ということでドキュメントを閉じるタイミングでフレームとコンテナウィンドウのリスナーを削除しないといけないことがわかりました。

デスクトップに追加するリスナーも同様にしないといけません。

どのリスナーのメソッドでこれらのリスナーの削除をするかを考えないといけません。


デスクトップ、フレーム、コンテナウィンドウのリスナーを削除しないまま、ファイル→閉じる、ではなく、ファイル→LibreOfficeの終了、としたときはフレームのnotifyClosing()メソッドとそれに続いてコンテナウィンドウのwindowClosed()メソッドが(なぜ2回なのかは不明)呼びだされていることがわかりました。

これらのメソッドはドキュメントを閉じるだけでは呼び出されないので、フレームなどのリスナーを削除するのには使えません。

ドキュメントを閉じるときに発火するのは下から7行目のシートのXModifyListenerのdisposing()メソッドまでです。

コンテナウィンドウのwindowDeactivated()メソッドやドキュメントのOnUnfocusイベントはドキュメントを閉じるときに限らずに発火するので、ドキュメントのOnUnloadイベントが発生したときにフレームとコンテナウィンドウ、デスクトップのリスナーを削除することにしました(上記マクロの228行目)。

これで再度ドキュメントを開いてもフレームなどに複数無駄なリスナーが追加されることを避けることができました。


上記のマクロのWindows10での実行結果です。

linuxBean14.04でも同じ結果でした。

12行目のRemoveListenersとあるファイル名のタイミングでフレームとコンテナウィンドウ、デスクトップのリスナーを削除しています。

(2018.1.14追記。Sourceアトリビュートの内容をファイルに出力するように追加した
ListenersForCalcSourceAttr.odsを作成しました。disposing()の引数のEventObject StructのSourceアトリビュートにはそのリスナーを追加したオブジェクトが入ってくるようです。)

参考にしたサイト


OOoBasic/Window/Listeners - ...?
ウィンドウが閉じられるときに呼ばれるリスナーの解説。今回と結果が異なりました。

次の関連記事:Calc(56)追加できるリスナー一覧: その8

ブログ検索 by Blogger

Translate

Created by Calendar Gadget

QooQ