Emacsでカレンダーコンポーネントを作った

(2011/07/25 追記: 設定方法などについては最新の記事を参照してください→[emacs][ui] Emacs用カレンダー calfw v1.2 リリース - 技術日記@kiwanami)

Emacsで使えるカレンダーが必要だったので作りました。

(追記 2011/01/08 ical連携の設定に足りないところがありましたので修正いたしました。また、Emacs22ではpeccuさんの所の追加設定を参照してみてください。 → CarbonEmacsでかるふわ使っちゃうよ!(ぺっくブログミラー@peccul))



こんな感じ

完成までには近所の人たちから、「カレンダーなら壁に掛かってるだろう」とか、「Google Calendarという無料のカレンダーがありまして」とか、「カレンダーならiPadにもあります」とか、幾多の助言を頂きました。ありがとう。分かってます。自分だって、2年前にPythonで作ったり、雑誌の特集でも実装した自覚もあります。でも自分にとって納得いくものがなかったので作りたかったんです。後悔はしていません。

あらすじ

  • インストールと使い方説明
    • howmとの連携
    • iCalendar形式(Google Calendarとか)との連携
  • 各種設定について
  • 現状と今後の展開

使い方

インストール

カレンダーの基本機能についてはEmacs本体(手元では23.1で試しています)があれば十分です。日本用の祝日を表示させるために japanese-holiday.el を入れておくと便利です。japanese-holidayについては以下のサイトなどを参照してください。

自分の所では以下のように書いています。

(add-hook 'calendar-load-hook
          (lambda ()
            (require 'japanese-holidays)
            (setq calendar-holidays
                  (append japanese-holidays local-holidays other-holidays))))

カレンダー本体 calfw.el については auto-install.elで以下の式を評価して入れるか、ダウンロードしてload-pathの場所に置いてください。

;; auto-installを使う場合
(auto-install-from-url "https://github.com/kiwanami/emacs-calfw/raw/master/calfw.el")

ダウンロード

手元では安定して動いていますが、今後インタフェースや追加のビューを実装しようと思っています。しばらく変化すると思いますが、区切りの良いときにリリース報告をしていきたいと思っています。

開発はgithubにて行っていますので、興味ある方は clone しておくと便利かも知れません。

単純なカレンダー表示

まず、単純な使い方について説明します。

以下のコードを評価すると今月のカレンダーが表示されます。

(require 'calfw) ; 初回一度だけ
(cfw:open-calendar-buffer) ; カレンダー表示



起動した画面(動作確認用データ)

単純に起動すると何も表示されずに寂しいかも知れません。そんなときは動作確認用の予定がありますので、以下のコードを実行して2011年1月に移動してみてください。

(cfw:contents-debug-data) ; 動作確認用データを表示するようにする
キーバインド

カレンダーの中では以下のようなキーバインドが使えます。

日付移動
→, C-f, l 次の日へ
←, C-b, h 前の日へ
↑, C-p, k 前の週へ
↓, C-n, j 次の週へ
C-a, ^ 週の頭へ
C-e, $ 週末へ
[home] 月の初めへ
[end] 月末へ
[PgUp], < 前の月へ
[PgDown], > 次の月へ
t 今日へ
g 日付指定移動 (YYYY/MM/DD)
操作
r 表示更新

これらは cfw:calendar-mode-map で定義されていますので、カスタマイズすることが出来ます。

機能

このカレンダーの表示内容や機能について簡単に説明します。

  • 表示について
    • 通常のグレゴリオ暦で表示(calendar.elと同じ)
      • デフォルトでは Google Calendar 風カラー
      • サイズは表示中のウインドウサイズから適当に計算
    • Emacs標準の holiday.el から祝日を取得
      • japanese-holiday.elを入れることによって日本の祝日に対応
    • 今日とフォーカスのある日付をハイライト(オーバーレイでハイライト)
    • 日々の予定を並べて表示
      • 予定の件数が表示されるのでスペースが小さくても何かあることは分かる
    • 期間予定の表示
      • 期間が重複していたらある程度無駄がないように積み重ねる
    • 日付に注釈を表示
      • 月齢や六曜、参考情報(今日は何の日とか)を表示できる


  • 拡張、組み込みなど
    • データ入力をカスタマイズ可能
    • 描画先も選べるので別のアプリケーションバッファに組み込める

calfw.el はそれなりにかっこいい見た目と、他のアプリケーションから使いやすいような設計(VBなどにあるカレンダーコンポーネントなどのイメージ)を目指して作りました。このあたりの詳細な設計や拡張方法などについてはまた別エントリでやると思います。

他アプリケーションとの連携

calfw.el はただのカレンダーコンポーネントですので、このままではあまりうれしくありません。他のアプリケーションのビューとして使うことによって便利になります。そもそも、今回ご紹介する howm と Google Calendar との連携がやりたくて作りました。

howmとの連携

howmのスケジュールやTODOをカレンダー内に表示することが出来ます。まず以下の追加プログラムをインストールします。(同じディレクトリにあります)

最小限の連携コードは以下のようです。
(追記 2011/01/11 howmロード前に評価されないようにしました。@litzerさんありがとうございます。)

(eval-after-load "howm-menu"
  '(progn
    (require 'calfw-howm)
    (cfw:install-howm-schedules)
    (define-key howm-mode-map (kbd "M-C") 'cfw:open-howm-calendar)
))

これで、howm-mode使用時(howmのメニューなど)で M-C でhowmのカレンダーを表示することが出来ます。
また、予定のある日付でEnterキーを押すと、日付でhowmの記事を検索します。ここから予定の編集などが出来ます。
qでカレンダーバッファを終了(kill-buffer)します。

HowmとElscreenの連携を使っている人は、バインド先のコマンドはこちらの方が良いかもしれません。

(define-key howm-mode-map (kbd "M-C") 'cfw:elscreen-open-howm-calendar)

こちらは新しいスクリーンを作成してそちらでカレンダーを表示します。

さらに、howmのメニューファイルに以下の行を追加すると、そこにカレンダーを表示させます。

%here%(cfw:howm-schedule-inline)

この場合、以下のような画面になります。



Howmのメニューに表示!

さらに、このカレンダー内部では前述の日付移動のキーバインドが使えますので、メニュー内で日付や月の移動、またその日の予定一覧の表示が出来ます。(これが自分がやりたかったことです!!!)

ちなみに、このままだとカレンダーのリージョンからカーソールが脱出できません。カレンダーからカーソールを脱出させたくなったら、TABキーで次のaction-lockに移動するか、マウスを使うと良いと思います。(今のところ自分はあまりこれで困ってはないのですが、キーマップをちょっと設定する必要があるかも知れません。)

さらに、自分は予定は一つのメモ(「2011年予定」とか)に集約しているのですが、そうするとカレンダーから日付を選んで予定を入力するように出来ます。自分が使っているHowm連携のコードは以下のようです。

;; howm and calendar framework

(defvar my-howm-schedule-page "2011年予定") ; 予定を入れるメモのタイトル

(defun my-cfw-open-schedule-buffer ()
  (interactive)
  (let* 
      ((date (cfw:cursor-to-nearest-date))
       (howm-items 
        (howm-folder-grep
         howm-directory
         (regexp-quote my-howm-schedule-page))))
    (cond
     ((null howm-items) ; create
      (howm-create-file-with-title my-howm-schedule-page nil nil nil nil))
     (t
      (howm-view-open-item (car howm-items))))
    (goto-char (point-max))
    (unless (bolp) (insert "\n"))
    (insert
     (format "[%04d-%02d-%02d]@ "
             (calendar-extract-year date)
             (calendar-extract-month date)
             (calendar-extract-day date)))))

(eval-after-load "howm-menu"
  '(progn
     (require 'calfw-howm)
     (cfw:install-howm-schedules)
     (define-key howm-mode-map (kbd "M-C") 'cfw:elscreen-open-howm-calendar)
     (define-key cfw:howm-schedule-map (kbd "i") 'my-cfw-open-schedule-buffer)
     (define-key cfw:howm-schedule-inline-keymap (kbd "i") 'my-cfw-open-schedule-buffer)
     ))

(setq cfw:howm-schedule-summary-transformer 
  (lambda (line) (split-string (replace-regexp-in-string "^[^@!]+[@!] " "" line) " / ")))

これで普通のスケジュール管理(Google Calendar, iCal, Outlookなど)と同じように、「カレンダーから空いている日を探して予定を入れる」というユースケースが実現できるようになりました。

うれしくなったので、どういう画面遷移になっているかをまとめました。



Howm連携の画面遷移

普通です! Howmを始めてはや6年。これでやっとEmacsにも普通のスケジュール管理がやってきました!

iCalendar形式(Google Calendarや他のスケジュール管理ソフトとの連携)

カレンダーといえばiCalendar形式です。Emacsにも icalendar.el というプログラムが付いていますので ICS ファイル自体は読み書きできるようになっています。ということで、iCalendar形式を表示できるようにしてみました。



Emacs と Google Calendar のツーショット

まず以下の追加プログラムをインストールします。(これも同じディレクトリにあります)

Google Calendar との最小限の連携コードは以下のようです。

(require 'calfw-ical)
(cfw:install-ical-schedules) ; (2010/01/08追記)
(setq cfw:ical-calendar-contents-sources '("http://www.google.com/calendar/ical/〜.ics"))
; (setq cfw:ical-calendar-annotations-sources '("http://www.google.com/calendar/ical/〜.ics")) ; ←省略可 (2010/01/11追記)

httpの場合はオンライン(url-retrieve-synchronously)で ICS ファイルを取ってきて表示します。設定すべきURLは、GoogleCalendarのページの左側にある、各カレンダーの設定のページに行って、そこの以下の図で示すあたりにあります。一般公開でないカレンダーの場合は(大抵そうだと思いますが)、「限定公開 URL:」の方のURLを使うと便利だと思います。



Google Calendar での iCalendarのURLの場所

一度取得したスケジュールの内容はメモリにキャッシュします。キャッシュをクリアして表示を更新したい場合は、 cfw:ical-calendar-clear-cache コマンドを実行してください。どこかのキーにバインドしたり、 rでの更新とくっつけても良いかもしれません。

cfw:ical-calendar-contents-sources に表示したいスケジュールのURLを入れるのですが、文字列のリストですので複数指定できます。また、ローカルのファイルパスも指定できますので、iCalやOutlookとも連携できると思います(が、手元では試してません)。

現在の実装では、完全なiCalendar形式には対応していません。自分が最低限必要な範囲のみ実装しています。特に繰り返し予定がまだです。単純な変換作業ですので、どなたか手伝ってもらえると助かります。

各種設定について

簡単な設定・カスタマイズについて簡単に紹介します。

月、曜日の表記

月や曜日の表示は calendar.el の設定をそのまま使っています。以下のコードを修正すれば変更できると思います。

;; 月
(setq calendar-month-name-array
  ["January" "February" "March"     "April"   "May"      "June"
   "July"    "August"   "September" "October" "November" "December"])

;; 曜日
(setq calendar-day-name-array
      ["Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday"])

;; 週の先頭の曜日
(setq calendar-week-start-day 0) ; 日曜日は0, 月曜日は1
キーバインド

基本的なキーバインドは cfw:calendar-mode-map で定義しています。
calendar.el から見ると足りないコマンドなどがあると思います。

howm連携するバッファのキーバインドは cfw:howm-schedule-map で定義しています。また、howmのメニュー埋め込みのキーバインドは、 cfw:howm-schedule-inline-keymap で定義しています。これらは表示時に上の cfw:calendar-mode-map を親キーマップにして適用されますので、howmで必要なキーマップのみ定義しています。

face : 色とかフォントとか

calfw.el の先頭でいろいろ設定しています。現在は Google Calendar 風の色になっています。

自分好みに変えるには、 defface の定義を自分の設定ファイルの (require 'calfw) の前にコピーして、そこでカスタマイズすると早いと思います。変わらない場合は defface 前で face-spec-reset-face とかでリセットするといいかもしれません。

自分がdark系の色を使っていないため、もし良い感じの色が出来たら教えてください。

フォントについてですが、見ての通り等幅前提です。Emacs23以上だと日本語込みの等幅がうまくいかないときがあるかも知れません。使用フォントやOSの環境依存が大きいですので、設定については各自でお願いします。

現状と今後の展開

コンポーネント化

現在、グローバル変数で情報ソースとのやりとりを行っていますが、今後引数などで渡せるようにして、カレンダーのインスタンスごとに分けたいと思っています。また、その場合データの持ち方とキーバインドとのつなぎ方が問題になってくると思いますので、このあたりのうまい解決方法を考えています。

とりあえず、現状の情報設定方法は変わるかも知れないということをお知らせしておきます。

別ビュー

1ヶ月単位でなくて、1週間や2週間、あるいは数ヶ月の単位で表示したいときがあると思いますので、そのビューを作ろうとしています。

他連携

まず、おそらくユーザーの多い org-mode との連携が考えられますが、自分が使っていないのでどなたかにお願いしたいです。それどころか、そもそもEmacs標準のdiaryとも連携してないです。

ドキュメントなど

ある程度コンポーネント化が固まってからがいいかなと思っていますが、作るのに時間がかかりそうだったら先に何か書くかも知れません。

(追記 2011/01/08-1:iCalのリンクの場所追加。@alfaladio さんにバグ報告を頂きました。ありがとうございました。)
(追記 2011/01/08-2:iCal連携の設定elispが足りないところを追加。id:peccuさんご指摘ありがとうございました。)
(追記 2011/01/11:iCal連携の設定elispで必須でないものがありましたのでコメントアウトしました。またまたid:peccuさんご指摘ありがとうございました。)