Calc(35)コンテクストメニューをカスタマイズする: その4

2017-10-29

旧ブログ

t f B! P L
Calc(30)コンテクストメニューをカスタマイズする: その3の続きです。Calcドキュメントに埋め込みマクロについて作り込んでいきます。

前の関連記事:Calc(34)セルに入力された日付を取得する方法


コンテクストメニューでセル範囲と日付をセルに入力する例

#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import datetime
import os
from com.sun.star.ui import XContextMenuInterceptor
from com.sun.star.ui import ActionTriggerSeparatorType  # 定数
from com.sun.star.ui.ContextMenuInterceptorAction import EXECUTE_MODIFIED  # enum
from com.sun.star.lang import Locale
def macro(documentevent):  # 引数はcom.sun.star.document.DocumentEvent Struct。
 doc = documentevent.Source  # ドキュメントの取得。
 sheets = doc.getSheets()  # シートコレクションの取得。
 sheet = sheets[0]  # インデックス0のシートを取得。
 sheet.clearContents(511)  # シートのセルの内容をすべてを削除。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.registerContextMenuInterceptor(ContextMenuInterceptor())  # コントローラにContextMenuInterceptorを登録する。
class ContextMenuInterceptor(unohelper.Base, XContextMenuInterceptor):  # コンテクストメニューのカスタマイズ。
 def __init__(self):
  filename = os.path.basename(__file__)  # このファイル名を取得。フルパスは"vnd.sun.star.tdoc:/4/Scripts/python/filename.py"というように番号(ContentID)が入ってしまう。
  self.baseurl = "vnd.sun.star.script:{}${}?language=Python&location=document".format(filename, "{}")  # ScriptingURLのbaseurlを取得。
 def notifyContextMenuExecute(self, contextmenuexecuteevent):  # 引数はContextMenuExecuteEvent Struct。
  baseurl = self.baseurl  # ScriptingURLのbaseurlを取得。
  contextmenu = contextmenuexecuteevent.ActionTriggerContainer  # すでにあるコンテクストメニュー(アクショントリガーコンテナ)を取得。
  submenucontainer = contextmenu.createInstance("com.sun.star.ui.ActionTriggerContainer")  # サブメニューにするアクショントリガーコンテナをインスタンス化。
  addMenuentry(submenucontainer, "ActionTrigger", 0, {"Text": "Set Address", "CommandURL": baseurl.format(getAddress.__name__)})  # サブメニュー0を挿入。引数のない関数名を渡す。
  addMenuentry(submenucontainer, "ActionTrigger", 1, {"Text": "Set Today", "CommandURL": baseurl.format(getToday.__name__)})  # サブメニュー1を挿入。引数のない関数名を渡す。 
  addMenuentry(contextmenu, "ActionTrigger", 0, {"Text": "Customized Menu", "SubContainer": submenucontainer})  # サブメニューを一番上に挿入。
  addMenuentry(contextmenu, "ActionTriggerSeparator", 1, {"SeparatorType": ActionTriggerSeparatorType.LINE})  # アクショントリガーコンテナのインデックス1にセパレーターを挿入。
  return EXECUTE_MODIFIED  # このContextMenuInterceptorでコンテクストメニューのカスタマイズを終わらす。
def getAddress():  # 選択範囲の左上セルに選択範囲の文字列アドレスを挿入。
 doc = XSCRIPTCONTEXT.getDocument()
 selection = doc.getCurrentSelection()
 firstcell = getFirtstCell(selection)
 firstcell.setString(getRangeAddressesAsString(selection))
def getToday():
 doc = XSCRIPTCONTEXT.getDocument()
 selection = doc.getCurrentSelection() 
 firstcell = getFirtstCell(selection)
 today = datetime.date.today()  # 今日の日付を取得。
 firstcell.setFormula(today.isoformat())  # 日付の入力は年-月-日 または 月/日/年 にしないといけないらしい。
 numberformats = doc.getNumberFormats()  # ドキュメントのフォーマット一覧を取得。
 formatstring = "YYYY-MM-DD"  # フォーマット。デフォルトのフォーマット一覧はCalcの書式→セル→数値でみれる。
 locale = Locale(Language="ja", Country="JP")  # フォーマット一覧をくくる言語と国を設定。
 formatkey = numberformats.queryKey(formatstring, locale, True)  # formatstringが既存のフォーマット一覧にあるか調べて取得。第3引数のブーリアンは意味はないはず。
 firstcell.setPropertyValue("NumberFormat", formatkey)  # セルの書式を設定。
def getFirtstCell(rng):  # セル範囲の左上のセルを返す。引数はセルまたはセル範囲またはセル範囲コレクション。
 if rng.supportsService("com.sun.star.sheet.SheetCellRanges"):  # セル範囲コレクションのとき
  rng = rng[0]  # 最初のセル範囲のみ取得。
 return rng[0, 0]  # セル範囲の最初のセルを返す。
def addMenuentry(menucontainer, menutype, i, props):  # i: index, propsは辞書。menutypeはActionTriggerかActionTriggerSeparator。
 menuentry = menucontainer.createInstance("com.sun.star.ui.{}".format(menutype))  # ActionTriggerContainerからインスタンス化する。
 [menuentry.setPropertyValue(key, val) for key, val in props.items()]  #setPropertyValuesでは設定できない。エラーも出ない。
 menucontainer.insertByIndex(i, menuentry)  # submenucontainer[i]やsubmenucontainer[i:i]は不可。挿入以降のメニューコンテナの項目のインデックスは1増える。
def getRangeAddressesAsString(rng):  # セルまたはセル範囲、セル範囲コレクションから文字列アドレスを返す。
 absolutename = rng.getPropertyValue("AbsoluteName") # セル範囲コレクションは$Sheet1.$A$4:$A$6,$Sheet1.$B$4という形式で返る。
 names = absolutename.replace("$", "").split(",")  # $を削除してセル範囲のリストにする。
 addresses = []  # 出力するアドレスを入れるリスト。
 for name in names:  # 各セル範囲について
  addresses.append(name.split(".")[-1])  # シート名を削除する。
 return ", ".join(addresses)  # コンマでつなげて出力。
g_exportedScripts = macro, #マクロセレクター(ScriptingURLで呼び出すための設定は不要)に限定表示させる関数をタプルで指定。
このマクロをCalcドキュメントに埋め込んで、「文書を開いた時」にこのマクロを割り当てて保存します。

CalcExamples - p--qのcotextmenuembedded2.odsがそのファイルになります。


ファイルを開いて右クリックするとCustomized Menuという項目が一番上にでてきます。

Set Addressを選択すると選択範囲の文字列アドレスがその範囲の左上のセルに入力されます。

Set Todayを選択するとYYYY-MM-DDの書式で今日の日付を入力します。

ScriptingURLでは引数は混ぜ込めないようなのでコンテクストメニューの項目で呼び出す関数に引数を渡す方法を考えないといけません(OOoSF/Memo - ...?)。

とりあえずマクロなのでグローバル変数のXSCRIPTCONTEXTはどこでも使えます。

ScriptingURLの関数にグローバル変数で引数を渡す


渡したいものをグローバル変数にしてしまえば簡単に解決します、、、というか、グローバル変数を使わない方法にしたかったのですが、そのような方法は思いつきませんでした。
(2017.11.1追記。オートメーションなどではグローバル変数にしても引数を渡せませんでした。Calc(36)コンテクストメニューをカスタマイズする: その5参照。)
import os
from com.sun.star.ui import XContextMenuInterceptor
from com.sun.star.ui import ActionTriggerSeparatorType  # 定数
from com.sun.star.ui.ContextMenuInterceptorAction import EXECUTE_MODIFIED  # enum
from com.sun.star.lang import Locale
def macro(documentevent):  # 引数はcom.sun.star.document.DocumentEvent Struct。
 doc = documentevent.Source  # ドキュメントの取得。
 sheets = doc.getSheets()  # シートコレクションの取得。
 sheet = sheets[0]  # インデックス0のシートを取得。
 sheet.clearContents(511)  # シートのセルの内容をすべてを削除。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.registerContextMenuInterceptor(ContextMenuInterceptor())  # コントローラにContextMenuInterceptorを登録する。
 global getFormatKey  # ScriptingURLで呼び出されたマクロで使うためにグローバル変数にする。
 getFormatKey = formatkeyCreator(doc)  # ドキュメントのformatkeyを返す関数を取得。
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。  
 functionaccess = smgr.createInstanceWithContext("com.sun.star.sheet.FunctionAccess", ctx)
 global todayvalue
 todayvalue = functionaccess.callFunction("TODAY", ())  # スプレッドシート関数で今日の日付のシリアル値を取得。
class ContextMenuInterceptor(unohelper.Base, XContextMenuInterceptor):  # コンテクストメニューのカスタマイズ。
 def __init__(self):
  filename = os.path.basename(__file__)  # このファイル名を取得。フルパスは"vnd.sun.star.tdoc:/4/Scripts/python/filename.py"というように番号(LibreOfficeバージョン番号?)が入ってしまう。
  self.baseurl = "vnd.sun.star.script:{}${}?language=Python&location=document".format(filename, "{}")  # ScriptingURLのbaseurlを取得。
 def notifyContextMenuExecute(self, contextmenuexecuteevent):  # 引数はContextMenuExecuteEvent Struct。
  baseurl = self.baseurl  # ScriptingURLのbaseurlを取得。
  contextmenu = contextmenuexecuteevent.ActionTriggerContainer  # すでにあるコンテクストメニュー(アクショントリガーコンテナ)を取得。
  submenucontainer = contextmenu.createInstance("com.sun.star.ui.ActionTriggerContainer")  # サブメニューにするアクショントリガーコンテナをインスタンス化。
  addMenuentry(submenucontainer, "ActionTrigger", 0, {"Text": "Set ~Today", "CommandURL": baseurl.format(getToday.__name__)})  # サブメニューを挿入。引数のない関数名を渡す。
  addMenuentry(submenucontainer, "ActionTrigger", 1, {"Text": "Set ~Yesterday", "CommandURL": baseurl.format(getYesterday.__name__)})  # サブメニューを挿入。引数のない関数名を渡す。
  addMenuentry(submenucontainer, "ActionTrigger", 2, {"Text": "Set T~omorrow", "CommandURL": baseurl.format(getTomorrow.__name__)})  # サブメニューを挿入。引数のない関数名を渡す。
  addMenuentry(contextmenu, "ActionTrigger", 0, {"Text": "Customized Menu", "SubContainer": submenucontainer})  # サブメニューを一番上に挿入。
  addMenuentry(contextmenu, "ActionTriggerSeparator", 1, {"SeparatorType": ActionTriggerSeparatorType.LINE})  # アクショントリガーコンテナのインデックス1にセパレーターを挿入。
  return EXECUTE_MODIFIED  # このContextMenuInterceptorでコンテクストメニューのカスタマイズを終わらす。
def getToday():
 setDate(todayvalue)  # 今日の日付を取得。
def getYesterday():
 setDate(todayvalue-1)  # 昨日の日付を取得。
def getTomorrow():
 setDate(todayvalue+1)  # 明日の日付を取得。
def setDate(datetimevalue):  # セルに日付を入力する。
 doc = XSCRIPTCONTEXT.getDocument()  # ドキュメントを取得。
 selection = doc.getCurrentSelection()  # セル範囲を取得。
 firstcell = getFirtstCell(selection)  # セル範囲の左上のセルを取得。
 firstcell.setFormula(datetimevalue)  # 日付の入力は年-月-日 または 月/日/年 にしないといけないらしい。
 firstcell.setPropertyValue("NumberFormat", getFormatKey("YYYY-MM-DD"))  # セルの書式を設定。 
def formatkeyCreator(doc):
 numberformats = doc.getNumberFormats()  # ドキュメントのフォーマット一覧を取得。デフォルトのフォーマット一覧はCalcの書式→セル→数値でみれる。
 locale = Locale(Language="ja", Country="JP")  # フォーマット一覧をくくる言語と国を設定。 
 def getFormatKey(formatstring):  # formatstringからFormatKeyを返す。
  formatkey = numberformats.queryKey(formatstring, locale, True)  # formatstringが既存のフォーマット一覧にあるか調べて取得。第3引数のブーリアンは意味はないはず。 
  if formatkey == -1:  # デフォルトのフォーマットにformatstringがないとき。
   formatkey = numberformats.addNew(formatstring, locale)  # フォーマット一覧に追加する。保存はドキュメントごと。
  return formatkey
 return getFormatKey
def getFirtstCell(rng):  # セル範囲の左上のセルを返す。引数はセルまたはセル範囲またはセル範囲コレクション。
 if rng.supportsService("com.sun.star.sheet.SheetCellRanges"):  # セル範囲コレクションのとき
  rng = rng[0]  # 最初のセル範囲のみ取得。
 return rng[0, 0]  # セル範囲の最初のセルを返す。
def addMenuentry(menucontainer, menutype, i, props):  # i: index, propsは辞書。menutypeはActionTriggerかActionTriggerSeparator。
 menuentry = menucontainer.createInstance("com.sun.star.ui.{}".format(menutype))  # ActionTriggerContainerからインスタンス化する。
 [menuentry.setPropertyValue(key, val) for key, val in props.items()]  #setPropertyValuesでは設定できない。エラーも出ない。
 menucontainer.insertByIndex(i, menuentry)  # submenucontainer[i]やsubmenucontainer[i:i]は不可。挿入以降のメニューコンテナの項目のインデックスは1増える。
g_exportedScripts = macro, #マクロセレクター(ScriptingURLで呼び出すための設定は不要)に限定表示させる関数をタプルで指定。
18行目のFormatKeyを取得する関数をグローバル変数宣言して、45行目で使っています。

18行目でグローバル変数と宣言した今日の日付-時刻シリアル値を、コンテクストメニューの項目のCommandURLに割り当てられた関数の中の35、37、39行目で使っています。


コンテクストメニューの項目を選ぶと最初のセルに日付が入力されます。

Textプロパティで~を使って、ショートカットキーを指定しました。

CalcExamples - p--qのcotextmenuembedded3.odsがこのマクロを埋め込んで、文書を開いた時に呼び出すように設定したCalcドキュメントになります。

Windows10で実行してみると、一番最初に実行するときはコンパイルするのか、コンテクストメニューから呼び出した時に実行するのに結構時間がかかります。

ちなみに、このScriptingURLで呼び出す関数にデコレーターを付けることはできませんでした。

参考にしたサイト


OOoSF/Memo - ...?
コマンドURLではマクロに引数を渡せないようです。

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

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ