/* vim: set encoding=utf8:
 *
 * shiki.c
 *
 * Copyright(C)2006 WAKATSUKI toshihiro
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * $Id: shiki.c,v 1.22 2006/11/13 05:37:07 aloha Exp $
 */

#include<gauche.h>
#include<gtk/gtk.h>
#include<gdk/gdkkeysyms.h>

static gint editor_indent_width = 2;

/* タブごとの情報を管理する構造体 */
typedef struct {
  GtkScrolledWindow *tabpage;          /* タブ */
  gchar             *tabpage_label;    /* ベース (ファイル名) のみ */
  GtkTextView       *text_view;        /* タブの表示 */
  GtkTextBuffer     *text_buffer;      /* タブのテキストバッファ */
  gchar             *filename;         /* ファイル名のフルパス */
  ScmObj            env;               /* タブごとに独立した Scheme 環境 */
  guint             delete_handler_id; /* トップレベルに登録したハンドラ ID */
} ShikiTabInfo;

/* エディタ全体に関する情報と，現在表示されているタブ情報へのキャッシュを管理する構造体 */
struct {
  GtkWidget     *editor_window;
  GtkWidget     *statusbar;
  GtkWidget     *modeline_label;
  GList         *tabInfoList;
  gint          current_tabpage_num;
  ShikiTabInfo  *current_tabpage_info;
} shiki_editor;

#define Shiki_EDITOR_WINDOW         shiki_editor.editor_window
#define Shiki_EDITOR_STATUSBAR      shiki_editor.statusbar
#define Shiki_EDITOR_MODELINE_LABEL shiki_editor.modeline_label
#define Shiki_EDITOR_TAB_INFO_LIST  shiki_editor.tabInfoList
#define Shiki_CURRENT_TAB_NUM       shiki_editor.current_tabpage_num
#define Shiki_CURRENT_TAB_INFO      shiki_editor.current_tabpage_info
#define Shiki_CURRENT_TAB           (shiki_editor.current_tabpage_info)->tabpage
#define Shiki_CURRENT_TAB_TITLE     (shiki_editor.current_tabpage_info)->tabpage_label
#define Shiki_CURRENT_TEXT_VIEW     (shiki_editor.current_tabpage_info)->text_view
#define Shiki_CURRENT_TEXT_BUFFER   (shiki_editor.current_tabpage_info)->text_buffer
#define Shiki_CURRENT_FILENAME      (shiki_editor.current_tabpage_info)->filename
#define Shiki_CURRENT_SCHEME_ENV    (shiki_editor.current_tabpage_info)->env

/* プロトタイプ */
static void clear_current_buffer();
static void load_buffer_by_gauche();
static void load_scheme_file_by_gauche(GtkNotebook *notebook);
static gchar *load_cstring_by_gauche(gchar *s);
static void font_selection_ok(GtkWidget *button, GtkWidget *font_dialog);
static void select_font();
static void about_this_application();
static gint get_current_line_number(GtkTextBuffer *buffer); 
static void update_modeline_label();
static void text_buffer_cursor_moved_handler();
static gchar* get_all_buffer_contents(GtkTextBuffer *buffer);
static gboolean save_text_buffer(const gchar *filename, GtkTextBuffer *buffer);
static gchar *get_filename_from_dialog(const gchar *msg);
static void save_file_from_notebook(GtkNotebook *notebook);
static void save_file_handler(GtkWidget *widget, GtkWidget *notebook);
static void save_file_as_from_notebook(GtkNotebook *notebook);
static void save_file_as_handler(GtkWidget *widget, GtkWidget *notebook);
static void really_quit_dialog_yes(GtkWidget *widget, gboolean *flag);
static void really_quit_dialog_no(GtkWidget *widget, gint *flag);
static gboolean not_yet_save_changes_really_quit(GtkTextBuffer *buffer);
static gboolean delete_event_handler(GtkWidget *widget, GdkEvent *event, GtkWidget *buffer);
static void append_tabpage(GtkNotebook *notebook, gchar *filename);
static GtkWidget *new_scrolled_text_buffer(ShikiTabInfo **tabinfo);
static void open_file(GtkNotebook *notebook, gchar *filename);
static void open_file_from_notebook(GtkNotebook *notebook);
static void open_file_handler(GtkWidget *widget, GtkWidget *notebook);
static gchar *eval_cstring_by_gauche(gchar *s);
static void load_region_handler(GtkWidget *widget,  GtkWidget *notebook);
static gboolean is_kakko_or_kokka(gunichar ch, gpointer); 
static gboolean is_kokka(gunichar ch, gpointer);
static gboolean search_sexp_string(GtkTextIter *start);
static gint get_parent_nest_level_at_cursor(GtkTextBuffer *buffer);
static void switch_tabpage_handler(GtkNotebook *notebook, GtkNotebookPage *page, guint pagenum) ;
static void tabsborder_on_off(GtkButton *button, GtkNotebook *notebook);
static void remove_tabpage(GtkNotebook *notebook);
static void remove_tabpage_handler(GtkButton *button, GtkWidget *notebook);
static void append_tabpage_handler(GtkButton *button, GtkNotebook *notebook);
static void rotate_tab_position(GtkButton   *button, GtkNotebook *notebook);
static void forward_current_buffer();
static void backward_current_buffer();
static void line_forward_current_buffer();
static void line_backward_current_buffer();
static gboolean signal_key_press_handler(GtkWidget *notebook, GdkEventKey *event, gpointer contextid);
static gboolean signal_key_release_handler(GtkWidget *notebook, GdkEventKey *event, gpointer contextid);
static void open_online_help(GtkNotebook *notebook);
static void online_help_handler(GtkWidget *button, GtkNotebook *notebook);
static void shiki_editor_window_init(int argc, char **argv);

/* バッファの内容を消去 */
static void clear_current_buffer() {
  GtkTextIter start, end;
  gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &start);
  gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &end);
  gtk_text_buffer_delete(Shiki_CURRENT_TEXT_BUFFER, &start, &end);
}

/* バッファをまるごとロード */
static void load_buffer_by_gauche() {
  GtkTextIter p;
  gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &p);
  gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, "\n\n", -1);
  gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, load_cstring_by_gauche(get_all_buffer_contents(Shiki_CURRENT_TEXT_BUFFER)), -1); 
}

/* ファイルをロード */
static void load_scheme_file_by_gauche(GtkNotebook *notebook) {
  gchar *contents, *text;
  gsize br, bw, len;
  GError *err = NULL;
  gchar *filename = get_filename_from_dialog("File Selection");
  GtkTextIter p;

  if(!filename) return;
  
  gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &p);
  gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, "\n\n", -1);

  if(g_file_get_contents(filename, &contents, &len, NULL)) {
    if(!(text = g_locale_to_utf8(contents, -1, &br, &bw, &err)))
      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, load_cstring_by_gauche(text), -1); 
    else
      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, load_cstring_by_gauche(contents), -1); 
  }
  g_free(text); g_free(contents); g_free(filename);
}

/* gauche を起動して文字列をロード */
static gchar *load_cstring_by_gauche(gchar *s) {
  gchar *msg;

  ScmObj result, error;
  /* 入力文字列ポートを開く */
  ScmObj is = Scm_MakeInputStringPort(SCM_STRING(SCM_MAKE_STR(s)), TRUE);
  /* 出力文字列ポート開く */
  ScmObj os = Scm_MakeOutputStringPort(TRUE);

  Scm_Define(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*input*")), is);
  Scm_Define(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*error*")), SCM_FALSE);
  /* Scheme のレベルでエラーハンドリングをしつつ，ポートから順番に S 式を読み込み，評価していく．コンパイルエラーなどは *error* に捕捉される */
  result = Scm_EvalCString("(guard (e (else (set! *error* e) #f)) (eval (load-from-port *input*) (current-module)))", SCM_OBJ(Scm_UserModule()));

  error = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*error*")), 0);

  /* 文字列を評価した結果をポートに書き込む */
  if (!SCM_FALSEP(error))
    Scm_Write(error, os, SCM_WRITE_DISPLAY);
  else
    Scm_Write(result, os, SCM_WRITE_DISPLAY);

  msg = Scm_GetString(SCM_STRING(Scm_GetOutputString(SCM_PORT(os))));
  /* ポート閉じる */
  Scm_ClosePort(SCM_PORT(is));
  Scm_ClosePort(SCM_PORT(os));

  return msg;
}

static void font_selection_ok(GtkWidget *button, GtkWidget *font_dialog) {
  gchar *font_name = gtk_font_selection_dialog_get_font_name (GTK_FONT_SELECTION_DIALOG (font_dialog));
  if(font_name) {
    GtkRcStyle *style = gtk_rc_style_new ();
    pango_font_description_free(style->font_desc);
    style->font_desc = pango_font_description_from_string(font_name);
    gtk_widget_modify_style (GTK_WIDGET(Shiki_CURRENT_TEXT_VIEW), style);
    g_free (font_name);
  }
}

/* フォントを選択させるイベントハンドラ */
static void select_font(){
  GtkWidget *font_dialog = gtk_font_selection_dialog_new("Font Selection Dialog");
  g_signal_connect (GTK_FONT_SELECTION_DIALOG (font_dialog)->ok_button, "clicked", G_CALLBACK(font_selection_ok), font_dialog);
  gtk_dialog_run(GTK_DIALOG(font_dialog));
  gtk_widget_destroy(font_dialog);
}

/* このアプリケーションについて */
static void about_this_application() {
  GtkAboutDialog *about = GTK_ABOUT_DIALOG(gtk_about_dialog_new());
  const gchar *authors[] = {
    "若槻俊宏 (あろは) <alohakun@gmail.com>"
  };
  gtk_about_dialog_set_authors(about, authors);
  gtk_about_dialog_set_copyright(about, "Copyright(C)2006  WAKATSUKI Toshihiro");
  gtk_about_dialog_set_name(about, "式 (SHIKI)");
  gtk_about_dialog_set_website_label(about, "本当に30日でエディタが出来上がるのかを試してみるBlog");
  gtk_about_dialog_set_website(about, "http://alohakun.blog7.fc2.com/blog-category-29.html");
  gtk_dialog_run(GTK_DIALOG(about));
  gtk_widget_destroy(GTK_WIDGET(about));
}

/* 現在のバッファのカーソル位置の行番号を取得 */
static gint get_current_line_number(GtkTextBuffer *b) {
  GtkTextIter p;
  gtk_text_buffer_get_iter_at_mark(b, &p, gtk_text_buffer_get_insert(b));
  return gtk_text_iter_get_line(&p) + 1;
}

/* バッファの状態に合わせてモードラインを変更 */
static void update_modeline_label() {
  gchar* basename = g_path_get_basename(Shiki_CURRENT_TAB_TITLE);
  gchar* l = g_strdup_printf("-E:%s  %-10s (Gauche Interaction)--L%d--------------------------------------",
      gtk_text_buffer_get_modified(Shiki_CURRENT_TEXT_BUFFER) ? "**" : "--",
      basename, get_current_line_number(Shiki_CURRENT_TEXT_BUFFER));
  gtk_label_set_text(GTK_LABEL(Shiki_EDITOR_MODELINE_LABEL), l);
  g_free(l); g_free(basename);  
}

static void text_buffer_cursor_moved_handler(){
  update_modeline_label();
}

/* テキストバッファから全ての文字列を取り出す */
static gchar* get_all_buffer_contents(GtkTextBuffer *buffer) {
  GtkTextIter start, end;
  gtk_text_buffer_get_start_iter(buffer, &start);
  gtk_text_buffer_get_end_iter(buffer, &end);
  return gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
}

/* buffer の内容をファイル filename に保存 */
static gboolean save_text_buffer(const gchar *filename, GtkTextBuffer *buffer) {
  gchar *contents, *text;
  gsize br, bw;
  GError *err = NULL;

  if(!filename) return FALSE;
  contents = get_all_buffer_contents(buffer);
  text = g_locale_from_utf8(contents, -1, &br, &bw, &err);
  /* 文字列をファイルに保存 */
  g_file_set_contents(filename, text, -1, NULL);
  gtk_text_buffer_set_modified(buffer, FALSE);
  update_modeline_label();
  g_free(contents); g_free(text);
  return TRUE;
}

/* ダイアログを開き，ファイル名を取得．msg はダイアログのメッセージ */
static gchar *get_filename_from_dialog(const gchar *msg) {

  GtkWidget *dialog = gtk_file_selection_new(msg);
  int resp = gtk_dialog_run(GTK_DIALOG(dialog));
  gchar *filename = NULL;

  /* gtk_file_selection_get_filename が返す文字列定数は静的に確保されたバッファを指しているので，コピーしなければならない */
  if(resp == GTK_RESPONSE_OK)
    filename = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(dialog)));

  gtk_widget_destroy(dialog);
  return filename;
}

/* 現在表示されているページの内容をファイルに保存 */
static void save_file_from_notebook(GtkNotebook *notebook) {

  /* ヘルプへの変更は保存しない */
  if(g_ascii_strcasecmp("*help*", Shiki_CURRENT_TAB_TITLE) == 0) return;
    
  /* 変更が無ければ何もしない */
  if(!gtk_text_buffer_get_modified(Shiki_CURRENT_TEXT_BUFFER)) return;

  /* まだファイル名が設定されていなかったら，ダイアログを開いて入力させる */
  if(g_ascii_strcasecmp("*scratch*", Shiki_CURRENT_TAB_TITLE) == 0) {
    gchar *filename = get_filename_from_dialog("Save File As ...");
    if(!filename) return;
    if(!save_text_buffer(filename, Shiki_CURRENT_TEXT_BUFFER)) return;
    gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(Shiki_CURRENT_TAB), filename);
    gtk_window_set_title (GTK_WINDOW(Shiki_EDITOR_WINDOW), filename);
    g_free(filename);
  } else
    save_text_buffer(Shiki_CURRENT_TAB_TITLE, Shiki_CURRENT_TEXT_BUFFER);
}

/* ファイルを保存するイベントハンドラ */
static void save_file_handler(GtkWidget *widget, GtkWidget *notebook) {
  save_file_from_notebook(GTK_NOTEBOOK(notebook));
}

/* 現在表示されているページの内容をファイルに別名保存 */
static void save_file_as_from_notebook(GtkNotebook *notebook) {
  gchar *filename = get_filename_from_dialog("Save File As ...");

  if(!filename) return;
  if(!save_text_buffer(filename, Shiki_CURRENT_TEXT_BUFFER)) return;

  gtk_notebook_set_tab_label_text(notebook, GTK_WIDGET(Shiki_CURRENT_TAB), filename);
  gtk_window_set_title (GTK_WINDOW (Shiki_EDITOR_WINDOW), filename);

  g_free(filename);
}

/* ファイルを別名保存するイベントハンドラ */
static void save_file_as_handler(GtkWidget *widget, GtkWidget *notebook) {
  save_file_as_from_notebook(GTK_NOTEBOOK(notebook));
}

/* YES ボタン，NO ボタンそれぞれで呼ばれる callback */
static void really_quit_dialog_yes(GtkWidget *widget, gboolean *flag){*flag = FALSE;}
static void really_quit_dialog_no(GtkWidget *widget, gint *flag){*flag = TRUE;}

/* 本当に終了してもよろしいですか ? */
static gboolean not_yet_save_changes_really_quit(GtkTextBuffer *buffer) {
  GtkWidget *yes_button, *no_button;
  static GtkWidget *dialog_window = NULL;

  /* 変更が無ければそのまま終了 */
  if(!gtk_text_buffer_get_modified(buffer)) return FALSE;

  if(dialog_window == NULL) {
    gboolean flag = TRUE;
    dialog_window = gtk_dialog_new ();

    /* 本当に終了してもよろしいですか ? ダイアログの初期化 */
    g_signal_connect(G_OBJECT(dialog_window), "delete_event", G_CALLBACK(gtk_false), NULL);
    g_signal_connect(G_OBJECT(dialog_window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog_window)->vbox), 
        gtk_label_new("バッファが変更されています．\nセーブしないで終了しますか ?"), TRUE, TRUE, 0);
    gtk_window_set_title(GTK_WINDOW (dialog_window), "Really Quit ?");
    /* YES のボタン */
    yes_button = gtk_button_new_with_mnemonic("はい (_Y)");
    g_signal_connect(GTK_OBJECT(yes_button), "clicked", G_CALLBACK(really_quit_dialog_yes), &flag);
    g_signal_connect_swapped(GTK_OBJECT(yes_button), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(dialog_window));
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_window)->action_area),  yes_button, TRUE, TRUE, 0);

    /* NO のボタン */
    no_button = gtk_button_new_with_mnemonic("いいえ (_N)");
    g_signal_connect(GTK_OBJECT(no_button), "clicked", G_CALLBACK(really_quit_dialog_no), &flag);
    g_signal_connect_swapped(GTK_OBJECT(no_button), "clicked", G_CALLBACK(gtk_widget_destroy), G_OBJECT(dialog_window));
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog_window)->action_area),  no_button, TRUE, TRUE, 0);

    gtk_window_set_modal(GTK_WINDOW(dialog_window), TRUE);
    gtk_window_set_transient_for(GTK_WINDOW(dialog_window), GTK_WINDOW (Shiki_EDITOR_WINDOW));

    gtk_widget_show_all(dialog_window);
    gtk_main ();
    dialog_window = NULL;

    /* "delete_event" の返り値が FALSE ならば"destory" が発行され，window は破壊される */
    return flag;
  }
  return TRUE;
}

/* バッファがまだ保存されていないのに本当に終了するのかを尋ねる */
static gboolean delete_event_handler(GtkWidget *widget, GdkEvent *event, GtkWidget *buffer){
  return not_yet_save_changes_really_quit(GTK_TEXT_BUFFER(buffer));
}

/* 新しいタブを作る */
static GtkWidget *new_scrolled_text_buffer(ShikiTabInfo **tabinfo) {

  /* タブに対応するタブ情報を作る */
  *tabinfo = g_malloc(sizeof(ShikiTabInfo));

  /* スクロールウィンドを作る */
  (*tabinfo)->tabpage = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
  gtk_scrolled_window_set_policy ((*tabinfo)->tabpage, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  /* テキストビューワとバッファを作る */
  (*tabinfo)->text_view = GTK_TEXT_VIEW(gtk_text_view_new());
  (*tabinfo)->text_buffer = gtk_text_view_get_buffer((*tabinfo)->text_view);
  gtk_container_add(GTK_CONTAINER((*tabinfo)->tabpage), GTK_WIDGET((*tabinfo)->text_view));
  gtk_widget_set_size_request(GTK_WIDGET((*tabinfo)->text_view), 500, 600);
  g_signal_connect((*tabinfo)->text_buffer, "mark_set", G_CALLBACK(text_buffer_cursor_moved_handler), (*tabinfo)->text_view);
  /* タブを削除する際，デリートハンドラを削除しておかないと警告が出るから */
  (*tabinfo)->delete_handler_id = g_signal_connect(Shiki_EDITOR_WINDOW, "delete_event", G_CALLBACK(delete_event_handler), (*tabinfo)->text_buffer);
  /* 様々な初期化処理 */
  /* 括弧の強調表示のためのタグを作る */
  gtk_text_buffer_create_tag ((*tabinfo)->text_buffer, "parent_emphasis_background", "background", "green", NULL);

  return GTK_WIDGET((*tabinfo)->tabpage);
}

/* ファイルを開く */
static void open_file(GtkNotebook *notebook, gchar *filename) {
  gchar *contents, *text;
  gsize br, bw, len;
  GError *err = NULL;
  
  if(g_file_get_contents(filename, &contents, &len, NULL)) {
    GtkTextIter p;

    /* 新しいバッファを開く */
    append_tabpage(notebook, g_strdup(filename));

    if(!(text = g_locale_to_utf8(contents, -1, &br, &bw, &err)))
      gtk_text_buffer_set_text(Shiki_CURRENT_TEXT_BUFFER, contents, len);
    else
      gtk_text_buffer_set_text(Shiki_CURRENT_TEXT_BUFFER, text, len);

    /* バッファ未変更に */
    gtk_text_buffer_set_modified(Shiki_CURRENT_TEXT_BUFFER, FALSE);
    /* カーソル位置を先頭に */
    gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &p);
    gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);
    update_modeline_label();
    gtk_window_set_title (GTK_WINDOW (Shiki_EDITOR_WINDOW), filename);
    gtk_widget_show_all(GTK_WIDGET(notebook));
    g_free(contents); g_free(text); g_free(filename);
  } else
    g_printerr("Get file contents error !\n");
}

/* ファイルをダイアログで指定して開く */
static void open_file_from_notebook(GtkNotebook *notebook) {
  gchar *filename = get_filename_from_dialog("File Selection");

  if(!filename) return;
  open_file(notebook, filename);
}

/* ファイルを開くイベントハンドラ */
static void open_file_handler(GtkWidget *widget,  GtkWidget *notebook) {
  open_file_from_notebook(GTK_NOTEBOOK(notebook));
}

/* gauche を起動して文字列を評価 */
static gchar *eval_cstring_by_gauche(gchar *s) {
  gchar *msg;

  ScmObj result, error; 
  /* 出力文字列ポート開く */
  ScmObj os = Scm_MakeOutputStringPort(TRUE);

  /* Scheme レベルでエラーハンドリング */
  /* http://alohakun.blog7.fc2.com/blog-entry-517.html */
  Scm_Define(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*input*")), SCM_MAKE_STR(s));
  Scm_Define(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*error*")), SCM_FALSE);

  result = Scm_EvalCString("(guard (e (else (set! *error* e) #f)) (eval (read-from-string *input*) (current-module)))", SCM_OBJ(Scm_UserModule()));

  error = Scm_GlobalVariableRef(Scm_UserModule(), SCM_SYMBOL(SCM_INTERN("*error*")), 0);

  /* 文字列を評価した結果をポートに書き込む */
  if (!SCM_FALSEP(error))
    Scm_Write(error, os, SCM_WRITE_DISPLAY);
  else
    Scm_Write(result, os, SCM_WRITE_DISPLAY);

  msg = Scm_GetString(SCM_STRING(Scm_GetOutputString(SCM_PORT(os))));
  /* ポート閉じる */
  Scm_ClosePort(SCM_PORT(os));

  return msg;
}

/* 緑ボタンのハンドリング．バッファの選択範囲の S 式をロード (逐次評価) */
static void load_region_handler(GtkWidget *widget,  GtkWidget *notebook) {

  GtkTextIter start, end, p;
  gchar *code;
  gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &p);
  gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, "\n\n", -1);

  /* マウスで選択されている範囲の文字列を取得 */
  if(gtk_text_buffer_get_selection_bounds(Shiki_CURRENT_TEXT_BUFFER, &start, &end)) {
    code = gtk_text_buffer_get_text(Shiki_CURRENT_TEXT_BUFFER, &start, &end, FALSE);
    gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, load_cstring_by_gauche(code), -1);
    g_free(code);
  }
}

// GtkTextCharPredicate
static gboolean is_kakko_or_kokka(gunichar ch, gpointer p) {
  return ch == '(' || ch == ')';
}
static gboolean is_kokka(gunichar ch, gpointer p) {return ch == ')';}


/* ')' に対応する '(' までの文字列 (S 式) を切り出す */
static gboolean search_sexp_string(GtkTextIter *start) {
  gint nest_level = 0;
  /* カーソル位置の前にある S 式を切り出す */
  while(1) {
    if(!gtk_text_iter_backward_find_char(start, is_kakko_or_kokka, NULL, NULL))
      return FALSE;

    if(gtk_text_iter_get_char(start) == ')')
      nest_level++;
    else {
      if(!nest_level)
        break;
      else
        nest_level--;
    }
  }
  return TRUE;
}

/* カーソル位置のネストレベルを返す */
static gint get_parent_nest_level_at_cursor(GtkTextBuffer *buffer) {
  gint nest_level = 0;
  GtkTextIter start, end;
  gtk_text_buffer_get_start_iter(buffer, &start);
  if(gtk_text_iter_get_char(&start) == '(') nest_level++;

  /* カーソルの位置 (= end) を取得 */
  gtk_text_buffer_get_iter_at_mark(buffer,&end, gtk_text_buffer_get_insert(buffer));

  while(1) {
    /* end まで '(' か ')' を探して見つからなかったら終了 */
    if(!gtk_text_iter_forward_find_char(&start, is_kakko_or_kokka, NULL, &end))
      return nest_level;

    if(gtk_text_iter_get_char(&start) == '(')
      nest_level++;
    else 
      nest_level--;
  }
}

/* タブが切り替わる時のイベントハンドリング */
static void switch_tabpage_handler(GtkNotebook *notebook, GtkNotebookPage *page, guint pagenum) {
  /* タブに対応する情報が格納された構造体を切替える */
  Shiki_CURRENT_TAB_INFO = (ShikiTabInfo *)g_list_nth_data(Shiki_EDITOR_TAB_INFO_LIST, pagenum);
  /* 現在のタブ番号を切替える */
  Shiki_CURRENT_TAB_NUM = pagenum;

  /* タブのラベルをウィンドウのタイトルに */
  if(!Shiki_CURRENT_TAB_INFO) return;
  gtk_window_set_title (GTK_WINDOW(Shiki_EDITOR_WINDOW), Shiki_CURRENT_FILENAME);

  update_modeline_label();  
}

/* ページのタブと境界線を on/off */
static void tabsborder_on_off(GtkButton *button, GtkNotebook *notebook) {
  gint tval = FALSE;
  gint bval = FALSE;
  if(notebook->show_tabs == FALSE)
    tval = TRUE; 
  if(notebook->show_border == FALSE)
    bval = TRUE;

  gtk_notebook_set_show_tabs(notebook, tval);
  gtk_notebook_set_show_border(notebook, bval);
}

/* ノートブックからページを削除 */
static void remove_tabpage(GtkNotebook *notebook) {
  /* タブが 1 つしか残っていなかったら消させない */
  if(g_list_length(Shiki_EDITOR_TAB_INFO_LIST) == 1)
    return;
  if(!not_yet_save_changes_really_quit(Shiki_CURRENT_TEXT_BUFFER)) {
    /* デリートハンドラをエディタトップレベルのウィジットから取り除く */
    g_signal_handler_disconnect(Shiki_EDITOR_WINDOW, (Shiki_CURRENT_TAB_INFO)->delete_handler_id);
    /* タブの中身を削除 */
    gtk_widget_destroy(GTK_WIDGET(Shiki_CURRENT_TEXT_VIEW));
    /* タブに対応するタブ情報を削除 */
    g_free(Shiki_CURRENT_TAB_TITLE);
    g_free(Shiki_CURRENT_FILENAME);
    Shiki_EDITOR_TAB_INFO_LIST = g_list_delete_link(Shiki_EDITOR_TAB_INFO_LIST, g_list_nth(Shiki_EDITOR_TAB_INFO_LIST, Shiki_CURRENT_TAB_NUM));

    /* カレントのタブ情報を更新 */
    Shiki_CURRENT_TAB_INFO = g_list_nth_data(Shiki_EDITOR_TAB_INFO_LIST, Shiki_CURRENT_TAB_NUM);
    gtk_notebook_remove_page(notebook, Shiki_CURRENT_TAB_NUM);
    /* ウィジットを強制的に再描画 */
    gtk_widget_queue_draw(GTK_WIDGET(notebook));
  }
}

static void remove_tabpage_handler(GtkButton *button, GtkWidget *notebook) {
  remove_tabpage(GTK_NOTEBOOK(notebook));
}

/* ノートブックにページを追加 */
static void append_tabpage(GtkNotebook *notebook, gchar *filename) {
  ShikiTabInfo *tabinfo;
  GtkWidget *tabpage = new_scrolled_text_buffer(&tabinfo);
  tabinfo->filename = filename;
  tabinfo->tabpage_label = g_path_get_basename(filename);
  gtk_notebook_append_page(notebook, tabpage, gtk_label_new(tabinfo->tabpage_label));
  Shiki_EDITOR_TAB_INFO_LIST = g_list_append(Shiki_EDITOR_TAB_INFO_LIST, tabinfo);
  gtk_notebook_set_current_page(notebook, g_list_length(Shiki_EDITOR_TAB_INFO_LIST) - 1);
  gtk_widget_show_all(GTK_WIDGET(notebook));
  gtk_notebook_set_current_page(notebook, Shiki_CURRENT_TAB_NUM);
}

static void append_tabpage_handler(GtkButton *button, GtkNotebook *notebook) {
  append_tabpage(notebook, g_strdup("*scratch*"));
}

/* タブの位置を調整 */
static void rotate_tab_position(GtkButton *button, GtkNotebook *notebook ) {
  gtk_notebook_set_tab_pos(notebook, (notebook->tab_pos + 1) % 4);
}

/* 特定のキー操作のハンドリング */

/* カーソルの移動 ^npfb */
static void forward_current_buffer() {
  GtkTextIter p;
  gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER,&p, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
  gtk_text_iter_forward_char(&p);
  gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);
}
static void backward_current_buffer() {
  GtkTextIter p;
  gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER,&p, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
  gtk_text_iter_backward_char(&p);
  gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);
}
static void line_forward_current_buffer() {
  GtkTextIter p;
  gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &p, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
  gtk_text_view_forward_display_line(Shiki_CURRENT_TEXT_VIEW, &p);
  gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);
}
static void line_backward_current_buffer() {
  GtkTextIter p;
  gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER,&p, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
  gtk_text_view_backward_display_line(Shiki_CURRENT_TEXT_VIEW, &p);
  gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);
}

/* キーが押された */
static gboolean signal_key_press_handler (GtkWidget *notebook, GdkEventKey *event, gpointer contextid) {
  GtkTextIter start, end;

  /* 括弧の対応の強調を無効に */
  gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &start);
  gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &end);
  gtk_text_buffer_remove_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "parent_emphasis_background", &start, &end);

  if(event->state & GDK_CONTROL_MASK) {
    switch(event->keyval) {
      case GDK_f :  /* Ctrl + f : forward */
        forward_current_buffer();
        break;
      case GDK_b : /* Ctrl + b : backward */
        backward_current_buffer();
        break;
      case GDK_n : /* Ctrl + n : next line */
        line_forward_current_buffer();
        break;
      case GDK_p :  /* Ctrl + p : previous line */
        line_backward_current_buffer();
        break;

      case GDK_j :  /* Ctrl + j : カーソル手前の S 式の評価 */
        {
          gchar *code;
          GtkTextIter start, end;

          /* カーソルの位置を取得 */
          gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &end, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));

          gtk_text_iter_backward_find_char(&end, is_kokka, NULL, NULL);
          start = end;
          gtk_text_iter_forward_char(&end);

          /* カーソル位置の前にある S 式を切り出す */
          if(!search_sexp_string(&start)) return FALSE;

          code = gtk_text_buffer_get_text(Shiki_CURRENT_TEXT_BUFFER, &start, &end, FALSE);
          gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, "\n\n", -1);
          gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, eval_cstring_by_gauche(code), -1);
          g_free(code);
        }
        break;

      case GDK_t : /* Ctrl + t : タブを開く */
        append_tabpage(GTK_NOTEBOOK(notebook), g_strdup("*scratch*"));
        break;

      case GDK_k : /* Ctrl + k : タブを閉じる */
        remove_tabpage(GTK_NOTEBOOK(notebook));
        break;
    }
  }
  return FALSE;
}

/* キーが離された */
static gboolean signal_key_release_handler (GtkWidget *notebook, GdkEventKey *event, gpointer contextid) {
  static gint metakey_pressed = 0;
  static gint controlx_pressed = 0;

  if(event->keyval == GDK_parenright && event->state & GDK_SHIFT_MASK) {
    GtkTextIter start, end;

    /* カーソルの位置を取得 */
    gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &end, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));

    start = end;
    gtk_text_iter_backward_char(&start);

    /* カーソル位置の前にある S 式を切り出す */
    if(!search_sexp_string(&start)) return FALSE;

    gtk_text_buffer_apply_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "parent_emphasis_background", &start, &end);
  }

  /* 改行されるたびに，自動的に括弧のネストの深さに応じた数のスペース (インデント) が行頭に入る */
  if(event->keyval == GDK_Return) {
    gint indentWidth = get_parent_nest_level_at_cursor(Shiki_CURRENT_TEXT_BUFFER) * editor_indent_width;
    gchar *indent = g_strnfill(indentWidth, ' ');
    gtk_text_buffer_insert_at_cursor(Shiki_CURRENT_TEXT_BUFFER, indent, -1);
    g_free(indent);
  }

  /* C-x */
  if(event->keyval == GDK_x && event->state & GDK_CONTROL_MASK) {
    controlx_pressed++;
    gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "C-x -");
  } else if(event->state & GDK_CONTROL_MASK) {

    if(controlx_pressed > 0) {
      switch(event->keyval) {
        case GDK_c :/* C-x C-c : 終了 */
          gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "C-c");
          {/* "delete-event" を発生させる．ウィンドウの × ボタンが押されたのと同じ */
            GdkEvent ev;

            ev.any.type = GDK_DELETE;
            ev.any.window = Shiki_EDITOR_WINDOW->window;
            ev.any.send_event = FALSE;
            gdk_event_put (&ev);
          }
          break;

        case GDK_f : /* C-x C-f : ファイル開く */
          gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "C-f");
          open_file_from_notebook(GTK_NOTEBOOK(notebook));
          break;

        case GDK_s : /* C-x C-s : ファイル保存 */
          gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "C-s");
          save_file_from_notebook(GTK_NOTEBOOK(notebook));    
          break;

        case GDK_w : /* C-x C-w : ファイル別名保存 */
          gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "C-w");
          save_file_as_from_notebook(GTK_NOTEBOOK(notebook));    
          break;
      }
      controlx_pressed = 0;
    }

    switch(event->keyval) {
      case GDK_g :/* C-g : キャンセル */
        metakey_pressed = 0;
        controlx_pressed = 0;

        gtk_statusbar_push(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), GPOINTER_TO_INT(contextid), "Quit");
        break;
    }

  }
  return FALSE;
}
static void open_online_help(GtkNotebook *notebook) {
  GtkTextIter p;
  append_tabpage(notebook, g_strdup("*help*"));
  gtk_text_buffer_set_text(Shiki_CURRENT_TEXT_BUFFER,
"[フォルダのアイコン]             ファイルを開く (C-x C-f)\n"
"[フロッピーディスクのアイコン]   バッファを保存 (C-x C-s)\n"
"[フロッピーとえんぴつのアイコン] バッファを別名保存 (C-x C-w)\n"
"[ギアのアイコン]                 選択領域を gauche にロード (region-compile)\n"
"[マーキング (ちょん) のアイコン] タブの on/off\n"
"[工具 (スパナ) のアイコン]       タブの位置の調整\n"
"[まる + アイコン]                新規バッファを開く (Ctrl + t) (tab)\n"
"[ゴミ箱アイコン]                 バッファをクリア\n"
"[× アイコン]                     バッファを閉じる (Ctrl + k) (kill buffer)\n"
"[A アイコン]                     フォントの選択\n"
"[顔みたいなアイコン]             Scheme ファイルのロード\n"
"[緑っぽい，変換アイコン]         バッファの内容を全てロード\n"
"[ヘルプ (?) アイコン]            このヘルプ画面を表示します\n"
"[info アイコン]                  このアプリケーションについての情報\n\n"
"C-f : → に移動 (forward)\n"
"C-b : ← に移動 (backward)\n"
"C-n : ↓ に移動 (next line)\n"
"C-p : ↑ に移動 (previous line)\n\n"
"C-j : カーソル手前の S 式を評価 (eval-last-sexp)\n"
"(emacs/xyzzy の *scratch* バッファと同じ)\n\n"

"C-x C-c : 終了．ウィンドウの × ボタンを押したのと同じ\n\n"

"また，内容が失われる恐れがあるときには全て「Reqlly Quit ?」のように警告します．\n", -1);
    gtk_text_buffer_set_modified(Shiki_CURRENT_TEXT_BUFFER, FALSE);
    /* カーソル位置を先頭に */
    gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &p);
    gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &p);    
}

static void online_help_handler(GtkWidget *button, GtkNotebook *notebook) {
  open_online_help(notebook);
}

/* エディタの編集画面の初期化 */
static void shiki_editor_window_init(int argc, char **argv) {
  GtkWidget *vbox, *toolbar, *notebook;
  GtkToolItem *icon;
  GtkIconSize iconsize;
  GtkTooltips *toolbar_tips = gtk_tooltips_new();
  /* 「開く」「保存」「別名保存」「選択領域を実行」アイコン */
  GtkToolItem *oicon, *sicon, *saicon, *eicon;

  gint contextid;

  /* 窓を作る */
  Shiki_EDITOR_WINDOW = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_signal_connect(G_OBJECT(Shiki_EDITOR_WINDOW), "destroy", G_CALLBACK(gtk_main_quit), NULL);

  /* パッキングボックスを作る */
  vbox = gtk_vbox_new(FALSE, 0);
  /* ツールバー作る */
  toolbar = gtk_toolbar_new();
  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);

  notebook = gtk_notebook_new();
  g_signal_connect(G_OBJECT(notebook), "switch-page", GTK_SIGNAL_FUNC(switch_tabpage_handler), NULL);

  /* ツールバーに付けるアイコンの設定 */
  gtk_toolbar_set_style(GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS);
  iconsize = gtk_toolbar_get_icon_size (GTK_TOOLBAR (toolbar));

  /* アイコン作る */

  /* ファイル開く */
  oicon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-open", iconsize), "");
  /* 「開く」ボタンにファイルを読み込むアクションを関連付ける */
  g_signal_connect(G_OBJECT(oicon), "clicked", G_CALLBACK(open_file_handler), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(oicon));
  gtk_tool_item_set_tooltip(oicon, toolbar_tips, "ファイルを開きます",
      "新しいバッファを開いて，そこにファイルの内容を展開します．");

  /* バッファ保存 */
  sicon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-save", iconsize), "");
  /* 「保存」ボタンにファイルを書き出すアクションを関連付ける */
  g_signal_connect(G_OBJECT(sicon), "clicked", G_CALLBACK(save_file_handler), G_OBJECT(notebook));
  gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET(sicon));      
  gtk_tool_item_set_tooltip(sicon, toolbar_tips, "バッファを保存します",
      "バッファにファイル名が設定されていない場合には，ダイアログを開いてファイル名を取得します");

  /* バッファを別名保存 */
  saicon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-save-as", iconsize), "");
  /* 「別名保存」ボタンに別名のファイル開いて内容を書き出すアクションを関連付ける */
  g_signal_connect(G_OBJECT(saicon), "clicked", G_CALLBACK(save_file_as_handler), G_OBJECT(notebook));
  gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET(saicon));
  gtk_tool_item_set_tooltip(saicon, toolbar_tips, "バッファを別名保存します",
      "");

  /* バッファ実行 */
  eicon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-execute", iconsize), "");
  /* 「選択範囲をロード」ボタンに libgauche を関連付ける */
  g_signal_connect(G_OBJECT(eicon), "clicked", G_CALLBACK(load_region_handler), G_OBJECT(notebook));
  gtk_container_add (GTK_CONTAINER (toolbar), GTK_WIDGET(eicon));
  gtk_tool_item_set_tooltip(eicon, toolbar_tips, "選択範囲の S 式をロードします (load-region-lisp)",
      "Scheme (gauche) で評価できる S 式を評価します．");

  gtk_container_add(GTK_CONTAINER(Shiki_EDITOR_WINDOW), vbox);
  gtk_container_add(GTK_CONTAINER(vbox), notebook);

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-apply", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(tabsborder_on_off), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "タブの on/off", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-preferences", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(rotate_tab_position), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "タブ位置の設定", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-add", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(append_tabpage_handler), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "新規バッファを開きます", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-delete", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(clear_current_buffer), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "バッファの内容を全消去",
      "まだ内容が保存されていない場合でも警告しません");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-close", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(remove_tabpage_handler), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "バッファを閉じます",
      "まだ内容が保存されていない場合は警告します");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-bold", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(select_font), NULL);
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "フォントの変更", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-connect", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(load_scheme_file_by_gauche), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "Scheme ファイルをロード", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-convert", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(load_buffer_by_gauche), G_OBJECT(notebook));
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "バッファをロード", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-dialog-question", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(online_help_handler), notebook);
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "ヘルプ", "");

  icon = gtk_tool_button_new(gtk_image_new_from_stock ("gtk-dialog-info", iconsize), "");
  g_signal_connect(G_OBJECT(icon), "clicked", G_CALLBACK(about_this_application), NULL);
  gtk_container_add(GTK_CONTAINER (toolbar), GTK_WIDGET(icon));
  gtk_tool_item_set_tooltip(icon, toolbar_tips, "このアプリケーションについて", "");

  gtk_box_pack_start(GTK_BOX(vbox), Shiki_EDITOR_MODELINE_LABEL = gtk_label_new("-E:**  *scratch*     (Gauche Interaction)--L1--All---------------------------------"), TRUE, TRUE, 0);

  /* C-x C-s などの状態を表示するステータスバーをウィンドウボトムに追加 */
  Shiki_EDITOR_STATUSBAR = gtk_statusbar_new();
  gtk_box_pack_start(GTK_BOX(vbox), Shiki_EDITOR_STATUSBAR, TRUE, TRUE, 0);
  contextid = gtk_statusbar_get_context_id(GTK_STATUSBAR(Shiki_EDITOR_STATUSBAR), "");

  /* キーバインドのハンドリングを登録 */
  g_signal_connect(G_OBJECT(notebook), "key-press-event", G_CALLBACK (signal_key_press_handler), GINT_TO_POINTER(contextid));
  g_signal_connect(G_OBJECT(notebook), "key-release-event", G_CALLBACK (signal_key_release_handler), GINT_TO_POINTER(contextid));

  /* 引数に指定されたファイルを開く */
  if(argc >= 2) {
    int i;
    for(i = 1; i < argc; i++)
      open_file(GTK_NOTEBOOK(notebook), g_strdup(argv[i]));
  } else /* 指定されてなければ，デフォルトのページを追加 */
    open_online_help(GTK_NOTEBOOK(notebook));

  gtk_widget_grab_focus(notebook);
  gtk_widget_show_all(Shiki_EDITOR_WINDOW);
}

int main(int argc, char *argv[]) {
  /* 初期化してメインループへ */
  gtk_set_locale(); 
  gtk_init(&argc, &argv);
  GC_INIT(); Scm_Init(GAUCHE_SIGNATURE);
  shiki_editor_window_init(argc, argv);
  gtk_main();
  Scm_Exit(0);
  return 0;
}
