前の関連記事:Calc(54)追加できるリスナー一覧: その6
listenersforcalc2.pyでリスナーのメソッドの発火をファイル名に記録する
#!/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 - ...?
ウィンドウが閉じられるときに呼ばれるリスナーの解説。今回と結果が異なりました。
0 件のコメント:
コメントを投稿