gotin blog

Whatever gotin wanna write

gmacsでプレゼンテーション

以下、最速インターフェース研究会 :: ライブドアのテクノロジーセミナーでしゃべってきました より引用。

発表資料はpdfかhtmlで公開する予定ですが、とりあえずテキストだけ先にアップしておきます。

http://ma.la/files/livedoor/seminar2006/seminar.txt

プレゼンツールがFirefox専用だったりするので、これも少し手直しして公開予定です。
こういう機会があるたびにプレゼンツールを作ってるような気がします。

プレゼン原稿をテキスト形式で用意して、それをJavaScriptなどを駆使してプレゼンテーションの形式にする、という技がとある業界ではやっているようです。私もそういうことをしてました。

あぁ、それならgmacs*1でそれをサポートできたら面白いかも、と思ってgmacs用拡張スクリプトを作ってみました。
とりあえず実行結果は↓こんな感じです。
f:id:gotin:20061215130123p:image

f:id:gotin:20061215130208p:image
↑ちなみにこれは
http://ma.la/files/livedoor/seminar2006/seminar.txt
を対象に実行したもので、私のプレゼン資料ではありません。
あ、ということは著作権的に問題ありかも?
ありでしたらご連絡ください。速攻で消しますので。
(って消せばいいって問題じゃない気もしますが。。)

そして↓こちらが肝心のgmacs用プレゼンテーション拡張スクリプトです。*2
presentation.js
中身はこちら↓

var Presentation = {
	sectionNumber:0,
	pageNumber:0,
	pages:[],
	init:function(path){
		Presentation.makeScreen();
	},
	
	execute:function(path){
                Editor.hide();
		var text = FileManager.load(path);
		with(Presentation){
			parse(text);
			show();
			sectionNumber = 0;
			pageNumber = 0;
			viewPage();
		}
	},

	viewPage:function(){
		var page = Presentation.pages[Presentation.sectionNumber];
		$("__presentation_title__").textContent = page.title;
		$("__presentation_body__").innerHTML = page.pages[Presentation.pageNumber];
	},

	viewNextPage:function(){
		Presentation.pageNumber++;
		if(Presentation.pageNumber >= Presentation.pages[Presentation.sectionNumber].pages.length){
			Presentation.pageNumber = 0;
			Presentation.sectionNumber++;
			if(Presentation.sectionNumber >= Presentation.pages.length){
				Presentation.sectionNumber = Presentation.pages.length - 1;
				return;
			}
		}
		Presentation.viewPage();
	},

	viewPreviousPage:function(){
		Presentation.pageNumber--;
		if(Presentation.pageNumber < 0){
			Presentation.sectionNumber--;
			if(Presentation.sectionNumber < 0){
  				Presentation.pageNumber = 0;
				Presentation.sectionNumber = 0;
				return;
			}
			Presentation.pageNumber = Presentation.pages[Presentation.sectionNumber].pages.length - 1;
		}
		Presentation.viewPage();
	},
	

	parse:function(text){
                Presentation.pages = [];
		var lines = text.replace(/\r/g, "").split("\n");
		var inTag = false;
		var inNestTag = false;
		var section = [];
		var buffer = [];
		var title = "";
		var makeTag = function(tag, text){
			buffer.push("<" + tag + ">" + text + "</" + tag + ">");
		};

		var openTag = function(tag){
			if(inNestTag){
				buffer.push("</" + inNestTag + ">");
				inNestTag = false;
			}
			if(inTag && inTag != tag){
				buffer.push("</" + inTag + ">");
			}
			if(inTag != tag){
				inTag = tag;
				buffer.push("<" + inTag + ">");
			}
			
		};

		var openNestTag = function(tag){
			if(inNestTag && inNestTag != tag){
				buffer.push("</" + inNestTag + ">");
			}
			if(!inTag){
				inTag = tag;
				buffer.push("<" + inTag + ">");
			}
			if(inNestTag != tag){
				inNestTag = tag;
				buffer.push("<" + inNestTag + ">");
			}
			
		};

		var closeNestTag = function(){
			if(inNestTag){
				buffer.push("</" + inNestTag + ">");
				inNestTag = false;
			}
		};


		var closeTag = function(){
			if(inTag){
				closeNestTag();
				buffer.push("</" + inTag + ">");
				inTag = false;
			}
		};

		lines.forEach(function(line){
				if(line.match(/^\*([^\*].*)/)){
					closeTag();
					section.push(buffer.join("\n"));
					Presentation.pages.push({title:title, pages:section});
					section = [];
					buffer = [];
					title = RegExp.$1;
				} else if(line.match(/^\*\*(.*)/)){
					closeTag();
					section.push(buffer.join("\n"));
					buffer = [];
					makeTag("h2", RegExp.$1);
				} else if(line.match(/^\-([^\-].*)/)){
					openTag("ul");
					makeTag("li", RegExp.$1);
				} else if(line.match(/^\-\-(.*)/)){
					openNestTag("ul");
					makeTag("li", RegExp.$1);
				} else if(line.match(/^\#([^\#].*)/)){
					openTag("ol");
					makeTag("li", RegExp.$1);
				} else if(line.match(/^\#\#(.*)/)){
					openNestTag("ol");
					makeTag("li", RegExp.$1);
				} else if(!line){
					// nothing to do
				} else {
					openTag("pre");
					buffer.push(line);
				}
			});
		closeTag();
		section.push(buffer.join("\n"));
		Presentation.pages.push({title:title, pages:section});
	},

	makeScreen:function(){
		var div = mktag("div", {id:"__presentation__"});
		div.style.display = "none";
		var title = mktag("h1", {id:"__presentation_title__"});
		div.appendChild(title);
		var body = mktag("div", {id:"__presentation_body__"});
		div.appendChild(body);
		document.body.appendChild(div);
		Presentation.makeStyle();
	},

	makeStyle:function(){
		var s= [];
		s.push("#__presentation__ {");
		s.push(" text-align:left;");
		s.push(" background-color:white;");
		s.push(" position:fixed;");
		s.push(" top:0;");
		s.push(" left:0;");
		s.push(" width:100%;");
		s.push(" height:100%;");
		s.push(" padding:10px;");
		s.push(" margin:0;");
		s.push(" opacity:1.00;");
		s.push(" z-index:9999999;");
		s.push(" display:none;");
		s.push("}");
		s.push("#__presentation__ *{");
                s.push(" font-family:'Lucida Grande','Hiragino Kaku Gothic Pro','ヒラギノ角ゴ Pro W3',Verdana,'MS Pゴシック',sans-serif;");
		s.push(" font-size:30px;");
		s.push(" text-align:left;");
		s.push(" line-height:150%;");
		s.push(" background-color:white;");
		s.push(" padding:0;");
		s.push(" margin:5px;");
		s.push("}");
		s.push("#__presentation_body__ {");
		s.push(" text-align:left;");
		s.push(" background-color:white;");
		s.push(" width:100%;");
		s.push(" height:100%;");
		s.push(" padding:10px;");
		s.push(" margin:0;");
		s.push("}");
		s.push("#__presentation_body__ h2{");
		s.push(" text-align:left;");
		s.push(" font-size:40px;");
		s.push(" font-weight:bold;");
		s.push(" background:white;");
		s.push("}");
		s.push("#__presentation_title__  {");
		s.push(" -moz-border-radius:10px;");
		s.push(" background:lightblue;");
		s.push(" font-size:50px;");
		s.push(" font-weight:bold;");
		s.push(" width:100%;");
		s.push(" margin:10px;");
		s.push(" text-align:left;");
		s.push(" vertical-align:middle;");
		s.push(" padding:2px;");
		s.push("}");
		GM_addStyle(s.join("\n"));
	},
	
	show:function(){
		$("__presentation__").style.display = "block";
	},

	hide:function(){
		$("__presentation__").style.display = "none";
	},

	isShown:function(){
		return $("__presentation__").style.display == "block";
	},

};

Presentation.init();

Commander.addCommand("present", 
                     Presentation.execute,
                     [{name:"path",defaultValue:function(){return Editor.currentPath;}}]);


document.addEventListener("keydown", function(event){if(KeyProcessor.code(event) == "n" && Presentation.isShown()){Presentation.viewNextPage();}}, true);
document.addEventListener("keydown", function(event){if(KeyProcessor.code(event) == "p" && Presentation.isShown()){Presentation.viewPreviousPage();}}, true);
document.addEventListener("keydown", function(event){if(KeyProcessor.code(event) == "q" && Presentation.isShown()){Presentation.hide();}}, true);


例によって上のスクリプトを /plugins/以下におきます(そうするとgmacs起動時に自動的に読み込まれる)。

その上で、
http://ma.la/files/livedoor/seminar2006/seminar.txt
↑のような形式のプレゼン原稿用テキストファイルを(gmacsファイルシステムの)どこかに保存します。

そして、Ctrl-c(winな方はAlt-x)でコマンド入力状態にして、"present"と入力
f:id:gotin:20061215132440p:image
次に出てくるダイアログで先ほど保存したプレゼン原稿用テキストファイルのパスを入力すると、
プレゼンモードになります。
f:id:gotin:20061215132558p:image
nで次のページ、pで前のページ、qでプレゼン終了です。

ただし、
http://ma.la/files/livedoor/seminar2006/seminar.txt
の中でimgタグが使われているのですが、これが相対パスになっているので、
http://ma.la/files/livedoor/seminar2006/
にアクセスした状態で実行しないとうまく画像が表示されません。

自分でつくるときはimgタグのsrc属性の値などは絶対パスにしておくとよいです。
もしくは、presentation.jsがうまいことbaseタグを挿入するようなつくりにするか、ですね。

ちなみに、presentation.jsの中でもスタイルシートを定義しているのですが、アクセスしているページ内のスタイルシートによって見栄えが変わってきます。テーマを選択してるような雰囲気ですよね。最初はどのページでも見ても同じ見栄えになるようにせねばな、と思っていましたが、それはそれで面白いや、ということでそのままにしてます。

*1:gmacsは[http://d.hatena.ne.jp/gotin/20061203/1165512290:title=GreasemonkeyEmacsチックなテキストエディタ]の最新版のGreasemonkeyユーザスクリプトのことです。最新版は[http://gomaxfire.dnsdojo.com/g_editor/gmacs.user.js:title=こちら]。

*2:本来ならスクリプトをウェブ上にアップしておくところなんですが、今それができる環境ではないので今の所こんな形で掲載しておきます。きっと今日中にはアップします→アップしました。