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

2018-01-03

旧ブログ

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

前の関連記事: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 - ...?
ウィンドウが閉じられるときに呼ばれるリスナーの解説。今回と結果が異なりました。

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

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ