Develop and Download Open Source Software

Browse CVS Repository

Diff of /shiki/shiki/buffer.c

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.12 by aloha, Thu Nov 30 14:45:49 2006 UTC revision 1.13 by aloha, Sat Dec 2 14:23:20 2006 UTC
# Line 182  GtkTextBuffer *Shiki_new_buffer_create(g Line 182  GtkTextBuffer *Shiki_new_buffer_create(g
182    /* 括弧の強調表示のためのタグを作る */    /* 括弧の強調表示のためのタグを作る */
183    gtk_text_buffer_create_tag(tabinfo->text_buffer, "parent_emphasis_background", "background", "green", NULL);    gtk_text_buffer_create_tag(tabinfo->text_buffer, "parent_emphasis_background", "background", "green", NULL);
184    
185      /* 検索/置換の際にマッチした部分 */
186      gtk_text_buffer_create_tag(tabinfo->text_buffer, "match_highlighting", "background", "pink", NULL);
187    
188    /* キーワードハイライティング用 */    /* キーワードハイライティング用 */
189    gtk_text_buffer_create_tag(tabinfo->text_buffer, "keyword_highlighting", "foreground", "blue", NULL);    gtk_text_buffer_create_tag(tabinfo->text_buffer, "keyword_highlighting", "foreground", "blue", NULL);
190    /* 関数 */    /* 関数 */
# Line 236  void Shiki_create_file_buffer(const gcha Line 239  void Shiki_create_file_buffer(const gcha
239    
240      if(g_file_get_contents(filename, &contents, &len, NULL)) {      if(g_file_get_contents(filename, &contents, &len, NULL)) {
241        if(!(text = g_locale_to_utf8(contents, -1, &br, &bw, &err)))        if(!(text = g_locale_to_utf8(contents, -1, &br, &bw, &err)))
242          gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, text, -1);          gtk_text_buffer_set_text(Shiki_CURRENT_TEXT_BUFFER, text, -1);
243        else        else
244          gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &p, contents, -1);          gtk_text_buffer_set_text(Shiki_CURRENT_TEXT_BUFFER, contents, -1);
245        g_free(contents);        g_free(contents);
246      }      }
247    }    }
# Line 493  void Shiki_msgbox(const gchar *msg) { Line 496  void Shiki_msgbox(const gchar *msg) {
496    gtk_dialog_run(GTK_DIALOG(dialog));    gtk_dialog_run(GTK_DIALOG(dialog));
497    gtk_widget_destroy(dialog);    gtk_widget_destroy(dialog);
498  }  }
499    
500    /* GtkTextCharPredicate */
501    static gboolean is_kakko_or_kokka(gunichar ch, gpointer p) {
502      return ch == '(' || ch == ')';
503    }
504    static gboolean is_kakko(gunichar ch, gpointer p) {return ch == '(';}
505    static gboolean is_kokka(gunichar ch, gpointer p) {return ch == ')';}
506    static gboolean search_sexp_kokka(GtkTextIter *end) {
507      gint nest_level = 0;
508    
509      /* 対応する ')' を探す */
510      while(1) {
511        if(!gtk_text_iter_forward_find_char(end, is_kakko_or_kokka, NULL, NULL))
512          return FALSE;
513    
514        if(gtk_text_iter_get_char(end) == '(')
515          nest_level++;
516        else {
517          if(!nest_level)
518            break;
519          else
520            nest_level--;
521        }
522      }
523      return TRUE;
524    }
525    
526    
527    /* ')' に対応する '(' までの文字列 (S 式) を切り出す */
528    static gboolean search_last_sexp_kakko(GtkTextIter *start) {
529      gint nest_level = 0;
530      /* ネストレベルを計算しながら ')' を探す */
531      while(1) {
532        if(!gtk_text_iter_backward_find_char(start, is_kakko_or_kokka, NULL, NULL))
533          return FALSE;
534    
535        if(gtk_text_iter_get_char(start) == ')')
536          nest_level++;
537        else {
538          if(!nest_level)
539            break;
540          else
541            nest_level--;
542        }
543      }
544      return TRUE;
545    }
546    
547    /* カーソル以降の '(' に対応する ')' までの文字列 (S 式) を切り出す */
548    static gboolean search_sexp(GtkTextIter *start, GtkTextIter *end) {
549    
550      /* カーソルの位置を取得 */
551      gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, start, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
552    
553      if(gtk_text_iter_get_char(start) != '(')
554        gtk_text_iter_forward_find_char(start, is_kakko, NULL, NULL);
555    
556      *end = *start;
557    
558      /* カーソル位置の前にある S 式を切り出す */
559      if(!search_sexp_kokka(end)) return FALSE;
560      gtk_text_iter_forward_char(end);
561      return TRUE;
562    }
563    
564    /* カーソル以前の ')' に対応する '(' までの文字列 (S 式) を切り出す */
565    static gboolean search_last_sexp(GtkTextIter *start, GtkTextIter *end) {
566    
567      /* カーソルの位置を取得 */
568      gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, end, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
569    
570      gtk_text_iter_backward_char(end);
571    
572      if(gtk_text_iter_get_char(end) != ')')
573        gtk_text_iter_backward_find_char(end, is_kokka, NULL, NULL);
574      *start = *end;
575      gtk_text_iter_forward_char(end);
576    
577      /* カーソル位置の前にある S 式を切り出す */
578      if(!search_last_sexp_kakko(start)) return FALSE;
579    
580      return TRUE;
581    }
582    
583    /* gauche を起動して文字列を評価 */
584    static gchar *eval_cstring_by_gauche(gchar *s) {
585      gchar *msg;
586    
587      ScmObj result, error;
588      /* 出力文字列ポート開く */
589      ScmObj os = Scm_MakeOutputStringPort(TRUE);
590    
591      /* Scheme レベルでエラーハンドリング */
592      /* http://alohakun.blog7.fc2.com/blog-entry-517.html */
593      Scm_Define(SCM_MODULE(Shiki_CURRENT_BUFFER_ENV), SCM_SYMBOL(SCM_INTERN("*input*")), SCM_MAKE_STR_COPYING(s));
594      Scm_Define(SCM_MODULE(Shiki_CURRENT_BUFFER_ENV), SCM_SYMBOL(SCM_INTERN("*error*")), SCM_FALSE);
595    
596      result = Scm_EvalCString("(guard (e (else (set! *error* e) #f)) (eval (read-from-string *input*) (current-module)))", SCM_OBJ(Shiki_CURRENT_BUFFER_ENV));
597    
598      error = Scm_GlobalVariableRef(SCM_MODULE(Shiki_CURRENT_BUFFER_ENV), SCM_SYMBOL(SCM_INTERN("*error*")), 0);
599    
600      /* 文字列を評価した結果をポートに書き込む */
601      if (!SCM_FALSEP(error))
602        Scm_Write(error, os, SCM_WRITE_DISPLAY);
603      else
604        Scm_Write(result, os, SCM_WRITE_DISPLAY);
605    
606      msg = Scm_GetString(SCM_STRING(Scm_GetOutputString(SCM_PORT(os))));
607      /* ポート閉じる */
608      Scm_ClosePort(SCM_PORT(os));
609    
610      return msg;
611    }
612    
613    void Shiki_eval_expression() {
614    
615      gchar *code, *result;
616      GtkTextIter start, end;
617    
618      if(!search_sexp(&start, &end)) return;
619    
620      code = gtk_text_buffer_get_text(Shiki_CURRENT_TEXT_BUFFER, &start, &end, FALSE);
621      result = eval_cstring_by_gauche(code);
622      g_free(code);
623    
624      gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &end, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
625    
626      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, "\n", -1);
627      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, result, -1);
628      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, "\n", -1);
629    }
630    
631    void Shiki_eval_last_sexp() {
632      gchar *code, *result;
633      GtkTextIter start, end;
634    
635      if(!search_last_sexp(&start, &end)) return;
636    
637      code = gtk_text_buffer_get_text(Shiki_CURRENT_TEXT_BUFFER, &start, &end, FALSE);
638      result = eval_cstring_by_gauche(code);
639      g_free(code);
640    
641      gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &end, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
642    
643      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, "\n", -1);
644      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, result, -1);
645      gtk_text_buffer_insert(Shiki_CURRENT_TEXT_BUFFER, &end, "\n", -1);
646    }
647    
648    typedef enum {
649      SHIKI_SEARCH_FORWARD,
650      SHIKI_SEARCH_BACKWARD
651    } ShikiSearchDirection;
652    
653    static struct {
654      GtkWidget *input;
655      gboolean ci;
656      gboolean word;
657      gboolean regexp;
658      gboolean escape;
659      gboolean loop;
660    } ShikiSearchBufferInfo;
661    
662    gboolean Shiki_search_string(const gchar *pattern, gboolean no_dup,
663        ShikiSearchDirection direction) {
664      GtkTextIter p, match_start, match_end, start, end;
665      gboolean result;
666    
667      gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &start);
668      gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &end);
669    
670      gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &p, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
671    
672      if(direction == SHIKI_SEARCH_FORWARD) {
673        if(no_dup)
674          gtk_text_iter_forward_char(&p);
675    
676        result = gtk_text_iter_forward_search(&p, pattern,
677            GTK_TEXT_SEARCH_TEXT_ONLY, &match_start, &match_end, &end);
678      } else {
679        if(no_dup)
680          gtk_text_iter_backward_char(&p);
681        
682        result = gtk_text_iter_backward_search(&p, pattern,
683            GTK_TEXT_SEARCH_TEXT_ONLY, &match_start, &match_end, &start);
684      }
685    
686      if(result) {
687        gtk_text_buffer_remove_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "match_highlighting", &start, &end);
688        gtk_text_buffer_apply_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "match_highlighting", &match_start, &match_end);
689        gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &match_start);
690        gtk_text_view_scroll_to_iter(Shiki_CURRENT_TEXT_VIEW,  &match_start,
691                                     0.0, FALSE, FALSE, FALSE);
692      }
693      return result;
694    }
695    
696    static void destroy_handler(GtkWidget *button, GtkWidget *widget) {gtk_widget_destroy(widget);}
697    
698    static void toggled_handler(GtkToggleButton *togglebutton, gboolean *flag) {
699      *flag = !*flag;
700    }
701    
702    static void search_forward_handler() {
703      Shiki_search_string(gtk_entry_get_text(GTK_ENTRY(ShikiSearchBufferInfo.input)), TRUE, SHIKI_SEARCH_FORWARD);
704    }
705    static void search_backward_handler() {
706      Shiki_search_string(gtk_entry_get_text(GTK_ENTRY(ShikiSearchBufferInfo.input)), TRUE, SHIKI_SEARCH_BACKWARD);
707    }
708    
709    void Shiki_search_buffer() {
710      static GtkWidget *input = NULL;
711      GtkWidget *dialog = gtk_dialog_new_with_buttons ("文字列の検索", GTK_WINDOW(Shiki_EDITOR_WINDOW), GTK_DIALOG_DESTROY_WITH_PARENT, NULL);
712      GtkWidget *table  = gtk_table_new(6, 3, FALSE);
713      GtkWidget *label  = gtk_label_new("検索 : ");
714      GtkWidget *check1 = gtk_check_button_new_with_label("大文字小文字を区別する");
715      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check1), TRUE);
716      GtkWidget *check2 = gtk_check_button_new_with_label("単語単位で検索する");
717      GtkWidget *check3 = gtk_check_button_new_with_label("正規表現");
718      GtkWidget *check4 = gtk_check_button_new_with_label("エスケープシーケンスを理解する");
719      GtkWidget *check5 = gtk_check_button_new_with_label("見つからなければ戻って");
720      GtkWidget *prev   = gtk_button_new_with_label ("上検索");
721      g_signal_connect (prev, "clicked", G_CALLBACK(search_backward_handler), NULL);  
722      GtkWidget *next   = gtk_button_new_with_label ("下検索");
723      g_signal_connect (next, "clicked", G_CALLBACK(search_forward_handler), NULL);
724      GtkWidget *cancel = gtk_button_new_with_label ("キャンセル");
725      
726      if(!input)
727        ShikiSearchBufferInfo.input = input = g_object_ref(gtk_entry_new());
728      ShikiSearchBufferInfo.ci  =
729      ShikiSearchBufferInfo.word  =
730      ShikiSearchBufferInfo.regexp  =
731      ShikiSearchBufferInfo.escape  =
732      ShikiSearchBufferInfo.loop = FALSE;
733    
734      g_signal_connect (check1, "toggled", G_CALLBACK (toggled_handler), &(ShikiSearchBufferInfo.ci));
735      g_signal_connect (check2, "toggled", G_CALLBACK (toggled_handler), &(ShikiSearchBufferInfo.word));
736      g_signal_connect (check3, "toggled", G_CALLBACK (toggled_handler), &(ShikiSearchBufferInfo.regexp));
737      g_signal_connect (check4, "toggled", G_CALLBACK (toggled_handler), &(ShikiSearchBufferInfo.escape));
738      g_signal_connect (check5, "toggled", G_CALLBACK (toggled_handler), &(ShikiSearchBufferInfo.loop));
739    
740      g_signal_connect (G_OBJECT(dialog), "delete_event", G_CALLBACK(gtk_widget_destroy), NULL);
741      g_signal_connect (G_OBJECT(cancel), "clicked", G_CALLBACK(destroy_handler), dialog);
742      gtk_table_set_row_spacings(GTK_TABLE(table), 10);
743      gtk_table_set_col_spacings(GTK_TABLE(table), 10);
744      gtk_container_border_width (GTK_CONTAINER (dialog), 10);
745      gtk_table_attach_defaults (GTK_TABLE(table), label,  0, 1, 0, 1);
746      gtk_table_attach_defaults (GTK_TABLE(table), input,  1, 2, 0, 1);
747      gtk_table_attach_defaults (GTK_TABLE(table), prev,   2, 3, 0, 1);
748      gtk_table_attach_defaults (GTK_TABLE(table), check1, 1, 2, 1, 2);
749      gtk_table_attach_defaults (GTK_TABLE(table), check2, 1, 2, 2, 3);
750      gtk_table_attach_defaults (GTK_TABLE(table), check3, 1, 2, 3, 4);
751      gtk_table_attach_defaults (GTK_TABLE(table), check4, 1, 2, 4, 5);
752      gtk_table_attach_defaults (GTK_TABLE(table), check5, 1, 2, 5, 6);
753      gtk_table_attach_defaults (GTK_TABLE(table), next,   2, 3, 1, 2);
754      gtk_table_attach_defaults (GTK_TABLE(table), cancel, 2, 3, 2, 3);
755      gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), table);
756      gtk_widget_show_all(table);
757      gtk_dialog_run(GTK_DIALOG(dialog));
758    }
759    
760    static struct {
761      GtkWidget *find;
762      GtkWidget *replace;
763      gboolean ci;
764      gboolean word;
765      gboolean regexp;
766      gboolean escape;
767      gboolean from_first;
768    } ShikiReplaceBufferInfo;
769    
770    gboolean Shiki_replace_string(const gchar *find, const gchar *replace, gboolean no_dup, gboolean interactive_p, gboolean from_first_p) {
771      GtkTextIter start, end, match_start, match_end;
772      gboolean result = FALSE;
773    
774      if(from_first_p)
775        gtk_text_buffer_get_start_iter(Shiki_CURRENT_TEXT_BUFFER, &start);
776      else
777        gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &start, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
778    
779      gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &end);
780    
781      while((result = gtk_text_iter_forward_search(&start, find,
782                       GTK_TEXT_SEARCH_TEXT_ONLY, &match_start, &match_end, &end))) {
783    
784        gtk_text_buffer_remove_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "match_highlighting", &start, &end);
785        gtk_text_buffer_apply_tag_by_name(Shiki_CURRENT_TEXT_BUFFER, "match_highlighting", &match_start, &match_end);
786        gtk_text_buffer_place_cursor(Shiki_CURRENT_TEXT_BUFFER, &match_start);
787        gtk_text_view_scroll_to_iter(Shiki_CURRENT_TEXT_VIEW,  &match_start,
788            0.0, FALSE, FALSE, FALSE);
789        if(!interactive_p
790            ||
791            (interactive_p && Shiki_yes_or_no_p("置換しますか ?"))) {
792          gtk_text_buffer_delete(Shiki_CURRENT_TEXT_BUFFER, &match_start, &match_end);
793          gtk_text_buffer_insert_at_cursor(Shiki_CURRENT_TEXT_BUFFER, replace, -1);
794        }
795        gtk_text_buffer_get_iter_at_mark(Shiki_CURRENT_TEXT_BUFFER, &start, gtk_text_buffer_get_insert(Shiki_CURRENT_TEXT_BUFFER));
796        gtk_text_iter_forward_char(&start);
797        gtk_text_buffer_get_end_iter(Shiki_CURRENT_TEXT_BUFFER, &end);
798      }
799    
800      return result;
801    }
802    
803    static void replace_interactive_handler() {
804      Shiki_replace_string(gtk_entry_get_text(GTK_ENTRY(ShikiReplaceBufferInfo.find)), gtk_entry_get_text(GTK_ENTRY(ShikiReplaceBufferInfo.replace)), TRUE, TRUE, ShikiReplaceBufferInfo.from_first);  
805    }
806    static void replace_all_handler() {
807      Shiki_replace_string(gtk_entry_get_text(GTK_ENTRY(ShikiReplaceBufferInfo.find)), gtk_entry_get_text(GTK_ENTRY(ShikiReplaceBufferInfo.replace)), TRUE, FALSE, ShikiReplaceBufferInfo.from_first);
808    }
809    
810    void Shiki_replace_buffer() {
811      static GtkWidget *find = NULL;
812      static GtkWidget *replace = NULL;
813      GtkWidget *dialog = gtk_dialog_new_with_buttons ("文字列の置換", GTK_WINDOW(Shiki_EDITOR_WINDOW), GTK_DIALOG_DESTROY_WITH_PARENT, NULL);
814      GtkWidget *table       = gtk_table_new(7, 3, FALSE);
815      GtkWidget *find_label  = gtk_label_new("検索 : ");
816      GtkWidget *rep_label   = gtk_label_new("置換 : ");
817      GtkWidget *check1      = gtk_check_button_new_with_label("大文字小文字を区別する");
818      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check1), TRUE);
819      GtkWidget *check2 = gtk_check_button_new_with_label("単語単位で検索する");
820      GtkWidget *check3 = gtk_check_button_new_with_label("正規表現");
821      GtkWidget *check4 = gtk_check_button_new_with_label("エスケープシーケンスを理解する");
822      GtkWidget *check5 = gtk_check_button_new_with_label("バッファの先頭から");
823      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check5), TRUE);  
824      GtkWidget *interactive   = gtk_button_new_with_label ("確認あり");
825      g_signal_connect (interactive, "clicked", G_CALLBACK(replace_interactive_handler), NULL);
826      GtkWidget *all   = gtk_button_new_with_label ("全て置換");
827      g_signal_connect (all, "clicked", G_CALLBACK(replace_all_handler), NULL);
828      GtkWidget *cancel = gtk_button_new_with_label ("キャンセル");
829    
830      if(!find && !replace) {
831        ShikiReplaceBufferInfo.find = find = g_object_ref(gtk_entry_new());
832        ShikiReplaceBufferInfo.replace = replace = g_object_ref(gtk_entry_new());
833      }
834      
835      ShikiReplaceBufferInfo.ci  =
836      ShikiReplaceBufferInfo.word  =
837      ShikiReplaceBufferInfo.regexp  =
838      ShikiReplaceBufferInfo.escape  = FALSE;
839      ShikiReplaceBufferInfo.from_first = TRUE;
840    
841      g_signal_connect (check1, "toggled", G_CALLBACK (toggled_handler), &(ShikiReplaceBufferInfo.ci));
842      g_signal_connect (check2, "toggled", G_CALLBACK (toggled_handler), &(ShikiReplaceBufferInfo.word));
843      g_signal_connect (check3, "toggled", G_CALLBACK (toggled_handler), &(ShikiReplaceBufferInfo.regexp));
844      g_signal_connect (check4, "toggled", G_CALLBACK (toggled_handler), &(ShikiReplaceBufferInfo.escape));
845      g_signal_connect (check5, "toggled", G_CALLBACK (toggled_handler), &(ShikiReplaceBufferInfo.from_first));
846    
847      g_signal_connect (G_OBJECT(dialog), "delete_event", G_CALLBACK(gtk_widget_destroy), NULL);
848      g_signal_connect (G_OBJECT(cancel), "clicked", G_CALLBACK(destroy_handler), dialog);
849      gtk_table_set_row_spacings(GTK_TABLE(table), 10);
850      gtk_table_set_col_spacings(GTK_TABLE(table), 10);
851      gtk_container_border_width (GTK_CONTAINER (dialog), 10);
852    
853      gtk_table_attach_defaults (GTK_TABLE(table), find_label,  0, 1, 0, 1);
854      gtk_table_attach_defaults (GTK_TABLE(table), find,        1, 2, 0, 1);
855      gtk_table_attach_defaults (GTK_TABLE(table), interactive, 2, 3, 0, 1);
856    
857      gtk_table_attach_defaults (GTK_TABLE(table), rep_label,   0, 1, 1, 2);
858      gtk_table_attach_defaults (GTK_TABLE(table), replace,     1, 2, 1, 2);
859      gtk_table_attach_defaults (GTK_TABLE(table), all,         2, 3, 1, 2);
860    
861      gtk_table_attach_defaults (GTK_TABLE(table), check1, 1, 2, 2, 3);
862      gtk_table_attach_defaults (GTK_TABLE(table), cancel, 2, 3, 2, 3);
863    
864      gtk_table_attach_defaults (GTK_TABLE(table), check2, 1, 2, 3, 4);
865      gtk_table_attach_defaults (GTK_TABLE(table), check3, 1, 2, 4, 5);
866      gtk_table_attach_defaults (GTK_TABLE(table), check4, 1, 2, 5, 6);
867      gtk_table_attach_defaults (GTK_TABLE(table), check5, 1, 2, 6, 7);
868    
869      gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), table);
870      gtk_widget_show_all(table);
871      gtk_dialog_run(GTK_DIALOG(dialog));
872    }
873    
874    

Legend:
Removed from v.1.12  
changed lines
  Added in v.1.13

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26