diff --git a/gnoemoe/Makefile.am b/gnoemoe/Makefile.am new file mode 100644 index 0000000..453189f --- /dev/null +++ b/gnoemoe/Makefile.am @@ -0,0 +1,46 @@ +## Process this file with automake to produce Makefile.in +# SUBDIRS = test +INCLUDES = \ + -DPACKAGE_DATA_DIR=\""$(datadir)"\" \ + -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + @PACKAGE_CFLAGS@ \ + @RUBYINCLUDE@ + +bin_PROGRAMS = gnoemoe +BUILT_SOURCES = gm-marshal.c gm-marshal.h + +gnoemoe_SOURCES = $(BUILT_SOURCES) \ + gm-app.c gm-app.h \ + gm-options.c gm-options.h \ + gm-color-table.c gm-color-table.h \ + gm-net.c gm-net.h \ + gm-triggers.c gm-triggers.h \ + gm-world.c gm-world.h \ + gm-string.c gm-string.h \ + gm-support.c gm-support.h \ + gm-editor.c gm-editor.h \ + gm-pixbuf.c gm-pixbuf.h \ + gm-debug.c gm-debug.h + +include $(srcdir)/widgets/Makefile.include +include $(srcdir)/dialogs/Makefile.include +include $(srcdir)/mcp/Makefile.include + +if HAVE_SCRIPT +gnoemoe_SOURCES += gm-scripts.c gm-scripts.h +endif +gm-marshal.h: gm-marshal.list $(GLIB_GENMARSHAL) + $(GLIB_GENMARSHAL) $< --header --prefix=gm_marshal > $@ + +gm-marshal.c: gm-marshal.list $(GLIB_GENMARSHAL) + echo "#include \"gm-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=gm_marshal >> $@ + +gnoemoe_LDADD = @PACKAGE_LIBS@ $(INTLLIBS) @RUBYLINK@ + +CLEANFILES = $(BUILT_SOURCES) + +dist-hook: + cd $(distdir); rm -f $(BUILT_SOURCES) + +AM_CFLAGS = -Werror -Wall -Wsign-compare diff --git a/gnoemoe/ansi.h b/gnoemoe/ansi.h new file mode 100644 index 0000000..7426ad1 --- /dev/null +++ b/gnoemoe/ansi.h @@ -0,0 +1,124 @@ +#ifndef ANSI_H +#define ANSI_H + +/** \defgroup ansi + * @{ + */ + +/** \brief enum indicating ansi code + * + * Enumeration which indicates the ansi code + */ +typedef enum _ansi_code { + A_DEFAULT = 0, /**< default (reset all attributes) */ + A_BOLD = 1, /**< bold text */ + A_FAINT = 2, /**< faint text */ + A_ITALIC = 3, /**< italic text */ + A_UNDERLINE = 4, /**< underlined text */ + A_INVERSE = 7, /**< inverse foreground/background colors */ + A_INVISIBLE = 8, /**< invisible text */ + A_CROSSOUT = 9, /**< crossed out text */ + A_DOUBLE_UNDERLINE = 21, /**< double underlined text */ + + A_BOLD_OFF = 22, /**< text no longer bold */ + A_ITALIC_OFF = 23, /**< text no longer italic */ + A_UNDERLINE_OFF = 24, /**< text no longer underlined */ + A_INVERSE_OFF = 27, /**< text no longer inversed */ + A_INVISIBLE_OFF = 28, /**< text no longer invisible */ + A_CROSSOUT_OFF = 29, /**< text no longer crossed out */ + + A_FG_BLACK = 30, /**< foreground color black */ + A_FG_RED = 31, /**< foreground color red */ + A_FG_GREEN = 32, /**< foreground color green */ + A_FG_YELLOW = 33, /**< foreground color yellow */ + A_FG_BLUE = 34, /**< foreground color blue */ + A_FG_PURPLE = 35, /**< foreground color purple */ + A_FG_CYAN = 36, /**< foreground color cyan */ + A_FG_WHITE = 37, /**< foreground color white */ + A_FG_DEFAULT = 39, /**< foreground color default */ + + A_BG_BLACK = 40, /**< background color black */ + A_BG_RED = 41, /**< background color red */ + A_BG_GREEN = 42, /**< background color green */ + A_BG_YELLOW = 43, /**< background color yellow */ + A_BG_BLUE = 44, /**< background color blue */ + A_BG_PURPLE = 45, /**< background color purple */ + A_BG_CYAN = 46, /**< background color cyan */ + A_BG_WHITE = 47, /**< background color white */ + A_BG_DEFAULT = 49, /**< background color default */ + + A_NOWRAP = 50, /**< do not wrap this text */ + + A_FG_BLACK_H, /**< foreground color black high */ + A_FG_RED_H, /**< foreground color red high */ + A_FG_GREEN_H, /**< foreground color green high */ + A_FG_YELLOW_H, /**< foreground color yellow high */ + A_FG_BLUE_H, /**< foreground color blue high */ + A_FG_PURPLE_H, /**< foreground color purple high */ + A_FG_CYAN_H, /**< foreground color cyan high */ + A_FG_WHITE_H, /**< foreground color white high */ + A_FG_DEFAULT_H /**< foreground color default high */ +} ansi_code; + +/** \brief struct for containing ansi-name pair + * + * Struct can be used to create a code to name to code mapping + */ +typedef struct _ansinamepair { + const ansi_code code; /**< the ansi code */ + const char *name; /**< the ansi name */ +} ansinamepair; + +/** \brief array containing color code/name mapping + * + * Array which can be used for color code/name mapping + */ +static const ansinamepair ansi_colors[] = { + {A_FG_BLACK, "fg_black"}, + {A_FG_RED, "fg_red"}, + {A_FG_GREEN, "fg_green"}, + {A_FG_YELLOW, "fg_yellow"}, + {A_FG_BLUE, "fg_blue"}, + {A_FG_PURPLE, "fg_purple"}, + {A_FG_CYAN, "fg_cyan"}, + {A_FG_WHITE, "fg_white"}, + {A_FG_DEFAULT, "fg_default"}, + {A_FG_BLACK_H, "fg_black_h"}, + {A_FG_RED_H, "fg_red_h"}, + {A_FG_GREEN_H, "fg_green_h"}, + {A_FG_YELLOW_H, "fg_yellow_h"}, + {A_FG_BLUE_H, "fg_blue_h"}, + {A_FG_PURPLE_H, "fg_purple_h"}, + {A_FG_CYAN_H, "fg_cyan_h"}, + {A_FG_WHITE_H, "fg_white_h"}, + {A_FG_DEFAULT_H, "fg_default_h"}, + {A_BG_BLACK, "bg_black"}, + {A_BG_RED, "bg_red"}, + {A_BG_GREEN, "bg_green"}, + {A_BG_YELLOW, "bg_yellow"}, + {A_BG_BLUE, "bg_blue"}, + {A_BG_PURPLE, "bg_purple"}, + {A_BG_CYAN, "bg_cyan"}, + {A_BG_WHITE, "bg_white"}, + {A_BG_DEFAULT, "bg_default"} +}; + +/** \brief array containing style code/name mapping + * + * Array which can be used for style code/name mapping + */ +static const ansinamepair ansi_styles[] = { + {A_BOLD, "bold"}, + {A_FAINT, "faint"}, + {A_BOLD_OFF, "bold-off"}, + {A_UNDERLINE, "underline"}, + {A_DOUBLE_UNDERLINE, "dblunderline"}, + {A_UNDERLINE_OFF, "underline-off"}, + {A_CROSSOUT, "crossout"}, + {A_CROSSOUT_OFF, "crossout-off"}, + {A_ITALIC, "italic"}, + {A_ITALIC_OFF, "italic-off"} +}; + +/** @} */ +#endif diff --git a/gnoemoe/dialogs/Makefile.include b/gnoemoe/dialogs/Makefile.include new file mode 100644 index 0000000..9eae265 --- /dev/null +++ b/gnoemoe/dialogs/Makefile.include @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in +dialogsdir = dialogs + +gnoemoe_SOURCES += \ + $(dialogsdir)/gm-world-info-dialog.c $(dialogsdir)/gm-world-info-dialog.h \ + $(dialogsdir)/gm-world-logs-dialog.c $(dialogsdir)/gm-world-logs-dialog.h \ + $(dialogsdir)/gm-world-properties-dialog.c \ + $(dialogsdir)/gm-world-properties-dialog.h \ + $(dialogsdir)/gm-worlds-list-dialog.c \ + $(dialogsdir)/gm-worlds-list-dialog.h \ + $(dialogsdir)/gm-scripts-dialog.c $(dialogsdir)/gm-scripts-dialog.h \ + $(dialogsdir)/gm-preferences-dialog.c $(dialogsdir)/gm-preferences-dialog.h \ + $(dialogsdir)/gm-triggers-dialog.c $(dialogsdir)/gm-triggers-dialog.h diff --git a/gnoemoe/dialogs/gm-preferences-dialog.c b/gnoemoe/dialogs/gm-preferences-dialog.c new file mode 100644 index 0000000..d802e2f --- /dev/null +++ b/gnoemoe/dialogs/gm-preferences-dialog.c @@ -0,0 +1,777 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../gm-app.h" +#include "gm-preferences-dialog.h" +#include "../gm-support.h" +#include "../gm-debug.h" +#include "../gm-options.h" +#include "../gm-color-table.h" + +void gm_preferences_dialog_run_dialog(); + +void on_gm_preferences_dialog_check_button_embed_editor_clicked( + GtkButton *button, gpointer user_data); +void on_gm_preferences_dialog_check_button_alt_editor_clicked( + GtkButton *button, gpointer user_data); + +void on_gm_preferences_dialog_combo_box_scheme_changed(GtkComboBox *box, + gpointer user_data); +void on_gm_preferences_dialog_check_button_fg_clicked( + GtkButton *button, gpointer user_data); +void on_gm_preferences_dialog_check_button_bg_clicked( + GtkButton *button, gpointer user_data); +void on_gm_preferences_dialog_toggle_button_style_clicked( + GtkButton *button, gpointer user_data); + +void on_gm_preferences_dialog_tree_view_editor_colors_row_changed( + GtkTreeSelection *selection, gpointer data); +void on_gm_preferences_dialog_color_button_fg_color_set(GtkColorButton *widget, + gpointer data); +void on_gm_preferences_dialog_color_button_bg_color_set(GtkColorButton *widget, + gpointer data); +void on_gm_preferences_dialog_response(GtkDialog *dialog, gint response, + gpointer user_data); + +void gm_preferences_dialog_load_colors(); + +typedef struct _GmPreferencesDialog { + GladeXML *xml; + GtkWidget *dialog; + +} GmPreferencesDialog; + +static const GmKeyValuePair color_mapping[] = { + {"color_button_fg_default", "fg_default"}, + {"color_button_fg_default_h", "fg_default_h"}, + {"color_button_bg_default", "bg_default"}, + + {"color_button_fg_black", "fg_black"}, + {"color_button_fg_black_h", "fg_black_h"}, + {"color_button_fg_red", "fg_red"}, + {"color_button_fg_red_h", "fg_red_h"}, + {"color_button_fg_green", "fg_green"}, + {"color_button_fg_green_h", "fg_green_h"}, + {"color_button_fg_yellow", "fg_yellow"}, + {"color_button_fg_yellow_h", "fg_yellow_h"}, + {"color_button_fg_blue", "fg_blue"}, + {"color_button_fg_blue_h", "fg_blue_h"}, + {"color_button_fg_purple", "fg_purple"}, + {"color_button_fg_purple_h", "fg_purple_h"}, + {"color_button_fg_cyan", "fg_cyan"}, + {"color_button_fg_cyan_h", "fg_cyan_h"}, + {"color_button_fg_white", "fg_white"}, + {"color_button_fg_white_h", "fg_white_h"}, + + {"color_button_bg_black", "bg_black"}, + {"color_button_bg_red", "bg_red"}, + {"color_button_bg_green", "bg_green"}, + {"color_button_bg_yellow", "bg_yellow"}, + {"color_button_bg_blue", "bg_blue"}, + {"color_button_bg_purple", "bg_purple"}, + {"color_button_bg_cyan", "bg_cyan"}, + {"color_button_bg_white", "bg_white"} +}; + +static const GmKeyValuePair color_schemes[] = { + {N_("Default"), "default"}, + {N_("White on black"), "white_on_black"}, + {N_("User defined"), "user"}, + {NULL, NULL} +}; + +static GmPreferencesDialog *preferences = NULL; + +enum { + EDITOR_NAME, + EDITOR_STYLE, + EDITOR_ID, + EDITOR_N_COLUMNS +}; + +enum { + SCHEME_NAME, + SCHEME_OPTION, + SCHEME_N_COLUMNS +}; + +GtkWidget * +gm_preferences_dialog_widget(gchar *name) { + return glade_xml_get_widget(preferences->xml, name); +} + +void +gm_preferences_dialog_init_combo_box_scheme() { + GtkComboBox *box = GTK_COMBO_BOX(gm_preferences_dialog_widget( + "combo_box_scheme")); + GtkListStore *store = gtk_list_store_new(SCHEME_N_COLUMNS, G_TYPE_STRING, + G_TYPE_STRING); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeIter iter; + int i, select = 0; + const gchar *scheme = gm_options_get(gm_app_options(gm_app_instance()), + "color_scheme"); + + gtk_cell_layout_clear(GTK_CELL_LAYOUT(box)); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(box), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(box), renderer, "text", + SCHEME_NAME, NULL); + + gtk_combo_box_set_model(box, GTK_TREE_MODEL(store)); + + i = 0; + + while (color_schemes[i].key != NULL) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, SCHEME_NAME, color_schemes[i].key, + SCHEME_OPTION, color_schemes[i].value, -1); + + if (scheme != NULL && strcasecmp(scheme, color_schemes[i].value) == 0) { + select = i; + } + + ++i; + } + + gtk_combo_box_set_active(box, select); +} + +void +gm_preferences_dialog_init_editor_colors_tree() { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeView *tv = GTK_TREE_VIEW( + gm_preferences_dialog_widget("tree_view_editor_colors")); + GtkListStore *store = gtk_list_store_new(EDITOR_N_COLUMNS, G_TYPE_STRING, + GTK_TYPE_SOURCE_TAG_STYLE, G_TYPE_STRING); + GtkTreeModel *model = + gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(store)); + GtkTreeSelection *select; + + gtk_tree_view_set_model(tv, model); + select = gtk_tree_view_get_selection(tv); + + g_signal_connect(select, "changed", G_CALLBACK( + on_gm_preferences_dialog_tree_view_editor_colors_row_changed), + NULL); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Tag"), renderer, + "text", EDITOR_NAME, NULL); + gtk_tree_view_append_column(tv, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Style"), renderer, + NULL); + gtk_tree_view_column_set_visible(column, FALSE); + + gtk_tree_view_append_column(tv, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Id"), renderer, NULL); + gtk_tree_view_column_set_visible(column, FALSE); + + gtk_tree_view_append_column(tv, column); + + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), EDITOR_NAME, + GTK_SORT_ASCENDING); +} + +void +gm_preferences_dialog_init_editor_colors() { + GtkSourceTag *st; + GtkSourceTagStyle *sty; + + GSList *tags, *tag; + gchar *name, *id; + GtkTreeIter iter; + GtkListStore *store; + GtkSourceLanguage *lang = NULL; + + // TODO: Fix this! + // lang = editor_get_language(); + + // Create column ed + gm_preferences_dialog_init_editor_colors_tree(); + store = GTK_LIST_STORE(gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT( + gtk_tree_view_get_model(GTK_TREE_VIEW( + gm_preferences_dialog_widget("tree_view_editor_colors")))))); + + if (lang) { + tags = gtk_source_language_get_tags(lang); + + for (tag = tags; tag; tag = tag->next) { + st = GTK_SOURCE_TAG(tag->data); + sty = gtk_source_tag_get_style(st); + + g_object_get(G_OBJECT(st), "name", &name, "id", &id, NULL); + + gtk_list_store_prepend(store, &iter); + gtk_list_store_set(store, &iter, EDITOR_NAME, name, + EDITOR_STYLE, sty, EDITOR_ID, id, -1); + + g_free(name); + g_free(id); + } + + g_slist_free(tags); + } +} + +#define GM_PREFERENCES_DIALOG_XML \ + PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-preferences.glade" + +void +gm_preferences_dialog_run() { + const gchar *alt_editor; + GmOptions *options = gm_app_options(gm_app_instance()); + + if (preferences != NULL) { + gtk_window_present(GTK_WINDOW(preferences->dialog)); + return; + } else { + preferences = g_new0(GmPreferencesDialog, 1); + } + + preferences->xml = glade_xml_new(GM_PREFERENCES_DIALOG_XML, + "gm_preferences_dialog", NULL); + preferences->dialog = + gm_preferences_dialog_widget("gm_preferences_dialog"); + gm_preferences_dialog_init_editor_colors(); + gm_preferences_dialog_load_colors(); + gm_preferences_dialog_init_combo_box_scheme(); + + gtk_font_button_set_font_name(GTK_FONT_BUTTON( + gm_preferences_dialog_widget("font_button_font")), + gm_options_get(options, "font-family")); + + alt_editor = gm_options_get(options, "editor_alternative"); + + if (strcmp(alt_editor, "0") == 0) { + alt_editor = NULL; + } else { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_alt_editor")), + TRUE); + gtk_entry_set_text(GTK_ENTRY( + gm_preferences_dialog_widget("entry_alt_editor")), + alt_editor); + } + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_embed_editor")), + gm_options_get_int(options, "editor_embed")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_needs_terminal")), + gm_options_get_int(options, "editor_needs_terminal")); + + gtk_widget_set_sensitive( + gm_preferences_dialog_widget("entry_alt_editor"), + alt_editor != NULL); + gtk_widget_set_sensitive( + gm_preferences_dialog_widget("check_button_embed_editor"), + alt_editor != NULL); + gtk_widget_set_sensitive( + gm_preferences_dialog_widget("check_button_needs_terminal"), + alt_editor != NULL + && gm_options_get_int(options, "editor_embed") == 0); + + glade_xml_signal_connect(preferences->xml, + "on_check_button_alt_editor_clicked", G_CALLBACK( + on_gm_preferences_dialog_check_button_alt_editor_clicked)); + glade_xml_signal_connect(preferences->xml, + "on_check_button_embed_editor_clicked", G_CALLBACK( + on_gm_preferences_dialog_check_button_embed_editor_clicked)); + glade_xml_signal_connect(preferences->xml, + "on_check_button_fg_clicked", G_CALLBACK( + on_gm_preferences_dialog_check_button_fg_clicked)); + glade_xml_signal_connect(preferences->xml, + "on_check_button_bg_clicked", G_CALLBACK( + on_gm_preferences_dialog_check_button_bg_clicked)); + + glade_xml_signal_connect(preferences->xml, + "on_combo_box_scheme_changed", G_CALLBACK( + on_gm_preferences_dialog_combo_box_scheme_changed)); + + glade_xml_signal_connect_data(preferences->xml, + "on_toggle_button_bold_clicked", G_CALLBACK( + on_gm_preferences_dialog_toggle_button_style_clicked), + GINT_TO_POINTER(1)); + glade_xml_signal_connect_data(preferences->xml, + "on_toggle_button_style_clicked", G_CALLBACK( + on_gm_preferences_dialog_toggle_button_style_clicked), + GINT_TO_POINTER(2)); + glade_xml_signal_connect_data(preferences->xml, + "on_toggle_button_underline_clicked", G_CALLBACK( + on_gm_preferences_dialog_toggle_button_style_clicked), + GINT_TO_POINTER(3)); + glade_xml_signal_connect_data(preferences->xml, + "on_toggle_button_strike_trough_clicked", G_CALLBACK( + on_gm_preferences_dialog_toggle_button_style_clicked), + GINT_TO_POINTER(4)); + + glade_xml_signal_connect(preferences->xml, + "on_color_button_fg_color_set", G_CALLBACK( + on_gm_preferences_dialog_color_button_fg_color_set)); + glade_xml_signal_connect(preferences->xml, + "on_color_button_bg_color_set", G_CALLBACK( + on_gm_preferences_dialog_color_button_bg_color_set)); + + gm_preferences_dialog_run_dialog(); +} + +void +gm_preferences_dialog_load_colors() { + int i; + GtkColorButton *but; + GdkColor col; + GmColorTable *color_table = gm_app_color_table(gm_app_instance()); + + for (i = 0; i < (int)(sizeof(color_mapping) / sizeof(GmKeyValuePair)); i++) { + but = GTK_COLOR_BUTTON(gm_preferences_dialog_widget( + color_mapping[i].key)); + + gm_color_table_get(color_table, color_mapping[i].value, &col); + gtk_color_button_set_color(but, &col); + } +} + +void +gm_preferences_dialog_save_colors() { + int i; + GtkColorButton *but; + GdkColor col; + GmColorTable *color_table = gm_app_color_table(gm_app_instance()); + gchar *col_str; + + for (i = 0; i < (int)(sizeof(color_mapping) / sizeof(GmKeyValuePair)); i++) { + but = GTK_COLOR_BUTTON(gm_preferences_dialog_widget( + color_mapping[i].key)); + + gtk_color_button_get_color(but, &col); + col_str = g_strdup_printf("#%04X%04X%04X", col.red, col.green, + col.blue); + + gm_color_table_set(color_table, color_mapping[i].value, col_str); + g_free(col_str); + } +} + +void +gm_preferences_dialog_save_editor_color(GtkTreeModel *model, + GtkTreeIter *iter) { + gchar *name, *id, *opt_key, *opt_value, *fg, *bg; + GtkSourceTagStyle *style; + GmOptions *options = gm_app_options(gm_app_instance()); + + gtk_tree_model_get(model, iter, EDITOR_NAME, &name, EDITOR_STYLE, &style, + EDITOR_ID, &id, -1); + + opt_key = g_strconcat("editor_", name, NULL); + + if (!(style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND)) { + fg = g_strdup("0"); + } else { + fg = g_strdup_printf("#%04X%04X%04X", style->foreground.red, + style->foreground.green, style->foreground.blue); + } + + if (!(style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND)) { + bg = g_strdup("0"); + } else { + bg = g_strdup_printf("#%04X%04X%04X", style->background.red, + style->background.green, style->background.blue); + } + + opt_value = g_strdup_printf("%s,%s,%d,%d,%d,%d", fg, bg, style->bold, + style->italic, style->underline, + style->strikethrough); + + gm_debug_msg(DEBUG_DEFAULT, "GmPreferencesDialog.SaveEditorColor %s to %s", opt_key, + opt_value); + + gm_options_set(options, opt_key, opt_value); + + // TODO: do something about this + //gtk_source_language_set_tag_style(editor_get_language(), id, style); + + g_free(opt_key); + g_free(opt_value); + g_free(id); + g_free(fg); + g_free(bg); + g_free(name); + + // CHECK this: + //gtk_source_tag_style_free(style); +} + +void +gm_preferences_dialog_save_editor_colors() { + GtkTreeIter iter; + GtkTreeView *vw = GTK_TREE_VIEW( + gm_preferences_dialog_widget("tree_view_editor_colors")); + GtkTreeModelSort *model = GTK_TREE_MODEL_SORT(gtk_tree_view_get_model(vw)); + GtkTreeModel *store = gtk_tree_model_sort_get_model(model); + + if (gtk_tree_model_get_iter_first(store, &iter)) { + do { + gm_preferences_dialog_save_editor_color(store, &iter); + } while (gtk_tree_model_iter_next(store, &iter)); + } +} + +gboolean +gm_preferences_dialog_check_values() { + const gchar *alt_editor = gtk_entry_get_text(GTK_ENTRY( + gm_preferences_dialog_widget("entry_alt_editor"))); + const gchar *font_description = gtk_font_button_get_font_name( + GTK_FONT_BUTTON(gm_preferences_dialog_widget("font_button_font"))); + gboolean use_alt_editor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_alt_editor"))); + GmOptions *options = gm_app_options(gm_app_instance()); + + if (use_alt_editor && alt_editor[0] != '\0') { + gm_error_dialog(_("Editor can't be empty, enter a non empty command"), + GTK_WINDOW(preferences->dialog)); + gm_notebook_focus_from_label(GTK_NOTEBOOK(gm_preferences_dialog_widget( + "notebook_preferences")), _("Editor")); + gtk_widget_grab_focus(gm_preferences_dialog_widget("entry_alt_editor")); + + return FALSE; + } + + if (use_alt_editor) { + gm_options_set(options, "editor_alternative", alt_editor); + } else { + gm_options_set(options, "editor_alternative", "0"); + } + + gm_options_set_int(options, "editor_embed", gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget + ("check_button_embed_editor")))); + gm_options_set_int(options, "editor_needs_terminal", + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_needs_terminal")))); + + gm_options_set(options, "font-family", font_description); + gm_preferences_dialog_save_colors(); + gm_preferences_dialog_save_editor_colors(); + + return TRUE; +} + +void +gm_preferences_dialog_run_dialog() { + g_signal_connect(preferences->dialog, "response", + G_CALLBACK(on_gm_preferences_dialog_response), NULL); + + gtk_widget_show(GTK_WIDGET(preferences->dialog)); +} + +/* CALLBACKS */ + +void +on_gm_preferences_dialog_response(GtkDialog *dialog, gint response, + gpointer user_data) { + gboolean is_okay = TRUE; + + switch (response) { + case GTK_RESPONSE_OK: case GTK_RESPONSE_APPLY: + if (gm_preferences_dialog_check_values()) { + gm_options_save(gm_app_options(gm_app_instance())); + + if (response == GTK_RESPONSE_APPLY) { + is_okay = FALSE; + } + } else { + is_okay = FALSE; + } + break; + default: + break; + } + + if (is_okay) { + gtk_widget_destroy(GTK_WIDGET(dialog)); + g_object_unref(preferences->xml); + g_free(preferences); + preferences = NULL; + } +} + +void +on_gm_preferences_dialog_check_button_embed_editor_clicked( + GtkButton *button, gpointer user_data) { + gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + GmOptions *options = gm_app_options(gm_app_instance()); + + if (active) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_needs_terminal")), + TRUE); + } else { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_needs_terminal")), + gm_options_get_int(options, "editor_needs_terminal")); + } + + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "check_button_needs_terminal"), !active); +} + +void +on_gm_preferences_dialog_check_button_alt_editor_clicked( + GtkButton *button, gpointer user_data) { + gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + + if (!active) { + gtk_entry_set_text(GTK_ENTRY(gm_preferences_dialog_widget( + "entry_alt_editor")), ""); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_embed_editor")), + FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_preferences_dialog_widget("check_button_needs_terminal")), + FALSE); + } + + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "entry_alt_editor"), active); + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "check_button_embed_editor"), active); + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "check_button_needs_terminal"), active); +} + +void +gm_preferences_dialog_editor_style_do_enable() { + gtk_widget_set_sensitive(gm_preferences_dialog_widget("check_button_fg"), + TRUE); + gtk_widget_set_sensitive(gm_preferences_dialog_widget("check_button_bg"), + TRUE); + gtk_widget_set_sensitive(gm_preferences_dialog_widget("toggle_button_bold"), + TRUE); + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "toggle_button_style"), TRUE); + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "toggle_button_underline"), TRUE); + gtk_widget_set_sensitive(gm_preferences_dialog_widget( + "toggle_button_strike_through"), TRUE); +} + +void +on_gm_preferences_dialog_tree_view_editor_colors_row_changed( + GtkTreeSelection *selection, gpointer data) { + GtkTreeIter iter; + GtkTreeModel *model; + GtkSourceTagStyle *style; + GtkColorButton *fg, *bg; + GtkToggleButton *fgt, *bgt; + GdkColor empty_col; + + gdk_color_parse("black", &empty_col); + + gtk_tree_selection_get_selected(selection, &model, &iter); + gtk_tree_model_get(model, &iter, EDITOR_STYLE, &style, -1); + + fg = GTK_COLOR_BUTTON(gm_preferences_dialog_widget("color_button_fg")); + bg = GTK_COLOR_BUTTON(gm_preferences_dialog_widget("color_button_bg")); + fgt = GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget("check_button_fg")); + bgt = GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget("check_button_bg")); + + if (style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) { + gtk_color_button_set_color(fg, &(style->foreground)); + gtk_widget_set_sensitive(GTK_WIDGET(fg), TRUE); + gtk_toggle_button_set_active(fgt, TRUE); + } else { + gtk_color_button_set_color(fg, &empty_col); + gtk_widget_set_sensitive(GTK_WIDGET(fg), FALSE); + gtk_toggle_button_set_active(fgt, FALSE); + } + + if (style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) { + gtk_color_button_set_color(bg, &(style->background)); + gtk_widget_set_sensitive(GTK_WIDGET(bg), TRUE); + gtk_toggle_button_set_active(bgt, TRUE); + } else { + gtk_color_button_set_color(bg, &empty_col); + gtk_widget_set_sensitive(GTK_WIDGET(bg), FALSE); + gtk_toggle_button_set_active(bgt, FALSE); + } + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget( + "check_button_bold")), style->bold); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget( + "check_button_style")), style->italic); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget( + "check_button_underline")), style->underline); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gm_preferences_dialog_widget( + "check_button_strike_through")), style->strikethrough); + gm_preferences_dialog_editor_style_do_enable(); +} + +typedef struct _GmSelectionInfo { + GtkTreeIter siter; + GtkTreeIter iter; + GtkListStore *store; + GtkTreeModel *model; + GtkSourceTagStyle *style; +} GmSelectionInfo; + +GmSelectionInfo +gm_preferences_dialog_get_selection_info() { + GtkTreeSelection *selection; + GmSelectionInfo info; + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW( + gm_preferences_dialog_widget("tree_view_editor_colors"))); + + gtk_tree_selection_get_selected(selection, &(info.model), &(info.siter)); + gtk_tree_model_get(info.model, &(info.siter), EDITOR_STYLE, &(info.style), + -1); + gtk_tree_model_sort_convert_iter_to_child_iter(GTK_TREE_MODEL_SORT( + info.model), &(info.iter), &(info.siter)); + + info.store = GTK_LIST_STORE(gtk_tree_model_sort_get_model( + GTK_TREE_MODEL_SORT(info.model))); + + return info; +} + +void +on_gm_preferences_dialog_check_button_fg_clicked(GtkButton *button, + gpointer user_data) { + GmSelectionInfo info; + gboolean tog = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + GtkWidget *clb = gm_preferences_dialog_widget("color_button_fg"); + GdkColor empty_col; + + info = gm_preferences_dialog_get_selection_info(); + gdk_color_parse("black", &empty_col); + + if (tog) { + info.style->mask = info.style->mask | + GTK_SOURCE_TAG_STYLE_USE_FOREGROUND; + } else { + info.style->mask = info.style->mask & + ~GTK_SOURCE_TAG_STYLE_USE_FOREGROUND; + gtk_color_button_set_color(GTK_COLOR_BUTTON(clb), &empty_col); + info.style->foreground = empty_col; + } + + gtk_list_store_set(info.store, &(info.iter), EDITOR_STYLE, info.style, -1); + gtk_source_tag_style_free(info.style); + + gtk_widget_set_sensitive(clb, tog); +} + +void +on_gm_preferences_dialog_check_button_bg_clicked(GtkButton *button, + gpointer user_data) { + GmSelectionInfo info; + gboolean tog = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + GtkWidget *clb = gm_preferences_dialog_widget("color_button_bg"); + GdkColor empty_col; + + info = gm_preferences_dialog_get_selection_info(); + gdk_color_parse("black", &empty_col); + + if (tog) { + info.style->mask = info.style->mask | + GTK_SOURCE_TAG_STYLE_USE_BACKGROUND; + } else { + info.style->mask = info.style->mask & + ~GTK_SOURCE_TAG_STYLE_USE_BACKGROUND; + gtk_color_button_set_color(GTK_COLOR_BUTTON(clb), &empty_col); + info.style->background = empty_col; + } + + gtk_list_store_set(info.store, &(info.iter), EDITOR_STYLE, info.style, -1); + gtk_source_tag_style_free(info.style); + + gtk_widget_set_sensitive(clb, tog); +} + +void +on_gm_preferences_dialog_toggle_button_style_clicked(GtkButton *button, + gpointer user_data) { + GmSelectionInfo info; + int type = GPOINTER_TO_INT(user_data); + gboolean tog = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)); + + info = gm_preferences_dialog_get_selection_info(); + + switch (type) { + case 1: /* bold */ + info.style->bold = tog; + break; + case 2: /* italic */ + info.style->italic = tog; + break; + case 3: /* underline */ + info.style->underline = tog; + break; + case 4: + info.style->strikethrough = tog; + break; + } + + gtk_list_store_set(info.store, &(info.iter), EDITOR_STYLE, info.style, -1); + gtk_source_tag_style_free(info.style); +} + +void +on_gm_preferences_dialog_color_button_fg_color_set(GtkColorButton *widget, + gpointer user_data) { + GmSelectionInfo info; + + info = gm_preferences_dialog_get_selection_info(); + gtk_color_button_get_color(widget, &(info.style->foreground)); + + gtk_list_store_set(info.store, &(info.iter), EDITOR_STYLE, info.style, -1); + gtk_source_tag_style_free(info.style); +} + +void +on_gm_preferences_dialog_color_button_bg_color_set(GtkColorButton *widget, + gpointer user_data) { + GmSelectionInfo info; + + info = gm_preferences_dialog_get_selection_info(); + gtk_color_button_get_color(widget, &(info.style->background)); + + gtk_list_store_set(info.store, &(info.iter), EDITOR_STYLE, info.style, -1); + gtk_source_tag_style_free(info.style); +} + +void +on_gm_preferences_dialog_combo_box_scheme_changed(GtkComboBox *box, + gpointer user_data) { + GtkTreeIter iter; + gchar *scheme; + GtkTreeModel *model = gtk_combo_box_get_model(box); + GmColorTable *color_table = gm_app_color_table(gm_app_instance()); + + gtk_combo_box_get_active_iter(box, &iter); + gtk_tree_model_get(model, &iter, SCHEME_OPTION, &scheme, -1); + gm_color_table_set_from_scheme_name(color_table, scheme); + + g_free(scheme); +} diff --git a/gnoemoe/dialogs/gm-preferences-dialog.h b/gnoemoe/dialogs/gm-preferences-dialog.h new file mode 100644 index 0000000..75cf454 --- /dev/null +++ b/gnoemoe/dialogs/gm-preferences-dialog.h @@ -0,0 +1,6 @@ +#ifndef __GM_PREFERENCES_DIALOG_H__ +#define __GM_PREFERENCES_DIALOG_H__ + +void gm_preferences_dialog_run(); + +#endif /* __GM_PREFERENCES_DIALOG_H__ */ diff --git a/gnoemoe/dialogs/gm-scripts-dialog.c b/gnoemoe/dialogs/gm-scripts-dialog.c new file mode 100644 index 0000000..0430427 --- /dev/null +++ b/gnoemoe/dialogs/gm-scripts-dialog.c @@ -0,0 +1,920 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_RUBY +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "../gm-scripts.h" +#include "../gm-world.h" +#include "../gm-debug.h" +#include "../gm-app.h" +#include "../gm-support.h" +#include "../widgets/gm-app-view.h" +#include "../widgets/gm-text-scroller.h" + +#define SCRIPT_TEMPLATE \ +_("=begin\n" \ +"\t\n" \ +"\t<copyright>\n" \ +"\n\tRegister functions in the register_functions method defined below.\n" \ +"\tRegister functions with " \ +"$scripts.register(<name>, <description>[, <alias>])\n=end" \ +"\n\ndef register_functions\n" \ +"\t$scripts.register(\"myscript\", \"use /myscript\")\nend\n\n" \ +"def myscript(argstr)\n\t# Insert code here\nend\n") + +void on_gm_scripts_dialog_script_added(GmScripts *scripts, GmScript *script, + gpointer user_data); +void on_gm_scripts_dialog_script_changed(GmScripts *scripts, GmScript *script, + gpointer user_data); +void on_gm_scripts_dialog_script_removed(GmScripts *scripts, GmScript *script, + gpointer user_data); +void on_gm_scripts_dialog_message(GmScripts *scripts, gchar *message, + gpointer user_data); +void on_gm_scripts_dialog_error(GmScripts *scripts, gchar *message, + gpointer user_data); +void on_gm_scripts_dialog_run(GmScripts *scripts, gchar *message, + gpointer user_data); + +typedef enum _MessageType { + SCRIPT_MESSAGE, + SCRIPT_ERROR, + SCRIPT_RUN +} MessageType; + +typedef enum _scripts_columns { + SCRIPTS_NAME, + SCRIPTS_OBJECT, + SCRIPTS_N +} scripts_columns; + +typedef struct _SelectionInfo { + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter seliter; + GtkTreeIter parent; + gboolean has_parent; + gboolean can_save; + gchar *filename; +} SelectionInfo; + +typedef struct _GmScriptsDialog { + GtkTextTagTable *tag_table; + GladeXML *xml; + GtkTextBuffer *text_buffer_console; + gchar *current_edit; + GtkTextBuffer *text_buffer_editor; + + GtkWidget *dialog; + GtkWidget *text_view_console; + GtkWidget *statusbar_editor; + GtkWidget *button_save; + GtkWidget *tree_view_scripts; + GtkWidget *tree_view_files; + GtkWidget *button_delete; + GmTextScroller *text_scroller; + + SelectionInfo info; +} GmScriptsDialog; + +static GmScriptsDialog *scripts_dialog = NULL; + +#define G_SCRIPTS_XML PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-scripts.glade" + +gboolean on_gm_scripts_dialog_delete(GtkWidget *widget, GdkEvent *event, + gpointer user_data); + +void on_files_changed(GtkTreeSelection *treeselection, gpointer user_data); +void on_tree_view_files_row_activated(GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, gpointer user_data); +void on_text_buffer_editor_modified_changed(GtkTextBuffer *buffer, + gpointer user_data); + +void on_tool_button_new_clicked(GtkToolButton *button, gpointer user_data); +void on_tool_button_save_clicked(GtkToolButton *button, gpointer user_data); +void on_tool_button_save_as_clicked(GtkToolButton *button, gpointer user_data); +void on_tool_button_delete_clicked(GtkToolButton *button, gpointer user_data); + +gboolean +gm_scripts_dialog_can_write(gchar *filename) { + return (access(filename, W_OK) == 0); +} + +void +gm_scripts_dialog_set_status(gchar *msg) { + gtk_statusbar_pop(GTK_STATUSBAR(scripts_dialog->statusbar_editor), + 0); + gtk_statusbar_push(GTK_STATUSBAR(scripts_dialog->statusbar_editor), + 0, msg); +} + +gboolean +gm_scripts_dialog_editor_save(gchar *filename) { + FILE *f; + gchar *msg, *text; + GtkTextIter start, end; + + if (!filename) { + return FALSE; + } + + f = fopen(filename, "w"); + + if (f) { + gtk_text_buffer_get_start_iter(scripts_dialog->text_buffer_editor, + &start); + gtk_text_buffer_get_end_iter(scripts_dialog->text_buffer_editor, &end); + text = gtk_text_buffer_get_text(scripts_dialog->text_buffer_editor, + &start, &end, TRUE); + + fputs(text, f); + + msg = g_strconcat(_("Saved "), filename, NULL); + gm_scripts_dialog_set_status(msg); + g_free(msg); + + if (scripts_dialog->current_edit != filename) { + g_free(scripts_dialog->current_edit); + scripts_dialog->current_edit = g_strdup(filename); + } + + gtk_text_buffer_set_modified(scripts_dialog->text_buffer_editor, + FALSE); + + fclose(f); + return TRUE; + } else { + text = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); + msg = g_strconcat(_("Saving failed: "), strerror(errno), " (", + text, ")", NULL); + gm_error_dialog(msg, GTK_WINDOW(scripts_dialog->dialog)); + g_free(text); + g_free(msg); + + return FALSE; + } +} + +void +gm_scripts_dialog_editor_load(SelectionInfo *info) { + FILE *f; + gchar line[1024]; + GtkTextIter end; + gchar *lline, *msg; + + if (info) { + gtk_text_buffer_set_text(scripts_dialog->text_buffer_editor, "", 0); + gtk_text_buffer_get_end_iter(scripts_dialog->text_buffer_editor, &end); + + f = fopen(info->filename, "r"); + + if (f) { + while (fgets((char *) &line, 1024 - 1, f) != NULL) { + lline = g_locale_to_utf8((gchar *)&line, strlen((char *)&line), + NULL, NULL, NULL); + + if (lline) { + gtk_text_buffer_insert(scripts_dialog->text_buffer_editor, + &end, lline, strlen(lline)); + g_free(lline); + } else { + gtk_text_buffer_insert(scripts_dialog->text_buffer_editor, + &end, (gchar *)&line, strlen((char *)&line)); + } + } + + fclose(f); + + gtk_widget_set_sensitive(scripts_dialog->button_save, + info->can_save); + + g_free(scripts_dialog->current_edit); + scripts_dialog->current_edit = g_strdup(info->filename); + + g_free(scripts_dialog->current_edit); + scripts_dialog->current_edit = g_strdup(info->filename); + + msg = g_strconcat(_("Loaded "), info->filename, NULL); + gm_scripts_dialog_set_status(msg); + g_free(msg); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmScript.EditorLoad: file (%s) could not be read: %s", + scripts_dialog->current_edit, strerror(errno)); + } + } else { + gtk_text_buffer_set_text(scripts_dialog->text_buffer_editor, + SCRIPT_TEMPLATE, -1); + g_free(scripts_dialog->current_edit); + scripts_dialog->current_edit = NULL; + + gm_scripts_dialog_set_status(_("New <untitled>")); + } +} + +void +gm_scripts_dialog_init_console() { + GtkTextView *txt = GTK_TEXT_VIEW(scripts_dialog->text_view_console); + PangoFontDescription *f; + + gtk_text_view_set_buffer(txt, scripts_dialog->text_buffer_console); + gtk_text_view_set_editable(txt, FALSE); + + f = + pango_font_description_from_string("monospace 8"); + + if (f) { + gtk_widget_modify_font(GTK_WIDGET(txt), f); + pango_font_description_free(f); + } +} + +void +gm_scripts_dialog_tree_add_function(GtkTreeStore *model, GtkTreeIter *parent, + GmScriptFunction *fi) { + GtkTreeIter item; + gchar *name, *description, *all, *markup; + + gtk_tree_store_append(model, &item, parent); + name = g_filename_to_utf8(fi->name, strlen(fi->name), NULL, + NULL, NULL); + description = g_locale_to_utf8(fi->description, strlen(fi->description), + NULL, NULL, NULL); + + markup = g_markup_escape_text(description, g_utf8_strlen(description, -1)); + all = g_strconcat("<b>", name, "</b>\n", markup, NULL); + gtk_tree_store_set(model, &item, SCRIPTS_NAME, all, -1); + + g_free(all); + g_free(markup); + g_free(description); + g_free(name); +} + +void +gm_scripts_dialog_tree_update_script_item(GmScript *script, GtkTreeIter *iter) { + GList *list; + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model( + GTK_TREE_VIEW(scripts_dialog->tree_view_scripts))); + GtkTreeIter item; + + if (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &item, iter)) { + while (gtk_tree_store_remove(model, &item)); + } + + for (list = script->functions; list; list = list->next) { + gm_scripts_dialog_tree_add_function(model, iter, + (GmScriptFunction *)(list->data)); + } +} + +gboolean +gm_scripts_dialog_model_find_script(GtkTreeModel *model, + GmScript *script, GtkTreeIter *item, GtkTreeIter *parent) { + GmScript *obj; + + if (gtk_tree_model_iter_children(model, item, parent)) { + do { + gtk_tree_model_get(model, item, SCRIPTS_OBJECT, + &obj, -1); + + if (obj == script) { + return TRUE; + } + } while (gtk_tree_model_iter_next(model, item)); + } + + return FALSE; +} + +void +gm_scripts_dialog_tree_update_script(GmScript *script) { + GtkTreeModel *model = gtk_tree_view_get_model( + GTK_TREE_VIEW(scripts_dialog->tree_view_scripts)); + GtkTreeIter item; + + if (gm_scripts_dialog_model_find_script(model, script, &item, NULL)) { + gm_scripts_dialog_tree_update_script_item(script, &item); + } +} + +void +gm_scripts_dialog_tree_remove_script(GmScript *script) { + GtkTreeModel *model = gtk_tree_view_get_model( + GTK_TREE_VIEW(scripts_dialog->tree_view_scripts)); + GtkTreeIter item; + + if (gm_scripts_dialog_model_find_script(model, script, &item, NULL)) { + gtk_tree_store_remove(GTK_TREE_STORE(model), &item); + } +} + +void +gm_scripts_dialog_tree_add_script(GmScript *script) { + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model( + GTK_TREE_VIEW(scripts_dialog->tree_view_scripts))); + GtkTreeIter item; + gchar *name, *base; + + gtk_tree_store_append(model, &item, NULL); + name = g_filename_to_utf8(script->filename, strlen(script->filename), NULL, + NULL, NULL); + base = g_path_get_basename(name); + g_free(name); + + if (script->type == GM_SCRIPT_TYPE_USER) { + name = g_strconcat(_("<b>User:</b> "), base, NULL); + } else { + name = g_strconcat(_("<b>Share:</b> "), base, NULL); + } + + g_free(base); + gtk_tree_store_set(model, &item, SCRIPTS_NAME, name, SCRIPTS_OBJECT, + script, -1); + g_free(name); + + gm_scripts_dialog_tree_update_script_item(script, &item); +} + +void +gm_scripts_dialog_fill_tree() { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_scripts); + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model(view)); + GList *scripts; + + gtk_tree_store_clear(model); + + for (scripts = gm_scripts_scripts(gm_app_scripts(gm_app_instance())); + scripts; scripts = scripts->next) { + gm_scripts_dialog_tree_add_script((GmScript *)(scripts->data)); + } +} + +void +gm_scripts_dialog_init_tree() { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_scripts); + GtkTreeStore *model = gtk_tree_store_new(SCRIPTS_N, G_TYPE_STRING, + G_TYPE_POINTER); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Scripts"), renderer, + "markup", SCRIPTS_NAME, NULL); + + gtk_tree_view_append_column(view, column); + gtk_tree_view_set_model(view, GTK_TREE_MODEL(model)); + + gm_scripts_dialog_fill_tree(); +} + +void +gm_scripts_dialog_remove_file(GmScript *script) { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_files); + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model(view)); + const gchar *sharedir = PACKAGE_DATA_DIR "/" PACKAGE "/scripts"; + GtkTreeIter parent; + GtkTreeIter item; + gchar *name = g_filename_to_utf8(script->filename, + strlen(script->filename), NULL, NULL, NULL); + + if (strncmp(sharedir, name, strlen(sharedir)) == 0) { + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &parent, "0"); + } else { + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &parent, "1"); + } + + g_free(name); + + if (gm_scripts_dialog_model_find_script(GTK_TREE_MODEL(model), script, + &item, &parent)) { + gtk_tree_store_remove(model, &item); + } +} + +gboolean +gm_scripts_dialog_add_file(GmScript *script) { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_files); + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model(view)); + GtkTreeIter share, home, parent, item; + gchar *sharedir = g_strdup(PACKAGE_DATA_DIR "/" PACKAGE "/scripts"); + gchar *homedir = g_strconcat(gm_app_path(gm_app_instance()), "/scripts", + NULL); + gchar *name; + + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &share, "0"); + gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &home, "1"); + + name = g_filename_to_utf8(script->filename, strlen(script->filename), NULL, + NULL, NULL); + + if (strncmp(sharedir, name, strlen(sharedir)) == 0) { + parent = share; + } else if (strncmp(homedir, name, strlen(homedir)) == 0) { + parent = home; + } else { + g_free(sharedir); + g_free(homedir); + g_free(name); + return FALSE; + } + + gtk_tree_store_append(model, &item, &parent); + gtk_tree_store_set(model, &item, SCRIPTS_NAME, g_strrstr(name, "/") + 1, + SCRIPTS_OBJECT, script, -1); + + g_free(name); + g_free(sharedir); + g_free(homedir); + + return TRUE; +} + +void +gm_scripts_dialog_fill_files() { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_files); + GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model(view)); + GtkTreeIter share, home; + GtkTreePath *path; + GList *scripts; + GmScript *script; + + gtk_tree_store_clear(model); + + gtk_tree_store_append(model, &share, NULL); + gtk_tree_store_set(model, &share, SCRIPTS_NAME, _("Share"), -1); + + gtk_tree_store_append(model, &home, NULL); + gtk_tree_store_set(model, &home, SCRIPTS_NAME, _("User"), -1); + + for (scripts = gm_scripts_scripts(gm_app_scripts(gm_app_instance())); + scripts; scripts = scripts->next) { + script = (GmScript *)(scripts->data); + gm_scripts_dialog_add_file(script); + } + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &home); + gtk_tree_view_expand_to_path(view, path); + gtk_tree_path_free(path); + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &share); + gtk_tree_view_expand_to_path(view, path); + gtk_tree_path_free(path); +} + +void +gm_scripts_dialog_init_files() { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_files); + GtkTreeStore *model = gtk_tree_store_new(SCRIPTS_N, G_TYPE_STRING, + G_TYPE_POINTER); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeSelection *selection; + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes( + _("Filename"), renderer, "text", SCRIPTS_NAME, NULL); + + gtk_tree_view_append_column(view, column); + gtk_tree_view_set_model(view, GTK_TREE_MODEL(model)); + + selection = gtk_tree_view_get_selection(view); + + g_signal_connect((gpointer)selection, "changed", + G_CALLBACK(on_files_changed), NULL); + gm_scripts_dialog_fill_files(); +} + +GtkWidget * +gm_scripts_dialog_init_editor() { + GtkSourceLanguagesManager *lm = gtk_source_languages_manager_new(); + GtkSourceLanguage *lang; + GtkWidget *view; + PangoFontDescription *f; + + lang = gtk_source_languages_manager_get_language_from_mime_type(lm, + "application/x-ruby"); + + view = gtk_source_view_new(); + scripts_dialog->text_buffer_editor = gtk_text_view_get_buffer( + GTK_TEXT_VIEW(view)); + + g_signal_connect((gpointer)scripts_dialog->text_buffer_editor, + "modified-changed", + G_CALLBACK(on_text_buffer_editor_modified_changed), NULL); + + if (lang) { + gtk_source_buffer_set_language( + GTK_SOURCE_BUFFER(scripts_dialog->text_buffer_editor), lang); + } + + f = pango_font_description_from_string(gm_options_get(gm_app_options( + gm_app_instance()), "font-family")); + gtk_widget_modify_font(view, f); + + gtk_source_view_set_insert_spaces_instead_of_tabs(GTK_SOURCE_VIEW(view), + FALSE); + gtk_source_view_set_auto_indent(GTK_SOURCE_VIEW(view), TRUE); + gtk_source_view_set_show_line_numbers(GTK_SOURCE_VIEW(view), TRUE); + gtk_source_view_set_smart_home_end(GTK_SOURCE_VIEW(view), TRUE); + gtk_source_view_set_tabs_width(GTK_SOURCE_VIEW(view), 4); + + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 6); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 6); + + gtk_source_buffer_set_highlight(GTK_SOURCE_BUFFER( + scripts_dialog->text_buffer_editor), TRUE); + + // Load default template + gtk_text_buffer_set_text(scripts_dialog->text_buffer_editor, + SCRIPT_TEMPLATE, -1); + + gtk_widget_show(view); + + return view; +} + +GtkTextTag * +gm_scripts_dialog_create_tag(gchar *name, gchar *fg) { + GtkTextTag *tag; + GdkColor col; + + gdk_color_parse(fg, &col); + tag = gtk_text_tag_new(name); + g_object_set(G_OBJECT(tag), "foreground-gdk", &col, NULL); + + return tag; +} + +void +gm_scripts_dialog_init() { + GmScripts *scripts = gm_app_scripts(gm_app_instance()); + + scripts_dialog = g_new0(GmScriptsDialog, 1); + scripts_dialog->tag_table = g_object_ref(gtk_text_tag_table_new()); + scripts_dialog->dialog = NULL; + + gtk_text_tag_table_add(scripts_dialog->tag_table, + gm_scripts_dialog_create_tag("scripts-run", "#445632")); + gtk_text_tag_table_add(scripts_dialog->tag_table, + gm_scripts_dialog_create_tag("scripts-msg", "#314E6C")); + gtk_text_tag_table_add(scripts_dialog->tag_table, + gm_scripts_dialog_create_tag("scripts-error", "#663822")); + + scripts_dialog->text_buffer_console = + g_object_ref(gtk_text_buffer_new(scripts_dialog->tag_table)); + + // Attach signals to GmScripts object to know about changes + g_signal_connect(scripts, "script_added", + G_CALLBACK(on_gm_scripts_dialog_script_added), NULL); + g_signal_connect(scripts, "script_changed", + G_CALLBACK(on_gm_scripts_dialog_script_changed), NULL); + g_signal_connect(scripts, "script_removed", + G_CALLBACK(on_gm_scripts_dialog_script_removed), NULL); + g_signal_connect(scripts, "message", + G_CALLBACK(on_gm_scripts_dialog_message), NULL); + g_signal_connect(scripts, "error", + G_CALLBACK(on_gm_scripts_dialog_error), NULL); + g_signal_connect(scripts, "run", + G_CALLBACK(on_gm_scripts_dialog_run), NULL); +} + +void +gm_scripts_dialog_fini() { + g_object_unref(scripts_dialog->tag_table); + g_object_unref(scripts_dialog->text_buffer_console); + g_free(scripts_dialog); +} + +gboolean +gm_scripts_scroll_end_idle(gpointer user_data) { + //gm_text_scroller_scroll_end(scripts_dialog->text_scroller); + return FALSE; +} + +void +gm_scripts_dialog_run(GmAppView *view) { + GtkWidget *editor; + + if (scripts_dialog->dialog != NULL) { + gtk_window_present(GTK_WINDOW(scripts_dialog->dialog)); + return; + } + + scripts_dialog->xml = glade_xml_new(G_SCRIPTS_XML, "gm_scripts_dialog", NULL); + scripts_dialog->dialog = glade_xml_get_widget(scripts_dialog->xml, + "gm_scripts_dialog"); + scripts_dialog->text_view_console = glade_xml_get_widget(scripts_dialog->xml, + "text_view_console"); + scripts_dialog->statusbar_editor = glade_xml_get_widget(scripts_dialog->xml, + "statusbar_editor"); + scripts_dialog->button_save = glade_xml_get_widget(scripts_dialog->xml, + "tool_button_save"); + scripts_dialog->tree_view_scripts = glade_xml_get_widget(scripts_dialog->xml, + "tree_view_scripts"); + scripts_dialog->tree_view_files = glade_xml_get_widget(scripts_dialog->xml, + "tree_view_files"); + scripts_dialog->button_delete = glade_xml_get_widget(scripts_dialog->xml, + "tool_button_delete"); + + // Create new text scroller, this object will take care of itself and will + // destroy itself when the view dies, neat! + scripts_dialog->text_scroller = gm_text_scroller_new(GTK_TEXT_VIEW( + scripts_dialog->text_view_console)); + + gm_scripts_dialog_init_console(); + gm_scripts_dialog_init_tree(); + gm_scripts_dialog_init_files(); + + editor = gm_scripts_dialog_init_editor(); + + gtk_container_add(GTK_CONTAINER(glade_xml_get_widget(scripts_dialog->xml, + "scrolled_window_editor")), editor); + + glade_xml_signal_connect(scripts_dialog->xml, "on_gm_scripts_dialog_delete", + G_CALLBACK(on_gm_scripts_dialog_delete)); + glade_xml_signal_connect(scripts_dialog->xml, + "on_tree_view_files_row_activated", + G_CALLBACK(on_tree_view_files_row_activated)); + glade_xml_signal_connect(scripts_dialog->xml, + "on_tool_button_new_clicked", + G_CALLBACK(on_tool_button_new_clicked)); + glade_xml_signal_connect(scripts_dialog->xml, + "on_tool_button_save_clicked", + G_CALLBACK(on_tool_button_save_clicked)); + glade_xml_signal_connect(scripts_dialog->xml, + "on_tool_button_save_as_clicked", + G_CALLBACK(on_tool_button_save_as_clicked)); + glade_xml_signal_connect(scripts_dialog->xml, + "on_tool_button_delete_clicked", + G_CALLBACK(on_tool_button_delete_clicked)); + + gtk_window_set_transient_for(GTK_WINDOW(scripts_dialog->dialog), + GTK_WINDOW(view)); + gm_scripts_dialog_set_status(_("New <untitled>")); + + g_object_unref(scripts_dialog->xml); + + g_idle_add((GSourceFunc)(gm_scripts_scroll_end_idle), NULL); +} + + +void +gm_scripts_dialog_add(MessageType mtype, gchar *msg) { + gchar *m, *tagName = NULL, *newLine; + gchar p[3] = {' ', ' ', '\0'}; + GtkTextIter end; + + switch (mtype) { + case SCRIPT_RUN: + p[0] = ':'; + tagName = g_strdup("scripts-run"); + break; + case SCRIPT_MESSAGE: + p[0] = '#'; + tagName = g_strdup("scripts-msg"); + break; + case SCRIPT_ERROR: + p[0] = '!'; + tagName = g_strdup("scripts-error"); + break; + } + + m = g_strconcat(p, msg, "\n", NULL); + gtk_text_buffer_get_end_iter(scripts_dialog->text_buffer_console, &end); + + // convert to UTF-8 + newLine = g_locale_to_utf8(m, strlen(m), NULL, NULL, NULL); + + if (newLine == NULL) { + gtk_text_buffer_insert_with_tags_by_name( + scripts_dialog->text_buffer_console, &end, m, strlen(m), + tagName, NULL); + } else { + gtk_text_buffer_insert_with_tags_by_name( + scripts_dialog->text_buffer_console, &end, newLine, + strlen(newLine), tagName, NULL); + g_free(newLine); + } + + g_free(m); + g_free(tagName); +} + +gboolean +gm_scripts_dialog_selection_info(SelectionInfo *info) { + GtkTreeView *view = GTK_TREE_VIEW(scripts_dialog->tree_view_files); + GtkTreeModel *model = gtk_tree_view_get_model(view); + GtkTreeSelection *selection = gtk_tree_view_get_selection(view); + gchar *parentName, *name, *filename; + + info->view = view; + info->model = model; + + if (gtk_tree_selection_get_selected(selection, &model, &(info->seliter))) { + info->has_parent = gtk_tree_model_iter_parent(model, + &(info->parent), &(info->seliter)); + + if (info->has_parent) { + gtk_tree_model_get(model, &(info->parent), SCRIPTS_NAME, + &parentName, -1); + gtk_tree_model_get(model, &(info->seliter), SCRIPTS_NAME, + &name, -1); + + if (strcmp(parentName, _("Share")) == 0) { + filename = g_strconcat( + PACKAGE_DATA_DIR "/" PACKAGE "/scripts/", name, NULL); + } else { + filename = g_strconcat(gm_app_path(gm_app_instance()), + "/scripts/", name, NULL); + } + + info->filename = g_filename_from_utf8(filename, -1, + NULL, NULL, NULL); + + g_free(name); + g_free(parentName); + g_free(filename); + info->can_save = gm_scripts_dialog_can_write(info->filename); + } else { + info->filename = NULL; + } + + return TRUE; + } else { + return FALSE; + } +} + +/* CALLBACKS */ + +gboolean +on_gm_scripts_dialog_delete(GtkWidget *widget, GdkEvent *event, + gpointer user_data) { + g_free(scripts_dialog->current_edit); + scripts_dialog->current_edit = NULL; + scripts_dialog->dialog = NULL; + + return FALSE; +} + +void +on_files_changed(GtkTreeSelection *treeselection, gpointer user_data) { + SelectionInfo info; + + if (gm_scripts_dialog_selection_info(&info)) { + gtk_widget_set_sensitive(scripts_dialog->button_delete, + info.has_parent && info.can_save); + + if (info.filename) { + g_free(info.filename); + } + } +} + +void +on_tree_view_files_row_activated(GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, gpointer user_data) { + SelectionInfo si; + + if (gm_scripts_dialog_selection_info(&si) && si.filename) { + gm_scripts_dialog_editor_load(&si); + g_free(si.filename); + } +} + +void +on_text_buffer_editor_modified_changed(GtkTextBuffer *buffer, + gpointer user_data) { + gchar *msg; + gboolean modified = gtk_text_buffer_get_modified(buffer); + + if (modified) { + if (scripts_dialog->current_edit) { + msg = g_strconcat(_("Changed "), scripts_dialog->current_edit, NULL); + } else { + msg = g_strdup(_("Changed <untitled>")); + } + + gm_scripts_dialog_set_status(msg); + g_free(msg); + } +} + +void +on_tool_button_new_clicked(GtkToolButton *button, gpointer user_data) { + gm_scripts_dialog_editor_load(NULL); +} + +void +on_tool_button_save_clicked(GtkToolButton *button, gpointer user_data) { + if (scripts_dialog->current_edit) { + gm_scripts_dialog_editor_save(scripts_dialog->current_edit); + } else { + on_tool_button_save_as_clicked(button, user_data); + } +} + +void +on_tool_button_save_as_clicked(GtkToolButton *button, gpointer user_data) { + GtkWidget *dlg; + gchar *filename, *di; + + dlg = gtk_file_chooser_dialog_new(_("Save file"), + GTK_WINDOW(scripts_dialog->dialog), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + + if (scripts_dialog->current_edit) { + gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), + scripts_dialog->current_edit); + } else { + di = g_strconcat(gm_app_path(gm_app_instance()), + "/scripts/untitled.rb", NULL); + gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(dlg), di); + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dlg), "untitled.rb"); + g_free(di); + } + + if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg)); + + gm_scripts_dialog_editor_save(filename); + g_free(filename); + } + + gtk_widget_destroy(dlg); +} + +void +on_tool_button_delete_clicked(GtkToolButton *button, gpointer user_data) { + SelectionInfo info; + + if (gm_scripts_dialog_selection_info(&info)) { + if (info.has_parent && info.can_save) { + remove(info.filename); + } + + g_free(info.filename); + } +} + +void +on_gm_scripts_dialog_script_added(GmScripts *scripts, GmScript *script, + gpointer user_data) { + if (scripts_dialog->dialog != NULL) { + gm_scripts_dialog_tree_add_script(script); + gm_scripts_dialog_add_file(script); + } +} + +void +on_gm_scripts_dialog_script_changed(GmScripts *scripts, GmScript *script, + gpointer user_data) { + if (scripts_dialog->dialog != NULL) { + gm_scripts_dialog_tree_update_script(script); + } +} + +void +on_gm_scripts_dialog_script_removed(GmScripts *scripts, GmScript *script, + gpointer user_data) { + if (scripts_dialog->dialog != NULL) { + gm_scripts_dialog_tree_remove_script(script); + gm_scripts_dialog_remove_file(script); + + if (strcmp(script->filename, scripts_dialog->current_edit) == 0) { + gm_scripts_dialog_editor_load(NULL); + } + } +} + +void on_gm_scripts_dialog_message(GmScripts *scripts, gchar *message, + gpointer user_data) { + gm_scripts_dialog_add(SCRIPT_MESSAGE, message); +} + +void on_gm_scripts_dialog_error(GmScripts *scripts, gchar *message, + gpointer user_data) { + gm_scripts_dialog_add(SCRIPT_ERROR, message); +} + +void on_gm_scripts_dialog_run(GmScripts *scripts, gchar *message, + gpointer user_data) { + gm_scripts_dialog_add(SCRIPT_RUN, message); +} + +#endif diff --git a/gnoemoe/dialogs/gm-scripts-dialog.h b/gnoemoe/dialogs/gm-scripts-dialog.h new file mode 100644 index 0000000..602b0f0 --- /dev/null +++ b/gnoemoe/dialogs/gm-scripts-dialog.h @@ -0,0 +1,18 @@ +#ifndef __GM_SCRIPTS_DIALOG_H__ +#define __GM_SCRIPTS_DIALOG_H__ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_RUBY +#include <gtk/gtk.h> +#include "../gm-world.h" +#include "../widgets/gm-app-view.h" + +void gm_scripts_dialog_run(GmAppView *view); +void gm_scripts_dialog_init(); +void gm_scripts_dialog_fini(); + +#endif +#endif /* __GM_SCRIPTS_DIALOG_H__ */ diff --git a/gnoemoe/dialogs/gm-triggers-dialog.c b/gnoemoe/dialogs/gm-triggers-dialog.c new file mode 100644 index 0000000..e85fc17 --- /dev/null +++ b/gnoemoe/dialogs/gm-triggers-dialog.c @@ -0,0 +1,991 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libgnomeui/libgnomeui.h> +#include <glade/glade.h> + +#include "../gm-support.h" + +#include "../gm-debug.h" +#include "gm-triggers-dialog.h" +#include "../gm-app.h" +#include "../gm-pixbuf.h" +#include "../gm-scripts.h" + +typedef struct _GmTriggersDialog { + GtkWidget *dialog; + GladeXML *xml; + GmWorld *world; + GmTrigger *trigger; + gboolean is_new; + + GtkWidget *entry_name; + GtkWidget *button_ok; + GtkWidget *button_next; + GtkWidget *vbox_conditions; + GtkWidget *vbox_actions; + GtkWidget *notebook_triggers; + GtkWidget *tree_view_event_types; + GtkWidget *hbox_add_condition; + GtkWidget *hbox_add_action; + GtkTreeModel *action_model; + GtkTreeModel *condition_model; + GtkTreeModel *highlight_model; + +#ifdef HAVE_RUBY + GtkTreeModel *script_model; +#endif +} GmTriggersDialog; + +enum { + NO_ARGS, + SINGLE_ARGS, + CUSTOM_ARGS +}; + +typedef enum _CustomArgType { + CUSTOM_ARG_CREATE, + CUSTOM_ARG_GET_DATA +} CustomArgType; + +gpointer gm_triggers_dialog_custom_arg_highlight(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data); +gpointer gm_triggers_dialog_custom_arg_browse(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data); + +#ifdef HAVE_RUBY +gpointer gm_triggers_dialog_custom_arg_script(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data); +#endif + +#define CUSTOM_ARG_FUNC(x) (CustomArgFunc *)(x) +typedef gpointer (*CustomArgFunc) (GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data); + +enum { + COLUMN_NAME, + COLUMN_DATA, + N_COLUMNS +}; + +enum { + COLUMN_HIGH_NAME, + COLUMN_HIGH_TAG, + N_COLUMNS_HIGH +}; + +typedef struct _ModelData { + gint type; + gchar *title; + gint args; + CustomArgFunc func; +} ModelData; + +typedef struct _TagPair { + const gchar *name; + const gchar *tag; +} TagPair; + +typedef struct _ComboBoxTypeData { + GmTriggersDialog *triggers; + GmTriggerData *data; +} ComboBoxTypeData; + +static const TagPair highlightTags[] = { + {N_("Black"), "bg_black"}, + {N_("Red"), "bg_red"}, + {N_("Green"), "bg_green"}, + {N_("Yellow"), "bg_yellow"}, + {N_("Blue"), "bg_blue"}, + {N_("Purple"), "bg_purple"}, + {N_("Cyan"), "bg_cyan"}, + {N_("White"), "bg_white"}, + {NULL, NULL} +}; + +static const ModelData dataConditionOutput[] = { + {TCT_CONTAINS, N_("Contains"), SINGLE_ARGS, NULL}, + {TCT_NOT_CONTAINS, N_("Not contains"), SINGLE_ARGS, NULL}, + {TCT_BEGINS, N_("Begins with"), SINGLE_ARGS, NULL}, + {TCT_NOT_BEGINS, N_("Not begins with"), SINGLE_ARGS, NULL}, + {TCT_ENDS, N_("Ends with"), SINGLE_ARGS, NULL}, + {TCT_NOT_ENDS, N_("Not ends with"), SINGLE_ARGS, NULL}, + {TCT_MATCHES, N_("Matches"), SINGLE_ARGS, NULL}, + {TCT_NOT_MATCHES, N_("Not matches"), SINGLE_ARGS, NULL}, + {-1, NULL, 0, NULL} +}; + +static const ModelData dataConditionUsers[] = { + {TCT_USER_ONLINE, N_("Online"), SINGLE_ARGS, NULL}, + {TCT_USER_OFFLINE, N_("Offline"), SINGLE_ARGS, NULL}, + {TCT_USER_IDLE, N_("Idle"), SINGLE_ARGS, NULL}, + {TCT_USER_IDLE_OFF, N_("No longer idle"), SINGLE_ARGS, NULL}, + {TCT_USER_AWAY, N_("Away"), SINGLE_ARGS, NULL}, + {TCT_USER_AWAY_OFF, N_("No longer away"), SINGLE_ARGS, NULL}, + {-1, NULL, 0, NULL} +}; + +static const ModelData dataActionOutput[] = { + {TAT_HIGHLIGHT_LINE, N_("Highlight line"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_highlight}, + {TAT_HIGHLIGHT_MATCH, N_("Highlight match"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_highlight}, + {TAT_BEEP, N_("Beep"), NO_ARGS, NULL}, + {TAT_PLAY_SOUND, N_("Play sound"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_browse}, + {TAT_NOTIFY, N_("Notify"), SINGLE_ARGS, NULL}, + #ifdef HAVE_RUBY + {TAT_RUN_SCRIPT, N_("Run script"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_script}, + #endif + {TAT_RUN, N_("Run"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_browse}, + {-1, NULL, 0, NULL} +}; + +static const ModelData dataActionUsers[] = { + {TAT_BEEP, N_("Beep"), NO_ARGS, NULL}, + {TAT_PLAY_SOUND, N_("Play sound"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_browse}, + {TAT_NOTIFY, N_("Notify"), SINGLE_ARGS, NULL}, + #ifdef HAVE_RUBY + {TAT_RUN_SCRIPT, N_("Run script"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_script}, + #endif + {TAT_RUN, N_("Run"), CUSTOM_ARGS, + gm_triggers_dialog_custom_arg_browse}, + {-1, NULL, 0, NULL} +}; + +void on_button_next_clicked(GtkButton *button, GmTriggersDialog *triggers); +void on_button_add_condition_clicked(GtkButton *button, + GmTriggersDialog *triggers); +void on_button_add_action_clicked(GtkButton *button, + GmTriggersDialog *triggers); +void on_notebook_triggers_switch_page(GtkNotebook *notebook, GtkNotebookPage + *page, guint page_num, GmTriggersDialog *triggers); +void on_combo_box_type_changed(GtkComboBox *widget, ComboBoxTypeData *tdata); +void on_combo_box_type_destroy(GtkObject *object, ComboBoxTypeData *tdata); +void on_button_remove_clicked(GtkButton *button, GmTriggersDialog *triggers); +void on_tree_view_event_types_changed(GtkTreeSelection *treeselection, + GmTriggersDialog *triggers); + +#define G_TRIGGERS_XML PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-triggers.glade" + +void +gm_triggers_dialog_create_models(GmTriggersDialog *triggers, + const ModelData *conditionData, const ModelData *actionData) { + int i; + GtkTreeIter iter; + ModelData *data; + GList *scripts, *item; + + #ifdef HAVE_RUBY + GmScript *script; + GmScriptFunction *func; + #endif + + triggers->action_model = GTK_TREE_MODEL(gtk_list_store_new( + N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER)); + triggers->condition_model = GTK_TREE_MODEL(gtk_list_store_new( + N_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER)); + triggers->highlight_model = GTK_TREE_MODEL(gtk_list_store_new( + N_COLUMNS_HIGH, G_TYPE_STRING, G_TYPE_STRING)); + + #ifdef HAVE_RUBY + triggers->script_model = GTK_TREE_MODEL(gtk_list_store_new(1, + G_TYPE_STRING)); + #endif + + for (i = 0; conditionData[i].type != -1; i++) { + data = (ModelData *)(&(conditionData[i])); + gtk_list_store_append(GTK_LIST_STORE(triggers->condition_model), &iter); + gtk_list_store_set(GTK_LIST_STORE(triggers->condition_model), &iter, + COLUMN_NAME, _(data->title), COLUMN_DATA, data, -1); + } + + for (i = 0; actionData[i].type != -1; i++) { + data = (ModelData *)(&(actionData[i])); + gtk_list_store_append(GTK_LIST_STORE(triggers->action_model), &iter); + gtk_list_store_set(GTK_LIST_STORE(triggers->action_model), &iter, + COLUMN_NAME, _(data->title), COLUMN_DATA, data, -1); + } + + for (i = 0; highlightTags[i].name != NULL; i++) { + gtk_list_store_append(GTK_LIST_STORE(triggers->highlight_model), &iter); + gtk_list_store_set(GTK_LIST_STORE(triggers->highlight_model), &iter, + COLUMN_HIGH_NAME, _(highlightTags[i].name), COLUMN_HIGH_TAG, + highlightTags[i].tag, -1); + } + + #ifdef HAVE_RUBY + for (scripts = gm_scripts_scripts(gm_app_scripts(gm_app_instance())); + scripts; scripts = scripts->next) { + script = (GmScript *)(scripts->data); + + for (item = script->functions; item; item = item->next) { + func = (GmScriptFunction *)(item->data); + gtk_list_store_append(GTK_LIST_STORE(triggers->script_model), + &iter); + gtk_list_store_set(GTK_LIST_STORE(triggers->script_model), &iter, + 0, func->name, -1); + } + } + #endif +} + +ModelData * +gm_triggers_dialog_combo_get_selected_data(GmTriggersDialog *triggers, + GtkComboBox *combo) { + GtkTreeIter iter; + ModelData *data; + + gtk_combo_box_get_active_iter(combo, &iter); + gtk_tree_model_get(gtk_combo_box_get_model(combo), &iter, COLUMN_DATA, &data, + -1); + + return data; +} + +GList * +gm_triggers_dialog_collect_rules(GmTriggersDialog *triggers, GtkWidget *vbox) { + gchar *text; + GList *children, *child, *item, *result = NULL; + GtkComboBox *combo; + GtkEntry *entry; + ModelData *data; + GmTriggerData *d; + + children = gtk_container_get_children(GTK_CONTAINER(vbox)); + + for (item = children; item; item = item->next) { + if (item->data == triggers->hbox_add_condition || + item->data == triggers->hbox_add_action) { + break; + } + + if (GTK_IS_HBOX(item->data)) { + child = gtk_container_get_children(GTK_CONTAINER(item->data)); + combo = GTK_COMBO_BOX(child->data); + data = gm_triggers_dialog_combo_get_selected_data(triggers, combo); + d = NULL; + + switch (data->args) { + case NO_ARGS: + d = gm_trigger_data_new(data->type, NULL); + break; + case SINGLE_ARGS: + if (child->next) { + entry = GTK_ENTRY(child->next->data); + text = (gchar *)gtk_entry_get_text(entry); + + if (g_utf8_strlen(text, -1) > 0) { + d = gm_trigger_data_new(data->type, g_strdup(text)); + } + } + break; + case CUSTOM_ARGS: + text = (gchar *)(data->func(triggers, CUSTOM_ARG_GET_DATA, + child->next, NULL)); + + if (text != NULL && g_utf8_strlen(text, -1) > 0) { + d = gm_trigger_data_new(data->type, text); + } else { + g_free(text); + } + break; + default: + break; + } + + if (d) { + result = g_list_append(result, d); + } + + g_list_free(child); + } + } + + g_list_free(children); + return result; +} + +gboolean +gm_triggers_dialog_fill_trigger(GmTriggersDialog *triggers) { + const gchar *name = gtk_entry_get_text(GTK_ENTRY(triggers->entry_name)); + GList *conditions = NULL; + GList *actions = NULL; + + if (g_utf8_strlen(name, -1) == 0) { + gm_error_dialog(_("Please fill in a trigger name"), + GTK_WINDOW(triggers->dialog)); + gtk_widget_grab_focus(triggers->entry_name); + return FALSE; + } + + conditions = gm_triggers_dialog_collect_rules(triggers, + triggers->vbox_conditions); + + if (conditions == NULL) { + gm_error_dialog(_("Please specify at least one condition"), + GTK_WINDOW(triggers->dialog)); + return FALSE; + } + + actions = gm_triggers_dialog_collect_rules(triggers, triggers->vbox_actions); + + if (actions == NULL) { + gm_trigger_free_list(conditions); + gm_error_dialog(_("Please specify at least one action"), + GTK_WINDOW(triggers->dialog)); + return FALSE; + } + + gm_trigger_set_name(triggers->trigger, name); + gm_trigger_set_conditions(triggers->trigger, conditions); + gm_trigger_set_actions(triggers->trigger, actions); + + return TRUE; +} + +void +gm_triggers_dialog_initialize_event_types(GmTriggersDialog *triggers) { + GtkListStore *store = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, + G_TYPE_INT); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeIter iter; + + gtk_tree_view_set_model(GTK_TREE_VIEW(triggers->tree_view_event_types), + GTK_TREE_MODEL(store)); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "pixbuf", + 0, NULL); + gtk_tree_view_column_set_min_width(column, 40); + + gtk_tree_view_append_column(GTK_TREE_VIEW(triggers->tree_view_event_types), + column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "markup", + 1, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(triggers->tree_view_event_types), + column); + + gtk_list_store_prepend(store, &iter); + gtk_list_store_set(store, &iter, 0, + gm_pixbuf_get_at_size("ice-userlist/programmer.svg", 32, 32), 1, + _("<b>Player event</b>\nPlayer events are triggered on userlist " + "activity"), 2, TT_USERS, -1); + + gtk_list_store_prepend(store, &iter); + gtk_list_store_set(store, &iter, 0, + gm_pixbuf_get_at_size("world.svg", 32, 32), 1, + _("<b>World event</b>\nWorld events are triggered on incoming " + "lines of text"), 2, TT_OUTPUT, -1); + + gtk_tree_selection_select_iter(gtk_tree_view_get_selection( + GTK_TREE_VIEW(triggers->tree_view_event_types)), &iter); + g_signal_connect(gtk_tree_view_get_selection(GTK_TREE_VIEW( + triggers->tree_view_event_types)), "changed", + G_CALLBACK(on_tree_view_event_types_changed), triggers); +} + +GmTrigger * +gm_triggers_dialog_run_dialog(GmTriggersDialog *triggers) { + gboolean done = FALSE; + GmTrigger *result = NULL; + + while (!done) { + done = TRUE; + + switch (gtk_dialog_run(GTK_DIALOG(triggers->dialog))) { + case GTK_RESPONSE_OK: + done = gm_triggers_dialog_fill_trigger(triggers); + + if (done) { + result = triggers->trigger; + } + break; + default: + break; + } + } + + if (triggers->action_model != NULL) + g_object_unref(triggers->action_model); + if (triggers->condition_model != NULL) + g_object_unref(triggers->condition_model); + if (triggers->highlight_model != NULL) + g_object_unref(triggers->highlight_model); + + #ifdef HAVE_RUBY + if (triggers->script_model != NULL) + g_object_unref(triggers->script_model); + #endif + + if (!result && triggers->is_new) { + gm_trigger_free(triggers->trigger); + } + + g_object_unref(triggers->xml); + gtk_widget_destroy(triggers->dialog); + + g_free(triggers); + + + return result; +} + +GmTrigger * +gm_triggers_dialog_run_priv(GmWorld *world, GmTrigger *trigger, + gboolean is_new) { + GmTriggersDialog *triggers = g_new0(GmTriggersDialog, 1); + + triggers->is_new = is_new; + triggers->world = world; + triggers->xml = glade_xml_new(G_TRIGGERS_XML, "gm_triggers_dialog", NULL); + triggers->dialog = glade_xml_get_widget(triggers->xml, + "gm_triggers_dialog"); + triggers->entry_name = glade_xml_get_widget(triggers->xml, "entry_name"); + triggers->vbox_conditions = glade_xml_get_widget(triggers->xml, + "vbox_conditions"); + triggers->vbox_actions = glade_xml_get_widget(triggers->xml, + "vbox_actions"); + triggers->notebook_triggers = glade_xml_get_widget(triggers->xml, + "notebook_triggers"); + triggers->tree_view_event_types = glade_xml_get_widget(triggers->xml, + "tree_view_event_types"); + triggers->button_ok = glade_xml_get_widget(triggers->xml, "button_ok"); + triggers->hbox_add_condition = glade_xml_get_widget(triggers->xml, + "hbox_add_condition"); + triggers->hbox_add_action = glade_xml_get_widget(triggers->xml, + "hbox_add_action"); + triggers->button_next = glade_xml_get_widget(triggers->xml, "button_next"); + + gm_triggers_dialog_initialize_event_types(triggers); + + glade_xml_signal_connect_data(triggers->xml, "on_button_next_clicked", + G_CALLBACK(on_button_next_clicked), triggers); + glade_xml_signal_connect_data(triggers->xml, + "on_button_add_condition_clicked", + G_CALLBACK(on_button_add_condition_clicked), triggers); + glade_xml_signal_connect_data(triggers->xml, "on_button_add_action_clicked", + G_CALLBACK(on_button_add_action_clicked), triggers); + glade_xml_signal_connect_data(triggers->xml, + "on_notebook_triggers_switch_page", + G_CALLBACK(on_notebook_triggers_switch_page), triggers); + + triggers->trigger = trigger; + + if (is_new) { + gtk_notebook_set_current_page( + GTK_NOTEBOOK(triggers->notebook_triggers), 0); + } else { + gtk_notebook_set_current_page( + GTK_NOTEBOOK(triggers->notebook_triggers), 1); + } + + gtk_widget_show_all(triggers->dialog); + return gm_triggers_dialog_run_dialog(triggers); +} + +GmTrigger * +gm_triggers_dialog_run(GmWorld *world, GmTrigger *trigger) { + return gm_triggers_dialog_run_priv(world, trigger, FALSE); +} + +GmTrigger * +gm_triggers_dialog_run_new(GmWorld *world, GmTrigger *trigger) { + if (!trigger) { + trigger = gm_trigger_new(); + } + + return gm_triggers_dialog_run_priv(world, trigger, TRUE); +} + +void +gm_triggers_dialog_select_combo_by_type(GmTriggersDialog *triggers, + GtkComboBox *combo, gint type) { + GtkTreeModel *model = gtk_combo_box_get_model(combo); + GtkTreeIter iter; + ModelData *data; + + if (gtk_tree_model_get_iter_first(model, &iter)) { + do { + gtk_tree_model_get(model, &iter, COLUMN_DATA, &data, -1); + + if (type == data->type) { + gtk_combo_box_set_active_iter(combo, &iter); + return; + } + } while (gtk_tree_model_iter_next(model, &iter)); + } +} + +GtkWidget * +gm_triggers_dialog_create_item(GmTriggersDialog *triggers, GtkTreeModel *model, + GmTriggerData *t) { + GtkWidget *hbox, *combo, *button; + ComboBoxTypeData *data; + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + hbox = gtk_hbox_new(FALSE, 6); + + combo = gtk_combo_box_new_with_model(model); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "text", + COLUMN_NAME, NULL); + + + data = g_new0(ComboBoxTypeData, 1); + data->triggers = triggers; + data->data = t; + + g_signal_connect(combo, "changed", G_CALLBACK(on_combo_box_type_changed), + data); + g_signal_connect(combo, "destroy", G_CALLBACK(on_combo_box_type_destroy), + data); + + button = gtk_button_new_from_stock(GTK_STOCK_REMOVE); + g_signal_connect(button, "clicked", G_CALLBACK(on_button_remove_clicked), + triggers); + + gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0); + + gtk_widget_show_all(hbox); + return hbox; +} + +void +gm_triggers_dialog_new_condition(GmTriggersDialog *triggers) { + GtkWidget *hbox; + GList *children; + + hbox = gm_triggers_dialog_create_item(triggers, triggers->condition_model, + NULL); + children = gtk_container_get_children(GTK_CONTAINER(hbox)); + + if (triggers->trigger->event == TT_OUTPUT) { + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), dataConditionOutput[0].type); + } else { + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), dataConditionUsers[0].type); + } + + gtk_box_pack_start(GTK_BOX(triggers->vbox_conditions), hbox, FALSE, TRUE, + 0); + g_list_free(children); +} + +void +gm_triggers_dialog_populate_conditions(GmTriggersDialog *triggers) { + GList *item; + GtkWidget *hbox; + GmTriggerData *t; + GList *children; + + if (triggers->trigger->conditions) { + for (item = triggers->trigger->conditions; item; item = item->next) { + t = (GmTriggerData *)(item->data); + + hbox = gm_triggers_dialog_create_item(triggers, + triggers->condition_model, t); + children = gtk_container_get_children(GTK_CONTAINER(hbox)); + + gtk_box_pack_start(GTK_BOX(triggers->vbox_conditions), hbox, + FALSE, TRUE, 0); + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), t->type); + g_list_free(children); + } + } else { + gm_triggers_dialog_new_condition(triggers); + } +} + +void +gm_triggers_dialog_new_action(GmTriggersDialog *triggers) { + GtkWidget *hbox; + GList *children; + + hbox = gm_triggers_dialog_create_item(triggers, triggers->action_model, + NULL); + children = gtk_container_get_children(GTK_CONTAINER(hbox)); + + if (triggers->trigger->event == TT_OUTPUT) { + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), dataActionOutput[0].type); + } else { + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), dataActionUsers[0].type); + } + + gtk_box_pack_start(GTK_BOX(triggers->vbox_actions), hbox, FALSE, TRUE, 0); + g_list_free(children); +} + +void +gm_triggers_dialog_populate_actions(GmTriggersDialog *triggers) { + GList *item; + GtkWidget *hbox; + GmTriggerData *t; + GList *children; + + if (triggers->trigger->actions) { + for (item = triggers->trigger->actions; item; item = item->next) { + t = (GmTriggerData *)(item->data); + hbox = gm_triggers_dialog_create_item(triggers, + triggers->action_model, t); + children = gtk_container_get_children(GTK_CONTAINER(hbox)); + + gtk_box_pack_start(GTK_BOX(triggers->vbox_actions), hbox, FALSE, + TRUE, 0); + gm_triggers_dialog_select_combo_by_type(triggers, + GTK_COMBO_BOX(children->data), t->type); + + g_list_free(children); + } + } else { + gm_triggers_dialog_new_action(triggers); + } +} + +/* CALLBACKS */ +void +on_button_next_clicked(GtkButton *button, GmTriggersDialog *triggers) { + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + gint type; + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW( + triggers->tree_view_event_types)); + model = gtk_tree_view_get_model(GTK_TREE_VIEW( + triggers->tree_view_event_types)); + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + gtk_tree_model_get(model, &iter, 2, &type, -1); + + triggers->trigger->event = type; + gtk_notebook_set_current_page(GTK_NOTEBOOK( + triggers->notebook_triggers), 1); + } else { + gm_error_dialog(_("Select a event type first"), + GTK_WINDOW(triggers->dialog)); + } +} + +void +on_tree_view_event_types_changed(GtkTreeSelection *treeselection, + GmTriggersDialog *triggers) { + GtkTreeIter iter; + GtkTreeModel *model; + gint type; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW( + triggers->tree_view_event_types)); + + if (!gtk_tree_selection_get_selected(treeselection, &model, &iter)) { + gtk_widget_set_sensitive(triggers->button_next, FALSE); + return; + } + + gtk_widget_set_sensitive(triggers->button_next, TRUE); + gtk_tree_model_get(model, &iter, 2, &type, -1); + + switch (type) { + case TT_OUTPUT: + gtk_window_set_icon(GTK_WINDOW(triggers->dialog), + gm_pixbuf_get_at_size("world.svg", 16, 16)); + break; + case TT_USERS: + gtk_window_set_icon(GTK_WINDOW(triggers->dialog), + gm_pixbuf_get_at_size("ice-userlist/programmer.svg", + 16, 16)); + break; + default: + break; + } +} + +void +on_notebook_triggers_switch_page(GtkNotebook *notebook, GtkNotebookPage *page, + guint page_num, GmTriggersDialog *triggers) { + const ModelData *conditionData = NULL; + const ModelData *actionData = NULL; + + if (page_num != 1) { + return; + } + + gtk_widget_set_sensitive(triggers->button_ok, TRUE); + + switch (triggers->trigger->event) { + case TT_OUTPUT: + gtk_window_set_icon(GTK_WINDOW(triggers->dialog), + gm_pixbuf_get_at_size("world.svg", 16, 16)); + conditionData = dataConditionOutput; + actionData = dataActionOutput; + break; + case TT_USERS: + gtk_window_set_icon(GTK_WINDOW(triggers->dialog), + gm_pixbuf_get_at_size("ice-userlist/programmer.svg", + 16, 16)); + conditionData = dataConditionUsers; + actionData = dataActionUsers; + break; + default: + break; + } + + gm_triggers_dialog_create_models(triggers, conditionData, actionData); + + if (triggers->trigger->name) { + gtk_entry_set_text(GTK_ENTRY(triggers->entry_name), + triggers->trigger->name); + } + + gm_triggers_dialog_populate_conditions(triggers); + gm_triggers_dialog_populate_actions(triggers); +} + +gpointer +gm_triggers_dialog_custom_arg_highlight(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data) { + GtkWidget *hbox; + GtkWidget *combo; + GtkCellRenderer *renderer; + GList *item; + GtkTreeIter iter; + GtkTreeModel *model; + gchar *tag; + GmTriggerData *t = (GmTriggerData *)(user_data); + + switch (type) { + case CUSTOM_ARG_CREATE: + hbox = GTK_WIDGET(data); + combo = gtk_combo_box_new_with_model(triggers->highlight_model); + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, + "text", COLUMN_HIGH_NAME, NULL); + + if (!user_data) { + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); + } else { + model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); + + if (gtk_tree_model_get_iter_first(model, &iter)) { + do { + gtk_tree_model_get(model, &iter, COLUMN_HIGH_TAG, + &tag, -1); + + if (strcmp(tag, t->data) == 0) { + gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), + &iter); + break; + } + + g_free(tag); + } while (gtk_tree_model_iter_next(model, &iter)); + } + } + + gtk_widget_show(combo); + gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0); + break; + case CUSTOM_ARG_GET_DATA: + item = (GList *)(data); + model = gtk_combo_box_get_model(GTK_COMBO_BOX(item->data)); + gtk_combo_box_get_active_iter(GTK_COMBO_BOX(item->data), &iter); + gtk_tree_model_get(model, &iter, COLUMN_HIGH_TAG, &tag, -1); + + return tag; + break; + } + + return NULL; +} + +void +on_button_browse_clicked(GtkButton *widget, GtkEntry *entry) { + gchar *tmp; + GtkWidget *d = gtk_file_chooser_dialog_new(_("Select file"), + NULL, GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) { + tmp = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d)); + gtk_entry_set_text(entry, tmp); + g_free(tmp); + } + + gtk_widget_destroy(d); +} + +gpointer +gm_triggers_dialog_custom_arg_browse(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data) { + GtkWidget *hbox; + GtkWidget *entry; + GtkWidget *browse, *tmp; + GmTriggerData *t = (GmTriggerData *)(user_data); + GList *item; + + switch (type) { + case CUSTOM_ARG_CREATE: + hbox = GTK_WIDGET(data); + entry = gtk_entry_new(); + + if (t) { + gtk_entry_set_text(GTK_ENTRY(entry), t->data); + } + + browse = gtk_button_new(); + tmp = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(browse), tmp); + gtk_box_pack_start(GTK_BOX(tmp), gtk_image_new_from_stock( + GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON), FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(tmp), gtk_label_new(_("Browse")), + TRUE, TRUE, 0); + + g_signal_connect(browse, "clicked", + G_CALLBACK(on_button_browse_clicked), entry); + + gtk_widget_show(entry); + gtk_widget_show_all(browse); + gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), browse, FALSE, TRUE, 0); + break; + case CUSTOM_ARG_GET_DATA: + item = (GList *)(data); + return g_strdup(gtk_entry_get_text(GTK_ENTRY(item->data))); + break; + } + + return NULL; +} + +void +on_combo_box_type_changed(GtkComboBox *widget, ComboBoxTypeData *tdata) { + GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(widget)); + GList *item, *children = gtk_container_get_children(GTK_CONTAINER(parent)); + ModelData *data; + GtkTreeIter iter; + GtkWidget *tmp; + + for (item = children->next; item && item->next; item = item->next) { + gtk_widget_destroy(GTK_WIDGET(item->data)); + } + + g_list_free(children); + + if (gtk_combo_box_get_active_iter(widget, &iter)) { + gtk_tree_model_get(gtk_combo_box_get_model(widget), &iter, + COLUMN_DATA, &data, -1); + + switch (data->args) { + case SINGLE_ARGS: + tmp = gtk_entry_new(); + + if (tdata->data) { + gtk_entry_set_text(GTK_ENTRY(tmp), tdata->data->data); + } + + gtk_widget_show(tmp); + gtk_box_pack_start(GTK_BOX(parent), tmp, TRUE, TRUE, 0); + break; + case CUSTOM_ARGS: + data->func(tdata->triggers, + CUSTOM_ARG_CREATE, (gpointer)parent, + (gpointer)tdata->data); + break; + default: + break; + } + + if (tdata->data != NULL) { + tdata->data = NULL; + } + } else { + gm_debug_msg(DEBUG_ALWAYS, "No active iter!"); + } +} + +void +on_combo_box_type_destroy(GtkObject *object, ComboBoxTypeData *tdata) { + g_free(tdata); +} + +#ifdef HAVE_RUBY +gpointer +gm_triggers_dialog_custom_arg_script(GmTriggersDialog *triggers, + CustomArgType type, gpointer data, gpointer user_data) { + GtkWidget *hbox; + GtkWidget *entry; + GtkEntryCompletion *entry_completion; + GList *item; + GmTriggerData *t = (GmTriggerData *)(user_data); + + switch (type) { + case CUSTOM_ARG_CREATE: + hbox = GTK_WIDGET(data); + entry = gtk_entry_new(); + entry_completion = gtk_entry_completion_new(); + gtk_entry_completion_set_model(entry_completion, + triggers->script_model); + gtk_entry_completion_set_text_column(entry_completion, 0); + gtk_entry_set_completion(GTK_ENTRY(entry), entry_completion); + + if (user_data) { + gtk_entry_set_text(GTK_ENTRY(entry), t->data); + } + + gtk_widget_show(entry); + gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); + break; + case CUSTOM_ARG_GET_DATA: + item = (GList *)(data); + return g_strdup(gtk_entry_get_text(GTK_ENTRY(item->data))); + break; + } + + return NULL; +} +#endif + +gboolean +gm_triggers_dialog_idle_remove_item(GtkWidget *parent) { + gtk_widget_destroy(parent); + return FALSE; +} + +void +on_button_remove_clicked(GtkButton *button, GmTriggersDialog *triggers) { + g_idle_add((GSourceFunc)(gm_triggers_dialog_idle_remove_item), + gtk_widget_get_parent(GTK_WIDGET(button))); +} + +void +on_button_add_condition_clicked(GtkButton *button, GmTriggersDialog *triggers) { + gm_triggers_dialog_new_condition(triggers); +} + +void +on_button_add_action_clicked(GtkButton *button, GmTriggersDialog *triggers) { + gm_triggers_dialog_new_action(triggers); +} diff --git a/gnoemoe/dialogs/gm-triggers-dialog.h b/gnoemoe/dialogs/gm-triggers-dialog.h new file mode 100644 index 0000000..7d6f886 --- /dev/null +++ b/gnoemoe/dialogs/gm-triggers-dialog.h @@ -0,0 +1,10 @@ +#ifndef __GM_TRIGGERS_DIALOG__ +#define __GM_TRIGGERS_DIALOG__ + +#include "../gm-triggers.h" +#include "../gm-world.h" + +GmTrigger *gm_triggers_dialog_run(GmWorld *world, GmTrigger *trig); +GmTrigger *gm_triggers_dialog_run_new(GmWorld *world, GmTrigger *trig); + +#endif /* __GM_TRIGGERS_DIALOG__ */ diff --git a/gnoemoe/dialogs/gm-world-info-dialog.c b/gnoemoe/dialogs/gm-world-info-dialog.c new file mode 100644 index 0000000..e259c6e --- /dev/null +++ b/gnoemoe/dialogs/gm-world-info-dialog.c @@ -0,0 +1,111 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <libgnome/libgnome.h> + +#include "../gm-world.h" +#include "../gm-support.h" +#include "../gm-pixbuf.h" + +#define G_WORLD_INFO_XML PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-world-info.glade" + +gboolean on_gm_world_info_dialog_url_button_release(GtkWidget *button, + GdkEventButton *event, gchar *link); + +/* Private */ +void +gm_world_info_dialog_set_label(GtkLabel *label, gchar *text) { + if (text) { + gtk_label_set_markup(label, text); + } else { + gtk_label_set_markup(label, _("<i>unspecified</i>")); + } +} + +/* Public */ + +GtkDialog * +gm_world_info_dialog_new(GmWorldInfo world_info) { + GladeXML *xml; + GtkWidget *dlg, *widget; + gchar *tmp; + + xml = glade_xml_new(G_WORLD_INFO_XML, "dlgWorldInfo", NULL); + dlg = glade_xml_get_widget(xml, "dlgWorldInfo"); + + gm_world_info_dialog_set_label(GTK_LABEL(glade_xml_get_widget(xml, + "lblName")), world_info.admin); + gm_world_info_dialog_set_label(GTK_LABEL(glade_xml_get_widget(xml, + "lblLocation")), world_info.location); + gm_world_info_dialog_set_label(GTK_LABEL(glade_xml_get_widget(xml, + "lblSystem")), world_info.system); + gm_world_info_dialog_set_label(GTK_LABEL(glade_xml_get_widget(xml, + "lblCharset")), world_info.charset); + gm_world_info_dialog_set_label(GTK_LABEL(glade_xml_get_widget(xml, + "lblLanguage")), world_info.language); + + if (world_info.homepage) { + tmp = g_strconcat("<u><span color=\"#0000ff\">", world_info.homepage, + "</span></u>", NULL); + glade_xml_signal_connect_data(xml, + "on_gm_world_info_dialog_url_button_release", + G_CALLBACK(on_gm_world_info_dialog_url_button_release), NULL); + widget = glade_xml_get_widget(xml, "lblHomepage"); + gtk_label_set_markup(GTK_LABEL(widget), tmp); + g_free(tmp); + } + + if (world_info.contact) { + tmp = g_strconcat("<u><span color=\"#0000ff\">", world_info.contact, + "</span></u>", NULL); + glade_xml_signal_connect_data(xml, + "on_gm_world_info_dialog_url_button_release", + G_CALLBACK(on_gm_world_info_dialog_url_button_release), "mailto:"); + widget = glade_xml_get_widget(xml, "lblEmail"); + gtk_label_set_markup(GTK_LABEL(widget), tmp); + g_free(tmp); + } + + if (world_info.logo) { + gtk_image_set_from_pixbuf(GTK_IMAGE(glade_xml_get_widget(xml, + "imageLogo")), gm_pixbuf_get(world_info.logo)); + } + + glade_xml_signal_connect(xml, "on_dlgWorldInfo_delete", + G_CALLBACK(gtk_widget_destroy)); + glade_xml_signal_connect_data(xml, "on_buttonClose_clicked", + G_CALLBACK(gm_widget_destroy_data), dlg); + + gtk_widget_show_all(dlg); + + g_object_unref(xml); + return GTK_DIALOG(dlg); +} + +/* Callbacks */ + +gboolean +on_gm_world_info_dialog_url_button_release(GtkWidget *label, + GdkEventButton *event, gchar *prefix) { + GError *err = NULL; + gchar *tmp, *link; + + if (prefix != NULL) { + link = g_strconcat(prefix, gtk_label_get_text(GTK_LABEL(label)), NULL); + } else { + link = g_strdup(gtk_label_get_text(GTK_LABEL(label))); + } + + if (!gnome_url_show(link, &err)) { + tmp = g_strdup_printf("Could not open link: %s", err->message); + gm_error_dialog(tmp, NULL); + g_free(tmp); + g_error_free(err); + } + + g_free(link); + return FALSE; +} diff --git a/gnoemoe/dialogs/gm-world-info-dialog.h b/gnoemoe/dialogs/gm-world-info-dialog.h new file mode 100644 index 0000000..32e0bce --- /dev/null +++ b/gnoemoe/dialogs/gm-world-info-dialog.h @@ -0,0 +1,6 @@ +#ifndef __GM_WORLD_INFO_DIALOG_H__ +#define __GM_WORLD_INFO_DIALOG_H__ + +GtkDialog *gm_world_info_dialog_new(GmWorldInfo world_info); + +#endif /* __GM_WORLD_INFO_DIALOG_H__ */ diff --git a/gnoemoe/dialogs/gm-world-logs-dialog.c b/gnoemoe/dialogs/gm-world-logs-dialog.c new file mode 100644 index 0000000..1059d84 --- /dev/null +++ b/gnoemoe/dialogs/gm-world-logs-dialog.c @@ -0,0 +1,116 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include "../gm-world.h" +#include "../gm-support.h" + +#define G_WORLD_LOGS_XML PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-world-info.glade" + +void +on_gm_world_logs_dialog_row_activated(GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, GtkDialog *dlg); + +/* Public */ + +GtkDialog * +gm_world_logs_dialog_new(GmWorld *world, GtkTreeView **view, + GtkProgressBar **progress) { + GladeXML *xml = glade_xml_new(G_WORLD_LOGS_XML, "dlgLogs", NULL); + GtkDialog *dlg; + GtkTreeModel *model, *smodel; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + gchar *path, *tmp, *tmp2; + const gchar *fname; + GDir *dir; + GError *err; + GtkTreeIter iter; + FILE *f; + long fsize; + int res = 0; + + dlg = GTK_DIALOG(glade_xml_get_widget(xml, "dlgLogs")); + *view = GTK_TREE_VIEW(glade_xml_get_widget(xml, "tvwLogs")); + + model = GTK_TREE_MODEL(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING)); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Logs"), renderer, + "text", 1, NULL); + gtk_tree_view_append_column(*view, column); + + smodel = gtk_tree_model_sort_new_with_model(model); + gtk_tree_view_column_set_sort_column_id(column, 0); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(smodel), 0, + GTK_SORT_DESCENDING); + + gtk_tree_view_set_model(*view, GTK_TREE_MODEL(smodel)); + + /* Fill it in */ + path = g_strdup_printf("%s/logs/", gm_world_path(world)); + dir = g_dir_open(path, 0, &err); + + if (dir) { + while ((fname = g_dir_read_name(dir)) != NULL) { + res++; + tmp = g_strconcat(path, fname, NULL); + f = fopen(tmp, "r"); + + if (f) { + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fclose(f); + g_free(tmp); + + tmp = gnome_vfs_format_file_size_for_display(fsize); + tmp2 = g_strconcat(fname, " (", tmp, ")", NULL); + + gtk_list_store_prepend(GTK_LIST_STORE(model), &iter); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, fname, 1, + tmp2, -1); + g_free(tmp2); + } + + g_free(tmp); + } + + g_dir_close(dir); + + if (res != 0) { + gtk_tree_model_get_iter_first(smodel, &iter); + gtk_tree_selection_select_iter(gtk_tree_view_get_selection(*view), &iter); + glade_xml_signal_connect_data(xml, "on_tvwLogs_row_activated", + G_CALLBACK(on_gm_world_logs_dialog_row_activated), dlg); + + *progress = GTK_PROGRESS_BAR(glade_xml_get_widget(xml, + "progressBarLoading")); + } else { + gm_info_dialog(_("There are no log files for this world"), NULL); + gtk_widget_destroy(GTK_WIDGET(dlg)); + dlg = NULL; + } + } else { + tmp = g_strdup_printf(_("Couldn't open the log directory: %s"), err->message); + gm_error_dialog(tmp, NULL); + g_error_free(err); + g_free(tmp); + gtk_widget_destroy(GTK_WIDGET(dlg)); + dlg = NULL; + } + + g_free(path); + g_object_unref(xml); + + return dlg; +} + +/* Callbacks */ + +void +on_gm_world_logs_dialog_row_activated(GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, GtkDialog *dlg) { + gtk_dialog_response(dlg, GTK_RESPONSE_OK); +} diff --git a/gnoemoe/dialogs/gm-world-logs-dialog.h b/gnoemoe/dialogs/gm-world-logs-dialog.h new file mode 100644 index 0000000..41694da --- /dev/null +++ b/gnoemoe/dialogs/gm-world-logs-dialog.h @@ -0,0 +1,10 @@ +#ifndef __GM_WORLD_LOGS_DIALOG_H__ +#define __GM_WORLD_LOGS_DIALOG_H__ + +#include <gtk/gtk.h> +#include "../gm-world.h" + +GtkDialog *gm_world_logs_dialog_new(GmWorld *world, GtkTreeView **view, + GtkProgressBar **progress); + +#endif /* __GM_WORLD_LOGS_DIALOG_H__ */ diff --git a/gnoemoe/dialogs/gm-world-properties-dialog.c b/gnoemoe/dialogs/gm-world-properties-dialog.c new file mode 100644 index 0000000..ffe1058 --- /dev/null +++ b/gnoemoe/dialogs/gm-world-properties-dialog.c @@ -0,0 +1,675 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> + +#include "gm-world-properties-dialog.h" +#include "gm-triggers-dialog.h" +#include "../gm-triggers.h" +#include "../gm-support.h" +#include "../gm-app.h" +#include "../gm-debug.h" +#include "../gm-options.h" +#include "../gm-pixbuf.h" + +typedef struct _GmWorldPropertiesDialog { + GtkWidget *dialog; + GtkWidget *combo_box_charset; + GtkWidget *tree_view_triggers; + + GladeXML *xml; + GmWorld *world; + gboolean is_new; + + gulong handler_id; +} GmWorldPropertiesDialog; + +static const encoding encodings[] = { + {"ISO-8859-1", N_("Western")}, + {"ISO-8859-2", N_("Central European")}, + {"ISO-8859-3", N_("South European")}, + {"ISO-8859-4", N_("Baltic")}, + {"ISO-8859-5", N_("Cyrillic")}, + {"ISO-8859-6", N_("Arabic")}, + {"ISO-8859-7", N_("Greek")}, + {"ISO-8859-8", N_("Hebrew Visual")}, + {"ISO-8859-8-I", N_("Hebrew")}, + {"ISO-8859-9", N_("Turkish")}, + {"ISO-8859-10", N_("Nordic")}, + {"ISO-8859-13", N_("Baltic")}, + {"ISO-8859-14", N_("Celtic")}, + {"ISO-8859-15", N_("Western")}, + {"ISO-8859-16", N_("Romanian")}, + {"UTF-7", N_("Unicode")}, + {"UTF-8", N_("Unicode")}, + {"UTF-16", N_("Unicode")}, + {"UCS-2", N_("Unicode")}, + {"UCS-4", N_("Unicode")}, + {"ARMSCII-8", N_("Armenian")}, + {"BIG5", N_("Chinese Traditional")}, + {"BIG5-HKSCS", N_("Chinese Traditional")}, + {"CP866", N_("Cyrillic/Russian")}, + + {"EUC-JP", N_("Japanese")}, + {"EUC-KR", N_("Korean")}, + {"EUC-TW", N_("Chinese Traditional")}, + {"GB18030", N_("Chinese Simplified")}, + {"GB2312", N_("Chinese Simplified")}, + {"GBK", N_("Chinese Simplified")}, + {"GEORGIAN-ACADEMY", N_("Georgian")}, + {"HZ", N_("Chinese Simplified")}, + + {"IBM850", N_("Western")}, + {"IBM852", N_("Central European")}, + {"IBM855", N_("Cyrillic")}, + {"IBM857", N_("Turkish")}, + {"IBM862", N_("Hebrew")}, + {"IBM864", N_("Arabic")}, + + {"ISO-2022-JP", N_("Japanese")}, + {"ISO-2022-KR", N_("Korean")}, + {"ISO-IR-111", N_("Cyrillic")}, + {"JOHAB", N_("Korean")}, + {"KOI8R", N_("Cyrillic")}, + {"KOI8-R", N_("Cyrillic")}, + {"KOI8U", N_("Cyrillic/Ukrainian")}, + + {"SHIFT_JIS", N_("Japanese")}, + {"TCVN", N_("Vietnamese")}, + {"TIS-620", N_("Thai")}, + {"UHC", N_("Korean")}, + {"VISCII", N_("Vietnamese")}, + + {"WINDOWS-1250", N_("Central European")}, + {"WINDOWS-1251", N_("Cyrillic")}, + {"WINDOWS-1252", N_("Western")}, + {"WINDOWS-1253", N_("Greek")}, + {"WINDOWS-1254", N_("Turkish")}, + {"WINDOWS-1255", N_("Hebrew")}, + {"WINDOWS-1256", N_("Arabic")}, + {"WINDOWS-1257", N_("Baltic")}, + {"WINDOWS-1258", N_("Vietnamese")} +}; + +static GList *gm_world_properties_dialog_open = NULL; + +void gm_world_properties_dialog_run_dialog(GmWorldPropertiesDialog *properties); + +void on_button_add_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties); +void on_button_edit_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties); +void on_button_delete_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties); +void on_tree_view_triggers_row_activated(GtkTreeView *treeview, + GtkTreePath *arg1, GtkTreeViewColumn *arg2, + GmWorldPropertiesDialog *properties); +void on_gm_world_properties_dialog_app_world_removed(GmApp *app, GmWorld *world, + GmWorldPropertiesDialog *properties); + +void on_gm_world_properties_dialog_response(GtkDialog *dialog, gint response, + GmWorldPropertiesDialog *properties); + +GtkWidget * +gm_world_properties_dialog_widget(GmWorldPropertiesDialog *properties, + gchar *name) { + return glade_xml_get_widget(properties->xml, name); +} + +void +gm_world_properties_dialog_populate_charsets( + GmWorldPropertiesDialog *properties) { + int i; + const gchar *select = gm_options_get(gm_world_options(properties->world), + "charset"); + gchar text[255]; + GtkListStore *store; + GtkComboBoxEntry *combo = GTK_COMBO_BOX_ENTRY(properties->combo_box_charset); + store = gtk_list_store_new(1, G_TYPE_STRING); + gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store)); + gtk_combo_box_entry_set_text_column(combo, 0); + + for (i = 0; i < (int)(sizeof(encodings) / sizeof(encoding)); i++) { + sprintf(text, "%s - %s", encodings[i].charset, _(encodings[i].name)); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text); + + if (strcasecmp(encodings[i].charset, select) == 0) { + gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), text); + } + } +} + +void +gm_world_properties_dialog_remove_trigger(GmWorldPropertiesDialog *properties, + GtkTreeIter *iter) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view)); + + gtk_list_store_remove(store, iter); + + if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL) == 0) { + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_edit_trigger"), FALSE); + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_delete_trigger"), FALSE); + } +} + +void +gm_world_properties_dialog_update_trigger(GmWorldPropertiesDialog *properties, + GtkTreeIter *iter, GmTrigger *t) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view)); + gchar *text; + + text = g_strdup_printf(_("<b>%s</b>\nConditions: %d\nActions: %d"), + t->name, g_list_length(t->conditions), g_list_length(t->actions)); + gtk_list_store_set(store, iter, 1, text, 2, t, -1); + + switch (t->event) { + case TT_OUTPUT: + gtk_list_store_set(store, iter, 0, + gm_pixbuf_get_at_size("world.svg", 32, 32), -1); + break; + case TT_USERS: + gtk_list_store_set(store, iter, 0, + gm_pixbuf_get_at_size("ice-userlist/programmer.svg", 32, + 32), -1); + break; + default: + break; + } + + g_free(text); +} + +void +gm_world_properties_dialog_add_trigger(GmWorldPropertiesDialog *properties, + GmTrigger *t) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view)); + GtkTreeIter iter; + + gtk_list_store_append(store, &iter); + gm_world_properties_dialog_update_trigger(properties, &iter, t); + + gtk_tree_selection_select_iter(gtk_tree_view_get_selection(tree_view), + &iter); + + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_edit_trigger"), TRUE); + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_delete_trigger"), TRUE); +} + +void +gm_world_properties_dialog_populate_tree_view_triggers( + GmWorldPropertiesDialog *properties) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkListStore *store = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, + G_TYPE_POINTER); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + const GList *item; + GmTrigger *t; + + gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(store)); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "pixbuf", + 0, NULL); + gtk_tree_view_column_set_min_width(column, 40); + + gtk_tree_view_append_column(tree_view, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "markup", + 1, NULL); + gtk_tree_view_append_column(tree_view, column); + + for (item = gm_triggers_list(gm_world_triggers(properties->world)); item; + item = item->next) { + t = gm_trigger_dup((GmTrigger *)(item->data)); + gm_world_properties_dialog_add_trigger(properties, t); + } + + if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL) == 0) { + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_edit_trigger"), FALSE); + gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties, + "button_delete_trigger"), FALSE); + } +} + +GmWorldPropertiesDialog * +gm_world_properties_dialog_find(GmWorld *world) { + GmWorldPropertiesDialog *result = NULL; + GList *item; + + for (item = gm_world_properties_dialog_open; item; item = item->next) { + result = (GmWorldPropertiesDialog *)(item->data); + + if (result->world == world) { + return result; + } + } + + return NULL; +} + +void +gm_world_properties_dialog_initialize(GmWorldPropertiesDialog *properties) { + GmOptions *options = gm_world_options(properties->world); + const gchar *logo; + + gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_name")), gm_options_get(options, "name")); + gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_host")), gm_options_get(options, "host")); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(gm_world_properties_dialog_widget( + properties, "spin_button_port")), (double)(gm_options_get_int( + options, "port"))); + gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_player_name")), gm_options_get(options, "player_name")); + gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_password")), gm_options_get(options, "password")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_world_properties_dialog_widget(properties, + "check_button_auto_reconnect")), + gm_options_get_int(options, "reconnect")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + gm_world_properties_dialog_widget(properties, + "check_button_auto_load")), + gm_options_get_int(options, "autoload")); + + gm_world_properties_dialog_populate_charsets(properties); + gm_world_properties_dialog_populate_tree_view_triggers(properties); + + glade_xml_signal_connect_data(properties->xml, + "on_button_add_trigger_clicked", + G_CALLBACK(on_button_add_trigger_clicked), properties); + glade_xml_signal_connect_data(properties->xml, + "on_button_edit_trigger_clicked", + G_CALLBACK(on_button_edit_trigger_clicked), properties); + glade_xml_signal_connect_data(properties->xml, + "on_button_delete_trigger_clicked", + G_CALLBACK(on_button_delete_trigger_clicked), properties); + glade_xml_signal_connect_data(properties->xml, + "on_tree_view_triggers_row_activated", + G_CALLBACK(on_tree_view_triggers_row_activated), properties); + + logo = gm_options_get(options, "logo"); + + if (logo != NULL) { + gtk_window_set_icon(GTK_WINDOW(properties->dialog), + gm_pixbuf_get(logo)); + } else { + gtk_window_set_icon(GTK_WINDOW(properties->dialog), + gm_pixbuf_get("world.svg")); + } +} + +#define G_WORLD_PROPERTIES_DIALOG_XML PACKAGE_DATA_DIR "/" PACKAGE \ + "/ui/gm-world-properties.glade" + +void +gm_world_properties_dialog_run_priv(GmWorld *world, gboolean is_new) { + GmWorldPropertiesDialog *properties = + gm_world_properties_dialog_find(world); + gchar *title; + + if (properties) { + gtk_widget_show(properties->dialog); + gtk_window_present(GTK_WINDOW(properties->dialog)); + return; + } + + properties = g_new0(GmWorldPropertiesDialog, 1); + gm_world_properties_dialog_open = g_list_append( + gm_world_properties_dialog_open, properties); + + properties->is_new = is_new; + properties->world = world; + properties->xml = glade_xml_new(G_WORLD_PROPERTIES_DIALOG_XML, + "gm_world_properties_dialog", NULL); + + title = g_strconcat(_("World properties - "), gm_world_name(world), NULL); + properties->dialog = gm_world_properties_dialog_widget(properties, + "gm_world_properties_dialog"); + properties->tree_view_triggers = gm_world_properties_dialog_widget( + properties, "tree_view_triggers"); + properties->combo_box_charset = gm_world_properties_dialog_widget( + properties, "combo_box_charset"); + + gtk_window_set_title(GTK_WINDOW(properties->dialog), title); + g_free(title); + + gm_world_properties_dialog_initialize(properties); + + if (!properties->is_new) { + // Connect signal so we can close the dialog when a world gets removed + properties->handler_id = g_signal_connect(gm_app_instance(), + "world_removed", + G_CALLBACK(on_gm_world_properties_dialog_app_world_removed), + properties); + } else { + properties->handler_id = 0; + } + + gm_world_properties_dialog_run_dialog(properties); +} + +void +gm_world_properties_dialog_run(GmWorld *world) { + gm_world_properties_dialog_run_priv(world, FALSE); +} + +void +gm_world_properties_dialog_run_new(GmWorld *world) { + if (!world) { + world = gm_world_new(NULL); + } + + gm_world_properties_dialog_run_priv(world, TRUE); +} + +gchar * +gm_world_properties_dialog_get_charset(gchar *charset) { + gchar *ch; + int i; + + for (i = 0; i < (int)(sizeof(encodings) / sizeof(encoding)); i++) { + ch = g_strconcat(encodings[i].charset, " - ", _(encodings[i].name), + NULL); + + if (g_strcasecmp(charset, ch) == 0) { + g_free(ch); + return g_strdup(encodings[i].charset); + } + + g_free(ch); + } + + return g_strdup(charset); +} + +void +gm_world_properties_dialog_free_triggers(GmWorldPropertiesDialog *properties) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkTreeModel *store = gtk_tree_view_get_model(tree_view); + GtkTreeIter iter; + GmTrigger *t; + + if (gtk_tree_model_get_iter_first(store, &iter)) { + do { + gtk_tree_model_get(store, &iter, 2, &t, -1); + gm_trigger_free(t); + } while (gtk_tree_model_iter_next(store, &iter)); + } +} + +void +gm_world_properties_dialog_set_triggers(GmWorldPropertiesDialog *properties) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkTreeModel *store = gtk_tree_view_get_model(tree_view); + GtkTreeIter iter; + GmTrigger *t; + GmTriggers *triggers = gm_world_triggers(properties->world); + + gm_triggers_clear(triggers); + + if (gtk_tree_model_get_iter_first(store, &iter)) { + do { + gtk_tree_model_get(store, &iter, 2, &t, -1); + gm_triggers_add(triggers, t); + } while (gtk_tree_model_iter_next(store, &iter)); + } +} + +gboolean +gm_world_properties_dialog_check_values(GmWorldPropertiesDialog *properties) { + GmWorld *same_world; + GmOptions *options = gm_world_options(properties->world); + GtkWidget *dialog; + GtkEntry *entry_name = GTK_ENTRY(gm_world_properties_dialog_widget( + properties, "entry_name")); + gchar *name = g_strdup(gtk_entry_get_text(entry_name)); + GtkEntry *entry_host = GTK_ENTRY(gm_world_properties_dialog_widget( + properties, "entry_host")); + gchar *host = g_strdup(gtk_entry_get_text(entry_host)); + + g_strstrip(name); + g_strstrip(host); + + same_world = gm_app_world_by_name(gm_app_instance(), name); + + if (strlen(name) == 0) { + dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + "Name can not be empty, please fill in a name."); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + gtk_notebook_set_current_page(GTK_NOTEBOOK( + gm_world_properties_dialog_widget(properties, + "notebook_main")), 0); + gtk_widget_grab_focus(GTK_WIDGET(entry_name)); + g_free(name); + g_free(host); + return FALSE; + } + + + if (same_world && same_world != properties->world) { + dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Name can not be %s because a world with that name " + "already exists."), + name); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + gtk_notebook_set_current_page(GTK_NOTEBOOK( + gm_world_properties_dialog_widget(properties, + "notebook_main")), 0); + gtk_widget_grab_focus(GTK_WIDGET(entry_name)); + g_free(name); + g_free(host); + return FALSE; + } + + if (strlen(host) == 0) { + dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + _("Host can not be empty, please fill in a host.")); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + gtk_notebook_set_current_page(GTK_NOTEBOOK( + gm_world_properties_dialog_widget(properties, + "notebook_main")), 0); + gtk_widget_grab_focus(GTK_WIDGET(entry_host)); + g_free(name); + g_free(host); + + return FALSE; + } + + gm_world_properties_dialog_set_triggers(properties); + + gm_options_set(options, "host", host); + gm_options_set_int(options, "port", gtk_spin_button_get_value_as_int( + GTK_SPIN_BUTTON(gm_world_properties_dialog_widget(properties, + "spin_button_port")))); + + gm_options_set_int(options, "reconnect", gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(gm_world_properties_dialog_widget(properties, + "check_button_auto_reconnect")))); + gm_options_set_int(options, "autoload", gtk_toggle_button_get_active( + GTK_TOGGLE_BUTTON(gm_world_properties_dialog_widget(properties, + "check_button_auto_load")))); + + gm_options_set(options, "player_name", (gchar *)gtk_entry_get_text( + GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_player_name")))); + gm_options_set(options, "password", (gchar *)gtk_entry_get_text( + GTK_ENTRY(gm_world_properties_dialog_widget(properties, + "entry_password")))); + gm_options_set(options, "charset", gm_world_properties_dialog_get_charset( + (gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_BIN( + properties->combo_box_charset)->child)))); + + // Only change the name when it has actually changed, when the name + // changes the options and triggers are automatically saved to the new + // world location + if (gm_options_get(options, "name") == NULL || + strcmp(name, gm_options_get(options, "name")) != 0) { + gm_options_set(options, "name", name); + } else { + // Now we need to save the options and triggers ourselfs + gm_options_save(options); + gm_triggers_save(gm_world_triggers(properties->world)); + } + + g_free(name); + g_free(host); + + return TRUE; +} + +void +gm_world_properties_dialog_run_dialog(GmWorldPropertiesDialog *properties) { + g_signal_connect(properties->dialog, "response", + G_CALLBACK(on_gm_world_properties_dialog_response), properties); + + gtk_widget_show(GTK_WIDGET(properties->dialog)); +} + +/* CALLBACKS */ + +void +on_gm_world_properties_dialog_response(GtkDialog *dialog, gint response, + GmWorldPropertiesDialog *properties) { + gboolean is_okay = TRUE; + + switch (response) { + case GTK_RESPONSE_OK: + is_okay = gm_world_properties_dialog_check_values(properties); + break; + default: + gm_world_properties_dialog_free_triggers(properties); + break; + } + + if (!is_okay) { + return; + } + + if (properties->is_new) { + if (response == GTK_RESPONSE_OK) { + gm_app_add_world(gm_app_instance(), properties->world); + } else { + g_object_unref(properties->world); + } + } + + gtk_widget_destroy(properties->dialog); + + if (properties->handler_id > 0) { + g_signal_handler_disconnect(gm_app_instance(), properties->handler_id); + } + + g_object_unref(properties->xml); + gm_world_properties_dialog_open = g_list_remove( + gm_world_properties_dialog_open, properties); + + g_free(properties); +} + +void +on_button_add_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties) { + GmTrigger *trigger = gm_triggers_dialog_run_new(properties->world, NULL); + + if (trigger != NULL) { + gm_world_properties_dialog_add_trigger(properties, trigger); + } +} + +GmTrigger * +gm_world_properties_dialog_selected_trigger(GmWorldPropertiesDialog *properties, + GtkTreeIter *iter) { + GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers); + GtkTreeModel *model = gtk_tree_view_get_model(tree_view); + GmTrigger *result = NULL; + + if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(tree_view), + &model, iter)) { + gtk_tree_model_get(model, iter, 2, &result, -1); + } + + return result; +} + +void +on_button_edit_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties) { + GtkTreeIter iter; + GmTrigger *trigger, *newt; + + trigger = gm_world_properties_dialog_selected_trigger(properties, &iter); + + if (trigger) { + newt = gm_triggers_dialog_run(properties->world, trigger); + + if (newt) { + gm_world_properties_dialog_update_trigger(properties, &iter, + trigger); + } + } else { + gm_error_dialog(_("Select a trigger to edit first"), + GTK_WINDOW(properties->dialog)); + } +} + +void +on_button_delete_trigger_clicked(GtkButton *button, + GmWorldPropertiesDialog *properties) { + GtkTreeIter iter; + GmTrigger *trigger; + + trigger = gm_world_properties_dialog_selected_trigger(properties, &iter); + + if (trigger) { + gm_world_properties_dialog_remove_trigger(properties, &iter); + gm_trigger_free(trigger); + } else { + gm_error_dialog(_("First select a trigger to remove"), + GTK_WINDOW(properties->dialog)); + } +} + +void +on_tree_view_triggers_row_activated(GtkTreeView *treeview, GtkTreePath *arg1, + GtkTreeViewColumn *arg2, GmWorldPropertiesDialog *properties) { + on_button_edit_trigger_clicked(NULL, properties); +} + +void +on_gm_world_properties_dialog_app_world_removed(GmApp *app, GmWorld *world, + GmWorldPropertiesDialog *properties) { + if (properties->world == world) { + gtk_dialog_response(GTK_DIALOG(properties->dialog), GTK_RESPONSE_CLOSE); + } +} diff --git a/gnoemoe/dialogs/gm-world-properties-dialog.h b/gnoemoe/dialogs/gm-world-properties-dialog.h new file mode 100644 index 0000000..82f9808 --- /dev/null +++ b/gnoemoe/dialogs/gm-world-properties-dialog.h @@ -0,0 +1,17 @@ +#ifndef __GM_WORLD_PROPERTIES_DIALOG__ +#define __GM_WORLD_PROPERTIES_DIALOG__ + +#include <gtk/gtk.h> +#include <glade/glade.h> + +#include "../gm-world.h" + +typedef struct _encoding { + const gchar *charset; + const gchar *name; +} encoding; + +void gm_world_properties_dialog_run(GmWorld *world); +void gm_world_properties_dialog_run_new(GmWorld *world); + +#endif /* __GM_WORLD_PROPERTIES_DIALOG__ */ diff --git a/gnoemoe/dialogs/gm-worlds-list-dialog.c b/gnoemoe/dialogs/gm-worlds-list-dialog.c new file mode 100644 index 0000000..afe8a23 --- /dev/null +++ b/gnoemoe/dialogs/gm-worlds-list-dialog.c @@ -0,0 +1,466 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libgnomeui/libgnomeui.h> + +#include "../gm-support.h" +#include "../gm-debug.h" +#include "../gm-app.h" +#include "gm-worlds-list-dialog.h" +#include "gm-world-properties-dialog.h" +#include "../gm-pixbuf.h" + +typedef struct _GmWorldsListDialog { + GladeXML *xml; + GtkWidget *dialog; + GtkTreeView *tree_view_worlds; + GtkTreeModel *model_worlds; +} GmWorldsListDialog; + +void on_gm_worlds_list_dialog_button_delete_clicked(GtkButton * utton, + gpointer user_data); +void on_gm_worlds_list_dialog_button_new_clicked(GtkButton *button, + gpointer user_data); +void on_gm_worlds_list_dialog_button_modify_clicked(GtkButton *button, + gpointer user_data); +void on_gm_worlds_list_dialog_button_duplicate_clicked(GtkButton *button, + gpointer user_data); +void on_gm_worlds_list_dialog_button_connect_clicked(GtkButton *button, + gpointer user_data); + +void on_gm_worlds_list_dialog_tree_view_worlds_key_press(GtkWidget *widget, + GdkEventKey *event, gpointer user_data); + +void on_gm_worlds_list_dialog_app_world_added(GmApp *app, GmWorld *world, + gpointer user_data); +void on_gm_worlds_list_dialog_app_world_removed(GmApp *app, GmWorld *world, + gpointer user_data); +void on_gm_worlds_list_dialog_world_option_changed(GmOptions *options, + const gchar *key, GmWorld *world); +void on_gm_worlds_list_dialog_response(GtkDialog *dialog, gint response, + gpointer user_data); + +static GmWorldsListDialog *gm_worlds_list_dialog_instance; + +enum { + LOGO_COLUMN, + NAME_COLUMN, + WORLD_COLUMN, + N_COLUMNS +}; + +GtkWidget * +gm_worlds_list_dialog_widget(gchar *name) { + return glade_xml_get_widget(gm_worlds_list_dialog_instance->xml, name); +} + +gchar * +gm_worlds_list_dialog_world_text(GmWorld *world) { + gchar *text; + const gchar *player, *server; + GmOptions *options = gm_world_options(world); + + player = gm_options_get(options, "player_name"); + server = gm_options_get(options, "host"); + + text = g_strconcat("<b>", gm_options_get(options, "name"), + _("</b>\nServer: "), + ((server && server[0] != '\0') ? server : _("<i>unspecified</i>")), + _("\nPlayer: "), + ((player && player[0] != '\0') ? player : _("<i>unspecified</i>")), + NULL); + + return text; +} + +static void +gm_worlds_list_dialog_add_world(GmWorld *world) { + GtkTreeIter iter; + GdkPixbuf *pix_logo = NULL; + gchar *name = gm_worlds_list_dialog_world_text(world); + const gchar *logo = gm_options_get(gm_world_options(world), "logo"); + GtkListStore *store = GTK_LIST_STORE( + gm_worlds_list_dialog_instance->model_worlds); + + if (logo) { + pix_logo = gm_pixbuf_get_at_size(logo, 32, 32); + } else { + pix_logo = gm_pixbuf_get_at_size("world.svg", 32, 32); + } + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, LOGO_COLUMN, pix_logo, NAME_COLUMN, + name, WORLD_COLUMN, world, -1); + + g_signal_connect(gm_world_options(world), "option_changed", + G_CALLBACK(on_gm_worlds_list_dialog_world_option_changed), world); + + g_free(name); +} + +#define G_WORLDS_LISTS_DIALOG_XML PACKAGE_DATA_DIR "/" PACKAGE \ + "/ui/gm-worlds-list.glade" + +static void +gm_worlds_list_dialog_populate_worlds() { + GmApp *app = gm_app_instance(); + GList *worlds = gm_app_worlds(app); + GList *item; + GmWorld *world; + + for (item = worlds; item; item = item->next) { + world = (GmWorld *)(item->data); + gm_worlds_list_dialog_add_world(world); + } + + g_list_free(worlds); +} + +static void +gm_worlds_list_dialog_create_tree_view_worlds() { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + GtkTreeView *tree_view; + GtkTreeModel *model; + + model = GTK_TREE_MODEL(gtk_list_store_new(N_COLUMNS, GDK_TYPE_PIXBUF, + G_TYPE_STRING, G_TYPE_POINTER)); + tree_view = GTK_TREE_VIEW(gm_worlds_list_dialog_widget("tree_view_worlds")); + gtk_tree_view_set_model(tree_view, model); + gtk_tree_selection_set_mode(gtk_tree_view_get_selection(tree_view), + GTK_SELECTION_MULTIPLE); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes(_("Logo"), renderer, + "pixbuf", LOGO_COLUMN, NULL); + gtk_tree_view_column_set_min_width(column, 40); + gtk_tree_view_append_column(tree_view, column); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, + "markup", NAME_COLUMN, NULL); + gtk_tree_view_append_column(tree_view, column); + + gm_worlds_list_dialog_instance->tree_view_worlds = tree_view; + gm_worlds_list_dialog_instance->model_worlds = model; + + gm_worlds_list_dialog_populate_worlds(); +} + +void +gm_worlds_list_dialog_run() { + GladeXML *xml; + + if (gm_worlds_list_dialog_instance) { + gtk_widget_show(gm_worlds_list_dialog_instance->dialog); + gtk_window_present(GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + return; + } + + gm_worlds_list_dialog_instance = g_new0(GmWorldsListDialog, 1); + xml = glade_xml_new(G_WORLDS_LISTS_DIALOG_XML, "gm_worlds_list_dialog", + NULL); + gm_worlds_list_dialog_instance->xml = xml; + + gm_worlds_list_dialog_create_tree_view_worlds(); + + glade_xml_signal_connect(xml, "on_button_delete_clicked", + G_CALLBACK(on_gm_worlds_list_dialog_button_delete_clicked)); + glade_xml_signal_connect(xml, "on_button_new_clicked", + G_CALLBACK(on_gm_worlds_list_dialog_button_new_clicked)); + glade_xml_signal_connect(xml, "on_button_modify_clicked", + G_CALLBACK(on_gm_worlds_list_dialog_button_modify_clicked)); + glade_xml_signal_connect(xml, "on_button_duplicate_clicked", + G_CALLBACK(on_gm_worlds_list_dialog_button_connect_clicked)); + glade_xml_signal_connect(xml, "on_button_connect_clicked", + G_CALLBACK(on_gm_worlds_list_dialog_button_connect_clicked)); + + glade_xml_signal_connect(xml, "on_tree_view_worlds_key_press", + G_CALLBACK(on_gm_worlds_list_dialog_tree_view_worlds_key_press)); + + gm_worlds_list_dialog_instance->dialog = + gm_worlds_list_dialog_widget("gm_worlds_list_dialog"); + + g_signal_connect(gm_app_instance(), "world_added", + G_CALLBACK(on_gm_worlds_list_dialog_app_world_added), NULL); + g_signal_connect(gm_app_instance(), "world_removed", + G_CALLBACK(on_gm_worlds_list_dialog_app_world_removed), NULL); + + gtk_window_set_icon(GTK_WINDOW(gm_worlds_list_dialog_instance->dialog), + gm_pixbuf_get("world.svg")); + gtk_widget_show(gm_worlds_list_dialog_instance->dialog); + + g_signal_connect(gm_worlds_list_dialog_instance->dialog, "response", + G_CALLBACK(on_gm_worlds_list_dialog_response), NULL); +} + +static void +gm_worlds_list_dialog_delete_selected_worlds() { + GtkTreeView *tree_view = gm_worlds_list_dialog_instance->tree_view_worlds; + GtkTreeModel *model = gm_worlds_list_dialog_instance->model_worlds; + GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); + GtkTreeIter iter; + GList *paths = gtk_tree_selection_get_selected_rows(selection, &model); + GList *refs = NULL; + GList *node = NULL; + gchar *message; + gpointer proxy = g_object_newv(G_TYPE_OBJECT, 0, NULL); + GmWorld *world; + + for (node = paths; node; node = node->next) { + refs = g_list_append(refs, + gtk_tree_row_reference_new_proxy(proxy, model, node->data)); + gtk_tree_path_free(node->data); + } + + g_list_free(paths); + + if (refs) { + for (node = refs; node; node = node->next) { + GtkTreePath *path = gtk_tree_row_reference_get_path(node->data); + + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, WORLD_COLUMN, &world, -1); + + if (!(world && gm_world_loaded(world))) { + gtk_list_store_remove(GTK_LIST_STORE(model), &iter); + gm_app_remove_world(gm_app_instance(), world); + } else { + message = g_strdup_printf(_ + ("Can't remove the world %s because it is loaded. " + "First close the world and then try again."), + gm_world_name(world)); + gm_error_dialog(message, + GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + g_free(message); + } + + gtk_tree_row_reference_deleted(proxy, path); + gtk_tree_path_free(path); + gtk_tree_row_reference_free(node->data); + } + } else { + gm_error_dialog(_("You first need to select a world to delete."), + GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + } + + g_list_free(refs); + g_object_unref(proxy); +} + +void +gm_worlds_list_dialog_modify_world() { + GtkTreeView *tree_view = gm_worlds_list_dialog_instance->tree_view_worlds; + GtkTreeModel *model = gm_worlds_list_dialog_instance->model_worlds; + GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); + GtkTreeIter iter; + GList *first_path = gtk_tree_selection_get_selected_rows(selection, &model); + GmWorld *world; + + if (first_path) { + gtk_tree_model_get_iter(model, &iter, first_path->data); + gtk_tree_model_get(model, &iter, WORLD_COLUMN, &world, -1); + + gm_world_properties_dialog_run(world); + } else { + gm_error_dialog(_("You first need to select a world to modify."), + GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + } + + g_list_free(first_path); +} + +GList * +gm_worlds_list_dialog_selected_path() { + GtkTreeView *tree_view = gm_worlds_list_dialog_instance->tree_view_worlds; + GtkTreeModel *model = gm_worlds_list_dialog_instance->model_worlds; + GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); + + return gtk_tree_selection_get_selected_rows(selection, &model); +} + +GmWorld * +gm_worlds_list_dialog_selected_world() { + GtkTreeIter iter; + GList *first_path = gm_worlds_list_dialog_selected_path(); + GmWorld *world = NULL; + + if (first_path) { + gtk_tree_model_get_iter(gm_worlds_list_dialog_instance->model_worlds, + &iter, first_path->data); + gtk_tree_model_get(gm_worlds_list_dialog_instance->model_worlds, &iter, + WORLD_COLUMN, &world, -1); + } + + g_list_free(first_path); + return world; +} + +gboolean +gm_worlds_list_dialog_find(GmWorld *world, GtkTreeIter *iter) { + GtkTreeModel *model = gm_worlds_list_dialog_instance->model_worlds; + GmWorld *list_world; + + if (gtk_tree_model_get_iter_first(model, iter)) { + do { + gtk_tree_model_get(model, iter, WORLD_COLUMN, &list_world, -1); + + if (world == list_world) { + return TRUE; + } + } while (gtk_tree_model_iter_next(model, iter)); + } + + return FALSE; +} + +// Callbacks + +void +on_gm_worlds_list_dialog_response(GtkDialog *dialog, gint response, + gpointer user_data) { + g_object_unref(gm_worlds_list_dialog_instance->xml); + gtk_widget_destroy(gm_worlds_list_dialog_instance->dialog); + g_free(gm_worlds_list_dialog_instance); + gm_worlds_list_dialog_instance = NULL; +} + +void +on_gm_worlds_list_dialog_button_delete_clicked(GtkButton *button, + gpointer user_data) { + gm_worlds_list_dialog_delete_selected_worlds(); +} + +void +on_gm_worlds_list_dialog_tree_view_worlds_key_press(GtkWidget *widget, + GdkEventKey *event, gpointer user_data) { + switch (event->keyval) { + case GDK_D: case GDK_d: + gm_worlds_list_dialog_delete_selected_worlds(); + break; + case GDK_O: case GDK_o: + break; + case GDK_N: case GDK_n: + gm_world_properties_dialog_run_new(NULL); + break; + case GDK_M: case GDK_m: + gm_worlds_list_dialog_modify_world(); + break; + } +} + +void +on_gm_worlds_list_dialog_button_new_clicked(GtkButton *button, + gpointer user_data) { + gm_world_properties_dialog_run_new(NULL); +} + +void +on_gm_worlds_list_dialog_button_modify_clicked(GtkButton *button, + gpointer user_data) { + gm_worlds_list_dialog_modify_world(); +} + +void +on_gm_worlds_list_dialog_button_duplicate_clicked(GtkButton *button, + gpointer user_data) { + GmWorld *world = gm_worlds_list_dialog_selected_world(); + GmWorld *new_world; + + if (world) { + // now duplicate the world + new_world = gm_world_dup(world); + + // Yeah we got some world, now show the props! + gm_world_properties_dialog_run_new(new_world); + } else { + gm_error_dialog( + _("You first need to select a world to duplicate."), + GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + } +} + +void +on_gm_worlds_list_dialog_button_connect_clicked(GtkButton *button, + gpointer user_data) { + GList *first_path = gm_worlds_list_dialog_selected_path(); + GList *path; + GmWorld *world; + GtkTreeIter iter; + + if (first_path) { + for (path = first_path; path; path = path->next) { + gtk_tree_model_get_iter( + gm_worlds_list_dialog_instance->model_worlds, &iter, + first_path->data); + gtk_tree_model_get(gm_worlds_list_dialog_instance->model_worlds, + &iter, WORLD_COLUMN, &world, -1); + gm_world_load(world); + } + } else { + gm_error_dialog( + _("You first need to select a world to connect to."), + GTK_WINDOW(gm_worlds_list_dialog_instance->dialog)); + } + + g_list_free(first_path); +} + +void +on_gm_worlds_list_dialog_app_world_added(GmApp *app, GmWorld *world, + gpointer user_data) { + gm_worlds_list_dialog_add_world(world); +} + +void +on_gm_worlds_list_dialog_app_world_removed(GmApp *app, GmWorld *world, + gpointer user_data) { + GtkTreeIter iter; + + if (gm_worlds_list_dialog_find(world, &iter)) { + gtk_list_store_remove(GTK_LIST_STORE( + gm_worlds_list_dialog_instance->model_worlds), &iter); + } +} + +void +on_gm_worlds_list_dialog_world_option_changed(GmOptions *options, + const gchar *key, GmWorld *world) { + GtkTreeIter iter; + GtkListStore *store = + GTK_LIST_STORE(gm_worlds_list_dialog_instance->model_worlds); + gchar *text; + const gchar *logo; + GdkPixbuf *pix_logo; + + if (!gm_worlds_list_dialog_find(world, &iter)) { + return; + } + + if (strcmp(key, "name") == 0 || + strcmp(key, "player_name") == 0 || + strcmp(key, "host") == 0) { + text = gm_worlds_list_dialog_world_text(world); + gtk_list_store_set(store, &iter, NAME_COLUMN, text, -1); + g_free(text); + } else if (strcmp(key, "logo")) { + logo = gm_options_get(options, "logo"); + + if (logo) { + pix_logo = gm_pixbuf_get_at_size(logo, 32, 32); + } else { + pix_logo = gm_pixbuf_get_at_size("world.svg", 32, 32); + } + + gtk_list_store_set(store, &iter, LOGO_COLUMN, pix_logo, -1); + } +} diff --git a/gnoemoe/dialogs/gm-worlds-list-dialog.h b/gnoemoe/dialogs/gm-worlds-list-dialog.h new file mode 100644 index 0000000..6d38b52 --- /dev/null +++ b/gnoemoe/dialogs/gm-worlds-list-dialog.h @@ -0,0 +1,6 @@ +#ifndef __GM_WORLDS_LIST_DIALOG__ +#define __GM_WORLDS_LIST_DIALOG__ + +void gm_worlds_list_dialog_run(); + +#endif /* __GM_WORLDS_LIST_DIALOG */ diff --git a/gnoemoe/genclass.rb b/gnoemoe/genclass.rb new file mode 100644 index 0000000..b139aca --- /dev/null +++ b/gnoemoe/genclass.rb @@ -0,0 +1,47 @@ +#!/usr/bin/ruby + +def replaceAll(line, subst) + subst.each do |sub,rep| + line = line.gsub('{' + sub + '}', rep) + end + + return line +end + +def degenerateCaps(text) + text = text.gsub(/[A-Z]+[a-z]+/) do |s| + s.downcase + '_' + end + + return text[0..-2] +end + +subst = {} +name = ARGV[0] + +if (name == nil) then + print "Class name (capitalized): " + name = gets[0..-2] +end + +subst['template_'] = degenerateCaps(name) +subst['template-'] = subst['template_'].sub('_', '-') +subst['TEMPLATE'] = subst['template_'].upcase +subst['Template'] = name + +tc = IO.readlines("template.c") +th = IO.readlines("template.h") + +f = File.open('gm-' + subst['template-'] + '.c', 'w') +tc.each do |line| + f.write(replaceAll(line, subst)) +end +f.close + +f = File.open('gm-' + subst['template-'] + '.h', 'w') +th.each do |line| + f.write(replaceAll(line, subst)) +end +f.close + +print "Done ...\n" diff --git a/gnoemoe/gengtkclass.rb b/gnoemoe/gengtkclass.rb new file mode 100755 index 0000000..2fceba5 --- /dev/null +++ b/gnoemoe/gengtkclass.rb @@ -0,0 +1,58 @@ +#!/usr/bin/ruby + +def replaceAll(line, subst) + subst.each do |sub,rep| + line = line.gsub('{' + sub + '}', rep) + end + + return line +end + +def degenerateCaps(text) + text = text.gsub(/[A-Z]+[a-z]+/) do |s| + s.downcase + '_' + end + + return text[0..-2] +end + +subst = {} +name = ARGV[0] +parent = ARGV[1] + +if (name == nil) then + print "Class name (capitalized): " + name = gets[0..-2] +end + +subst['template_'] = degenerateCaps(name) +subst['template-'] = subst['template_'].sub('_', '-') +subst['TEMPLATE'] = subst['template_'].upcase +subst['Template'] = name + +if (parent == nil) then + print "Parent name (capitalized): " + parent = gets[0..-2] +end + +subst['parent_'] = degenerateCaps(parent) +subst['parent-'] = subst['parent_'].sub('_', '-') +subst['PARENT'] = subst['parent_'].upcase +subst['Parent'] = parent + +tc = IO.readlines("gtktemplate.c") +th = IO.readlines("gtktemplate.h") + +f = File.open('gm-' + subst['template-'] + '.c', 'w') +tc.each do |line| + f.write(replaceAll(line, subst)) +end +f.close + +f = File.open('gm-' + subst['template-'] + '.h', 'w') +th.each do |line| + f.write(replaceAll(line, subst)) +end +f.close + +print "Done ...\n" diff --git a/gnoemoe/gm-app.c b/gnoemoe/gm-app.c new file mode 100644 index 0000000..5fcf223 --- /dev/null +++ b/gnoemoe/gm-app.c @@ -0,0 +1,559 @@ +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <gtk/gtk.h> +#include <libgnome/libgnome.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libgnomeui/libgnomeui.h> + +#include "widgets/gm-app-view.h" +#include "gm-world.h" +#include "gm-color-table.h" + +#include "gm-app.h" +//#include "editor.h" +#include "gm-debug.h" +#include "gm-pixbuf.h" +#include "gm-support.h" +#include "gm-scripts.h" + +static gchar *debug_level = NULL; +static gboolean show_version = FALSE; +static gboolean recover = FALSE; +static gchar *load_worlds = NULL; +static GmApp *application; + +//static void gm_app_tray_create(tray_info *t); +//gboolean on_gm_app_tray_button_press(GtkWidget *widget, GdkEventButton *event, +// gpointer user_data); +//gboolean on_gm_app_tray_destroy(GtkWidget *widget, gpointer user_data); +void on_gm_app_view_size_allocate(GmAppView *view, GtkAllocation *allocation, + GmApp *app); + +struct poptOption poptions[] = { + {"debug", 'd', POPT_ARG_STRING, &debug_level, 0, N_("Enable debugging. " + "Specify multiple debug levels with a comma seperated list. " + "Available levels: default, mcp, all"), N_("DBGGLEVEL")}, + {"version", 'v', POPT_ARG_NONE, &show_version, 0, N_("Show application " + "version"), NULL}, + {"load", 'l', POPT_ARG_STRING, &load_worlds, 0, N_("Load specified worlds. " + "Specify multiple worlds with a comma separated list"), + N_("WORLDS")}, + {"recover", 'r', POPT_ARG_NONE, &recover, 0, N_("Recover from previous " + "session (used with gnome session)"), NULL}, + {NULL, '\0', 0, NULL, 0, NULL, NULL} +}; + +#define GM_APP_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_APP, GmAppPrivate)) +void gm_app_destroy_worlds(GmApp *app); + +struct _GmAppPrivate { + gchar *path; + gchar *worlds_path; + gchar *options_path; + + GmAppView *view; + GmOptions *options; + GmColorTable *color_table; + GnomeClient *client; + GList *worlds; + + #ifdef HAVE_RUBY + GmScripts *scripts; + #endif + + //tray_info tray; +}; + +/* Signals */ + +enum { + WORLD_ADDED, + WORLD_REMOVED, + NUM_SIGNALS +}; + +static guint app_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmApp, gm_app, G_TYPE_OBJECT) + +static void +gm_app_finalize(GObject *object) { + GmApp *app = GM_APP(object); + + gnome_vfs_shutdown(); + //mcp_fini(); + //editor_fini(); + + #ifdef HAVE_RUBY + g_object_unref(app->priv->scripts); + #endif + + //mcpconsole_fini(); + gm_app_destroy_worlds(app); + gm_pixbuf_fini(); + + gm_options_save(app->priv->options); + g_object_unref(app->priv->options); + + gm_color_table_save(app->priv->color_table); + g_object_unref(app->priv->color_table); + + g_free(app->priv->path); + g_free(app->priv->worlds_path); + g_free(app->priv->options_path); + + G_OBJECT_CLASS(gm_app_parent_class)->finalize(object); +} + +static void +gm_app_class_init(GmAppClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_app_finalize; + + app_signals[WORLD_ADDED] = + g_signal_new("world_added", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmAppClass, world_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + app_signals[WORLD_REMOVED] = + g_signal_new("world_removed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmAppClass, world_removed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + + g_type_class_add_private(object_class, sizeof(GmAppPrivate)); +} + +static void +gm_app_init(GmApp *app) { + app->priv = GM_APP_GET_PRIVATE(app); + + app->priv->path = NULL; + app->priv->worlds_path = NULL; + app->priv->options_path = NULL; + app->priv->options = NULL; + app->priv->client = NULL; + app->priv->worlds = NULL; + + //gm_app_tray_create(&(app->priv->tray)); +} + +/* Private functions */ +void +gm_app_destroy_worlds(GmApp *app) { + // TODO +} + +static void +on_gm_app_session_die(GnomeClient * client, gpointer client_data) { + gtk_main_quit(); +} + +/*static void +gm_app_tray_create(tray_info *t) { + t->flash_timeout = 0; + t->icon = egg_tray_icon_new(_("GnoeMoe Gnome MOO Client")); + t->event_box = gtk_event_box_new(); + + t->iconnr = TRAY_ICON_DEFAULT; + t->image = gtk_image_new_from_pixbuf( + gnoe_pixbuf_get("tray/default.svg")); + + gtk_container_add(GTK_CONTAINER(t->event_box), t->image); + t->tooltips = gtk_tooltips_new(); + + gtk_widget_show(t->event_box); + gtk_widget_show(t->image); + + gtk_container_add(GTK_CONTAINER(t->icon), t->event_box); + gtk_widget_show(GTK_WIDGET(t->icon)); + + gtk_widget_add_events(GTK_WIDGET(t->icon), GDK_BUTTON_PRESS_MASK); + + g_signal_connect(t->icon, "button_press_event", + G_CALLBACK(on_gm_app_tray_button_press), NULL); + + // Handles when the area is removed from the panel. + g_signal_connect(t->icon, "destroy", G_CALLBACK(on_gm_app_tray_destroy), + t->event_box); +}*/ + +static gboolean +on_gm_app_save_session(GnomeClient * client, gint phase, + GnomeSaveStyle save_style, gint is_shutdown, + GnomeInteractStyle interact_style, gint is_fast, gchar ** client_data) { + gchar **argv; + gint argc = sizeof(client_data) / sizeof(gchar *) + 1; + int i; + GString *ws = g_string_new(""); + GList *elem, *list; + GmWorld *world; + + argv = g_new(gchar *, argc); + argv[0] = client_data[0]; + argv[1] = "--recover"; + + for (i = 1; i < argc - 1; i++) { + argv[i + 1] = client_data[i]; + } + + gnome_client_set_clone_command(client, argc, argv); + gnome_client_set_restart_command(client, argc, argv); + + // Saving worlds state + list = gm_app_worlds(application); + + for (elem = list; elem; elem = elem->next) { + world = (GmWorld *) (elem->data); + + if (gm_world_loaded(world)) { + if (strlen(ws->str) != 0) { + ws = g_string_append_c(ws, ';'); + } + + ws = g_string_append(ws, gm_world_name(world)); + } + } + + g_list_free(list); + gm_options_set(gm_app_options(application), "worlds_saved_state", ws->str); + gm_options_save(gm_app_options(application)); + g_string_free(ws, TRUE); + + return TRUE; +} + +void +gm_app_create_settings(GmApp *app) { + app->priv->options = gm_options_new(); + + gm_options_set(app->priv->options, "editor_alternative", "0"); + gm_options_set(app->priv->options, "editor_embed", "0"); + gm_options_set(app->priv->options, "editor_needs_terminal", "0"); + gm_options_set(app->priv->options, "worlds_saved_state", ""); + gm_options_set(app->priv->options, "search_direction", "1"); +} + +void +gm_app_load_worlds(GmApp *app, gboolean autoload) { + GDir *handle = g_dir_open(app->priv->worlds_path, 0, NULL); + char *name, *path; + GmWorld *new_world; + + if (handle != NULL) { + while ((name = (char *) g_dir_read_name(handle)) != NULL) { + path = g_strconcat(app->priv->worlds_path, G_DIR_SEPARATOR_S, + name, NULL); + + if (g_file_test(path, G_FILE_TEST_IS_DIR)) { + new_world = gm_world_new(path); + + if (new_world != NULL) { + gm_app_add_world(app, new_world); + + if (autoload && gm_options_get_int( + gm_world_options(new_world), "autoload")) { + gm_world_load(new_world); + } + } + } else { + gm_debug_msg(DEBUG_ALWAYS, "Nee: %s", path); + } + + g_free(path); + } + + g_dir_close(handle); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmApp.load_worlds: failed to open worlds path %s", + app->priv->path); + } +} + +void +gm_app_initialize(GmApp *app) { + gchar *colors_path; + + gm_debug_set_level(debug_level); + + app->priv->worlds_path = g_strconcat(app->priv->path, G_DIR_SEPARATOR_S, + "worlds", NULL); + app->priv->options_path = g_strconcat(app->priv->path, G_DIR_SEPARATOR_S, + "settings", NULL); + + if (!g_file_test(app->priv->path, G_FILE_TEST_EXISTS)) { + mkdir(app->priv->path, 0755); + mkdir(app->priv->worlds_path, 0755); + } + + #ifdef HAVE_RUBY + app->priv->scripts = gm_scripts_new(); + #endif + + gm_app_create_settings(app); + gm_options_load(app->priv->options, app->priv->options_path); + + // Load color table + colors_path = g_strconcat(app->priv->path, G_DIR_SEPARATOR_S, "colors", + NULL); + app->priv->color_table = gm_color_table_new_from_options(colors_path); + g_free(colors_path); +} + +void +gm_app_run(GmApp *app) { + gchar **wrlds; + const gchar *savedState; + int i = 0; + GmWorld *world; + int width, height; + + app->priv->view = gm_app_view_new(app); + + width = gm_options_get_int(app->priv->options, "width"); + height = gm_options_get_int(app->priv->options, "height"); + + if (height > 10 && width > 10) { + gtk_window_set_default_size(GTK_WINDOW(app->priv->view), width, height); + } + + gtk_widget_show(GTK_WIDGET(app->priv->view)); + + //mcpconsole_init(); + + g_signal_connect(app->priv->view, "size_allocate", + G_CALLBACK(on_gm_app_view_size_allocate), app); + + #ifdef HAVE_RUBY + gm_scripts_load(app->priv->scripts); + #endif + + gm_app_load_worlds(app, !(recover || load_worlds)); + + if (recover) { + savedState = gm_options_get(app->priv->options, "worlds_saved_state"); + + if (strlen(savedState) != 0) { + wrlds = g_strsplit(savedState, ";", -1); + + for (i = 0; wrlds[i]; i++) { + if (strlen(wrlds[i]) != 0) { + world = gm_app_world_by_name(app, wrlds[i]); + + if (world) { + gm_world_load(world); + } + } + } + + g_strfreev(wrlds); + } + } else if (load_worlds) { + wrlds = g_strsplit(load_worlds, ",", -1); + + for (i = 0; wrlds[i]; i++) { + world = gm_app_world_by_name(app, wrlds[i]); + + if (world) { + gm_world_load(world); + } + } + + g_strfreev(wrlds); + } + + gtk_main(); +} + +/* Public functions */ +GmApp * +gm_app_new(int argc, char *argv[]) { + GmApp *app = GM_APP(g_object_new(GM_TYPE_APP, NULL)); + + app->priv->path = gnome_util_home_file("gnoemoe"); + + if (!app->priv->path) { + printf(_("GnoeMoe Application: there is no application directory, " + "this is very bad!!!\n")); + return app; + } + +#ifdef ENABLE_NLS + bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); +#endif + + gtk_set_locale(); + gtk_init(&argc, &argv); + gnome_program_init(PACKAGE, VERSION, LIBGNOMEUI_MODULE, + argc, argv, GNOME_PARAM_POPT_TABLE, poptions, + GNOME_PARAM_APP_DATADIR, PACKAGE_DATA_DIR, NULL); + + if (show_version) { + printf(_("Current version of GnoeMoe is %s\n"), VERSION); + return app; + } + + app->priv->client = gnome_master_client(); + + gtk_signal_connect(GTK_OBJECT(app->priv->client), "save_yourself", + GTK_SIGNAL_FUNC(on_gm_app_save_session), argv); + gtk_signal_connect(GTK_OBJECT(app->priv->client), "die", + GTK_SIGNAL_FUNC(on_gm_app_session_die), NULL); + + /* Initialize everything */ + gnome_vfs_init(); + glade_init(); + //mcp_init(); + gm_pixbuf_init(); + gm_app_initialize(app); + //editor_init(); + + return app; +} + +void +gm_app_add_world(GmApp *app, GmWorld *world) { + app->priv->worlds = g_list_append(app->priv->worlds, g_object_ref(world)); + g_signal_emit(app, app_signals[WORLD_ADDED], 0, world); +} + +void +gm_app_remove_world(GmApp *app, GmWorld *world) { + // Only remove when not loaded + const gchar *path = gm_world_path(world); + + if (!gm_world_loaded(world)) { + app->priv->worlds = g_list_remove(app->priv->worlds, world); + g_signal_emit(app, app_signals[WORLD_REMOVED], 0, world); + + g_object_unref(world); + gm_directory_remove_all(path, TRUE); + } +} + +GmWorld * +gm_app_world_by_name(GmApp *app, gchar *name) { + GList *elem; + const gchar *world_name; + + for (elem = app->priv->worlds; elem; elem = elem->next) { + world_name = gm_world_name(GM_WORLD(elem->data)); + + if (!g_strcasecmp(name, world_name)) { + return GM_WORLD(elem->data); + } + } + + return NULL; +} + +GmOptions * +gm_app_options(GmApp *app) { + return app->priv->options; +} + +#ifdef HAVE_RUBY +GmScripts * +gm_app_scripts(GmApp *app) { + return app->priv->scripts; +} +#endif + +const gchar * +gm_app_worlds_path(GmApp *app) { + return app->priv->worlds_path; +} + +const gchar * +gm_app_path(GmApp *app) { + return app->priv->path; +} + +GList * +gm_app_worlds(GmApp *app) { + return g_list_copy(app->priv->worlds); +} + +GmColorTable * +gm_app_color_table(GmApp *app) { + return app->priv->color_table; +} + +GmApp * +gm_app_instance() { + return application; +} + +int +main(int argc, char *argv[]) { + g_type_init(); + application = gm_app_new(argc, argv); + gm_app_run(application); + g_object_unref(application); + return 0; +} + +/* Callbacks */ +void +on_gm_app_view_size_allocate(GmAppView *view, GtkAllocation *allocation, + GmApp *app) { + gm_options_set_int(app->priv->options, "width", allocation->width); + gm_options_set_int(app->priv->options, "height", allocation->height); +} + +/*gboolean +on_gm_app_tray_button_press(GtkWidget *widget, GdkEventButton *event, + gpointer user_data) { + if (event->type == GDK_2BUTTON_PRESS || + event->type == GDK_3BUTTON_PRESS) { + return FALSE; + } + + switch (event->button) { + case 1: + if_main_show_hide(!GTK_WIDGET_VISIBLE(wndMain)); + break; + default: + return FALSE; + } + + return TRUE; +} + +gboolean +on_gm_app_tray_destroy(GtkWidget *widget, gpointer user_data) { + gtk_widget_destroy(GTK_WIDGET(app_tray_info.icon)); + app_tray_info.icon = NULL; + app_tray_info.event_box = NULL; + app_tray_info.image = NULL; + app_tray_info.tooltips = NULL; + + if (app_tray_info.flash_timeout != 0) { + g_source_remove(app_tray_info.flash_timeout); + } + + tray_create(&app_tray_info); + + if (!have_tray()) { + gtk_widget_show(if_main_get_widget("wndMain")); + } + + return TRUE; +}*/ diff --git a/gnoemoe/gm-app.h b/gnoemoe/gm-app.h new file mode 100644 index 0000000..e8f9449 --- /dev/null +++ b/gnoemoe/gm-app.h @@ -0,0 +1,79 @@ +#ifndef __GM_APP_H__ +#define __GM_APP_H__ + +//#define G_XML PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-main.glade" + +#include <gtk/gtk.h> +#include "gm-world.h" +#include "gm-options.h" +#include "gm-color-table.h" +#include "gm-scripts.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_APP (gm_app_get_type()) +#define GM_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_APP, GmApp)) +#define GM_APP_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_APP, GmApp const)) +#define GM_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_APP, GmAppClass)) +#define GM_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_APP)) +#define GM_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_APP)) +#define GM_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_APP, GmAppClass)) + +/* Private structure type */ +typedef struct _GmAppPrivate GmAppPrivate; + +/* + * Main object structure + */ +typedef struct _GmApp GmApp; + +struct _GmApp { + GObject object; + + /*< private > */ + GmAppPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmAppClass GmAppClass; + +struct _GmAppClass { + GObjectClass parent_class; + + /* Signals */ + void (* world_added) (GmApp *app, GmWorld *world); + void (* world_removed) (GmApp *app, GmWorld *world); +}; + +GmApp *gm_app_instance(); +GType gm_app_get_type(void) G_GNUC_CONST; +GmApp *gm_app_new(int argc, char *argv[]); + +void gm_app_add_world(GmApp *app, GmWorld *world); +void gm_app_remove_world(GmApp *app, GmWorld *world); + +const gchar *gm_app_worlds_path(GmApp *app); +const gchar *gm_app_path(GmApp *app); +GmOptions *gm_app_options(GmApp *app); +GList *gm_app_worlds(GmApp *app); +GmColorTable *gm_app_color_table(GmApp *app); +GmWorld *gm_app_world_by_name(GmApp *app, gchar *name); + +#ifdef HAVE_RUBY +GmScripts *gm_app_scripts(GmApp *app); +#endif + +G_END_DECLS + +#endif /* __GM_APP_H__ */ diff --git a/gnoemoe/gm-bogus.h b/gnoemoe/gm-bogus.h new file mode 100644 index 0000000..9fd0198 --- /dev/null +++ b/gnoemoe/gm-bogus.h @@ -0,0 +1,4 @@ +#ifndef __GM_BOGUS_H__ +#define __GM_BOGUS_H__ + +#endif /* __GM_BOGUS_H__ */ diff --git a/gnoemoe/gm-color-table.c b/gnoemoe/gm-color-table.c new file mode 100644 index 0000000..8721bf8 --- /dev/null +++ b/gnoemoe/gm-color-table.c @@ -0,0 +1,359 @@ +#include <glib.h> +#include "gm-color-table.h" +#include "string.h" +#include "ansi.h" +#include "gm-debug.h" + +#define GM_COLOR_TABLE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_COLOR_TABLE, GmColorTablePrivate)) + +void gm_color_table_item_free(gpointer item); + +typedef struct _GmColorTableItem GmColorTableItem; + +struct _GmColorTableItem { + gchar *hex; + GdkColor color; +}; + +typedef struct _GmColorTableSchemeItem { + const gchar *name; + const gchar *hex; +} GmColorTableSchemeItem; + +static const GmColorTableSchemeItem scheme_default[] = { + {"fg_default", "#000000"}, + {"fg_black", "#000000"}, + {"fg_red", "#663822"}, + {"fg_green", "#445632"}, + {"fg_yellow", "#D1940C"}, + {"fg_blue", "#314E6C"}, + {"fg_purple", "#494066"}, + {"fg_cyan", "#0000FFFFFFFF"}, + {"fg_white", "#BAB5AB"}, + + {"fg_default_h", "#565248"}, + {"fg_black_h", "#565248"}, + {"fg_red_h", "#990000"}, + {"fg_green_h", "#267726"}, + {"fg_yellow_h", "#EED680"}, + {"fg_blue_h", "#9DB8D2"}, + {"fg_purple_h", "#ADA7C8"}, + {"fg_cyan_h", "#86EEFFFFFFFF"}, + {"fg_white_h", "#807D74"}, + + //{"bg_default", "#EAE8E3"}, + {"bg_default", "#FFFFFF"}, + {"bg_black", "#000000"}, + {"bg_red", "#663822"}, + {"bg_green", "#445632"}, + {"bg_yellow", "#D1940C"}, + {"bg_blue", "#314E6C"}, + {"bg_purple", "#494066"}, + {"bg_cyan", "#0000FFFFFFFF"}, + {"bg_white", "#FFFFFFFFFFFF"}, + {NULL, NULL} +}; + +static const GmColorTableSchemeItem scheme_white_on_black[] = { + {"fg_default", "#D6B5D6B5D6B5"}, + {"fg_black", "#2D6B2D6B2D6B"}, + {"fg_red", "#FFFF00000000"}, + {"fg_green", "#0000FFFF0000"}, + {"fg_yellow", "#FFFFD0450000"}, + {"fg_blue", "#3EF73EF7BFFF"}, + {"fg_purple", "#A0A02020F0F0"}, + {"fg_cyan", "#0000FFFFFFFF"}, + {"fg_white", "#D8C5D8C5D8C5"}, + + {"fg_default_h", "#FFFFFFFFFFFF"}, + {"fg_black_h", "#529452945294"}, + {"fg_red_h", "#FFFF785F785F"}, + {"fg_green_h", "#66ADFFFF66AD"}, + {"fg_yellow_h", "#FFFFFFFF58C6"}, + {"fg_blue_h", "#86318631FFFF"}, + {"fg_purple_h", "#C6576A18FFFF"}, + {"fg_cyan_h", "#86EEFFFFFFFF"}, + {"fg_white_h", "#FFFFFFFFFFFF"}, + + {"bg_default", "#000000000000"}, + {"bg_black", "#2B5B2B5B2B5B"}, + {"bg_red", "#FFFF00000000"}, + {"bg_green", "#000080000000"}, + {"bg_yellow", "#C047C0470000"}, + {"bg_blue", "#00000000FFFF"}, + {"bg_purple", "#A0A02020F0F0"}, + {"bg_cyan", "#0000B74CB74C"}, + {"bg_white", "#FFFFFFFFFFFF"}, + {NULL, NULL} +}; + +typedef struct _GmColorScheme { + GmColorTableScheme scheme; + const gchar *name; + const GmColorTableSchemeItem *values; +} GmColorScheme; + +static const GmColorScheme scheme_names[] = { + {SCHEME_NONE, "none", NULL}, + {SCHEME_DEFAULT, "default", scheme_default}, + {SCHEME_WHITE_ON_BLACK, "white_on_black", scheme_white_on_black}, + {SCHEME_USER, "user", NULL}, + {SCHEME_NONE, NULL, NULL} +}; + +struct _GmColorTablePrivate { + GHashTable *colors; + gchar *font_description; + GmOptions *options; + + GmColorTableScheme scheme; +}; + +/* Signals */ + +enum { + COLOR_CHANGED, + FONT_CHANGED, + NUM_SIGNALS +}; + +static guint color_table_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmColorTable, gm_color_table, G_TYPE_OBJECT) + +static void +gm_color_table_finalize(GObject *object) { + //GmColorTable *table = GM_COLOR_TABLE(object); + + G_OBJECT_CLASS(gm_color_table_parent_class)->finalize(object); +} + +static void +gm_color_table_class_init(GmColorTableClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_color_table_finalize; + + color_table_signals[COLOR_CHANGED] = + g_signal_new("color_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmColorTableClass, color_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + color_table_signals[FONT_CHANGED] = + g_signal_new("font_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmColorTableClass, font_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + g_type_class_add_private(object_class, sizeof(GmColorTablePrivate)); +} + +static void +gm_color_table_init(GmColorTable *table) { + table->priv = GM_COLOR_TABLE_GET_PRIVATE(table); + table->priv->colors = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, gm_color_table_item_free); + table->priv->scheme = SCHEME_NONE; +} + +/* Private functions */ +void +gm_color_table_item_free(gpointer item) { + GmColorTableItem *i = (GmColorTableItem *)(item); + + g_free(i->hex); + g_free(i); +} + +void +gm_color_table_load_scheme(GmColorTable *table, + GmColorTableScheme scheme) { + int i = 0; + const GmColorTableSchemeItem *values = scheme_names[scheme].values; + + while (values[i].name != NULL) { + gm_color_table_set(table, values[i].name, values[i].hex); + ++i; + } + + table->priv->scheme = scheme; +} + +void +gm_color_table_initialize(GmColorTable *table) { + gm_color_table_set_font_description(table, "Monospace 10"); + gm_color_table_load_scheme(table, SCHEME_DEFAULT); +} + +void +gm_color_table_fill_from_options(GmColorTable *table) { + unsigned int i; + const gchar *value; + GmOptions *options = table->priv->options; + + // New, color schemes + value = gm_options_get(options, "color_scheme"); + + if (value == NULL || strcmp(value, "user") == 0) { + for (i = 0; i < sizeof(ansi_colors) / sizeof(ansinamepair); i++) { + value = gm_options_get(options, ansi_colors[i].name); + + if (value != NULL) { + gm_color_table_set(table, ansi_colors[i].name, value); + } + } + } else { + gm_color_table_set_from_scheme_name(table, value); + } + + value = gm_options_get(options, "font_family"); + + if (value && *value != '\0') { + gm_color_table_set_font_description(table, value); + } else { + gm_options_set(options, "font_family", "Monospace 10"); + } +} + +/* Public functions */ + +GmColorTable * +gm_color_table_new(void) { + GmColorTable *table = GM_COLOR_TABLE(g_object_new(GM_TYPE_COLOR_TABLE, NULL)); + + gm_color_table_load_scheme(table, SCHEME_DEFAULT); + return table; +} + +GmColorTable * +gm_color_table_new_from_options(gchar *filename) { + GmColorTable *table = GM_COLOR_TABLE(g_object_new(GM_TYPE_COLOR_TABLE, NULL)); + + table->priv->options = gm_options_new(); + gm_options_set(table->priv->options, "color_scheme", "default"); + gm_options_load(table->priv->options, filename); + gm_color_table_fill_from_options(table); + + return table; +} + +void gm_color_table_save(GmColorTable *table) { + if (table->priv->options) { + gm_options_save(table->priv->options); + } +} + +void +gm_color_table_set(GmColorTable *table, const gchar *name, const gchar *hex) { + GmColorTableItem *item; + + item = g_hash_table_lookup(table->priv->colors, name); + + if (table->priv->scheme == SCHEME_USER) { + gm_options_set(table->priv->options, name, hex); + } + + if (!item) { + item = g_new0(GmColorTableItem, 1); + g_hash_table_insert(table->priv->colors, g_strdup(name), item); + } + + if (item->hex == NULL || strcmp(hex, item->hex) != 0) { + g_free(item->hex); + item->hex = g_strdup(hex); + gdk_color_parse(item->hex, &(item->color)); + + g_signal_emit(table, color_table_signals[COLOR_CHANGED], 0, name); + } +} + +gboolean +gm_color_table_get(GmColorTable *table, const gchar *name, GdkColor *color) { + GmColorTableItem *item; + + item = g_hash_table_lookup(table->priv->colors, name); + + if (item != NULL) { + *color = item->color; + return TRUE; + } else { + return FALSE; + } +} + +const gchar * +gm_color_table_get_hex(GmColorTable *table, const gchar *name) { + GmColorTableItem *item; + + item = g_hash_table_lookup(table->priv->colors, name); + + if (item != NULL) { + return item->hex; + } else { + return FALSE; + } +} + +void +gm_color_table_set_font_description(GmColorTable *table, + const gchar *font_description) { + const gchar *fd; + + if (font_description == NULL) { + fd = "Monospace 10"; + } else { + fd = font_description; + } + + gm_options_set(table->priv->options, "font_family", + fd); + + if (table->priv->font_description == NULL || + strcmp(table->priv->font_description, fd) != 0) { + g_free(table->priv->font_description); + table->priv->font_description = g_strdup(fd); + + g_signal_emit(table, color_table_signals[FONT_CHANGED], 0, + table->priv->font_description); + } +} + +const gchar * +gm_color_table_font_description(GmColorTable *table) { + return table->priv->font_description; +} + +void +gm_color_table_set_from_scheme_name(GmColorTable *table, const gchar *scheme) { + int i = 0; + + while (scheme_names[i].name != NULL) { + if (strcasecmp(scheme_names[i].name, scheme) == 0 && + scheme_names[i].values != NULL) { + gm_color_table_load_scheme(table, scheme_names[i].scheme); + break; + } + ++i; + } + + if (scheme_names[i].name == NULL) { + gm_color_table_load_scheme(table, SCHEME_DEFAULT); + + gm_options_set(table->priv->options, "color_scheme", + "default"); + } else { + gm_options_set(table->priv->options, "color_scheme", + scheme_names[i].name); + } +} diff --git a/gnoemoe/gm-color-table.h b/gnoemoe/gm-color-table.h new file mode 100644 index 0000000..363898e --- /dev/null +++ b/gnoemoe/gm-color-table.h @@ -0,0 +1,79 @@ +#ifndef __GM_COLOR_TABLE_H__ +#define __GM_COLOR_TABLE_H__ + +#include <gtk/gtk.h> +#include "gm-options.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_COLOR_TABLE (gm_color_table_get_type()) +#define GM_COLOR_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_COLOR_TABLE, GmColorTable)) +#define GM_COLOR_TABLE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_COLOR_TABLE, GmColorTable const)) +#define GM_COLOR_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_COLOR_TABLE, GmColorTableClass)) +#define GM_IS_COLOR_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_COLOR_TABLE)) +#define GM_IS_COLOR_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_COLOR_TABLE)) +#define GM_COLOR_TABLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_COLOR_TABLE, GmColorTableClass)) + +/* Private structure type */ +typedef struct _GmColorTablePrivate GmColorTablePrivate; + +/* + * Main object structure + */ +typedef struct _GmColorTable GmColorTable; + +struct _GmColorTable { + GObject object; + + /*< private > */ + GmColorTablePrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmColorTableClass GmColorTableClass; + +struct _GmColorTableClass { + GObjectClass parent_class; + + /* Signals */ + void (* color_changed) (GmColorTable *table, const gchar *color); + void (* font_changed) (GmColorTable *table, const gchar *font_description); +}; + +typedef enum _GmColorTableScheme { + SCHEME_NONE = 0, + SCHEME_DEFAULT = 1, + SCHEME_WHITE_ON_BLACK = 2, + SCHEME_USER = 3 +} GmColorTableScheme; + +GType gm_color_table_get_type(void) G_GNUC_CONST; + +GmColorTable *gm_color_table_new(void); +GmColorTable *gm_color_table_new_from_options(gchar *filename); +void gm_color_table_save(GmColorTable *table); + +void gm_color_table_set(GmColorTable *table, const gchar *name, + const gchar *hex); +gboolean gm_color_table_get(GmColorTable *table, const gchar *name, + GdkColor *color); +const gchar *gm_color_table_get_hex(GmColorTable *table, const gchar *name); + +void gm_color_table_set_font_description(GmColorTable *table, + const gchar *font_description); +const gchar *gm_color_table_font_description(GmColorTable *table); + +void gm_color_table_load_scheme(GmColorTable *table, + GmColorTableScheme scheme); +void gm_color_table_set_from_scheme_name(GmColorTable *table, + const gchar *name); +void gm_color_table_set_from_options(GmColorTable *table, GmOptions *options); + +G_END_DECLS +#endif /* __GM_COLOR_TABLE_H__ */ + diff --git a/gnoemoe/gm-debug.c b/gnoemoe/gm-debug.c new file mode 100644 index 0000000..0b1eeac --- /dev/null +++ b/gnoemoe/gm-debug.c @@ -0,0 +1,77 @@ +#include <time.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <glib.h> + +#include "gm-debug.h" + +typedef struct _LevelMap { + gchar const *name; + GmDebugLevel level; +} LevelMap; + +static LevelMap level_mapping[] = { + {"default", DEBUG_DEFAULT}, + {"mcp", DEBUG_MCP}, + {"all", DEBUG_ALL}, + {NULL, 0} +}; + +static gint debug_level = DEBUG_ALWAYS; + +static void +gm_debug_msg_real(FILE *f, struct tm *timet, gchar *line, va_list args) { + fprintf(f, "[%02d:%02d:%02d] # ", timet->tm_hour, timet->tm_min, + timet->tm_sec); + vfprintf(f, line, args); + fprintf(f, "\n"); +} + +void +gm_debug_msg(gint level, gchar *line, ...) { + struct tm *timet; + time_t timer; + va_list args; + + if ((debug_level | level) == debug_level) { + va_start(args, line); + timer = time(0); + timet = localtime(&timer); + + if (level & DEBUG_ALWAYS) { + gm_debug_msg_real(stdout, timet, line, args); + } + + if ((level & DEBUG_ALWAYS) != level) { + gm_debug_msg_real(stderr, timet, line, args); + } + + va_end(args); + } +} + +void +gm_debug_set_level(gchar *level) { + gchar **levels, **iter; + debug_level = DEBUG_ALWAYS; + LevelMap *map; + + if (level == NULL) { + return; + } + + levels = g_strsplit(level, ",", -1); + + for (iter = levels; *iter; ++iter) { + for (map = level_mapping; map->name; ++map) { + if (strcasecmp(map->name, *iter) == 0) { + debug_level = debug_level | map->level; + break; + } + } + } + + g_strfreev(levels); +} diff --git a/gnoemoe/gm-debug.h b/gnoemoe/gm-debug.h new file mode 100644 index 0000000..ec8bb05 --- /dev/null +++ b/gnoemoe/gm-debug.h @@ -0,0 +1,14 @@ +#ifndef __GM_DEBUG_H__ +#define __GM_DEBUG_H__ 1 + +typedef enum _GmDebugLevel { + DEBUG_ALWAYS = 1 << 1, + DEBUG_DEFAULT = 1 << 2, + DEBUG_MCP = 1 << 3, + DEBUG_ALL = 0xFFFF +} GmDebugLevel; + +void gm_debug_msg(gint level, char *line, ...); +void gm_debug_set_level(gchar *level); + +#endif /* __GM_DEBUG_H__ */ diff --git a/gnoemoe/gm-editor.c b/gnoemoe/gm-editor.c new file mode 100644 index 0000000..412c53b --- /dev/null +++ b/gnoemoe/gm-editor.c @@ -0,0 +1,128 @@ +#include <string.h> + +#include "gm-editor.h" +#include "gm-world.h" +#include "gm-support.h" + +#define GM_EDITOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_EDITOR, GmEditorPrivate)) + +struct _GmEditorPrivate { + gchar *name; + gchar *upload_cmd; + gchar *mcp_type; + + GmEditType type; + gboolean is_code; + + GList *lines; +}; + +/* Signals */ + +enum { + SAVE, + NUM_SIGNALS +}; + +static guint editor_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmEditor, gm_editor, G_TYPE_OBJECT) + +static void +gm_editor_finalize(GObject *object) { + GmEditor *editor = GM_EDITOR(object); + + g_free(editor->priv->name); + g_free(editor->priv->upload_cmd); + g_free(editor->priv->mcp_type); + g_list_free_simple(editor->priv->lines); + + G_OBJECT_CLASS(gm_editor_parent_class)->finalize(object); +} + +static void +gm_editor_class_init(GmEditorClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_editor_finalize; + + editor_signals[SAVE] = + g_signal_new("save", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmEditorClass, save), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private(object_class, sizeof(GmEditorPrivate)); +} + +static void +gm_editor_init(GmEditor *view) { + view->priv = GM_EDITOR_GET_PRIVATE(view); +} + +GmEditor * +gm_editor_new(gchar *name, gchar *uploadcmd, GList *text) { + GmEditor *editor = GM_EDITOR(g_object_new(GM_TYPE_EDITOR, NULL)); + + editor->priv->name = g_strdup(name); + editor->priv->upload_cmd = g_strdup(uploadcmd); + editor->priv->mcp_type = NULL; + editor->priv->lines = g_list_copy(text); + editor->priv->type = E_LEGACY; + editor->priv->is_code = (strncmp(uploadcmd, "@program", 7) == 0); + return editor; +} + +GmEditor * +gm_editor_new_mcp(gchar *name, gchar *reference, gchar *type, + GList *text) { + GmEditor *editor = GM_EDITOR(g_object_new(GM_TYPE_EDITOR, NULL)); + + editor->priv->name = g_strdup(name); + editor->priv->upload_cmd = g_strdup(reference); + editor->priv->mcp_type = g_strdup(type); + editor->priv->lines = g_list_copy(text); + editor->priv->type = E_LEGACY; + editor->priv->is_code = (strcmp(type, "moo-code") == 0); + + return editor; +} + +gboolean +gm_editor_is_code(GmEditor *editor) { + return editor->priv->is_code; +} + +gchar * +gm_editor_name(GmEditor *editor) { + return editor->priv->name; +} + +gchar * +gm_editor_upload_cmd(GmEditor *editor) { + return editor->priv->upload_cmd; +} + +gchar * +gm_editor_mcp_type(GmEditor *editor) { + return editor->priv->mcp_type; +} + +GList * +gm_editor_lines(GmEditor *editor) { + return editor->priv->lines; +} + +GmEditType +gm_editor_type(GmEditor *editor) { + return editor->priv->type; +} + +void +gm_editor_save(GmEditor *editor) { + g_signal_emit(editor, editor_signals[SAVE], 0); +} diff --git a/gnoemoe/gm-editor.h b/gnoemoe/gm-editor.h new file mode 100644 index 0000000..8b2da75 --- /dev/null +++ b/gnoemoe/gm-editor.h @@ -0,0 +1,68 @@ +#ifndef __GM_EDITOR_H__ +#define __GM_EDITOR_H__ + +#include <glib-object.h> +#include <glib.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_EDITOR (gm_editor_get_type()) +#define GM_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_EDITOR, GmEditor)) +#define GM_EDITOR_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_EDITOR, GmEditor const)) +#define GM_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_EDITOR, GmEditorClass)) +#define GM_IS_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_EDITOR)) +#define GM_IS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_EDITOR)) +#define GM_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_EDITOR, GmEditorClass)) + +/* Private structure type */ +typedef struct _GmEditorPrivate GmEditorPrivate; + +/* + * Main object structure + */ +typedef struct _GmEditor GmEditor; + +struct _GmEditor { + GObject object; + + /*< private > */ + GmEditorPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmEditorClass GmEditorClass; + +struct _GmEditorClass { + GObject parent_class; + + /* Signals */ + void (* save) (GmEditor *editor); +}; + +typedef enum _GmEditType GmEditType; +enum _GmEditType { + E_LEGACY, + E_MCP +}; + +GType gm_editor_get_type(void) G_GNUC_CONST; +GmEditor *gm_editor_new(gchar *name, gchar *uploadcmd, GList *text); +GmEditor *gm_editor_new_mcp(gchar *name, gchar *reference, gchar *type, + GList *text); + +void gm_editor_save(GmEditor *editor); + +gboolean gm_editor_is_code(GmEditor *editor); +gchar *gm_editor_name(GmEditor *editor); +gchar *gm_editor_upload_cmd(GmEditor *editor); +gchar *gm_editor_mcp_type(GmEditor *editor); +GList *gm_editor_lines(GmEditor *editor); +GmEditType gm_editor_type(GmEditor *editor); + +G_END_DECLS +#endif /* __GM_EDITOR_H__ */ diff --git a/gnoemoe/gm-marshal.list b/gnoemoe/gm-marshal.list new file mode 100644 index 0000000..bd0a3c1 --- /dev/null +++ b/gnoemoe/gm-marshal.list @@ -0,0 +1,4 @@ +VOID:STRING,UINT +VOID:STRING,INT +VOID:INT,INT +VOID:INT,STRING diff --git a/gnoemoe/gm-net.c b/gnoemoe/gm-net.c new file mode 100644 index 0000000..7fde019 --- /dev/null +++ b/gnoemoe/gm-net.c @@ -0,0 +1,537 @@ + +#include <glib.h> +#include <sys/time.h> +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include "gm-net.h" +#include "gm-marshal.h" +#include "gm-debug.h" +#include "gm-support.h" + +#define GM_NET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_NET, GmNetPrivate)) + +struct _GmNetPrivate { + int socket; /**< the connection socket, is -1 if not connected */ + GIOChannel *channel; /**< the channel which is used to 'listen' on the socket via the glib main loop */ + guint source; /**< the id of the socket watch */ + guint connect_timeout_id; /**< the connect timeout id */ + guint connect_check_id; /**< the connect timeout id */ + struct timeval last_connected; /**< contains when the last connect happened */ + int tn_last; /**< used for telnet */ + int tn_subneg; /**< used for telnet */ + + struct addrinfo *addr; + struct addrinfo *current; + + GmNetState state; /**< state of the connection */ + gchar *current_host; + gchar *current_port; +}; + +/* Signals */ + +enum { + STATE_CHANGING, + NET_ERROR, + BYTES_RECV, + NUM_SIGNALS +}; + +static guint net_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmNet, gm_net, G_TYPE_OBJECT) + +/* These values taken from RFC 854 and RFC 857. */ +#define TN_WILL 251 +#define TN_WONT 252 +#define TN_DO 253 +#define TN_DONT 254 +#define TN_IAC 255 /* Interpret As Command */ +#define TN_SB 250 /* start of subnegotiation */ +#define TN_SE 240 /* end of subnegotiaton */ + +void gm_net_connect_next(GmNet *net); + +gboolean on_gm_net_input_recv(GIOChannel * source, GIOCondition condition, + GmNet *net); +gboolean on_gm_net_connect_check(GIOChannel * source, GIOCondition condition, + GmNet *net); +gboolean on_gm_net_connect_timeout(GmNet *net); + +static void +gm_net_finalize(GObject *object) { + //GmNet *net = GM_NET(object); + + G_OBJECT_CLASS(gm_net_parent_class)->finalize(object); +} + +static void +gm_net_class_init(GmNetClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_net_finalize; + + net_signals[STATE_CHANGING] = + g_signal_new("state_changing", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmNetClass, state_changing), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + net_signals[NET_ERROR] = + g_signal_new("net_error", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmNetClass, net_error), + NULL, NULL, + gm_marshal_VOID__STRING_INT, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + + net_signals[BYTES_RECV] = + g_signal_new("bytes_recv", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmNetClass, bytes_recv), + NULL, NULL, + gm_marshal_VOID__STRING_UINT, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_UINT); + + g_type_class_add_private(object_class, sizeof(GmNetPrivate)); +} + +static void +gm_net_init(GmNet *net) { + net->priv = GM_NET_GET_PRIVATE(net); + net->priv->state = GM_NET_STATE_DISCONNECTED; + net->priv->addr = NULL; + net->priv->current = NULL; + net->priv->channel = NULL; + + net->priv->source = 0; + net->priv->connect_timeout_id = 0; + net->priv->connect_check_id = 0; +} + +void +gm_net_set_state(GmNet *net, GmNetState state) { + g_signal_emit(net, net_signals[STATE_CHANGING], 0, state); + net->priv->state = state; +} + +void +gm_net_clean_disconnection(GmNet *net) { + GError *err = NULL; + + if (!net->priv->channel) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.CleanDisconnection: NOT clean for %d", + net->priv->socket); + return; + } + + gm_debug_msg(DEBUG_DEFAULT, "GmNet.CleanDisconnection: clean disconnect for %d", + net->priv->socket); + + // Shutdown the channel + g_io_channel_shutdown(net->priv->channel, TRUE, &err); + + if (err) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.CleanDisconnection: error on channel shutdown: " + "%s", err->message); + g_error_free(err); + err = NULL; + } + + g_io_channel_unref(net->priv->channel); + + net->priv->channel = NULL; + net->priv->socket = -1; + + net->priv->tn_last = 0; + net->priv->tn_subneg = 0; + + gm_net_set_state(net, GM_NET_STATE_DISCONNECTED); +} + +void +gm_net_dirty_disconnection(GmNet *net, int err) { + gchar *msg; + + // Pff, stupid, we print a message and pass it on to clean_disconnection + gm_debug_msg(DEBUG_DEFAULT, "GmNet.DirtyDisconnection: dirty disconnect %d", + net->priv->socket); + + msg = g_strdup_printf(_("Connection lost... (%s)"), strerror(err)); + g_signal_emit(net, net_signals[NET_ERROR], 0, msg, + GM_NET_ERROR_DISCONNECTED); + g_free(msg); + + gm_net_clean_disconnection(net); +} + +void +gm_net_connect_succeed(GmNet *net) { + freeaddrinfo(net->priv->addr); + net->priv->addr = NULL; + net->priv->current = NULL; + + net->priv->source = g_io_add_watch(net->priv->channel, + G_IO_IN | G_IO_HUP, (GIOFunc)on_gm_net_input_recv, net); + + if (net->priv->connect_timeout_id != 0) { + g_source_remove(net->priv->connect_timeout_id); + net->priv->connect_timeout_id = 0; + } + + if (net->priv->connect_check_id != 0) { + g_source_remove(net->priv->connect_check_id); + net->priv->connect_check_id = 0; + } + + gettimeofday(&(net->priv->last_connected), NULL); + gm_net_set_state(net, GM_NET_STATE_CONNECTED); +} + +void +gm_net_connect_failed(GmNet *net, gchar *err, gint code) { + if (net->priv->channel) { + g_io_channel_shutdown(net->priv->channel, TRUE, NULL); + g_io_channel_unref(net->priv->channel); + net->priv->channel = NULL; + } + + g_signal_emit(net, net_signals[NET_ERROR], 0, err, GM_NET_ERROR_CONNECTING); + + if (net->priv->addr && net->priv->current->ai_next) { + net->priv->current = net->priv->current->ai_next; + gm_net_connect_next(net); + } else { + net->priv->socket = -1; + + if (net->priv->addr) { + freeaddrinfo(net->priv->addr); + net->priv->addr = NULL; + net->priv->current = NULL; + } + + // Don't use set_state here because we weren't really connected before + gm_net_set_state(net, GM_NET_STATE_DISCONNECTED); + } +} + +void +gm_net_connect_next(GmNet *net) { + char host[NI_MAXHOST], port[NI_MAXSERV]; + int ret, result; + struct addrinfo *tmp = net->priv->current; + + if (tmp == NULL) { + return; + } else { + if ((ret = getnameinfo(tmp->ai_addr, tmp->ai_addrlen, host, NI_MAXHOST, + port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.ConnectNext: getnameinfo error: %s", + gai_strerror(ret)); + gm_net_connect_failed(net, (gchar *)gai_strerror(ret), ret); + return; + } + + net->priv->current_host = host; + net->priv->current_port = port; + gm_net_set_state(net, GM_NET_STATE_TRY_ADDRESS); + + net->priv->socket = socket(tmp->ai_family, tmp->ai_socktype, + tmp->ai_protocol); + + if (net->priv->socket < 0) { + gm_net_connect_failed(net, strerror(errno), errno); + } else { + fcntl(net->priv->socket, F_SETFL, + fcntl(net->priv->socket, F_GETFL) | O_NONBLOCK); + + if ((result = connect(net->priv->socket, tmp->ai_addr, + net->priv->addr->ai_addrlen)) == -1 && errno != EINPROGRESS) { + gm_net_connect_failed(net, strerror(errno), errno); + } else { + net->priv->channel = g_io_channel_unix_new(net->priv->socket); + g_io_channel_set_close_on_unref(net->priv->channel, TRUE); + + if (result == 0) { + gm_net_connect_succeed(net); + } else { + net->priv->connect_check_id = g_io_add_watch(net->priv->channel, + G_IO_OUT|G_IO_ERR, (GIOFunc)on_gm_net_connect_check, net); + net->priv->connect_timeout_id = g_timeout_add(5000, + (GSourceFunc)on_gm_net_connect_timeout, net); + } + } + } + } +} + +void +gm_net_handle_telnet(GmNet *net, unsigned char *buf, int *len) { + int i, j; + unsigned char c; + + j = 0; + + for (i = 0; i < *len; ++i) { + c = buf[i]; + + if (net->priv->tn_last) { + switch (net->priv->tn_last) { + case TN_WILL: case TN_WONT: case TN_DO: case TN_DONT: + net->priv->tn_last = 0; + break; + case TN_IAC: + switch (c) { + case TN_WILL: case TN_WONT: case TN_DO: case TN_DONT: + net->priv->tn_last = c; + break; + case TN_SB: + net->priv->tn_subneg = 1; + net->priv->tn_last = 0; + break; + case TN_SE: + net->priv->tn_subneg = 0; + net->priv->tn_last = 0; + break; + case TN_IAC: + if (!net->priv->tn_subneg) { + buf[j] = c; + ++j; + } + net->priv->tn_last = 0; + break; + default: + net->priv->tn_last = 0; + break; + } + } + } else if (c == TN_IAC) { + net->priv->tn_last = TN_IAC; + } else if (net->priv->tn_subneg) { + continue; + } else { + buf[j] = c; + ++j; + } + } + + *len = j; //Since j-- is the last written char +} + +/* Public */ +GmNet * +gm_net_new() { + GmNet *net = GM_NET(g_object_new(GM_TYPE_NET, NULL)); + + return net; +} + +GmNetState +gm_net_state(GmNet *net) { + return net->priv->state; +} + +void +gm_net_connect(GmNet *net, const gchar *host, const gchar *port) { + struct addrinfo hint; + int ret; + char shost[NI_MAXHOST], sport[NI_MAXSERV]; + + if (net->priv->state != GM_NET_STATE_DISCONNECTED) { + return; + } + + snprintf(shost, NI_MAXHOST - 1, "%s", host); + snprintf(sport, NI_MAXSERV - 1, "%s", port); + + net->priv->current_host = shost; + net->priv->current_port = sport; + + gm_net_set_state(net, GM_NET_STATE_CONNECTING); + + memset(&hint, 0, sizeof(hint)); + hint.ai_flags = 0; + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo: %s : %s", shost, sport); + + if ((ret = getaddrinfo(shost, sport, &hint, &(net->priv->addr))) != 0) { + net->priv->addr = NULL; + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo failed: %s", gai_strerror(ret)); + gm_net_connect_failed(net, (gchar *)gai_strerror(ret), ret); + return; + } + + if (net->priv->addr != NULL) { + net->priv->current = net->priv->addr; + gm_net_connect_next(net); + } else { + gm_net_connect_failed(net, _("No addresses available"), 0); + } +} + +void +gm_net_disconnect(GmNet *net) { + if (net->priv->state == GM_NET_STATE_CONNECTED) { + gm_net_set_state(net, GM_NET_STATE_DISCONNECTING); + + // Remove the watch + g_source_remove(net->priv->source); + gm_net_clean_disconnection(net); + } +} + +void +gm_net_send_line(GmNet *net, gchar *line) { + gchar *send_line; + + send_line = (gchar *)(g_strconcat(line, "\r\n", NULL)); + gm_net_send(net, send_line); + g_free(send_line); +} + +void +gm_net_send(GmNet *net, gchar *text) { + int result; + fd_set connect_set; + + if (net->priv->state == GM_NET_STATE_CONNECTED) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Send: %s", text); + + if ((result = send(net->priv->socket, text, strlen(text), 0)) == -1 + && (errno == EAGAIN || errno == EWOULDBLOCK)) { + FD_ZERO(&connect_set); + FD_SET(net->priv->socket, &connect_set); + + // Wait for sending to be done + select(net->priv->socket + 1, NULL, &connect_set, NULL, NULL); + } else if (result == -1) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Send: error on sending line: %s", strerror(errno)); + gm_net_dirty_disconnection(net, errno); + } + } else { + g_signal_emit(net, net_signals[NET_ERROR], 0, _("Not connected"), + GM_NET_ERROR); + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Send: not connected!"); + } +} + +const gchar * +gm_net_current_host(GmNet *net) { + return net->priv->current_host; +} + +const gchar * +gm_net_current_port(GmNet *net) { + return net->priv->current_port; +} + +/* Callbacks */ + +#define MAX_RECV_BUF 1024 + +gboolean +on_gm_net_input_recv(GIOChannel * source, GIOCondition condition, GmNet *net) { + unsigned char lbuf[MAX_RECV_BUF]; + int len; + + if (condition == G_IO_HUP) { + gm_net_set_state(net, GM_NET_STATE_DISCONNECTING); + gm_net_clean_disconnection(net); + return FALSE; + } + + if (net->priv->state != GM_NET_STATE_CONNECTED) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.OnInputRecv: not connected!"); + return FALSE; + } + + // Break the received line by newline (skip \r) + len = recv(net->priv->socket, lbuf, MAX_RECV_BUF - 2, 0); + + gm_debug_msg(DEBUG_DEFAULT, "GmNet.OnInputRecv: received %d bytes", len); + + if (len < 1) { + // Disconnected, either clean or dirty + // (shouldn't this be caught by G_IO_HUP?) + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return TRUE; + } + + gm_net_set_state(net, GM_NET_STATE_DISCONNECTING); + gm_net_dirty_disconnection(net, errno); + } else { + gm_net_set_state(net, GM_NET_STATE_DISCONNECTING); + gm_net_clean_disconnection(net); + } + + return FALSE; + } else { + // It's fine, we have text! + gm_net_handle_telnet(net, lbuf, &len); + g_signal_emit(net, net_signals[BYTES_RECV], 0, lbuf, len); + } + + return TRUE; +} + +gboolean +on_gm_net_connect_check(GIOChannel * source, GIOCondition condition, + GmNet *net) { + int option = 0; + socklen_t optionsize = sizeof(option); + + if (net->priv->connect_timeout_id != 0) { + g_source_remove(net->priv->connect_timeout_id); + net->priv->connect_timeout_id = 0; + } + + if (condition == G_IO_ERR) { + getsockopt(net->priv->socket, SOL_SOCKET, SO_ERROR, &option, &optionsize); + gm_net_connect_failed(net, strerror(option), option); + } else { + gm_net_connect_succeed(net); + } + + return FALSE; +} + +gboolean +on_gm_net_connect_timeout(GmNet *net) { + net->priv->connect_timeout_id = 0; + + if (net->priv->connect_check_id != 0) { + g_source_remove(net->priv->connect_check_id); + net->priv->connect_check_id = 0; + } + + gm_net_connect_failed(net, _("Connect timeout (5)"), 0); + return FALSE; +} diff --git a/gnoemoe/gm-net.h b/gnoemoe/gm-net.h new file mode 100644 index 0000000..c07c633 --- /dev/null +++ b/gnoemoe/gm-net.h @@ -0,0 +1,81 @@ +#ifndef __GM_NET_H__ +#define __GM_NET_H__ + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_NET (gm_net_get_type()) +#define GM_NET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_NET, GmNet)) +#define GM_NET_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_NET, GmNet const)) +#define GM_NET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_NET, GmNetClass)) +#define GM_IS_NET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_NET)) +#define GM_IS_NET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_NET)) +#define GM_NET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_NET, GmNetClass)) + +typedef enum _GmNetState { + GM_NET_STATE_CONNECTED, /**< socket connected */ + GM_NET_STATE_DISCONNECTED, /**< socket disconnected */ + GM_NET_STATE_CONNECTING, /**< socket still connecting */ + GM_NET_STATE_TRY_ADDRESS, /**< connecting to address */ + GM_NET_STATE_DISCONNECTING /**< socket still disconnecting */ +} GmNetState; + +typedef enum _GmNetError { + GM_NET_ERROR_CONNECTING, /**< error while connecting */ + GM_NET_ERROR_DISCONNECTED, /**< error while connecting */ + GM_NET_ERROR /**< general error */ +} GmNetError; + +/* Private structure type */ +typedef struct _GmNetPrivate GmNetPrivate; + +/* + * Main object structure + */ +typedef struct _GmNet GmNet; + +struct _GmNet { + GObject object; + + /*< private > */ + GmNetPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmNetClass GmNetClass; + +struct _GmNetClass { + GObjectClass parent_class; + + /* Signals */ + void (* state_changing) (GmNet *net, guint state); + void (* net_error) (GmNet *net, gchar *error, gint code); + void (* bytes_recv) (GmNet *net, gchar *text); +}; + +GType gm_net_get_type(void) G_GNUC_CONST; +GmNet *gm_net_new(void); + +GmNetState gm_net_state(GmNet *net); +void gm_net_connect(GmNet *net, const gchar *host, const gchar *port); +void gm_net_disconnect(GmNet *net); +void gm_net_send_line(GmNet *net, gchar *line); +void gm_net_send(GmNet *net, gchar *text); +const gchar *gm_net_current_host(GmNet *net); +const gchar *gm_net_current_port(GmNet *net); + +G_END_DECLS +#endif /* __GM_NET_H__ */ diff --git a/gnoemoe/gm-options.c b/gnoemoe/gm-options.c new file mode 100644 index 0000000..666dea1 --- /dev/null +++ b/gnoemoe/gm-options.c @@ -0,0 +1,212 @@ +#include <glib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "gm-options.h" +#include "gm-string.h" +#include "gm-debug.h" + +extern int errno; + +#define GM_OPTIONS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_OPTIONS, GmOptionsPrivate)) + +struct _GmOptionsPrivate { + GHashTable *options; + gchar *filepath; +}; + +/* Signals */ + +enum { + OPTION_CHANGED, + NUM_SIGNALS +}; + +static guint options_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmOptions, gm_options, G_TYPE_OBJECT) + +static void +gm_options_finalize(GObject *object) { + GmOptions *options = GM_OPTIONS(object); + + g_hash_table_destroy(options->priv->options); + g_free(options->priv->filepath); + + G_OBJECT_CLASS(gm_options_parent_class)->finalize(object); +} + +static void +gm_options_class_init(GmOptionsClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_options_finalize; + + options_signals[OPTION_CHANGED] = + g_signal_new("option_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmOptionsClass, option_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + g_type_class_add_private(object_class, sizeof(GmOptionsPrivate)); +} + +static void +gm_options_init(GmOptions *options) { + options->priv = GM_OPTIONS_GET_PRIVATE(options); + options->priv->options = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + options->priv->filepath = NULL; +} + +GmOptions * +gm_options_new(void) { + GmOptions *options = GM_OPTIONS(g_object_new(GM_TYPE_OPTIONS, NULL)); + + return options; +} + +void +gm_options_dup_option(gchar *key, gchar *value, GmOptions *copy) { + gm_options_set(copy, key, value); +} + +GmOptions * +gm_options_dup(GmOptions *source) { + GmOptions *copy = gm_options_new(); + + g_hash_table_foreach(source->priv->options, (GHFunc)gm_options_dup_option, + copy); + + return copy; +} + +// Adds an option to opt, even if key already exists +void +gm_options_set(GmOptions * options, const gchar *key, const gchar *value) { + gchar *trimmed = gm_string_trim(key); + gchar *lookup = g_hash_table_lookup(options->priv->options, trimmed); + gboolean changed = lookup != NULL && lookup != value; + + if (lookup != value) { + g_hash_table_insert(options->priv->options, g_strdup(trimmed), + g_strdup(value)); + } + + if (changed) { + g_signal_emit(options, options_signals[OPTION_CHANGED], 0, trimmed); + } + + g_free(trimmed); +} + +const gchar * +gm_options_get(GmOptions *options, const gchar *key) { + return g_hash_table_lookup(options->priv->options, key); +} + +void +gm_options_set_int(GmOptions *options, const gchar *key, int value) { + gchar val[15]; + + g_snprintf((gchar *) &val, 15, "%d", value); + gm_options_set(options, key, (const gchar *)val); +} + +int +gm_options_get_int(GmOptions *options, const gchar *key) { + const gchar *val = gm_options_get(options, key); + int ret; + + if (val && gm_string_to_int(val, &ret)) { + return ret; + } else { + return 0; + } +} + +void +gm_options_save_value(gchar *key, gchar *value, FILE *f) { + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.SaveValue: saving %s, %s", key, value); + fprintf(f, "%s=%s\n", key, value); +} + +void +gm_options_save(GmOptions *options) { + FILE *f; + + if (options->priv->filepath == NULL) { + return; + } + + f = fopen(options->priv->filepath, "w"); + + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.save: saving options (%s)!", options->priv->filepath); + + if (f) { + g_hash_table_foreach(options->priv->options, + (GHFunc)gm_options_save_value, f); + + fclose(f); + chmod(options->priv->filepath, 0660); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.save: couldn't open option file for saving: %s", + strerror(errno)); + } +} + +void +gm_options_save_as(GmOptions *options, const gchar *filename) { + g_free(options->priv->filepath); + options->priv->filepath = g_strdup(filename); + + gm_options_save(options); +} + +void +gm_options_load(GmOptions *options, const char *filename) { + FILE *f; + gchar **keyvalue, line[1024]; + int i; + + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.load: loading options (%s)!", filename); + + if ((f = fopen(filename, "r")) != NULL) { + i = 0; + while (fgets((char *) &line, 1024 - 1, f) != NULL) { + line[strlen((char *) &line) - 1] = '\0'; + i++; + + if (strlen(line) != 0) { // Empty lines, we don't need to process those + keyvalue = g_strsplit(line, "=", 2); + // This will return at least 1 element, at most 2, we need 2 + if (strncmp(keyvalue[0], "#", 1) != 0) { // Commented lines, well ignore them too + if (keyvalue[1] != NULL) { + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.load: adding %s, %s", keyvalue[0], keyvalue[1]); + gm_options_set(options, keyvalue[0], keyvalue[1]); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.load: wrong syntax of options " + "line in %s line %d", filename, i); + } + } + + g_strfreev(keyvalue); + } + } + + fclose(f); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmOptions.load: could not retrieve contents of file " + "%s (%s)", filename, strerror(errno)); + } + + g_free(options->priv->filepath); + options->priv->filepath = g_strdup(filename); +} diff --git a/gnoemoe/gm-options.h b/gnoemoe/gm-options.h new file mode 100644 index 0000000..14c9fbc --- /dev/null +++ b/gnoemoe/gm-options.h @@ -0,0 +1,69 @@ +#ifndef __GM_OPTIONS_H__ +#define __GM_OPTIONS_H__ + +#include <glib.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_OPTIONS (gm_options_get_type()) +#define GM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_OPTIONS, GmOptions)) +#define GM_OPTIONS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_OPTIONS, GmOptions const)) +#define GM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_OPTIONS, GmOptionsClass)) +#define GM_IS_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_OPTIONS)) +#define GM_IS_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_OPTIONS)) +#define GM_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_OPTIONS, GmOptionsClass)) + +/* Private structure type */ +typedef struct _GmOptionsPrivate GmOptionsPrivate; + +/* + * Main object structure + */ +typedef struct _GmOptions GmOptions; + +struct _GmOptions { + GObject object; + + /*< private > */ + GmOptionsPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmOptionsClass GmOptionsClass; + +struct _GmOptionsClass { + GObjectClass parent_class; + + /* Signals */ + void (* option_changed) (GmOptions *options, const gchar *key); +}; + +GType gm_options_get_type(void) G_GNUC_CONST; +GmOptions *gm_options_new(void); + +GmOptions *gm_options_dup(GmOptions *source); +void gm_options_add(GmOptions *options, const gchar *key, const gchar *value); +void gm_options_set(GmOptions *options, const gchar *key, const gchar *value); +const gchar *gm_options_get(GmOptions *options, const gchar *key); +void gm_options_set_int(GmOptions *options, const gchar *key, int value); +int gm_options_get_int(GmOptions *options, const gchar *key); + +void gm_options_save(GmOptions *options); +void gm_options_save_as(GmOptions *options, const gchar *filename); +void gm_options_load(GmOptions *options, const gchar *filename); + +G_END_DECLS + +#endif /* __GM_OPTIONS_H__ */ diff --git a/gnoemoe/gm-pixbuf.c b/gnoemoe/gm-pixbuf.c new file mode 100644 index 0000000..1b0d2be --- /dev/null +++ b/gnoemoe/gm-pixbuf.c @@ -0,0 +1,182 @@ +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <gtk/gtk.h> +#include <glib.h> +#include <string.h> +#include <strings.h> + +#include "gm-debug.h" +#include "gm-pixbuf.h" + +static GList *gm_pixbuf_directories = NULL; +static GList *gm_pixbufs = NULL; + +GdkPixbuf *gm_pixbuf_create(const gchar * filename, int width, int height); + +void +gm_pixbuf_add_directory(const gchar *directory) { + gm_pixbuf_directories = + g_list_prepend(gm_pixbuf_directories, g_strdup(directory)); +} + +void +gm_pixbuf_init() { + gm_pixbuf_add_directory(PACKAGE_DATA_DIR "/" PACKAGE "/pixmaps"); +} + +void +gm_pixbuf_fini() { + GList *l; + GmPixbufInfo *i; + + for (l = gm_pixbuf_directories; l; l = l->next) { + g_free(l->data); + } + + g_list_free(gm_pixbuf_directories); + + for (l = gm_pixbufs; l; l = l->next) { + i = (GmPixbufInfo *)(l->data); + g_free(i->name); + g_object_unref(i->pixbuf); + g_free(i); + } + + g_list_free(gm_pixbufs); +} + +gchar * +gm_pixbuf_find(const gchar *filename) { + GList *elem; + + if (g_file_test(filename, G_FILE_TEST_EXISTS)) { + return g_strdup(filename); + } + + for (elem = gm_pixbuf_directories; elem; elem = elem->next) { + gchar *pathname = + g_strdup_printf("%s%s%s", (gchar *) elem->data, + G_DIR_SEPARATOR_S, filename); + + if (g_file_test(pathname, G_FILE_TEST_EXISTS)) { + return pathname; + } + + g_free(pathname); + } + + return NULL; +} + +GdkPixbuf * +gm_pixbuf_create(const gchar * filename, int width, int height) { + gchar *pathname = NULL, *ext; + GdkPixbuf *pixbuf = NULL; + GError *error = NULL; + + if (!filename || strlen(filename) == 0) { + return NULL; + } + + pathname = gm_pixbuf_find(filename); + + if (!pathname) { + gm_debug_msg(DEBUG_DEFAULT, "gm_pixbuf_create: couldn't find pixbuf file: %s", filename); + return NULL; + } + + ext = rindex(pathname, '.'); + + if (width < 1 || height < 1) { + pixbuf = gdk_pixbuf_new_from_file(pathname, &error); + } else { + pixbuf = gdk_pixbuf_new_from_file_at_size(pathname, width, height, &error); + } + + if (!pixbuf) { + gm_debug_msg(DEBUG_DEFAULT, "gm_pixbuf_create: failed to load pixbuf from file: %s: %s\n", + pathname, error->message); + g_error_free(error); + error = NULL; + } + + g_free(pathname); + return pixbuf; +} + +GdkPixbuf * +gm_pixbuf_get_at_size(const gchar *filename, int width, int height) { + GdkPixbuf *pix; + GList *elem; + GmPixbufInfo *i; + + for (elem = gm_pixbufs; elem; elem = elem->next) { + i = (GmPixbufInfo *) (elem->data); + + if (strcmp(i->name, filename) == 0 && + i->width == width && i->height == height) { + return i->pixbuf; + } + } + + // Not found, so create it + pix = gm_pixbuf_create(filename, width, height); + + if (pix) { + i = g_new(GmPixbufInfo, 1); + i->name = g_strdup(filename); + i->width = width; + i->height = height; + i->pixbuf = pix; + gm_pixbufs = g_list_append(gm_pixbufs, i); + return pix; + } else { + return NULL; + } +} + +// Returns pixbuf if it is already loaded in pixmaps +GdkPixbuf * +gm_pixbuf_get(const gchar *filename) { + return gm_pixbuf_get_at_size(filename, -1, -1); +} + +void gm_pixbuf_set_alpha(GdkPixbuf **pixs, guchar alpha) { + int width, height, n_channels, rowstride, y, x; + GdkPixbuf *pix; + guchar *pixels, *p; + + if (!gdk_pixbuf_get_has_alpha(*pixs)) { + pix = gdk_pixbuf_add_alpha(*pixs, FALSE, 0, 0, 0); + gdk_pixbuf_unref(*pixs); + } else { + pix = *pixs; + } + + n_channels = gdk_pixbuf_get_n_channels(pix); + + if (gdk_pixbuf_get_colorspace(pix) != GDK_COLORSPACE_RGB || + gdk_pixbuf_get_bits_per_sample(pix) != 8 || + !gdk_pixbuf_get_has_alpha(pix) || + n_channels != 4) { + *pixs = pix; + return; + } + + width = gdk_pixbuf_get_width(pix); + height = gdk_pixbuf_get_height(pix); + rowstride = gdk_pixbuf_get_rowstride(pix); + pixels = gdk_pixbuf_get_pixels(pix); + p = pixels; + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + p[3] = alpha; + p = p + n_channels; + } + } + + *pixs = pix; +} diff --git a/gnoemoe/gm-pixbuf.h b/gnoemoe/gm-pixbuf.h new file mode 100644 index 0000000..6aed34f --- /dev/null +++ b/gnoemoe/gm-pixbuf.h @@ -0,0 +1,24 @@ +#ifndef gm_pixbuf_H +#define gm_pixbuf_H 1 + +#include <gtk/gtk.h> +#include <glib.h> + +typedef struct _GmPixbufInfo { + gchar *name; + int width; + int height; + GdkPixbuf *pixbuf; +} GmPixbufInfo; + +void gm_pixbuf_init(); +void gm_pixbuf_fini(); + +void gm_pixbuf_add_directory(const gchar *directory); + +GdkPixbuf *gm_pixbuf_get(const gchar *filename); +GdkPixbuf *gm_pixbuf_get_at_size(const gchar *filename, int width, int height); + +void gm_pixbuf_set_alpha(GdkPixbuf **pixs, guchar alpha); + +#endif diff --git a/gnoemoe/gm-scripts.c b/gnoemoe/gm-scripts.c new file mode 100644 index 0000000..44360a7 --- /dev/null +++ b/gnoemoe/gm-scripts.c @@ -0,0 +1,870 @@ +#include <glib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <gtk/gtk.h> +#include <ruby.h> +#include <libgnomevfs/gnome-vfs-mime-utils.h> +#include <libgnomevfs/gnome-vfs.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "gm-debug.h" +#include "gm-world.h" +#include "gm-app.h" +#include "gm-scripts.h" +#include "gm-support.h" +#include "gm-world.h" + +#define GM_SCRIPTS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_SCRIPTS, GmScriptsPrivate)) +#define RB_CALLBACK(x) (VALUE (*)())(x) +#define GM_SCRIPTS_GLOBAL PACKAGE_DATA_DIR "/" PACKAGE "/scripts" + +static VALUE rb_world_class, rb_client_class, rb_scripts_class; + +VALUE script_world_name(VALUE self); +VALUE gm_scripts_rb_world_new(GmWorld *world); +VALUE gm_scripts_rb_scripts_new(GmScripts *scripts); + +void gm_scripts_rb_world_class_init(); +void gm_scripts_rb_client_class_init(); +void gm_scripts_rb_scripts_class_init(); + +void gm_scripts_unload(GmScripts *scripts); +void gm_scripts_rb_init(); + +struct _GmScriptsPrivate { + GList *files; + GmScript *loading; + GList *monitors; +}; + +/* Signals */ + +enum { + SCRIPT_ADDED, + SCRIPT_CHANGED, + SCRIPT_REMOVED, + RELOAD, + MESSAGE, + ERROR, + RUN, + NUM_SIGNALS +}; + +static guint gm_scripts_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmScripts, gm_scripts, G_TYPE_OBJECT) + + +static void +gm_scripts_finalize(GObject *object) { + GmScripts *scripts = GM_SCRIPTS(object); + GList *monitors; + + gm_scripts_unload(scripts); + + for (monitors = scripts->priv->monitors; monitors; + monitors = monitors->next) { + gnome_vfs_monitor_cancel((GnomeVFSMonitorHandle *)(monitors->data)); + } + + g_list_free(scripts->priv->monitors); + scripts->priv->monitors = NULL; + + G_OBJECT_CLASS(gm_scripts_parent_class)->finalize(object); +} + +static void +gm_scripts_class_init(GmScriptsClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_scripts_finalize; + + gm_scripts_signals[SCRIPT_ADDED] = + g_signal_new("script_added", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, script_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + gm_scripts_signals[SCRIPT_CHANGED] = + g_signal_new("script_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, script_changed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + gm_scripts_signals[SCRIPT_REMOVED] = + g_signal_new("script_removed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, script_removed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, + 1, + G_TYPE_POINTER); + gm_scripts_signals[RELOAD] = + g_signal_new("reload", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, reload), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + gm_scripts_signals[MESSAGE] = + g_signal_new("message", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, message), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + gm_scripts_signals[ERROR] = + g_signal_new("error", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, error), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + gm_scripts_signals[RUN] = + g_signal_new("run", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmScriptsClass, run), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + g_type_class_add_private(object_class, sizeof(GmScriptsPrivate)); + + gm_scripts_rb_init(); +} + +void +gm_scripts_rb_init(GmScriptsClass *klass) { + ruby_init(); + + gm_scripts_rb_world_class_init(); + gm_scripts_rb_client_class_init(); + gm_scripts_rb_scripts_class_init(); +} + +static void +gm_scripts_init(GmScripts *scripts) { + scripts->priv = GM_SCRIPTS_GET_PRIVATE(scripts); + + scripts->priv->monitors = NULL; + scripts->priv->files = NULL; +} + +GmScripts * +gm_scripts_new() { + GmScripts *scripts = GM_SCRIPTS(g_object_new(GM_TYPE_SCRIPTS, NULL)); + + return scripts; +} + +void gm_scripts_monitor_cb (GnomeVFSMonitorHandle *handle, + const gchar *monitor_uri, const gchar *info_uri, + GnomeVFSMonitorEventType event_type, + GmScripts *scripts) { + gchar *filename = gnome_vfs_get_local_path_from_uri(info_uri); + + switch (event_type) { + case GNOME_VFS_MONITOR_EVENT_CHANGED: + gm_scripts_reload_file(scripts, filename); + break; + case GNOME_VFS_MONITOR_EVENT_DELETED: + gm_scripts_remove_file(scripts, filename); + break; + case GNOME_VFS_MONITOR_EVENT_CREATED: + gm_scripts_add_file(scripts, filename); + break; + default: + break; + } + + g_free(filename); +} + +void +gm_script_function_destroy(GmScriptFunction *fi) { + g_free(fi->name); + g_free(fi->fname); + g_free(fi->description); + g_free(fi); +} + +void +gm_script_destroy_functions(GmScript *script) { + GList *functions; + + for (functions = script->functions; functions; + functions = functions->next) { + gm_script_function_destroy((GmScriptFunction *)(functions->data)); + } + + g_list_free(script->functions); + script->functions = NULL; +} + +void +gm_script_destroy(GmScript *script) { + gm_script_destroy_functions(script); + g_free(script->filename); + g_free(script); +} + +void +gm_scripts_unload(GmScripts *scripts) { + GList *files; + + for (files = scripts->priv->files; files; files = files->next) { + gm_script_destroy((GmScript *)(files->data)); + } + + g_list_free(scripts->priv->files); + scripts->priv->files = NULL; +} + +GmScriptFunction * +gm_scripts_find(GmScripts *scripts, gchar *name) { + GList *files; + GList *functions; + GmScriptFunction *func; + GmScript *script; + + for (files = scripts->priv->files; files; files = files->next) { + script = (GmScript *)(files->data); + for (functions = script->functions; functions; + functions = functions->next) { + func = (GmScriptFunction *)(functions->data); + + if (strcasecmp(func->name, name) == 0) { + return func; + } + } + } + + return NULL; +} + +gboolean +gm_scripts_add(GmScripts *scripts, gchar *name, gchar *fname, + gchar *description) { + GmScriptFunction *func; + + if (gm_scripts_find(scripts, name) == NULL) { + func = g_new(GmScriptFunction, 1); + func->script = scripts->priv->loading; + func->name = g_strdup(name); + func->fname = g_strdup(fname);; + func->description = g_strdup(description); + + scripts->priv->loading->functions = + g_list_append(scripts->priv->loading->functions, func); + return TRUE; + } else { + return FALSE; + } +} + +VALUE +gm_scripts_rb_register_functions_wrap(VALUE arg) { + return rb_eval_string("register_functions"); +} + +void +gm_scripts_rb_script_define_world(VALUE *world) { + rb_define_variable("$world", world); +} + +void +gm_scripts_rb_error(GmScripts *scripts) { + int c; + VALUE lasterr; + char *err; + gchar *msg; + VALUE ary; + + if(!NIL_P(ruby_errinfo)) { + lasterr = rb_gv_get("$!"); + err = RSTRING(rb_obj_as_string(lasterr))->ptr; + + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: Error while executing Ruby code: %s", + err); + + msg = g_strdup_printf(_("Error in execution: %s"), err); + g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); + g_free(msg); + + ary = rb_funcall(ruby_errinfo, rb_intern("backtrace"), 0); + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: Ruby backtrace:"); + g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, + _("Ruby backtrace:")); + + for (c = 0; c < RARRAY(ary)->len; c++) { + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.Error: \tfrom %s", + RSTRING(RARRAY(ary)->ptr[c])->ptr); + + msg = g_strdup_printf(_("\tfrom %s"), + RSTRING(RARRAY(ary)->ptr[c])->ptr); + g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); + g_free(msg); + } + } +} + +int +gm_scripts_rb_do(GmScripts *scripts, VALUE (*body)(), VALUE arg) { + int status; + + rb_protect(body, arg, &status); + + if (status != 0) { + gm_scripts_rb_error(scripts); + ruby_cleanup(status); + return 0; + } + + return 1; +} + +VALUE +gm_scripts_run_function(GmScriptInfo *arg) { + VALUE ret; + gchar *argstr; + gchar *funcAll; + + if (arg->argstr) { + argstr = gm_str_escape(arg->argstr); + funcAll = g_strconcat(arg->name, "(\"", argstr, "\")", NULL); + g_free(argstr); + } else { + funcAll = g_strconcat(arg->name, "()", NULL); + } + + ret = rb_eval_string(funcAll); + g_free(funcAll); + + return ret; +} + +gboolean +gm_scripts_run(GmScripts *scripts, GmWorld *world, gchar *name, gchar *argstr) { + VALUE rbWorld, rbClient; + gchar *msg; + GmScriptInfo *info; + GmScriptFunction *f = gm_scripts_find(scripts, name); + + if (!f) { + return FALSE; + } + + info = g_new0(GmScriptInfo, 1); + info->name = g_strdup(f->fname); + + if (argstr) { + info->argstr = g_strdup(argstr); + msg = g_strdup_printf(_("Run script '%s' from '%s' (%s)"), f->fname, + f->script->filename, argstr); + } else { + info->argstr = NULL; + msg = g_strdup_printf(_("Run script '%s' from '%s' ()"), f->fname, + f->script->filename); + } + + g_signal_emit(scripts, gm_scripts_signals[RUN], 0, msg); + g_free(msg); + + gm_scripts_rb_do(scripts, RB_CALLBACK(rb_load_file), + (VALUE)(f->script->filename)); + ruby_exec(); + + rbWorld = gm_scripts_rb_world_new(world); + rb_define_variable("$world", &rbWorld); + + rbClient = rb_class_new_instance(0, NULL, rb_client_class); + rb_define_variable("$client", &rbClient); + + gm_scripts_rb_do(scripts, RB_CALLBACK(gm_scripts_run_function), + (VALUE)info); + + g_free(info->name); + g_free(info->argstr); + g_free(info); + + return TRUE; +} + +VALUE +gm_scripts_rb_register_func_old(int argc, VALUE *argv) { + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RegisterFunc: This is the deprecated way to " + "register functions is does no longer work. Use $scripts.register " + "instead."); + + return Qfalse; +} + +void +gm_scripts_register_functions(GmScripts *scripts) { + gchar *msg; + VALUE rbScripts; + + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RegisterFunctions: registering functions in %s", + scripts->priv->loading->filename); + + msg = g_strdup_printf(_("Registering functions from '%s'"), + scripts->priv->loading->filename); + g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); + g_free(msg); + + // Okay, I'm desperate... define an empty register_functions method so + // that the previous when gets cleared ... :( + rb_eval_string("def register_functions() end"); + + if (!gm_scripts_rb_do(scripts, RB_CALLBACK(rb_load_file), + (VALUE) scripts->priv->loading->filename)) { + return; + } + + ruby_exec(); + + rbScripts = gm_scripts_rb_scripts_new(scripts); + rb_define_variable("$scripts", &rbScripts); + rb_define_global_function("register_func", &gm_scripts_rb_register_func_old, + -1); + + gm_scripts_rb_do(scripts, + RB_CALLBACK(gm_scripts_rb_register_functions_wrap), 0); +} + +void +gm_scripts_remove_file(GmScripts *scripts, const gchar *uri) { + GList *f, *l; + GmScript *script; + + l = g_list_copy(scripts->priv->files); + + for (f = l; f; f = f->next) { + script = (GmScript *)(f->data); + + if (strcmp(script->filename, uri) == 0) { + scripts->priv->files = g_list_remove(scripts->priv->files, script); + + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RemoveFile: Removing scripts from `%s'", + script->filename); + g_signal_emit(scripts, gm_scripts_signals[SCRIPT_REMOVED], 0, + script); + + gm_script_destroy(script); + } + } + + g_list_free(l); +} + +gboolean +gm_scripts_add_file(GmScripts *scripts, const gchar *uri) { + GList *f; + GmScript *script; + gchar *msg; + + for (f = scripts->priv->files; f; f = f->next) { + script = (GmScript *)(f->data); + + if (strcmp(script->filename, uri) == 0) { + msg = g_strdup_printf(_("File `%s' already loaded"), uri); + + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.AddFile: %s", msg); + g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); + + g_free(msg); + return FALSE; + } + } + + msg = g_strdup_printf(_("File `%s' added"), uri); + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.AddFile: %s", msg); + g_free(msg); + + script = g_new0(GmScript, 1); + script->filename = g_strdup(uri); + + if (strncmp(uri, GM_SCRIPTS_GLOBAL, + strlen(GM_SCRIPTS_GLOBAL)) == 0) { + script->type = GM_SCRIPT_TYPE_SHARE; + } else { + script->type = GM_SCRIPT_TYPE_USER; + } + + scripts->priv->files = g_list_append(scripts->priv->files, script); + scripts->priv->loading = script; + + gm_scripts_register_functions(scripts); + g_signal_emit(scripts, gm_scripts_signals[SCRIPT_ADDED], 0, script); + + return TRUE; +} + +void +gm_scripts_reload_file(GmScripts *scripts, const gchar *uri) { + GList *files; + GmScript *script; + + for (files = scripts->priv->files; files; files = files->next) { + script = (GmScript *)(files->data); + + if (strcmp(script->filename, uri) == 0) { + // Remove all functions and reregister the file + scripts->priv->loading = script; + + gm_script_destroy_functions(script); + gm_scripts_register_functions(scripts); + + g_signal_emit(scripts, gm_scripts_signals[SCRIPT_CHANGED], 0, + script); + return; + } + } + + // If the script does not yet exist, add it + gm_scripts_add_file(scripts, uri); +} + +void +gm_scripts_load_dir(GmScripts *scripts, gchar *dirname) { + gchar *filename; + gchar *file; + GDir *d; + GnomeVFSMonitorHandle *handle; + + if (g_file_test(dirname, G_FILE_TEST_EXISTS) && + g_file_test(dirname, G_FILE_TEST_IS_DIR)) { + + if ((d = g_dir_open(dirname, 0, NULL))) { + while ((file = (gchar *)g_dir_read_name(d))) { + filename = g_strconcat(dirname, "/", file, NULL); + gm_scripts_add_file(scripts, filename); + g_free(filename); + } + } + + gnome_vfs_monitor_add(&handle, dirname, GNOME_VFS_MONITOR_DIRECTORY, + (GnomeVFSMonitorCallback)gm_scripts_monitor_cb, scripts); + scripts->priv->monitors = g_list_append(scripts->priv->monitors, + handle); + } +} + +void +gm_scripts_load(GmScripts *scripts) { + gchar *path; + + if (scripts->priv->files) { + g_signal_emit(scripts, gm_scripts_signals[RELOAD], 0); + gm_scripts_unload(scripts); + } + + path = g_strconcat(gm_app_path(gm_app_instance()), "/scripts", NULL); + + gm_scripts_load_dir(scripts, path); + gm_scripts_load_dir(scripts, GM_SCRIPTS_GLOBAL); +} + +GList * +gm_scripts_scripts(GmScripts *scripts) { + return scripts->priv->files; +} +// Ruby class functions + +VALUE +gm_scripts_rb_world_new(GmWorld *world) { + VALUE tdata = Data_Wrap_Struct(rb_world_class, 0, 0, world); + + return tdata; +} + +VALUE +gm_scripts_rb_scripts_new(GmScripts *scripts) { + VALUE tdata = Data_Wrap_Struct(rb_scripts_class, 0, 0, scripts); + + return tdata; +} + +// Ruby world class functions +static VALUE +gm_scripts_rb_world_name(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + return rb_str_new2(gm_options_get(gm_world_options(world), "name")); +} + +static VALUE +gm_scripts_rb_world_host(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + return rb_str_new2(gm_options_get(gm_world_options(world), "host")); +} + +static VALUE +gm_scripts_rb_world_port(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + return rb_str_new2(gm_options_get(gm_world_options(world), "port")); +} + +static VALUE +gm_scripts_rb_world_writeln(VALUE self, VALUE str) { + GmWorld *world; + gchar *strVal; + + Data_Get_Struct(self, GmWorld, world); + strVal = rb_string_value_cstr(&str); + + gm_world_writeln(world, strVal); + + return Qnil; +} + +static VALUE +gm_scripts_rb_world_sendln(VALUE self, VALUE str) { + GmWorld *world; + gchar *strVal; + + Data_Get_Struct(self, GmWorld, world); + strVal = rb_string_value_cstr(&str); + + gm_world_sendln(world, strVal); + + return Qnil; +} + +static VALUE +gm_scripts_rb_world_input(VALUE self, VALUE str) { + GmWorld *world; + gchar *strVal; + + Data_Get_Struct(self, GmWorld, world); + strVal = rb_string_value_cstr(&str); + + gm_world_process_input(world, strVal); + + return Qnil; +} + +static VALUE +gm_scripts_rb_world_loaded(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + if (gm_world_loaded(world)) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE +gm_scripts_rb_world_connected(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + if (gm_world_state(world) == GM_NET_STATE_CONNECTED) { + return Qtrue; + } else { + return Qfalse; + } +} + +static VALUE +gm_scripts_rb_world_quit(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + gm_world_unload(world); + return Qnil; +} + +static VALUE +gm_scripts_rb_world_connect(int argc, VALUE *argv, VALUE self) { + GmWorld *world; + const gchar *strHost, *strPort; + + Data_Get_Struct(self, GmWorld, world); + + if (argc == 0) { + strHost = gm_options_get(gm_world_options(world), "host"); + } else { + strHost = rb_string_value_cstr(&(argv[0])); + } + + if (argc == 0 || argc == 1) { + strPort = gm_options_get(gm_world_options(world), "port"); + } else { + strPort = rb_string_value_cstr(&(argv[1])); + } + + gm_world_connect_to(world, (gchar *)strHost, (gchar *)strPort); + + return Qnil; +} + +static VALUE +gm_scripts_rb_world_disconnect(VALUE self) { + GmWorld *world; + + Data_Get_Struct(self, GmWorld, world); + + gm_world_disconnect(world); + return Qnil; +} + +// Ruby client class functions + +static VALUE +gm_scripts_rb_client_version(VALUE self) { + return rb_str_new2(VERSION); +} + +static VALUE +gm_scripts_rb_client_worlds(VALUE self) { + GList *world; + VALUE rb_array = rb_ary_new(); + VALUE rb_world; + + for (world = gm_app_worlds(gm_app_instance()); world; world = world->next) { + rb_world = gm_scripts_rb_world_new((GmWorld *)(world->data)); + rb_ary_push(rb_array, rb_world); + } + + return rb_array; +} + +static VALUE +gm_scripts_rb_client_open(VALUE self, VALUE str) { + GmWorld *world; + gchar *strVal; + + strVal = rb_string_value_cstr(&str); + + world = gm_app_world_by_name(gm_app_instance(), strVal); + + if (world == NULL) { + return Qfalse; + } else { + gm_world_load(world); + return Qtrue; + } +} + +// Ruby scripts class functions +VALUE +gm_scripts_rb_scripts_register(int argc, VALUE *argv, VALUE self) { + char *name, *fname = NULL, *description = NULL; + gchar *msg; + GmScripts *scripts; + + Data_Get_Struct(self, GmScripts, scripts); + + if (argc > 1) { + name = rb_string_value_cstr(&argv[0]); + description = rb_string_value_cstr(&argv[1]); + + if (argc == 2) { + fname = name; + } else { + fname = rb_string_value_cstr(&argv[2]); + } + + if (gm_scripts_add(scripts, name, fname, description)) { + msg = g_strdup_printf(_("Register function '%s' from '%s'"), name, + scripts->priv->loading->filename); + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RbScriptsRegister: Adding script function " + "%s from %s", name, scripts->priv->loading->filename); + g_signal_emit(scripts, gm_scripts_signals[MESSAGE], 0, msg); + g_free(msg); + return Qtrue; + } else { + msg = g_strdup_printf(_("Script '%s' is already defined"), name); + gm_debug_msg(DEBUG_DEFAULT, "GmScripts.RbScriptsRegister: Script function %s " + "already defined!", name); + g_signal_emit(scripts, gm_scripts_signals[ERROR], 0, msg); + g_free(msg); + return Qfalse; + } + } else { + return Qfalse; + } +} + +// Ruby class initializations + +void +gm_scripts_rb_world_class_init() { + rb_world_class = rb_define_class("World", rb_cObject); + + rb_define_method(rb_world_class, "name", gm_scripts_rb_world_name, 0); + rb_define_method(rb_world_class, "host", gm_scripts_rb_world_host, 0); + rb_define_method(rb_world_class, "port", gm_scripts_rb_world_port, 0); + + rb_define_method(rb_world_class, "writeln", gm_scripts_rb_world_writeln, 1); + rb_define_method(rb_world_class, "println", gm_scripts_rb_world_writeln, 1); + rb_define_method(rb_world_class, "sendln", gm_scripts_rb_world_sendln, 1); + rb_define_method(rb_world_class, "input", gm_scripts_rb_world_input, 1); + rb_define_method(rb_world_class, "quit", gm_scripts_rb_world_quit, 0); + rb_define_method(rb_world_class, "connect", + gm_scripts_rb_world_connect, -1); + rb_define_method(rb_world_class, "disconnect", + gm_scripts_rb_world_disconnect, 0); + rb_define_method(rb_world_class, "loaded?", gm_scripts_rb_world_loaded, 0); + rb_define_method(rb_world_class, "connected?", + gm_scripts_rb_world_connected, 0); +} + +void +gm_scripts_rb_client_class_init() { + rb_client_class = rb_define_class("Client", rb_cObject); + + rb_define_method(rb_client_class, "version", + gm_scripts_rb_client_version, 0); + rb_define_method(rb_client_class, "worlds", + gm_scripts_rb_client_worlds, 0); + rb_define_method(rb_client_class, "open", gm_scripts_rb_client_open, 1); +} + +void +gm_scripts_rb_scripts_class_init() { + rb_scripts_class = rb_define_class("Scripts", rb_cObject); + + rb_define_method(rb_scripts_class, "register", + gm_scripts_rb_scripts_register, -1); +} diff --git a/gnoemoe/gm-scripts.h b/gnoemoe/gm-scripts.h new file mode 100644 index 0000000..1f897ca --- /dev/null +++ b/gnoemoe/gm-scripts.h @@ -0,0 +1,109 @@ +#ifndef __GM_SCRIPTS_H__ +#define __GM_SCRIPTS_H__ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#ifdef HAVE_RUBY +#include <glib.h> +#include <gtk/gtk.h> + +#include "gm-world.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_SCRIPTS (gm_scripts_get_type()) +#define GM_SCRIPTS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_SCRIPTS, GmScripts)) +#define GM_SCRIPTS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_SCRIPTS, GmScripts const)) +#define GM_SCRIPTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_SCRIPTS, GmScriptsClass)) +#define GM_IS_SCRIPTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_SCRIPTS)) +#define GM_IS_SCRIPTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_SCRIPTS)) +#define GM_SCRIPTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_SCRIPTS, GmScriptsClass)) + +/* Private structure type */ +typedef struct _GmScriptsPrivate GmScriptsPrivate; + +/* + * Main object structure + */ +typedef struct _GmScripts GmScripts; + +struct _GmScripts { + GObject object; + + /*< private > */ + GmScriptsPrivate *priv; +}; + +typedef enum _GmScriptType { + GM_SCRIPT_TYPE_USER, + GM_SCRIPT_TYPE_SHARE +} GmScriptType; + +typedef struct _GmScript { + gchar *filename; + GmScriptType type; + GList *functions; +} GmScript; + +typedef struct _GmScriptFunction { + GmScript *script; + gchar *name; + gchar *fname; + gchar *description; +} GmScriptFunction; + +typedef struct _GmScriptInfo { + gchar *name; + gchar *argstr; +} GmScriptInfo; + +/* + * Class definition + */ +typedef struct _GmScriptsClass GmScriptsClass; + +struct _GmScriptsClass { + GObjectClass parent_class; + + /* Signals */ + void (* script_added) (GmScripts *scripts, GmScript *script); + void (* script_changed) (GmScripts *scripts, GmScript *script); + void (* script_removed) (GmScripts *scripts, GmScript *script); + void (* reload) (GmScripts *scripts); + void (* message) (GmScripts *scripts, gchar *message); + void (* error) (GmScripts *scripts, gchar *message); + void (* run) (GmScripts *scripts, gchar *message); +}; + +GType gm_scripts_get_type(void) G_GNUC_CONST; +GmScripts *gm_scripts_new(); + +//void gm_script_init(); +//void gm_script_fini(); + +void gm_scripts_reload_file(GmScripts *scripts, const gchar *uri); +gboolean gm_scripts_add_file(GmScripts *scripts, const gchar *uri); +void gm_scripts_remove_file(GmScripts *scripts, const gchar *uri); + +GmScriptFunction *gm_scripts_find(GmScripts *scripts, gchar *name); +GList *gm_scripts_scripts(GmScripts *scripts); + +gboolean gm_scripts_run(GmScripts *scripts, GmWorld *world, gchar *name, + gchar *argstr); +void gm_scripts_load(GmScripts *scripts); + +G_END_DECLS + +#endif /* HAVE_RUBY */ +#endif /* __GM_SCRIPTS_H__ */ diff --git a/gnoemoe/gm-string.c b/gnoemoe/gm-string.c new file mode 100644 index 0000000..0c46e40 --- /dev/null +++ b/gnoemoe/gm-string.c @@ -0,0 +1,203 @@ +#include <string.h> +#include <stdlib.h> +#include <glib.h> + +#include "gm-string.h" +#include "gm-debug.h" + +/* +void +stringlist_add(stringlist * strl, char *data) { + stringlist_item *newStringlistItem = + (stringlist_item *) malloc(sizeof(stringlist_item)); + newStringlistItem->data = strdup(data); + + if (strl->firstItem == NULL) { + strl->firstItem = newStringlistItem; + } else { + newStringlistItem->prev = strl->lastItem; + + if (strl->lastItem != NULL) + strl->lastItem->next = newStringlistItem; + } + + strl->lastItem = newStringlistItem; + newStringlistItem->next = NULL; + + strl->count++; +} + +void +stringlist_remove(stringlist * strl, stringlist_item * removed) { + if (removed == strl->firstItem) { + strl->firstItem = removed->next; + } else { + removed->prev->next = removed->next; + } + + if (removed == strl->lastItem) { + strl->lastItem = removed->prev; + } else { + removed->next->prev = removed->next; + } + + free(removed->data); + free(removed); + strl->count--; +} + +stringlist * +stringlist_create(char *argstr, char *delim) { + stringlist *newStringlist = (stringlist *) malloc(sizeof(stringlist)); + char *data, *work, *working, *strPos; + + newStringlist->firstItem = NULL; + newStringlist->lastItem = NULL; + newStringlist->count = 0; + + if (delim == NULL) { + stringlist_add(newStringlist, argstr); + return newStringlist; + } + + working = strdup(argstr); + working = mystring_cat(working, delim); + work = working; + + while (strlen(work) != 0) { + + if ((strPos = strstr(work, delim)) != NULL && strPos > work) { + data = NULL; + data = mystring_catn(data, work, (strPos - work)); + stringlist_add(newStringlist, data); + free(data); + } + work = strPos + strlen(delim); + + } + + free(working); + return newStringlist; +} + +void +stringlist_destroy(stringlist * strl) { + stringlist_item *curItem = strl->firstItem; + + while (curItem != NULL) { + stringlist_remove(strl, curItem); + curItem = strl->firstItem; + } + + free(strl); +} + +char * +stringlist_glue_it(stringlist * strl, char *glue) { + char *result = NULL; + stringlist_item *curItem = strl->firstItem; + + while (curItem != NULL) { + if (curItem != strl->firstItem) { + result = mystring_cat(result, glue); + result = mystring_cat(result, curItem->data); + } else { + result = strdup(curItem->data); + } + } + + return result; +} +*/ +gboolean +gm_string_to_int(const gchar *str, int *result) { + *result = 0; + + if (str == NULL || *str == '\0') { + return FALSE; + } + + *result = atoi(str); + return TRUE; +} + +gchar * +gm_string_catn(gchar *str, gchar *add, guint n) { + guint relen; + gchar *newstr; + + if (str == NULL) { + relen = 0; + } else { + relen = strlen(str); + } + + if ((newstr = (gchar *) realloc(str, (relen + n + 1) * sizeof(gchar))) == NULL) { + gm_debug_msg(DEBUG_DEFAULT, "mystring_catn: REALLOC FAILED!"); + return str; + } else { + if (relen == 0) { + newstr[0] = '\0'; + } + + strncat(newstr, add, n); + return newstr; + } +} + +gchar * +gm_string_cat(gchar *str, gchar *add) { + return gm_string_catn(str, add, strlen(add)); +} + +gchar * +gm_string_trim(const gchar *str) { + gchar *newstr = NULL; + const gchar *r, *l; + + if (str == NULL) { + return NULL; + } + + r = str + strlen(str); + l = str; + + while (*l == ' ' || *r == ' ') { + if (l == r) { + break; + } + + if (*l == ' ') { + l++; + } + + if (l == r) { + break; + } + + if (*r == ' ') { + r--; + } + } + + if (l == r) { + return g_strdup(""); + } else { + newstr = g_strndup(l, r - l); + return newstr; + } +} + +void +gm_string_remove_char(char *str, char rem) { + int i, j = 0; + + for (i = 0; str[i] != '\0'; i++) { + if (str[i] != rem) { + str[j] = str[i]; + j++; + } + } + + str[j] = '\0'; +} diff --git a/gnoemoe/gm-string.h b/gnoemoe/gm-string.h new file mode 100644 index 0000000..9bd2b03 --- /dev/null +++ b/gnoemoe/gm-string.h @@ -0,0 +1,36 @@ +#ifndef MY_STRING_H +#define MY_STRING_H 1 + +#include <string.h> +#include <stdlib.h> + +/*typedef struct _stringlist_item stringlist_item; +struct _stringlist_item { + char *data; + + stringlist_item *next; + stringlist_item *prev; +}; + +typedef struct _stringlist stringlist; +struct _stringlist { + stringlist_item *firstItem; + stringlist_item *lastItem; + + unsigned int count; +}; + +void stringlist_add(stringlist * strl, char *data); +void stringlist_remove(stringlist * strl, stringlist_item * removed); +stringlist *stringlist_create(char *argstr, char *delim); +void stringlist_destroy(stringlist * strl); +char *stringlist_glue_it(stringlist * strl, char *glue); +*/ + +int gm_string_to_int(const gchar *str, int *result); +char *gm_string_catn(char *str, char *add, unsigned int n); +char *gm_string_cat(char *str, char *add); +char *gm_string_trim(const gchar *str); +void gm_string_remove_char(char *str, char rem); + +#endif diff --git a/gnoemoe/gm-support.c b/gnoemoe/gm-support.c new file mode 100644 index 0000000..1a7f3b1 --- /dev/null +++ b/gnoemoe/gm-support.c @@ -0,0 +1,647 @@ +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <locale.h> + +#include <gtk/gtk.h> +#include <regex.h> +#include <libgnome/gnome-url.h> +#include <libgnomevfs/gnome-vfs.h> +#include <errno.h> + +#include "gm-support.h" +#include "gm-debug.h" +#include "gm-pixbuf.h" +//#include "if_main.h" + +#define URL_REGEXP "(((mailto|news|telnet|nttp|file|http|sftp|ftp|https|dav|callto)://)|(www|ftp)[-A-Za-z0-9]*\\.)[-A-Za-z0-9\\.@:]+[^]''\\.}>\\) ,\\/\\\"\\!]+(:[0-9]*)?(/|/[-A-Za-z0-9_\\$\\.\\+\\!\\*\\(\\),;:@&=\\?/~\\#\\%]*[^]'\\.}>\\) ,\\\"\\!])?" +static regex_t url_regexp; + +gchar * +gm_fix_decimal_point(gchar *line, int len) { + int i; + struct lconv *l = localeconv(); + + if (l->decimal_point[0] == '.') { + return line; + } + + for (i = 0; i < len; i++) { + if (line[i] == '.') { + line[i] = l->decimal_point[0]; + } + } + + return line; +} + +gchar * +gm_fix_decimal_point_rev(gchar *line, int len) { + int i; + struct lconv *l = localeconv(); + + for (i = 0; i < len; i++) { + if (line[i] == l->decimal_point[0]) { + line[i] = '.'; + } + } + + return line; +} + +gchar * +gm_ansi_strip(gchar * s) { + int i, j = 0; + + for (i = 0; s[i] != '\0'; i++) { + // Escape sequence, advance to character after 'm' + if (s[i] == '\x1B') { + while (s[i] != '\0' && s[i] != 'm') { + i++; + } + } else if (s[i] != '\x07') { + s[j] = s[i]; + j++; + } + } + + s[j] = '\0'; + return s; +} + +int +garray_length(gchar **s) { + int i = 0; + + while (s[i] != NULL) { + i++; + } + + return i; +} + +void g_list_free_simple(GList *s) { + GList *tmp; + + for (tmp = s; tmp; tmp = tmp->next) { + g_free(tmp->data); + } + + g_list_free(s); +} + +gchar * +g_list_find_simple(GList *s, gchar *f) { + GList *tmp; + + for (tmp = s; tmp; tmp = tmp->next) { + if (strcmp(tmp->data, f) == 0) { + return tmp->data; + } + } + + return NULL; +} + +void +gm_dialog(gchar * message, GtkMessageType messagebox_type, + GtkWindow * parent) { + GtkWidget *dlg; + + if (parent == NULL) { + //parent = GTK_WINDOW(if_main_get_widget("wndMain")); + } + + dlg = + gtk_message_dialog_new(parent, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + messagebox_type, GTK_BUTTONS_OK, message, NULL); + gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_destroy(dlg); +} + +void +gm_error_dialog(gchar * message, GtkWindow * parent) { + gm_dialog(message, GTK_MESSAGE_ERROR, parent); +} + +void +gm_warning_dialog(gchar * message, GtkWindow * parent) { + gm_dialog(message, GTK_MESSAGE_WARNING, parent); +} + +void +gm_info_dialog(gchar * message, GtkWindow * parent) { + gm_dialog(message, GTK_MESSAGE_INFO, parent); +} + +void +gm_question_dialog(gchar * message, GtkWindow * parent) { + gm_dialog(message, GTK_MESSAGE_QUESTION, parent); +} + +void +gm_do_events() { + while (gtk_events_pending()) { + gtk_main_iteration(); + } +} + +gchar * +gm_str_escape(gchar * line) { + gchar *newLine; + int i, j = 0; + + if (strlen(line) == 0) { + return g_strdup(""); + } + + if (strstr(line, "\"") || strstr(line, "\\")) { + newLine = g_new(gchar, (strlen(line) * 2) + 1); + + for (i = 0; i < (int)strlen(line); i++) { + if (line[i] == '"' || line[i] == '\\') { + newLine[j] = '\\'; + j++; + } + newLine[j] = line[i]; + j++; + } + + newLine[j] = '\0'; + return newLine; + } else { + return g_strdup(line); + } +} + +void +gm_directory_remove_all(const gchar * path, gboolean remove_self) { + GDir *cDir; + gchar *name; + gchar *newPath; + + if (g_file_test(path, G_FILE_TEST_IS_DIR)) { + // Iterate through the files and do the right thingie + if ((cDir = g_dir_open(path, 0, NULL))) { + while ((name = (gchar *) (g_dir_read_name(cDir))) != NULL) { + newPath = g_strconcat(path, "/", name, NULL); + gm_directory_remove_all(newPath, TRUE); + g_free(newPath); + } + g_dir_close(cDir); + } + } + + if (remove_self) { + // Its a file, or just empty! MUST...BE...REMOVEEEED! + remove(path); + } +} + +gint +gm_url_regex_match(const gchar *msg, int len, GArray *start, GArray *end) { + static gboolean inited = FALSE; + regmatch_t matches[1]; + gint ret = 0, num_matches = 0, offset = 0; + gchar *tmp; + gint s; + + if (!inited) { + memset(&url_regexp, 0, sizeof (regex_t)); + regcomp(&url_regexp, URL_REGEXP, REG_EXTENDED); + inited = TRUE; + } + + tmp = g_strndup(msg, len); + + while (!ret) { + ret = regexec(&url_regexp, (char *)(tmp + offset), 1, matches, 0); + + if (ret == 0) { + if (matches[0].rm_so > matches[0].rm_eo) { + break; + } + + num_matches++; + + s = matches[0].rm_so + offset; + offset = matches[0].rm_eo + offset; + + g_array_append_val(start, s); + g_array_append_val(end, offset); + } + } + + g_free(tmp); + + return num_matches; +} + +void +gm_open_url (const gchar *url) { + if (!url || strlen (url) == 0) { + return; + } + + /* gnome_url_show doesn't work when there's no protocol, so we might + * need to add one. + */ + if (strstr (url, "://") == NULL) { + gchar *tmp; + + tmp = g_strconcat ("http://", url, NULL); + gnome_url_show(tmp, NULL); + g_free (tmp); + return; + } + + gnome_url_show (url, NULL); +} + +void +gm_fetch_handle_free(GmFetchHandle *g) { + GList *tmp; + + g_free(g->cur_file_name); + + for (tmp = g->source_uri; tmp; tmp = tmp->next) { + gnome_vfs_uri_unref((GnomeVFSURI*)(tmp->data)); + } + g_list_free(g->source_uri); + + + for (tmp = g->dest_uri; tmp; tmp = tmp->next) { + gnome_vfs_uri_unref((GnomeVFSURI*)(tmp->data)); + } + g_list_free(g->dest_uri); + + g_free(g); +} + +GmFetchHandle * +gm_fetch_handle_create(GFunc cb, gpointer user_data) { + GmFetchHandle *g = g_new0(GmFetchHandle, 1); + + g->cb = cb; + g->user_data = user_data; + g->prev_status = GNOME_VFS_XFER_PROGRESS_STATUS_OK; + g->cur_phase = -1; + g->prev_phase = -1; + g->cur_file = -1; + g->prev_file = -1; + g->source_uri = NULL; + g->dest_uri = NULL; + g->cur_file_name = NULL; + g->files_total = 0; + g->done = FALSE; + g->aborted = FALSE; + + return g; +} + +gint +gm_fetch_progress(GnomeVFSAsyncHandle *handle, + GnomeVFSXferProgressInfo *info, + GmFetchHandle *g) { + gchar *name; + const gchar *err; + + g->cur_phase = info->phase; + g->cur_file = info->file_index; + g->files_total = info->files_total; + g->bytes_total = info->bytes_total; + g->file_size = info->file_size; + g->bytes_copied = info->bytes_copied; + g->total_bytes_copied = info->total_bytes_copied; + g->status = info->status; + + if (g->aborted) { + g->cb(g, g->user_data); + gm_fetch_handle_free(g); + return FALSE; + } + + if (info->target_name != NULL) { + if (g->cur_file_name && strcmp(g->cur_file_name, info->target_name) != 0) { + g->cur_phase = GNOME_VFS_XFER_PHASE_FILECOMPLETED; + g->cb(g, g->user_data); + g->cur_phase = info->phase; + + g_free(g->cur_file_name); + g->cur_file_name = NULL; + } + + if (!g->cur_file_name) { + g->cur_file_name = g_strdup(info->target_name); + } + } + + if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE) { + name = gnome_vfs_get_local_path_from_uri(info->target_name); + gm_debug_msg(DEBUG_DEFAULT, "gnoemoe_fetch_progress: asking for overwriting %s: yes", name); + + g->prev_status = GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE; + return GNOME_VFS_XFER_OVERWRITE_ACTION_REPLACE; + } else if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR) { + name = gnome_vfs_get_local_path_from_uri(info->target_name); + err = gnome_vfs_result_to_string(info->vfs_status); + + gdk_threads_enter(); + gm_debug_msg(DEBUG_DEFAULT, "gnoemoe_fetch_progress: error for %s: %s", name, err); + g->cb(g, g->user_data); + gdk_threads_leave(); + + g_free(g->cur_file_name); + g->cur_file_name = NULL; + + g->prev_status = GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR; + g_free(name); + + return GNOME_VFS_XFER_ERROR_ACTION_SKIP; + } + + if (info->phase == GNOME_VFS_XFER_PHASE_COMPLETED) { + if (g->cur_file_name != NULL) { + g->cur_phase = GNOME_VFS_XFER_PHASE_FILECOMPLETED; + g->cb(g, g->user_data); + g->cur_phase = info->phase; + } + + g->done = TRUE; + g->cb(g, g->user_data); + gm_fetch_handle_free(g); + return TRUE; + } + + g->prev_status = info->status; + + return TRUE; +} + +gint +gm_fetch_interact(GnomeVFSXferProgressInfo *info, gpointer user_data) { + return 1; +} + +GmFetchHandle * +gm_fetch(const GList *source, const GList *dest, + GFunc cb, gpointer user_data) { + GmFetchHandle *g = gm_fetch_handle_create(cb, user_data); + gchar *uri; + + for (; source; source = source->next) { + uri = (gchar *)(source->data); + g->source_uri = g_list_append(g->source_uri, gnome_vfs_uri_new(uri)); + } + + for (; dest; dest = dest->next) { + uri = (gchar *)(dest->data); + g->dest_uri = g_list_append(g->dest_uri, gnome_vfs_uri_new(uri)); + } + + gnome_vfs_async_xfer(&(g->handle), g->source_uri, g->dest_uri, + GNOME_VFS_XFER_DEFAULT|GNOME_VFS_XFER_RECURSIVE, + GNOME_VFS_XFER_ERROR_MODE_QUERY, + GNOME_VFS_XFER_OVERWRITE_MODE_REPLACE, + GNOME_VFS_PRIORITY_DEFAULT, + (GnomeVFSAsyncXferProgressCallback)gm_fetch_progress, + g, gm_fetch_interact, g); + return g; +} + +gboolean +gm_is_end_scrolled(GtkScrolledWindow *wnd, guint charHeight) { + GtkAdjustment *ad = gtk_scrolled_window_get_vadjustment(wnd); + + return ((ad->page_size + ad->value) >= ad->upper - (double)charHeight); +} + +void +gm_scroll_end(GtkTextView *view, gboolean needs) { + GtkTextBuffer *buf; + GtkTextMark *mark; + GtkTextIter iter; + + if (!needs) { + return; + } + + buf = gtk_text_view_get_buffer(view); + mark = gtk_text_buffer_get_mark(buf, "end-of-buffer"); + + if (mark == NULL) { + gtk_text_buffer_get_end_iter(buf, &iter); + mark = gtk_text_buffer_create_mark(buf, "end-of-buffer", &iter, FALSE); + } + + gtk_text_view_scroll_to_mark(view, mark, 0.0, TRUE, 1.0, 1.0); +} + +#define MAX_BUF 1024 + +GString * +gm_read_file(const gchar *fname, gboolean readall, OpenLogProgress func, gpointer user_data) { + FILE *f; + gchar line[MAX_BUF], *tmp; + GString *str = NULL; + long bytes_read = 0, bytes_total = 0; + + if (!fname) { + return NULL; + } + + f = fopen(fname, "r"); + + if (f) { + fseek(f, 0, SEEK_END); + bytes_total = ftell(f); + rewind(f); + + str = g_string_new(""); + + while (fgets((char *) &line, MAX_BUF, f) != NULL) { + bytes_read += strlen((char *)&line); + tmp = NULL; + if (g_utf8_validate(line, -1, NULL)) { + if (readall) { + str = g_string_append(str, line); + } + tmp = g_strdup(line); + } else { + tmp = g_locale_to_utf8(line, -1, NULL, NULL, NULL); + + if (!tmp) { + tmp = g_convert(line, -1, "UTF-8", "ISO-8859-15", NULL, NULL, NULL); + } + if (!tmp) { + tmp = g_convert(line, -1, "UTF-8", "ISO-8859-15", NULL, NULL, NULL); + } + + if (readall) { + str = g_string_append(str, tmp); + } + } + + if (func != NULL) { + func(bytes_read, bytes_total, tmp, user_data); + } else { + g_free(tmp); + } + } + + fclose(f); + + return str; + } else { + gm_debug_msg(DEBUG_DEFAULT, "support_read_file: file (%s) could not be read: %s", + fname, strerror(errno)); + return NULL; + } +} + +GtkWidget * +gm_create_tab_label(const gchar *icon, const gchar *caption, gboolean has_exit, + GmLabelInfo *info) { + /* First create the gbox (size 3) which will contain an icon, a label and a + exit button if has_exit is true */ + GtkWidget *hboxTabLabel; + gint h, w; + + hboxTabLabel = gtk_hbox_new(FALSE, 3); + + gtk_widget_show(hboxTabLabel); + + info->image_icon = gtk_image_new_from_pixbuf( + gm_pixbuf_get_at_size(icon, 16, 16)); + gtk_widget_set_size_request(info->image_icon, 16, 16); + gtk_widget_show(info->image_icon); + gtk_box_pack_start(GTK_BOX(hboxTabLabel), info->image_icon, TRUE, TRUE, 0); + + info->label_name = gtk_label_new(caption); + gtk_widget_show(info->label_name); + gtk_box_pack_start(GTK_BOX(hboxTabLabel), info->label_name, FALSE, FALSE, 0); + + if (has_exit) { + info->button_exit = gtk_button_new(); + gtk_widget_show(info->button_exit); + + gtk_box_pack_end(GTK_BOX(hboxTabLabel), info->button_exit , FALSE, + FALSE, 0); + + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &w, &h); + gtk_widget_set_size_request(info->button_exit , w + 2, h + 2); + gtk_button_set_relief(GTK_BUTTON(info->button_exit ), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click(GTK_BUTTON(info->button_exit), FALSE); + + info->image_exit = gtk_image_new_from_stock("gtk-close", + GTK_ICON_SIZE_MENU); + gtk_widget_show(info->image_exit); + gtk_container_add(GTK_CONTAINER(info->button_exit), info->image_exit); + } + + return hboxTabLabel; +} + +void +gm_widget_destroy_data(GtkWidget *caller, GtkWidget *destroyer) { + if (GTK_IS_WIDGET(destroyer)) { + gtk_widget_destroy(destroyer); + } +} + +const gchar * +gm_default_charset() { + const gchar *loc = NULL; + g_get_charset(&loc); + + if (loc == NULL || strlen(loc) == 0) { + loc = "ISO-8859-15"; + } + + return loc; +} + +void +gm_notebook_focus_from_label(GtkNotebook *note, gchar *caption) { + int p = gtk_notebook_get_n_pages(note); + int i; + GtkWidget *child; + + for (i = 0; i < p; i++) { + child = gtk_notebook_get_nth_page(note, i); + if (!g_strcasecmp(gtk_notebook_get_tab_label_text(note, child), + caption)) { + gtk_notebook_set_current_page(note, i); + break; + } + } +} + +void +gm_string_skip_space(gchar **ptr) { + while (**ptr != '\0' && g_unichar_isspace(g_utf8_get_char(*ptr))) { + *ptr = g_utf8_next_char(*ptr); + } +} + +void +gm_string_skip_nonspace(gchar **ptr) { + while (**ptr != '\0' && !g_unichar_isspace(g_utf8_get_char(*ptr))) { + *ptr = g_utf8_next_char(*ptr); + } +} + +void +gm_string_skip_till(gchar **ptr, gchar const *find) { + gchar const *fptr; + gunichar check; + + while (**ptr != '\0') { + check = g_utf8_get_char(*ptr); + + for (fptr = find; *fptr; ++fptr) { + // CHECK: find should also be treated as utf8! + if (check == (gunichar)(*fptr)) { + return; + } + } + + *ptr = g_utf8_next_char(*ptr); + } +} + +gchar * +gm_to_utf8_with_fallback(gchar const *text, gssize len, gchar const *from, + gchar const *fallback) { + gchar *res; + gsize read, written; + GString *str = g_string_new(""); + + // TODO: use g_iconv instead of g_convert + + while ((res = g_convert(text, len, "UTF-8", from, &read, &written, NULL)) + == NULL) { + res = g_convert(text, read, "UTF-8", from, NULL, NULL, NULL); + str = g_string_append(str, res); + + str = g_string_append(str, fallback); + text = text + read + 1; + + if (len != -1) + len = len - read - 1; + } + + str = g_string_append(str, res); + g_free(res); + + res = str->str; + g_string_free(str, FALSE); + return res; +} diff --git a/gnoemoe/gm-support.h b/gnoemoe/gm-support.h new file mode 100644 index 0000000..ba7fb24 --- /dev/null +++ b/gnoemoe/gm-support.h @@ -0,0 +1,156 @@ +#ifndef __GM_SUPPORT_H__ +#define __GM_SUPPORT_H__ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <gtk/gtk.h> +#include <libgnomevfs/gnome-vfs.h> + +/* + * Standard gettext macros. + */ +#ifdef ENABLE_NLS +# include <libintl.h> +# undef _ +# define _(String) dgettext (PACKAGE, String) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +# define textdomain(String) (String) +# define gettext(String) (String) +# define dgettext(Domain,Message) (Message) +# define dcgettext(Domain,Message,Type) (Message) +# define bindtextdomain(Domain,Directory) (Domain) +#ifndef _ +# define _(String) (String) +#endif +# define N_(String) (String) +#endif + +#define CALC_COLOR_RANGE(x) (int)((-(1/((x * 0.5)+1))+1)*255) + +/** \defgroup support */ + +/** \ingroup support + * \brief Widget container for tabs + * + * Contains all widgets that could be useful to be modified. + * Stored here because it makes it easier to look them up. + * + */ +typedef struct _GmLabelInfo GmLabelInfo; +struct _GmLabelInfo { + /** \brief GtkButton widget + * + * GtkButton widget, reference to the tabs exit button + */ + GtkWidget *button_exit; + + /** \brief GtkImage widget + * + * GtkImage widget, reference to the tabs exit button image + */ + GtkWidget *image_exit; + + /** \brief GtkLabel widget + * + * GtkLabel widget, reference to the tabs label + */ + GtkWidget *label_name; + + /** \brief GtkImage widget + * + * GtkImage widget, reference to the tabs icon + */ + GtkWidget *image_icon; +}; + +/** \ingroup support + * \brief Key/value pair struct + * + * Contains two fields. Used to create hash tables + * + */ +typedef struct _GmKeyValuePair { + gchar *key; /**< the key */ + gchar *value; /**< the value */ +} GmKeyValuePair; + +typedef struct _GmFetchHandle GmFetchHandle; +struct _GmFetchHandle { + GnomeVFSAsyncHandle *handle; + + GFunc cb; + gpointer user_data; + + GList *source_uri; + GList *dest_uri; + + GnomeVFSXferPhase cur_phase; + GnomeVFSXferPhase prev_phase; + GnomeVFSXferProgressStatus prev_status; + GnomeVFSXferProgressStatus status; + + GnomeVFSFileSize bytes_total; + GnomeVFSFileSize file_size; + GnomeVFSFileSize bytes_copied; + GnomeVFSFileSize total_bytes_copied; + + gulong files_total; + gulong cur_file; + gulong prev_file; + gchar *cur_file_name; + + gboolean aborted; + gboolean done; +}; + +gchar *gm_fix_decimal_point(gchar *line, int len); +gchar *gm_fix_decimal_point_rev(gchar *line, int len); +gchar *gm_ansi_strip(gchar * s); +int garray_length(gchar **s); +void g_list_free_simple(GList *s); +gchar *g_list_find_simple(GList *s, gchar *f); + +void gm_error_dialog(gchar * message, GtkWindow * parent); +void gm_warning_dialog(gchar * message, GtkWindow * parent); +void gm_info_dialog(gchar * message, GtkWindow * parent); +void gm_question_dialog(gchar * message, GtkWindow * parent); + +void gm_do_events(); + +gchar *gm_str_escape(gchar * line); +void gm_directory_remove_all(const gchar * path, gboolean remove_self); + +gint gm_url_regex_match(const gchar *msg, int len, GArray *start, GArray *end); +void gm_open_url (const gchar *url); + +GmFetchHandle * gm_fetch(const GList *source, const GList *dest, + GFunc cb, gpointer user_data); +void gm_fetch_handle_free(GmFetchHandle *g); + +gboolean gm_is_end_scrolled(GtkScrolledWindow *wnd, guint charHeight); +void gm_scroll_end(GtkTextView *view, gboolean needs); + +typedef void (*OpenLogProgress) (long, long, gchar *, gpointer); +GString *gm_read_file(const gchar *fname, gboolean readall, + OpenLogProgress func, gpointer user_data); +GtkWidget *gm_create_tab_label(const gchar *icon, const gchar *caption, + gboolean has_exit, GmLabelInfo *info); +void gm_widget_destroy_data(GtkWidget *caller, GtkWidget *destroyer); +const gchar *gm_default_charset(); +void gm_notebook_focus_from_label(GtkNotebook *note, gchar *caption); + +void gm_string_skip_space(gchar **ptr); +void gm_string_skip_nonspace(gchar **ptr); +void gm_string_skip_till(gchar **ptr, gchar const *find); + +gchar *gm_to_utf8_with_fallback(gchar const *text, gssize len, gchar const *from, + gchar const *fallback); + +#endif /* __GM_SUPPORT_H__ */ diff --git a/gnoemoe/gm-tray.c b/gnoemoe/gm-tray.c new file mode 100644 index 0000000..06eb1b5 --- /dev/null +++ b/gnoemoe/gm-tray.c @@ -0,0 +1,55 @@ + +#include "gm-tray.h" +#include "eggtrayicon.h" + +#define GM_TRAY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_TRAY, GmTrayPrivate)) + +typedef enum _tray_type { + TRAY_ICON_DEFAULT, + TRAY_ICON_ACTIVE, + TRAY_ICON_NOTIFY +} tray_type; + +struct _GmTrayPrivate { + GtkWidget *event_box; + GtkWidget *image; + GtkTooltips *tooltips; + + GtkWidget *popup_menu; + GtkWidget *show_popup_item; + GtkWidget *hide_popup_item; + + guint flash_timeout; + tray_type iconnr; +}; + +/* Signals */ + +enum { + NUM_SIGNALS +}; + +static guint tray_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmTray, gm_tray, EGG_TYPE_TRAY_ICON) + +static void +gm_tray_finalize(GObject *object) { + GmTray *view = GM_TRAY(object); + + G_OBJECT_CLASS(gm_tray_parent_class)->finalize(object); +} + +static void +gm_tray_class_init(GmTrayClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_tray_finalize; + + g_type_class_add_private(object_class, sizeof(GmTrayPrivate)); +} + +static void +gm_tray_init(GmTray *tray) { + tray->private = GM_TRAY_GET_PRIVATE(tray); +} diff --git a/gnoemoe/gm-tray.h b/gnoemoe/gm-tray.h new file mode 100644 index 0000000..22a29e0 --- /dev/null +++ b/gnoemoe/gm-tray.h @@ -0,0 +1,50 @@ +#ifndef __GM_TRAY_H__ +#define __GM_TRAY_H__ + +#include <gtk/gtk.h> +#include "eggtrayicon.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_TRAY (gm_tray_get_type()) +#define GM_TRAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TRAY, GmTray)) +#define GM_TRAY_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TRAY, GmTray const)) +#define GM_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_TRAY, GmTrayClass)) +#define GM_IS_TRAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_TRAY)) +#define GM_IS_TRAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_TRAY)) +#define GM_TRAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_TRAY, GmTrayClass)) + +/* Private structure type */ +typedef struct _GmTrayPrivate GmTrayPrivate; + +/* + * Main object structure + */ +typedef struct _GmTray GmTray; + +struct _GmTray { + EggTrayIcon trayicon; + + /*< private > */ + GmTrayPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmTrayClass GmTrayClass; + +struct _GmTrayClass { + EggTrayIconClass parent_class; + + /* Signals */ +}; + +GType gm_tray_get_type(void) G_GNUC_CONST; +GmTray *gm_tray_new(void); + +G_END_DECLS +#endif /* __GM_TRAY_H__ */ diff --git a/gnoemoe/gm-triggers.c b/gnoemoe/gm-triggers.c new file mode 100644 index 0000000..ce111d3 --- /dev/null +++ b/gnoemoe/gm-triggers.c @@ -0,0 +1,440 @@ +#include <strings.h> +#include <string.h> + +#include <libxml/parser.h> +#include "gm-triggers.h" +#include "gm-debug.h" + +#define GM_TRIGGERS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_TRIGGERS, GmTriggersPrivate)) + +typedef struct _trigger_trans { + gint type; + const gchar *name; +} trigger_trans; + +static const trigger_trans table_conditions[] = { + {TCT_CONTAINS, "contains"}, + {TCT_NOT_CONTAINS, "not-contains"}, + {TCT_BEGINS, "begins"}, + {TCT_NOT_BEGINS, "not-begins"}, + {TCT_ENDS, "ends"}, + {TCT_NOT_ENDS, "not-ends"}, + {TCT_MATCHES, "matches"}, + {TCT_NOT_MATCHES, "not-matches"}, + {TCT_USER_ONLINE, "online"}, + {TCT_USER_OFFLINE, "offline"}, + {TCT_USER_IDLE, "idle"}, + {TCT_USER_IDLE_OFF, "idle-off"}, + {TCT_USER_AWAY, "away"}, + {TCT_USER_AWAY_OFF, "away-off"}, + {-1, NULL} +}; + +static const trigger_trans table_actions[] = { + {TAT_HIGHLIGHT_LINE, "highlight-line"}, + {TAT_HIGHLIGHT_MATCH, "highlight-match"}, + {TAT_BEEP, "beep"}, + {TAT_PLAY_SOUND, "play-sound"}, + {TAT_NOTIFY, "notify"}, + #ifdef HASRUBY + {TAT_RUN_SCRIPT, "run-script"}, + #endif + {TAT_RUN, "run"}, + {-1, NULL} +}; + +struct _GmTriggersPrivate { + GList *triggers; + gchar *path; +}; + +/* Signals */ + +/*enum { + PROTO + NUM_SIGNALS +}; + +static guint triggers_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmTriggers, gm_triggers, G_TYPE_OBJECT) + +static void +gm_triggers_finalize(GObject *object) { + GmTriggers *trg = GM_TRIGGERS(object); + GList *item; + + for (item = trg->priv->triggers; item; item = item->next) { + gm_trigger_free((GmTrigger *)(item->data)); + } + + g_free(trg->priv->path); + g_list_free(trg->priv->triggers); + + G_OBJECT_CLASS(gm_triggers_parent_class)->finalize(object); +} + +static void +gm_triggers_class_init(GmTriggersClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_triggers_finalize; + + g_type_class_add_private(object_class, sizeof(GmTriggersPrivate)); +} + +static void +gm_triggers_init(GmTriggers *trg) { + trg->priv = GM_TRIGGERS_GET_PRIVATE(trg); + trg->priv->triggers = NULL; + trg->priv->path = NULL; +} + +GmTrigger * +gm_trigger_new() { + return g_new0(GmTrigger, 1); +} + +GList * +gm_trigger_list_dup(GList *list) { + GList *result = NULL; + GList *item; + GmTriggerData *data; + + for (item = list; item; item = item->next) { + data = (GmTriggerData *)(item->data); + result = g_list_append(result, gm_trigger_data_new(data->type, + g_strdup(data->data))); + } + + return result; +} + +GmTrigger * +gm_trigger_dup(GmTrigger *source) { + GmTrigger *result = gm_trigger_new(); + + result->name = g_strdup(source->name); + result->event = source->event; + result->conditions = gm_trigger_list_dup(source->conditions); + result->actions = gm_trigger_list_dup(source->actions); + + return result; +} + +void +gm_trigger_set_name(GmTrigger *trigger, const gchar *name) { + g_free(trigger->name); + trigger->name = g_strdup(name); +} + +void +gm_trigger_free_list(GList *list) { + GList *item; + GmTriggerData *data; + + for (item = list; item; item = item->next) { + data = (GmTriggerData *)(item->data); + gm_trigger_data_free(data); + } + + g_list_free(list); +} + +void +gm_trigger_free(GmTrigger *trigger) { + gm_trigger_free_list(trigger->conditions); + gm_trigger_free_list(trigger->actions); + g_free(trigger->name); + + g_free(trigger); +} + +GmTriggerData * +gm_trigger_data_new(gint type, gchar *data) { + GmTriggerData *tdata = g_new0(GmTriggerData, 1); + tdata->type = type; + tdata->data = data; + + memset(&(tdata->expr), 0, sizeof(regex_t)); + + switch (tdata->type) { + case TCT_MATCHES: case TCT_NOT_MATCHES: case TCT_USER_ONLINE: + case TCT_USER_OFFLINE: case TCT_USER_IDLE: case TCT_USER_IDLE_OFF: + case TCT_USER_AWAY: case TCT_USER_AWAY_OFF: + regcomp(&(tdata->expr), tdata->data, REG_EXTENDED); + break; + default: + break; + } + + return tdata; +} + +void +gm_trigger_data_free(GmTriggerData *tdata) { + g_free(tdata->data); + g_free(tdata); +} + +void +gm_trigger_add_condition(GmTrigger *trigger, GmTriggerData *condition) { + trigger->conditions = g_list_append(trigger->conditions, condition); +} + +void +gm_trigger_add_action(GmTrigger *trigger, GmTriggerData *action) { + trigger->actions = g_list_append(trigger->actions, action); +} + +void +gm_trigger_set_conditions(GmTrigger *trigger, GList *conditions) { + gm_trigger_free_list(trigger->conditions); + trigger->conditions = conditions; +} + +void +gm_trigger_set_actions(GmTrigger *trigger, GList *actions) { + gm_trigger_free_list(trigger->actions); + trigger->actions = actions; +} + +gint +gm_trigger_type_from_name(const gchar *name, + const trigger_trans *trans_table) { + int i; + + for (i = 0; trans_table[i].type != -1; i++) { + if (strcasecmp(trans_table[i].name, name) == 0) { + return trans_table[i].type; + } + } + + return -1; +} + +const gchar * +gm_trigger_name_from_type(gint type, const trigger_trans *trans_table) { + int i; + + for (i = 0; trans_table[i].type != -1; i++) { + if (trans_table[i].type == type) { + return trans_table[i].name; + } + } + + return NULL; +} + +void +gm_triggers_parse_trigger(GmTriggers *trg, xmlDocPtr doc, xmlNodePtr node) { + GmTrigger *result = gm_trigger_new(); + xmlChar *tmp, *tmp2; + gint type; + + tmp = xmlGetProp(node, (const xmlChar *)"name"); + result->name = (gchar *)tmp; + + tmp = xmlGetProp(node, (const xmlChar *)"event"); + + if (xmlStrcmp(tmp, (const xmlChar *)("world")) == 0) { + result->event = TT_OUTPUT; + } else if (xmlStrcmp(tmp, (const xmlChar *)("player")) == 0){ + result->event = TT_USERS; + } else { + xmlFree(tmp); + gm_trigger_free(result); + return; + } + + xmlFree(tmp); + + for (node = node->xmlChildrenNode; node; node = node->next) { + tmp = xmlGetProp(node, (const xmlChar *)"type"); + tmp2 = xmlGetProp(node, (const xmlChar *)"data"); + + if (xmlStrcmp(node->name, (const xmlChar *)("condition")) == 0) { + type = gm_trigger_type_from_name((const gchar *)(tmp), + table_conditions); + + if (type != -1) { + gm_trigger_add_condition(result, gm_trigger_data_new(type, + g_strdup((char *)tmp2))); + } + } else if (xmlStrcmp(node->name, (const xmlChar *)("action")) == 0) { + type = gm_trigger_type_from_name((const gchar *)(tmp), + table_actions); + + if (type != -1) { + gm_trigger_add_action(result, gm_trigger_data_new(type, + g_strdup((char *)tmp2))); + } + } + + xmlFree(tmp2); + xmlFree(tmp); + } + + gm_triggers_add(trg, result); +} + +GmTriggers * +gm_triggers_new() { + GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); + + return trg; +} + +void +gm_triggers_add(GmTriggers *trg, GmTrigger *t) { + trg->priv->triggers = g_list_append(trg->priv->triggers, t); +} + +void +gm_triggers_clear(GmTriggers *trg) { + GList *item; + + for (item = trg->priv->triggers; item; item = item->next) { + gm_trigger_free((GmTrigger *)(item->data)); + } + + g_list_free(trg->priv->triggers); + trg->priv->triggers = NULL; +} + +GmTriggers * +gm_triggers_new_from_file(gchar *filename) { + GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); + xmlDocPtr doc; + xmlNodePtr cur; + + if (!g_file_test(filename, G_FILE_TEST_EXISTS)) { + gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: Trigger file does not exist"); + return trg; + + } + + trg->priv->path = g_strdup(filename); + doc = xmlParseFile(filename); + + if (doc == NULL) { + gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: Error on parsing triggers"); + return trg; + } + + cur = xmlDocGetRootElement(doc); + + if (cur == NULL) { + xmlFreeDoc(doc); + return trg; + } + + if (xmlStrcmp(cur->name, (const xmlChar *)("triggers"))) { + gm_debug_msg(DEBUG_DEFAULT, "GmTriggers.NewFromFile: invalid root node"); + xmlFreeDoc(doc); + return trg; + } + + for (cur = cur->xmlChildrenNode; cur; cur = cur->next) { + if (!xmlStrcmp(cur->name, (const xmlChar *)("trigger"))) { + gm_triggers_parse_trigger(trg, doc, cur); + } + } + + xmlFreeDoc(doc); + return trg; +} + +void +gm_triggers_set_path(GmTriggers *trg, gchar *path) { + g_free(trg->priv->path); + trg->priv->path = g_strdup(path); +} + +void +gm_trigger_rules_xml(GmTrigger *t, xmlNodePtr trig) { + GmTriggerData *data; + GList *item; + xmlNodePtr child; + + for (item = t->conditions; item; item = item->next) { + data = (GmTriggerData *)(item->data); + child = xmlNewChild(trig, NULL, (const xmlChar *)("condition"), NULL); + xmlNewProp(child, (const xmlChar *)("type"), (const xmlChar *) + (gm_trigger_name_from_type(data->type, table_conditions))); + + if (data->data) { + xmlNewProp(child, (const xmlChar *)("data"), (const xmlChar *) + (data->data)); + } + } + + for (item = t->actions; item; item = item->next) { + data = (GmTriggerData *)(item->data); + child = xmlNewChild(trig, NULL, (const xmlChar *)("action"), NULL); + xmlNewProp(child, (const xmlChar *)("type"), (const xmlChar *) + (gm_trigger_name_from_type(data->type, table_actions))); + + if (data->data) { + xmlNewProp(child, (const xmlChar *)("data"), (const xmlChar *) + (data->data)); + } + } +} + +void +gm_triggers_save(GmTriggers *trg) { + xmlDocPtr doc; + xmlNodePtr root; + xmlNodePtr trig; + GList *item; + GmTrigger *t; + + g_return_if_fail(trg->priv->path != NULL); + + doc = xmlNewDoc((const xmlChar *)("1.0")); + root = xmlNewNode(NULL, (const xmlChar *)("triggers")); + xmlDocSetRootElement(doc, root); + + for (item = trg->priv->triggers; item; item = item->next) { + t = (GmTrigger *)(item->data); + trig = xmlNewChild(root, NULL, (const xmlChar *)("trigger"), NULL); + xmlNewProp(trig, (const xmlChar *)("name"), (const xmlChar *)(t->name)); + + if (t->event == TT_OUTPUT) { + xmlNewProp(trig, (const xmlChar *)("event"), (const xmlChar *)("world")); + } else { + xmlNewProp(trig, (const xmlChar *)("event"), (const xmlChar *)("player")); + } + + gm_trigger_rules_xml(t, trig); + } + + xmlSaveFormatFileEnc(trg->priv->path, doc, "UTF-8", 1); + xmlFreeDoc(doc); +} + +void +gm_triggers_save_as(GmTriggers *trg, const gchar *path) { + g_free(trg->priv->path); + trg->priv->path = g_strdup(path); + gm_triggers_save(trg); +} + +GmTriggers * +gm_triggers_dup(GmTriggers *source) { + GmTriggers *trg = GM_TRIGGERS(g_object_new(GM_TYPE_TRIGGERS, NULL)); + GList *item; + + for (item = source->priv->triggers; item; item = item->next) { + trg->priv->triggers = g_list_append(trg->priv->triggers, + gm_trigger_dup((GmTrigger *)(item->data))); + } + + return trg; +} + +const GList * +gm_triggers_list(GmTriggers *trg) { + return trg->priv->triggers; +} diff --git a/gnoemoe/gm-triggers.h b/gnoemoe/gm-triggers.h new file mode 100644 index 0000000..c2f9196 --- /dev/null +++ b/gnoemoe/gm-triggers.h @@ -0,0 +1,118 @@ +#ifndef __GM_TRIGGERS_H__ +#define __GM_TRIGGERS_H__ + +#include <glib.h> +#include <glib-object.h> +#include <regex.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_TRIGGERS (gm_triggers_get_type()) +#define GM_TRIGGERS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TRIGGERS, GmTriggers)) +#define GM_TRIGGERS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TRIGGERS, GmTriggers const)) +#define GM_TRIGGERS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_TRIGGERS, GmTriggersClass)) +#define GM_IS_TRIGGERS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_TRIGGERS)) +#define GM_IS_TRIGGERS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_TRIGGERS)) +#define GM_TRIGGERS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_TRIGGERS, GmTriggersClass)) + +/* Private structure type */ +typedef struct _GmTriggersPrivate GmTriggersPrivate; + +/* + * Main object structure + */ +typedef struct _GmTriggers GmTriggers; + +struct _GmTriggers { + GObject object; + + /*< private > */ + GmTriggersPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmTriggersClass GmTriggersClass; + +struct _GmTriggersClass { + GObjectClass parent_class; + + /* Signals */ +}; + +typedef enum _GmTriggerType { + TT_OUTPUT = 0, + TT_USERS +} GmTriggerType; + +typedef enum _GmTriggerConditionType { + TCT_CONTAINS = 0, + TCT_NOT_CONTAINS, + TCT_BEGINS, + TCT_NOT_BEGINS, + TCT_ENDS, + TCT_NOT_ENDS, + TCT_MATCHES, + TCT_NOT_MATCHES, + TCT_USER_ONLINE, + TCT_USER_OFFLINE, + TCT_USER_IDLE, + TCT_USER_IDLE_OFF, + TCT_USER_AWAY, + TCT_USER_AWAY_OFF +} GmTriggerConditionType; + +typedef enum _GmTriggerActionType { + TAT_HIGHLIGHT_LINE = 0, + TAT_HIGHLIGHT_MATCH, + TAT_BEEP, + TAT_PLAY_SOUND, + TAT_NOTIFY, + TAT_RUN_SCRIPT, + TAT_RUN +} GmTriggerActionType; + +typedef struct _GmTriggerData { + gint type; + gchar *data; + regex_t expr; +} GmTriggerData; + +typedef struct _GmTrigger { + gchar *name; + GmTriggerType event; + + GList *conditions; + GList *actions; +} GmTrigger; + +GType gm_triggers_get_type(void) G_GNUC_CONST; +GmTriggers *gm_triggers_new(void); +GmTriggers *gm_triggers_new_from_file(gchar *filename); +void gm_triggers_save(GmTriggers *triggers); +void gm_triggers_save_as(GmTriggers *trg, const gchar *path); +void gm_triggers_set_path(GmTriggers *trg, gchar *path); +GmTriggers *gm_triggers_dup(GmTriggers *source); +const GList *gm_triggers_list(GmTriggers *trg); +void gm_triggers_add(GmTriggers *trg, GmTrigger *t); +void gm_triggers_clear(GmTriggers *trg); + +GmTrigger *gm_trigger_new(); +void gm_trigger_free(GmTrigger *trigger); +GmTrigger *gm_trigger_dup(GmTrigger *source); +void gm_trigger_set_name(GmTrigger *trigger, const gchar *name); +void gm_trigger_free_list(GList *list); +void gm_trigger_add_condition(GmTrigger *trigger, GmTriggerData *condition); +void gm_trigger_add_action(GmTrigger *trigger, GmTriggerData *action); +void gm_trigger_set_conditions(GmTrigger *trigger, GList *conditions); +void gm_trigger_set_actions(GmTrigger *trigger, GList *actions); + +GmTriggerData *gm_trigger_data_new(gint type, gchar *data); +void gm_trigger_data_free(GmTriggerData *tdata); + +G_END_DECLS +#endif /* __GM_TRIGGERS_H__ */ diff --git a/gnoemoe/gm-ui.h b/gnoemoe/gm-ui.h new file mode 100644 index 0000000..b727f96 --- /dev/null +++ b/gnoemoe/gm-ui.h @@ -0,0 +1,77 @@ +#ifndef __GM_UI_H__ +#define __GM_UI_H__ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <gtk/gtk.h> +#include "widgets/gm-app-view.h" +#include "gm-support.h" + +G_BEGIN_DECLS + +static const GtkActionEntry gm_sensitive_menu_entries[] = +{ + /* Toplevel */ + {"World", NULL, N_("_World")}, + {"Edit", NULL, N_("_Edit")}, + {"View", NULL, N_("_View")}, + {"Help", NULL, N_("_Help")}, + + /* World menu */ + {"WorldNew", GTK_STOCK_NEW, N_("New World..."), "<control>N", + N_("Create a new world"), G_CALLBACK(on_gm_app_view_world_new)}, + {"WorldQuit", GTK_STOCK_QUIT, NULL, NULL, + N_("Quit the program"), G_CALLBACK(on_gm_app_view_world_quit)}, + + /* Edit menu */ + {"EditWorlds", NULL, N_("Worlds..."), "<control>L", + N_("Edit worlds"), G_CALLBACK(on_gm_app_view_edit_worlds)}, + {"EditPreferences", GTK_STOCK_PREFERENCES, NULL, NULL, + N_("Configure the application"), + G_CALLBACK(on_gm_app_view_edit_preferences)}, + + /* View menu */ + {"ViewMcp", NULL, N_("MCP"), NULL, + N_("View MCP console"), G_CALLBACK(on_gm_app_view_view_mcp)}, + {"ViewScripts", NULL, N_("Scripts"), NULL, + N_("View scripts"), G_CALLBACK(on_gm_app_view_view_scripts)}, + + /* Help menu */ + {"HelpAbout", GTK_STOCK_ABOUT, NULL, NULL, + N_("About this application"), G_CALLBACK(on_gm_app_view_help_about)} +}; + +static const GtkActionEntry gm_menu_entries[] = +{ + /* File menu */ + {"WorldConnect", GTK_STOCK_NETWORK, N_("Connect"), "<control><shift>C", + N_("Connect or disconnect the current world"), + G_CALLBACK(on_gm_app_view_world_connect)}, + {"WorldClose", GTK_STOCK_CLOSE, NULL, NULL, + N_("Close current world"), G_CALLBACK(on_gm_app_view_world_close)}, + {"WorldLogs", GTK_STOCK_FILE, N_("Logs"), NULL, + N_("View current world logs"), G_CALLBACK(on_gm_app_view_world_logs)}, + {"WorldInfo", GTK_STOCK_ABOUT, N_("In_fo"), NULL, + N_("View current world info"), G_CALLBACK(on_gm_app_view_world_info)}, + + /* Edit menu */ + {"EditCut", GTK_STOCK_CUT, NULL, "<control>X", + N_("Cut the selection"), G_CALLBACK(on_gm_app_view_edit_cut)}, + {"EditCopy", GTK_STOCK_COPY, NULL, "<control>C", + N_("Copy the selection"), G_CALLBACK(on_gm_app_view_edit_copy)}, + {"EditPaste", GTK_STOCK_PASTE, NULL, "<control>V", + N_("Paste the clipboard"), G_CALLBACK(on_gm_app_view_edit_paste)}, + {"EditWorld", NULL, N_("Current world..."), "<control>E", + N_("Edit the current world"), G_CALLBACK(on_gm_app_view_edit_world)}, + {"EditFind", GTK_STOCK_FIND, NULL, "<control>F", + N_("Find text"), G_CALLBACK(on_gm_app_view_edit_find)}, + {"EditFindNext", GTK_STOCK_FIND, N_("Find next"), "<control>G", + N_("Find next occurence"), + G_CALLBACK(on_gm_app_view_edit_find_next)}, +}; + +G_END_DECLS + +#endif /* __GEDIT_UI_H__ */ diff --git a/gnoemoe/gm-world.c b/gnoemoe/gm-world.c new file mode 100644 index 0000000..2d0365f --- /dev/null +++ b/gnoemoe/gm-world.c @@ -0,0 +1,900 @@ +#include <string.h> +#include <errno.h> +#include <time.h> + +#include "gm-world.h" +#include "gm-app.h" +#include "mcp/gm-mcp-session.h" +#include "gm-triggers.h" +#include "gm-marshal.h" +#include "gm-net.h" +#include "gm-bogus.h" +#include "gm-support.h" +#include "gm-debug.h" + +#define GM_WORLD_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_WORLD, GmWorldPrivate)) + +void gm_world_save_input_history(GmWorld *world); +void gm_world_load_input_history(GmWorld *world); +void gm_world_load_triggers(GmWorld *world); + +void on_gm_world_net_state_changing(GmNet *net, GmNetState state, + GmWorld *world); +void on_gm_world_net_net_error(GmNet *net, gchar *error, gint code, + GmWorld *world); +void on_gm_world_net_bytes_recv(GmNet *net, gchar *text, gint len, + GmWorld *world); +void on_gm_world_options_option_changed(GmOptions *options, gchar *key, + GmWorld *world); + +struct _GmWorldPrivate { + gchar *path; + gboolean loaded; + gboolean active; + guint activity; + gchar *buffer; + gchar *status; + + GmOptions *options; + GmTriggers *triggers; + GmNet *net; + GmMcpSession *mcp; + GList *history; + GList *editors; + GmWorldInfo info; + GmEditingInfo editing_info; +}; + +/* Signals */ + +enum { + ACTIVATE_REQUEST, + LOAD, + UNLOAD, + STATE_CHANGING, + WORLD_ERROR, + TEXT_RECEIVED, + EDITOR_ADDED, + EDITOR_REMOVED, + NAME_CHANGED, + ACTIVE_CHANGED, + ACTIVITY_CHANGED, + STATUS_CHANGED, + NUM_SIGNALS +}; + +static guint world_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmWorld, gm_world, G_TYPE_OBJECT) + +static void +gm_world_finalize(GObject *object) { + GmWorld *world = GM_WORLD(object); + gchar *tmp_dir; + + if (world->priv->path) { + gm_options_save(world->priv->options); + gm_world_save_input_history(world); + gm_triggers_save(world->priv->triggers); + + // Removing all tmp files + tmp_dir = g_strconcat(world->priv->path, "/tmp", NULL); + gm_directory_remove_all(tmp_dir, FALSE); + g_free(tmp_dir); + } + + g_list_free_simple(world->priv->history); + + g_free(world->priv->path); + g_free(world->priv->buffer); + g_free(world->priv->status); + g_free(world->priv->editing_info.name); + g_free(world->priv->editing_info.upload); + g_list_free(world->priv->editing_info.lines); + + g_object_unref(world->priv->triggers); + g_object_unref(world->priv->options); + g_object_unref(world->priv->net); + g_object_unref(world->priv->mcp); + + G_OBJECT_CLASS(gm_world_parent_class)->finalize(object); +} + +static void +gm_world_class_init(GmWorldClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_world_finalize; + + world_signals[ACTIVATE_REQUEST] = + g_signal_new("activate_request", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, activate_request), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + world_signals[LOAD] = + g_signal_new("load", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, load), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + world_signals[UNLOAD] = + g_signal_new("unload", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, unload), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + world_signals[STATE_CHANGING] = + g_signal_new("state_changing", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, state_changing), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, + 1, + G_TYPE_UINT); + + world_signals[WORLD_ERROR] = + g_signal_new("world_error", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, world_error), + NULL, NULL, + gm_marshal_VOID__STRING_INT, + G_TYPE_NONE, + 2, + G_TYPE_STRING, + G_TYPE_INT); + + world_signals[TEXT_RECEIVED] = + g_signal_new("text_received", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, text_received), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + world_signals[EDITOR_ADDED] = + g_signal_new("editor_added", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, editor_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + world_signals[EDITOR_REMOVED] = + g_signal_new("editor_removed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, editor_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + world_signals[NAME_CHANGED] = + g_signal_new("name_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, name_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + world_signals[ACTIVE_CHANGED] = + g_signal_new("active_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, active_changed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, + 1, + G_TYPE_BOOLEAN); + + world_signals[ACTIVITY_CHANGED] = + g_signal_new("activity_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, activity_changed), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, + 1, + G_TYPE_INT); + + world_signals[STATUS_CHANGED] = + g_signal_new("status_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldClass, status_changed), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + g_type_class_add_private(object_class, sizeof(GmWorldPrivate)); +} + +void +gm_world_create_default_settings(GmWorld *world) { + const gchar *loc = gm_default_charset(); + + world->priv->options = gm_options_new(); + + gm_options_set(world->priv->options, "name", ""); + gm_options_set(world->priv->options, "autoload", "0"); + gm_options_set(world->priv->options, "host", ""); + gm_options_set(world->priv->options, "port", "1111"); + gm_options_set(world->priv->options, "player_name", ""); + gm_options_set(world->priv->options, "reconnect", "0"); + gm_options_set(world->priv->options, "password", ""); + gm_options_set(world->priv->options, "charset", (gchar *)loc); + gm_options_set(world->priv->options, "history_length", "500"); + + // Non configurable options + gm_options_set(world->priv->options, "pane_position", "150"); +} + +static void +gm_world_init(GmWorld *world) { + world->priv = GM_WORLD_GET_PRIVATE(world); + + gm_world_create_default_settings(world); + world->priv->path = NULL; + world->priv->loaded = FALSE; + world->priv->history = NULL; + world->priv->activity = 0; + world->priv->triggers = gm_triggers_new(); + world->priv->net = gm_net_new(); + world->priv->mcp = gm_mcp_session_new(G_OBJECT(world)); + world->priv->buffer = NULL; + world->priv->editing_info.is_editing = FALSE; + world->priv->status = NULL; + + g_signal_connect(world->priv->net, "state_changing", + G_CALLBACK(on_gm_world_net_state_changing), world); + g_signal_connect(world->priv->net, "net_error", + G_CALLBACK(on_gm_world_net_net_error), world); + g_signal_connect(world->priv->net, "bytes_recv", + G_CALLBACK(on_gm_world_net_bytes_recv), world); + g_signal_connect(world->priv->options, "option_changed", + G_CALLBACK(on_gm_world_options_option_changed), world); +} + +void +gm_world_load_input_history(GmWorld *world) { + FILE *f; + gchar line[1024], *filename; + GString *str; + + filename = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "history", + NULL); + + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.LoadInputHistory: loading history (%s)!", filename); + + if ((f = fopen(filename, "r")) != NULL) { + str = g_string_new(""); + + while (fgets(line, 1024 - 1, f) != NULL) { + g_string_append(str, line); + if (line[strlen(line) - 1] == '\n') { + if (line[1] != '\0') { // Empty lines, we don't need to process those + world->priv->history = g_list_append(world->priv->history, + g_strndup(str->str, strlen(str->str) - 1)); + } + g_string_erase(str, 0, -1); + } + } + + g_string_free(str, TRUE); + fclose(f); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.LoadInputHistory: could not retrieve contents of " + "file %s (%s)", filename, strerror(errno)); + } + + g_free(filename); +} + +void +gm_world_save_input_history(GmWorld *world) { + FILE *f; + gchar *filename; + GList *elem; + + if (world->priv->path == NULL) { + return; + } + + filename = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "history", NULL); + f = fopen(filename, "w"); + + if (f) { + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.SaveInputHistory: saving input history to %s", + filename); + + for (elem = world->priv->history; elem; elem = elem->next) { + fprintf(f, "%s\n", (gchar *) (elem->data)); + } + + fclose(f); + + chmod(filename, 0660); + } else { + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.SaveInputHistory: couldn't open history file (%s)" + " for saving: %s", filename, strerror(errno)); + } + + g_free(filename); +} + +void +gm_world_load_triggers(GmWorld *world) { + gchar *path; + + if (world->priv->triggers) { + g_object_unref(world->priv->triggers); + } + + path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "triggers", NULL); + world->priv->triggers = gm_triggers_new_from_file(path); + g_free(path); +} + +/* Public */ +GmWorld * +gm_world_new(gchar *path) { + GmWorld *world = GM_WORLD(g_object_new(GM_TYPE_WORLD, NULL)); + gchar *options_path; + + if (path != NULL) { + options_path = g_strconcat(path, "/settings", NULL); + + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.new: creating new world for %s", path); + world->priv->path = g_strdup(path); + + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.new: creating default world settings for %s", path); + gm_options_load(world->priv->options, options_path); + + if (strlen(gm_options_get(world->priv->options, "charset")) == 0) { + gm_options_set(world->priv->options, "charset", + gm_default_charset()); + } + + g_free(options_path); + + gm_world_load_input_history(world); + gm_world_load_triggers(world); + } + + /* CHECK: all done? */ + + return world; +} + +GmWorld * +gm_world_dup(GmWorld *source) { + GmWorld *copy = gm_world_new(NULL); + + g_object_unref(copy->priv->options); + + copy->priv->options = gm_options_dup(source->priv->options); + copy->priv->triggers = gm_triggers_dup(source->priv->triggers); + + return copy; +} + +void +gm_world_load(GmWorld *world) { + if (world->priv->loaded) { + g_signal_emit(world, world_signals[ACTIVATE_REQUEST], 0); + return; + } + + world->priv->loaded = TRUE; + g_signal_emit(world, world_signals[LOAD], 0); + g_signal_emit(world, world_signals[ACTIVATE_REQUEST], 0); + gm_world_connect(world); +} + +void +gm_world_unload(GmWorld *world) { + //GList *w; + + if (world->priv->loaded) { + world->priv->loaded = FALSE; + gm_world_disconnect(world); + + //TODO: Destroy editors + //editors_close(wld); + + g_signal_emit(world, world_signals[UNLOAD], 0); + } +} + +GmTriggers * +gm_world_triggers(GmWorld *world) { + return world->priv->triggers; +} + +GmOptions * +gm_world_options(GmWorld *world) { + return world->priv->options; +} + +const gchar * +gm_world_name(GmWorld *world) { + return gm_options_get(world->priv->options, "name"); +} + +const gchar * +gm_world_path(GmWorld *world) { + return world->priv->path; +} + +GList ** +gm_world_history(GmWorld *world) { + return &(world->priv->history); +} + +GmWorldInfo +gm_world_info(GmWorld *world) { + return world->priv->info; +} + +gboolean +gm_world_loaded(GmWorld *world) { + return world->priv->loaded; +} + +gboolean +gm_world_connected(GmWorld *world) { + return gm_net_state(world->priv->net) == GM_NET_STATE_CONNECTED; +} + +gboolean +gm_world_disconnected(GmWorld *world) { + return gm_net_state(world->priv->net) == GM_NET_STATE_DISCONNECTED; +} + +GmNetState +gm_world_state(GmWorld *world) { + return gm_net_state(world->priv->net); +} + +const gchar * +gm_world_current_host(GmWorld *world) { + return gm_net_current_host(world->priv->net); +} + +const gchar * +gm_world_current_port(GmWorld *world) { + return gm_net_current_port(world->priv->net); +} + +void +gm_world_connect(GmWorld *world) { + /*if (strlen(gm_options_get(world->priv->options, "host")) == 0) { + g_signal_emit(world, world_signals[WORLD_ERROR], 0, + _("World has no host, please fill in a host first")); + } else if (strlen(gm_options_get(world->priv->options, "port")) == 0) { + g_signal_emit(world, world_signals[WORLD_ERROR], 0, + _("World has no port, please fill in a port first")); + } else {*/ + gm_net_connect(world->priv->net, + gm_options_get(world->priv->options, "host"), + gm_options_get(world->priv->options, "port")); + //} +} + +void +gm_world_connect_to(GmWorld *world, gchar *host, gchar *port) { + gm_net_connect(world->priv->net, host, port); +} + +void +gm_world_disconnect(GmWorld *world) { + gm_net_disconnect(world->priv->net); +} + +void +gm_world_log(GmWorld *world, GmLogType type, gchar *text) { + FILE *f; + GString *s; + gchar *start, *log, *no_ansi; + struct tm *timet; + time_t timer; + + timer = time(0); + timet = localtime(&timer); + + log = g_strdup_printf("%s/logs/%04d-%02d-%02d.log", world->priv->path, + timet->tm_year + 1900, timet->tm_mon + 1, timet->tm_mday); + + f = fopen(log, "a"); + g_free(log); + + if (!f) { + return; + } + + start = g_strdup_printf("[%02d:%02d:%02d] ", timet->tm_hour, timet->tm_min, + timet->tm_sec); + + s = g_string_new(start); + g_free(start); + + switch (type) { + case LOG_IN: + s = g_string_append_c(s, '<'); + break; + case LOG_OUT: + s = g_string_append_c(s, '>'); + break; + case LOG_MCP_IN: + s = g_string_append(s, "[MCP] <"); + break; + case LOG_MCP_OUT: + s = g_string_append(s, "[MCP] >"); + break; + case LOG_STATUS: + s = g_string_append_c(s, '#'); + break; + } + + s = g_string_append(s, " "); + s = g_string_append(s, text); + + //no_ansi = gm_ansi_strip(g_strdup(s->str)); + no_ansi = g_strdup(s->str); + + if (no_ansi[strlen(no_ansi) - 1] != '\n') { + fputs(no_ansi, f); + fputc('\n', f); + } else { + fputs(no_ansi, f); + } + + fclose(f); + + g_free(no_ansi); + g_string_free(s, TRUE); +} + +void +gm_world_parse_legacy_editing_start(GmWorld *world, gchar *line) { + gchar *name_start, *upload_start; + GmEditingInfo *einfo = &(world->priv->editing_info); + + name_start = strstr(line, "name: "); + + if (!name_start) { + return; + } + + upload_start = strstr(line, " upload: "); + + if (!upload_start) { + return; + } + + einfo->is_editing = TRUE; + einfo->name = g_strndup(name_start + 6, (upload_start - name_start) - 6); + einfo->upload = g_strndup(upload_start + 9, (upload_start - line) + 9); + einfo->lines = NULL; +} + +void +gm_world_process_line(GmWorld *world, gchar *line) { + gchar *non_text_start = NULL; + GmEditingInfo *einfo = &(world->priv->editing_info); + + if (strncmp(line, "\x1B[0m", 4) == 0) { + non_text_start = g_strdup(line + 4); + } else { + non_text_start = g_strdup(line); + } + + if (einfo->is_editing) { + if (strcmp(non_text_start, ".") == 0) { + //TODO: implementation, create new editor object, invoke signal + gm_world_add_editor(world, gm_editor_new(einfo->name, einfo->upload, + einfo->lines)); + + einfo->is_editing = FALSE; + g_free(einfo->name); + einfo->name = NULL; + g_free(einfo->upload); + einfo->upload = NULL; + g_list_free(einfo->lines); + einfo->lines = NULL; + } else { + einfo->lines = g_list_append(einfo->lines, g_strdup(non_text_start)); + } + } else if (strncmp(non_text_start, "#$#", 3) == 0) { + if (strncasecmp(non_text_start + 3, " edit ", 6) == 0) { + gm_world_parse_legacy_editing_start(world, non_text_start + 9); + } else { + gm_mcp_session_handle_oob(world->priv->mcp, non_text_start + 3); + //gm_world_log(world, LOG_MCP_IN, non_text_start); + } + } else { + if (!gm_world_active(world)) { + gm_world_set_activity(world, world->priv->activity + 1); + } + + if (strncmp(non_text_start, "#$\"", 3) == 0) { + if (strlen(non_text_start) != strlen(line)) { + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\x1B[0m"); + } + + gm_world_log(world, LOG_IN, non_text_start + 3); + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, non_text_start + 3); + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\n"); + } else { + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, line); + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\n"); + gm_world_log(world, LOG_IN, line); + } + } + + g_free(non_text_start); +} + +void +gm_world_process_input(GmWorld *world, gchar *text) { +#ifdef HAVE_RUBY + gchar *space, *script, *argstr; + gboolean script_ran = FALSE; + + if (g_utf8_strlen(text, -1) > 1) { + if (text[0] == '/' && text[1] != '/') { + space = strstr(text, " "); + + if (space == NULL) { + script = g_strdup(text + 1); + argstr = g_strdup(""); + } else { + script = g_strndup(text + 1, (space - text) - 1); + argstr = g_strdup(space + 1); + } + + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.ProcessInput: Trying script %s (%s)", script, + argstr); + script_ran = gm_scripts_run(gm_app_scripts(gm_app_instance()), + world, script, argstr); + + g_free(script); + g_free(argstr); + + if (script_ran) { + return; + } + } else if (text[0] == '/' && text[1] == '/') { + text = text + 1; + } + } +#endif + + gm_world_sendln(world, text); +} + +void +gm_world_writeln(GmWorld *world, gchar *text) { + gchar *newline = g_strconcat(text, "\n", NULL); + g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, newline); + + g_free(newline); +} + +void +gm_world_remove_editor(GmWorld *world, GmEditor *editor) { + g_return_if_fail(g_list_find(world->priv->editors, editor) != NULL); + + world->priv->editors = g_list_remove(world->priv->editors, editor); + g_signal_emit(world, world_signals[EDITOR_REMOVED], 0, G_OBJECT(editor)); + + g_object_unref(editor); +} + +void +gm_world_add_editor(GmWorld *world, GmEditor *editor) { + world->priv->editors = g_list_append(world->priv->editors, editor); + + g_signal_emit(world, world_signals[EDITOR_ADDED], 0, G_OBJECT(editor)); +} + +void +gm_world_sendln(GmWorld *world, gchar *text) { + gchar *normal; + + // Convert text from utf-8 to the correct locale + normal = gm_to_utf8_with_fallback(text, -1, + gm_options_get(world->priv->options, "charset"), "?"); + + if (!normal) { + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.Send: conversion failed!"); + normal = g_strdup(text); + } + + gm_world_log(world, LOG_OUT, text); + gm_net_send_line(world->priv->net, normal); + g_free(normal); +} + +void +gm_world_set_active(GmWorld *world, gboolean active) { + world->priv->active = active; + + g_signal_emit(world, world_signals[ACTIVE_CHANGED], 0, active); + + if (active) { + gm_world_set_activity(world, 0); + } +} + +gboolean +gm_world_active(GmWorld *world) { + return world->priv->active; +} + +void +gm_world_set_activity(GmWorld *world, gint activity) { + world->priv->activity = activity; + + g_signal_emit(world, world_signals[ACTIVITY_CHANGED], 0, activity); +} + +gint +gm_world_activity(GmWorld *world) { + return world->priv->activity; +} + +void +gm_world_name_changed(GmWorld *world) { + gchar *tmp_path = world->priv->path ? g_strdup(world->priv->path) : NULL; + gchar *new_path = g_strconcat(gm_app_worlds_path(gm_app_instance()), "/", + gm_options_get(world->priv->options, "name"), NULL); + + if (tmp_path && strcmp(tmp_path, new_path) == 0) { + g_free(new_path); + g_free(tmp_path); + return; + } + + g_free(world->priv->path); + world->priv->path = new_path; + + if (tmp_path && g_file_test(tmp_path, G_FILE_TEST_EXISTS)) { + // Then! rename the old_path to the new_path + rename(tmp_path, world->priv->path); + } else if (!g_file_test(world->priv->path, G_FILE_TEST_EXISTS)) { + // There was no old path, so a new dir should be made + mkdir(world->priv->path, 0755); + } + + g_free(tmp_path); + + tmp_path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "settings", + NULL); + gm_options_save_as(world->priv->options, tmp_path); + g_free(tmp_path); + + tmp_path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "triggers", + NULL); + gm_triggers_save_as(world->priv->triggers, tmp_path); + g_free(tmp_path); + + g_signal_emit(world, world_signals[NAME_CHANGED], 0, + gm_options_get(world->priv->options, "name")); +} + +void +gm_world_set_status(GmWorld *world, gchar const *status) { + g_free(world->priv->status); + world->priv->status = g_strdup(status); + + g_signal_emit(world, world_signals[STATUS_CHANGED], 0, + world->priv->status); +} + +gchar const * +gm_world_get_status(GmWorld *world) { + return world->priv->status; +} + +/* Callbacks */ + +void +on_gm_world_net_state_changing(GmNet *net, GmNetState state, GmWorld *world) { + g_signal_emit(world, world_signals[STATE_CHANGING], 0, state); +} + +void +on_gm_world_net_net_error(GmNet *net, gchar *error, gint code, + GmWorld *world) { + g_signal_emit(world, world_signals[WORLD_ERROR], 0, error, code); +} + +void +on_gm_world_net_bytes_recv(GmNet *net, gchar *text, gint len, + GmWorld *world) { + gchar *all, *utext, *ptr, *line, *p; + gint i; + + utext = gm_to_utf8_with_fallback(text, len, + gm_options_get(world->priv->options, "charset"), "?"); + + if (!utext) { + gm_debug_msg(DEBUG_DEFAULT, "GmWorld.NetBytesRecv: conversion failed!"); + utext = g_strndup(text, len); + } + + if (world->priv->buffer != NULL) { + all = g_strconcat(world->priv->buffer, utext, NULL); + g_free(utext); + g_free(world->priv->buffer); + world->priv->buffer = NULL; + } else { + all = utext; + } + + // TODO: UTF-8 compliant + line = (gchar *)(malloc((strlen(all) * sizeof(gchar)) + 1)); + i = 0; + p = all; + + /* Find lines in `all' and process them */ + for (ptr = all; *ptr != '\0'; ptr++) { + if (*ptr == '\n') { + line[i] = '\0'; + line[i + 1] = '\0'; + + gm_world_process_line(world, line); + p = ptr + 1; + i = 0; + } else if (*ptr != '\r') { + line[i] = *ptr; + i++; + } + } + + if (i > 0) { + world->priv->buffer = g_strdup(p); + } + + g_free(all); +} + +void +on_gm_world_options_option_changed(GmOptions *options, gchar *key, + GmWorld *world) { + if (strcmp(key, "name") == 0) { + gm_world_name_changed(world); + } +} diff --git a/gnoemoe/gm-world.h b/gnoemoe/gm-world.h new file mode 100644 index 0000000..5c0b0e7 --- /dev/null +++ b/gnoemoe/gm-world.h @@ -0,0 +1,140 @@ +#ifndef __GM_WORLD_H__ +#define __GM_WORLD_H__ + +#include <gtk/gtk.h> +#include "gm-options.h" +#include "gm-net.h" +#include "gm-editor.h" +#include "gm-triggers.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_WORLD (gm_world_get_type()) +#define GM_WORLD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD, GmWorld)) +#define GM_WORLD_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD, GmWorld const)) +#define GM_WORLD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_WORLD, GmWorldClass)) +#define GM_IS_WORLD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_WORLD)) +#define GM_IS_WORLD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_WORLD)) +#define GM_WORLD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_WORLD, GmWorldClass)) + +typedef struct _GmWorldInfo { + gchar *homepage; + gchar *location; + gchar *admin; + gchar *contact; + gchar *charset; + gchar *language; + gchar *system; + gchar *logo; +} GmWorldInfo; + +/** \ingroup world + * \brief struct for world editing information + * + * Struct which contains various fields for storing editing information. This + * is only used if editing via mcp_simpleedit can't be used. + */ +typedef struct _GmEditingInfo { + gboolean is_editing; /**< are we editing at the moment? */ + GList *lines; /**< the lines to be edited */ + gchar *name; /**< the name of the editor */ + gchar *upload; /**< the command to send when sending the editor contents */ +} GmEditingInfo; + +/** \ingroup world + * \brief enum indicating log type + * + * Enumeration which indicates the log type + */ +typedef enum _GmLogType GmLogType; +enum _GmLogType { + LOG_IN, /**< incoming lines */ + LOG_OUT, /**< outgoing lines */ + LOG_MCP_IN, /**< mcp incoming lines */ + LOG_MCP_OUT, /**< mcp outgoing lines */ + LOG_STATUS /**< status lines (such as connecting information) */ +}; + +/* Private structure type */ +typedef struct _GmWorldPrivate GmWorldPrivate; + +/* + * Main object structure + */ +typedef struct _GmWorld GmWorld; + +struct _GmWorld { + GObject object; + + /*< private > */ + GmWorldPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmWorldClass GmWorldClass; + +struct _GmWorldClass { + GObjectClass parent_class; + + /* Signals */ + void (* activate_request) (GmWorld *world); + void (* load) (GmWorld *world); + void (* unload) (GmWorld *world); + void (* state_changing) (GmWorld *world, guint state); + void (* world_error) (GmWorld *world, gchar const *error, gint code); + void (* text_received) (GmWorld *world, gchar const *text); + void (* editor_added) (GmWorld *world, GObject *editor); + void (* editor_removed) (GmWorld *world, GObject *editor); + void (* name_changed) (GmWorld *world, gchar const *name); + void (* active_changed) (GmWorld *world, gboolean active); + void (* activity_changed) (GmWorld *world, gint activity); + void (* status_changed) (GmWorld *world, gchar const *status); +}; + +GType gm_world_get_type(void) G_GNUC_CONST; +GmWorld *gm_world_new(gchar *path); + +GmWorld *gm_world_dup(GmWorld *source); +void gm_world_load(GmWorld *world); +void gm_world_unload(GmWorld *world); + +const gchar *gm_world_name(GmWorld *world); +GmOptions *gm_world_options(GmWorld *world); +GmWorldInfo gm_world_info(GmWorld *world); +const gchar *gm_world_path(GmWorld *world); +GList **gm_world_history(GmWorld *world); +gint gm_world_activity(GmWorld *world); +gboolean gm_world_active(GmWorld *world); +GmTriggers *gm_world_triggers(GmWorld *world); + +const gchar *gm_world_current_host(GmWorld *world); +const gchar *gm_world_current_port(GmWorld *world); + +void gm_world_set_name(GmWorld *world, const gchar *name); +void gm_world_set_activity(GmWorld *world, gint activity); +void gm_world_set_active(GmWorld *world, gboolean active); +void gm_world_set_status(GmWorld *world, gchar const *status); +gchar const *gm_world_get_status(GmWorld *world); + +gboolean gm_world_loaded(GmWorld *world); +GmNetState gm_world_state(GmWorld *world); +gboolean gm_world_connected(GmWorld *world); +gboolean gm_world_disconnected(GmWorld *world); + +void gm_world_connect(GmWorld *world); +void gm_world_connect_to(GmWorld *world, gchar *host, gchar *port); +void gm_world_disconnect(GmWorld *world); +void gm_world_add_editor(GmWorld *world, GmEditor *editor); +void gm_world_remove_editor(GmWorld *world, GmEditor *editor); +void gm_world_sendln(GmWorld *world, gchar *text); +void gm_world_writeln(GmWorld *world, gchar *text); +void gm_world_process_input(GmWorld *world, gchar *text); +void gm_world_log(GmWorld *world, GmLogType type, gchar *text); + +G_END_DECLS +#endif /* __GM_WORLD_H__ */ diff --git a/gnoemoe/gtktemplate.c b/gnoemoe/gtktemplate.c new file mode 100644 index 0000000..dde9177 --- /dev/null +++ b/gnoemoe/gtktemplate.c @@ -0,0 +1,59 @@ +#include <gtk/gtk.h> +#include "gm-{template-}.h" + +#define GM_{TEMPLATE}_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Private)) + +struct _Gm{Template}Private { + +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_{template_}_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(Gm{Template}, gm_{template_}, GTK_TYPE_{PARENT}) + +static void +gm_{template_}_finalize(GObject *object) { + //Gm{Template} *obj = GM_{TEMPLATE}(object); + + G_OBJECT_CLASS(gm_{template_}_parent_class)->finalize(object); +} + +static void +gm_{template_}_class_init(Gm{Template}Class *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_{template_}_finalize; + + /*gm_{template_}_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(Gm{Template}Class, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + g_type_class_add_private(object_class, sizeof(Gm{Template}Private)); +} + +static void +gm_{template_}_init(Gm{Template} *obj) { + obj->priv = GM_{TEMPLATE}_GET_PRIVATE(obj); +} + +Gm{Template} * +gm_{template_}_new() { + Gm{Template} *obj = GM_{TEMPLATE}(g_object_new(GM_TYPE_{TEMPLATE}, NULL)); + + return obj; +} diff --git a/gnoemoe/gtktemplate.h b/gnoemoe/gtktemplate.h new file mode 100644 index 0000000..0c0eb43 --- /dev/null +++ b/gnoemoe/gtktemplate.h @@ -0,0 +1,56 @@ +#ifndef __GM_{TEMPLATE}_H__ +#define __GM_{TEMPLATE}_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_{TEMPLATE} (gm_{template_}_get_type()) +#define GM_{TEMPLATE}(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_{TEMPLATE}, Gm{Template})) +#define GM_{TEMPLATE}_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_{TEMPLATE}, Gm{Template} const)) +#define GM_{TEMPLATE}_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Class)) +#define GM_IS_{TEMPLATE}(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_{TEMPLATE})) +#define GM_IS_{TEMPLATE}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_{TEMPLATE})) +#define GM_{TEMPLATE}_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Class)) + +/* Private structure type */ +typedef struct _Gm{Template}Private Gm{Template}Private; + +/* + * Main object structure + */ +typedef struct _Gm{Template} Gm{Template}; + +struct _Gm{Template} { + Gtk{Parent} parent; + + /*< private > */ + Gm{Template}Private *priv; +}; + +/* + * Class definition + */ +typedef struct _Gm{Template}Class Gm{Template}Class; + +struct _Gm{Template}Class { + Gtk{Parent}Class parent_class; + + /* Signals + void (* proto) (Gm{Template} *obj); */ +}; + +GType gm_{template_}_get_type(void) G_GNUC_CONST; +Gm{Template} *gm_{template_}_new(void); + +G_END_DECLS +#endif /* __GM_{TEMPLATE}_H__ */ diff --git a/gnoemoe/mcp/Makefile.include b/gnoemoe/mcp/Makefile.include new file mode 100644 index 0000000..558a9d5 --- /dev/null +++ b/gnoemoe/mcp/Makefile.include @@ -0,0 +1,13 @@ +## Process this file with automake to produce Makefile.in +mcpdir = mcp + +gnoemoe_SOURCES += $(mcpdir)/gm-mcp-classes.c \ + $(mcpdir)/gm-mcp.c $(mcpdir)/gm-mcp.h \ + $(mcpdir)/gm-mcp-session.c $(mcpdir)/gm-mcp-session.h \ + $(mcpdir)/gm-mcp-package.c $(mcpdir)/gm-mcp-package.h \ + $(mcpdir)/gm-mcp-negotiate.c $(mcpdir)/gm-mcp-negotiate.h \ + $(mcpdir)/gm-mcp-awns-status.c $(mcpdir)/gm-mcp-awns-status.h + +$(mcpdir)/gm-mcp-classes.c: $(mcpdir)/packages.defs + ( cd $(srcdir) && ./$(mcpdir)/mcpinit.rb \ + $(mcpdir)/packages.defs $(mcpdir)/gm-mcp-classes ) > $@ diff --git a/gnoemoe/mcp/gm-mcp-awns-status.c b/gnoemoe/mcp/gm-mcp-awns-status.c new file mode 100644 index 0000000..f3bc312 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-awns-status.c @@ -0,0 +1,82 @@ +#include <glib-object.h> +#include "gm-mcp-awns-status.h" +#include "gm-mcp-session.h" +#include "gm-mcp.h" +#include "../gm-world.h" +#include "../gm-debug.h" + +#define GM_MCP_AWNS_STATUS_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_AWNS_STATUS, GmMcpAwnsStatusPrivate)) + +struct _GmMcpAwnsStatusPrivate { + +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_awns_status_signals[NUM_SIGNALS] = {0};*/ +void gm_mcp_awns_status_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields); + +G_DEFINE_TYPE(GmMcpAwnsStatus, gm_mcp_awns_status, GM_TYPE_MCP_PACKAGE) + +static void +gm_mcp_awns_status_finalize(GObject *object) { + //GmMcpAwnsStatus *obj = GM_MCP_AWNS_STATUS(object); + + G_OBJECT_CLASS(gm_mcp_awns_status_parent_class)->finalize(object); +} + +static void +gm_mcp_awns_status_class_init(GmMcpAwnsStatusClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GmMcpPackageClass *pklass = GM_MCP_PACKAGE_CLASS(klass); + + object_class->finalize = gm_mcp_awns_status_finalize; + + /*gm_mcp_awns_status_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpAwnsStatusClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + pklass->name = "dns-com-awns-status"; + pklass->handle_simple = &gm_mcp_awns_status_handle_simple; + + g_type_class_add_private(object_class, sizeof(GmMcpAwnsStatusPrivate)); +} + +static void +gm_mcp_awns_status_init(GmMcpAwnsStatus *obj) { + obj->priv = GM_MCP_AWNS_STATUS_GET_PRIVATE(obj); +} + +/* Public */ +GmMcpAwnsStatus * +gm_mcp_awns_status_new() { + GmMcpAwnsStatus *obj = GM_MCP_AWNS_STATUS(g_object_new( + GM_TYPE_MCP_AWNS_STATUS, NULL)); + + return obj; +} + +/* Private */ +void +gm_mcp_awns_status_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields) { + gchar const *text = gm_mcp_find_value(fields, "text"); + GmMcpSession *session = GM_MCP_PACKAGE_SESSION(package); + + gm_debug_msg(DEBUG_MCP, "GmMcpAwnsStatus.HandleSimple: set status %s", text); + gm_world_set_status(GM_MCP_SESSION_WORLD(session), text); +} diff --git a/gnoemoe/mcp/gm-mcp-awns-status.h b/gnoemoe/mcp/gm-mcp-awns-status.h new file mode 100644 index 0000000..0308ae8 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-awns-status.h @@ -0,0 +1,57 @@ +#ifndef __GM_MCP_AWNS_STATUS_H__ +#define __GM_MCP_AWNS_STATUS_H__ + +#include <glib-object.h> +#include "gm-mcp-package.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_AWNS_STATUS (gm_mcp_awns_status_get_type()) +#define GM_MCP_AWNS_STATUS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_AWNS_STATUS, GmMcpAwnsStatus)) +#define GM_MCP_AWNS_STATUS_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_AWNS_STATUS, GmMcpAwnsStatus const)) +#define GM_MCP_AWNS_STATUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_AWNS_STATUS, GmMcpAwnsStatusClass)) +#define GM_IS_MCP_AWNS_STATUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_AWNS_STATUS)) +#define GM_IS_MCP_AWNS_STATUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_AWNS_STATUS)) +#define GM_MCP_AWNS_STATUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_AWNS_STATUS, GmMcpAwnsStatusClass)) + +/* Private structure type */ +typedef struct _GmMcpAwnsStatusPrivate GmMcpAwnsStatusPrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpAwnsStatus GmMcpAwnsStatus; + +struct _GmMcpAwnsStatus { + GmMcpPackage parent; + + /*< private > */ + GmMcpAwnsStatusPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpAwnsStatusClass GmMcpAwnsStatusClass; + +struct _GmMcpAwnsStatusClass { + GmMcpPackageClass parent_class; + + /* Signals + void (* proto) (GmMcpAwnsStatus *obj); */ +}; + +GType gm_mcp_awns_status_get_type(void) G_GNUC_CONST; +GmMcpAwnsStatus *gm_mcp_awns_status_new(void); + +G_END_DECLS +#endif /* __GM_MCP_AWNS_STATUS_H__ */ diff --git a/gnoemoe/mcp/gm-mcp-negotiate.c b/gnoemoe/mcp/gm-mcp-negotiate.c new file mode 100644 index 0000000..556cb0c --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-negotiate.c @@ -0,0 +1,249 @@ +#include <glib-object.h> +#include <string.h> + +#include "gm-mcp-negotiate.h" +#include "gm-mcp.h" +#include "gm-mcp-session.h" +#include "gm-mcp-package.h" +#include "../gm-debug.h" +#include "../gm-support.h" + +#define GM_MCP_NEGOTIATE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiatePrivate)) + +typedef struct _PackageInfo { + GmMcpPackageClass *klass; + gdouble version; +} PackageInfo; + +struct _GmMcpNegotiatePrivate { + GList *packages; +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_negotiate_signals[NUM_SIGNALS] = {0};*/ + +void gm_mcp_negotiate_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields); + +G_DEFINE_TYPE(GmMcpNegotiate, gm_mcp_negotiate, GM_TYPE_MCP_PACKAGE); + +static void +gm_mcp_negotiate_finalize(GObject *object) { + //GmMcpNegotiate *obj = GM_MCP_NEGOTIATE(object); + + G_OBJECT_CLASS(gm_mcp_negotiate_parent_class)->finalize(object); +} + +static void +gm_mcp_negotiate_class_init(GmMcpNegotiateClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GmMcpPackageClass *pklass = GM_MCP_PACKAGE_CLASS(klass); + + object_class->finalize = gm_mcp_negotiate_finalize; + + /*gm_mcp_negotiate_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpNegotiateClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + pklass->name = "mcp-negotiate"; + pklass->handle_simple = &gm_mcp_negotiate_handle_simple; + + g_type_class_add_private(object_class, sizeof(GmMcpNegotiatePrivate)); +} + +static void +gm_mcp_negotiate_init(GmMcpNegotiate *obj) { + obj->priv = GM_MCP_NEGOTIATE_GET_PRIVATE(obj); + obj->priv->packages = NULL; +} + +PackageInfo * +gm_mcp_negotiate_find_package(GmMcpNegotiate *package, gchar *name) { + PackageInfo *pinfo; + GList *elem; + + for (elem = package->priv->packages; elem; elem = elem->next) { + pinfo = (PackageInfo *)(elem->data); + + if (strcasecmp(pinfo->klass->name, name) == 0) { + return pinfo; + } + } + + return NULL; +} + + +void +gm_mcp_negotiate_fix_overrides(GmMcpNegotiate *package) { + GList *l, *item, *over; + PackageInfo *pinfo, *pover; + gchar *name; + + l = g_list_copy(package->priv->packages); + + for (item = l; item; item = item->next) { + pinfo = (PackageInfo *)(item->data); + + for (over = pinfo->klass->overrides; over; over = over->next) { + name = (gchar *)(over->data); + + if ((pover = gm_mcp_negotiate_find_package(package, name))) { + gm_debug_msg(DEBUG_MCP, "GmMcpNegotiate.FixOverrides: package %s " + "overrides %s", pinfo->klass->name, pover->klass->name); + package->priv->packages = g_list_remove( + package->priv->packages, pover); + } + } + } + + g_list_free(l); +} + +void +gm_mcp_negotiate_fix_depends(GmMcpNegotiate *package) { + PackageInfo *pinfo, *pdep; + GList *l, *item, *dep; + gchar *name; + + l = g_list_copy(package->priv->packages); + + for (item = l; item; item = item->next) { + pinfo = (PackageInfo *)(item->data); + + for (dep = pinfo->klass->depends; dep; dep = dep->next) { + name = (gchar *)(dep->data); + + if (!(pdep = gm_mcp_negotiate_find_package(package, name))) { + gm_debug_msg(DEBUG_MCP, "GmMcpNegotiate.FixDepends: package %s depends " + "on %s, but %s is not supported", pinfo->klass->name, + name, name); + + // Remove package because depencendies are not met + package->priv->packages = g_list_remove( + package->priv->packages, pinfo); + break; + } else { + // Make sure this dependency is loaded before the package + package->priv->packages = g_list_remove( + package->priv->packages, pdep); + package->priv->packages = g_list_insert_before( + package->priv->packages, + g_list_find(package->priv->packages, pinfo), pdep); + } + } + } + + g_list_free(l); +} + +/* Public */ +GmMcpNegotiate * +gm_mcp_negotiate_new() { + GmMcpNegotiate *obj = GM_MCP_NEGOTIATE(g_object_new(GM_TYPE_MCP_NEGOTIATE, + NULL)); + + return obj; +} + +/* Private */ +gboolean +gm_mcp_negotiate_send_can(GmMcpPackageClass *klass, gpointer user_data) { + GmMcpNegotiate *package = GM_MCP_NEGOTIATE(user_data); + gchar min_v[16], max_v[16]; + + g_ascii_formatd(min_v, 16, "%.1f", klass->min_version); + g_ascii_formatd(max_v, 16, "%.1f", klass->max_version); + + gm_mcp_session_send_simple(GM_MCP_PACKAGE_SESSION(package), + "mcp-negotiate-can", "package", klass->name, "min-version", + min_v, "max-version", max_v, NULL); + + return FALSE; +} + +void +gm_mcp_negotiate_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields) { + gchar const *pname; + GmMcpPackageClass *pklass; + GmMcpNegotiate *negotiate = GM_MCP_NEGOTIATE(package); + PackageInfo *pinfo; + double version; + gchar const *min_v, *max_v; + gdouble cmin = 0.0, cmax = 0.0; + GList *elem; + + if (strcasecmp(suffix, "can") == 0) { + // Fields has package, min-version, max-version + pname = gm_mcp_find_value(fields, "package"); + pklass = gm_mcp_session_find_package_class(pname); + + if (pklass) { + min_v = gm_mcp_find_value(fields, "min-version"); + + if (min_v) { + cmin = g_ascii_strtod(min_v, NULL); + } + + max_v = gm_mcp_find_value(fields, "max-version"); + + if (max_v) { + cmax = g_ascii_strtod(max_v, NULL); + } + + version = gm_mcp_get_version(pklass->min_version, + pklass->max_version, cmin, cmax); + + if (version > 0.0) { + gm_debug_msg(DEBUG_MCP, "GmMcpNegotiate.HandleSimple: %s, " + "package is supported", pname); + pinfo = g_new(PackageInfo, 1); + pinfo->klass = pklass; + pinfo->version = version; + + negotiate->priv->packages = g_list_append( + negotiate->priv->packages, pinfo); + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpNegotiate.HandleSimple: %s, package " + "supported but wrong version!", pname); + } + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpNegotiate.HandleSimple: %s, package is not " + "supported!", pname); + } + } else if (strcasecmp(suffix, "end") == 0) { + gm_mcp_session_package_class_for_each(gm_mcp_negotiate_send_can, + (gpointer)(negotiate)); + + gm_mcp_session_send_simple(GM_MCP_PACKAGE_SESSION(negotiate), + "mcp-negotiate-end", NULL); + + gm_mcp_negotiate_fix_overrides(negotiate); + gm_mcp_negotiate_fix_depends(negotiate); + + for (elem = negotiate->priv->packages; elem; elem = elem->next) { + pinfo = (PackageInfo *)(elem->data); + gm_mcp_session_create_package(GM_MCP_PACKAGE_SESSION(negotiate), + pinfo->klass, pinfo->version); + g_free(pinfo); + } + + g_list_free(negotiate->priv->packages); + negotiate->priv->packages = NULL; + } +} diff --git a/gnoemoe/mcp/gm-mcp-negotiate.h b/gnoemoe/mcp/gm-mcp-negotiate.h new file mode 100644 index 0000000..eeba443 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-negotiate.h @@ -0,0 +1,57 @@ +#ifndef __GM_MCP_NEGOTIATE_H__ +#define __GM_MCP_NEGOTIATE_H__ + +#include <glib-object.h> +#include "gm-mcp-package.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_NEGOTIATE (gm_mcp_negotiate_get_type()) +#define GM_MCP_NEGOTIATE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiate)) +#define GM_MCP_NEGOTIATE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiate const)) +#define GM_MCP_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiateClass)) +#define GM_IS_MCP_NEGOTIATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_NEGOTIATE)) +#define GM_IS_MCP_NEGOTIATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_NEGOTIATE)) +#define GM_MCP_NEGOTIATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_NEGOTIATE, GmMcpNegotiateClass)) + +/* Private structure type */ +typedef struct _GmMcpNegotiatePrivate GmMcpNegotiatePrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpNegotiate GmMcpNegotiate; + +struct _GmMcpNegotiate { + GmMcpPackage parent; + + /*< private > */ + GmMcpNegotiatePrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpNegotiateClass GmMcpNegotiateClass; + +struct _GmMcpNegotiateClass { + GmMcpPackageClass parent_class; + + /* Signals + void (* proto) (GmMcpNegotiate *obj); */ +}; + +GType gm_mcp_negotiate_get_type(void) G_GNUC_CONST; +GmMcpNegotiate *gm_mcp_negotiate_new(void); + +G_END_DECLS +#endif /* __GM_MCP_NEGOTIATE_H__ */ diff --git a/gnoemoe/mcp/gm-mcp-package.c b/gnoemoe/mcp/gm-mcp-package.c new file mode 100644 index 0000000..fecbcb4 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-package.c @@ -0,0 +1,140 @@ +#include <glib-object.h> +#include "gm-mcp-session.h" +#include "gm-mcp-package.h" +#include "../gm-support.h" + +#define GM_MCP_PACKAGE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackagePrivate)) + +struct _GmMcpPackagePrivate { + GmMcpSession *session; + gdouble version; +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_package_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmMcpPackage, gm_mcp_package, G_TYPE_OBJECT) + +static void +gm_mcp_package_finalize(GObject *object) { + //GmMcpPackage *package = GM_MCP_PACKAGE(object); + G_OBJECT_CLASS(gm_mcp_package_parent_class)->finalize(object); +} + +static void +gm_mcp_package_class_init(GmMcpPackageClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_mcp_package_finalize; + + /*gm_mcp_package_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpPackageClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + klass->handle_simple = NULL; + klass->handle_multi = NULL; + klass->depends = NULL; + klass->overrides = NULL; + klass->name = NULL; + klass->min_version = 1.0; + klass->max_version = 1.0; + + g_type_class_add_private(object_class, sizeof(GmMcpPackagePrivate)); +} + +static void +gm_mcp_package_init(GmMcpPackage *obj) { + obj->priv = GM_MCP_PACKAGE_GET_PRIVATE(obj); + obj->priv->session = NULL; + obj->priv->version = 1.0; +} + +/* Public */ +GmMcpPackage * +gm_mcp_package_new() { + GmMcpPackage *obj = GM_MCP_PACKAGE(g_object_new(GM_TYPE_MCP_PACKAGE, NULL)); + return obj; +} + +void +gm_mcp_package_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields) { + return GM_MCP_PACKAGE_GET_CLASS(package)->handle_simple(package, + suffix, fields); +} + +gboolean +gm_mcp_package_can_handle_simple(GmMcpPackage *package) { + return (GM_MCP_PACKAGE_GET_CLASS(package)->handle_simple != NULL); +} + +gboolean +gm_mcp_package_handle_multi(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues) { + return GM_MCP_PACKAGE_GET_CLASS(package)->handle_multi(package, + data_tag, key, value, allValues); +} + +gboolean +gm_mcp_package_can_handle_multi(GmMcpPackage *package) { + return (GM_MCP_PACKAGE_GET_CLASS(package)->handle_multi != NULL); +} + +void +gm_mcp_package_set_session(GmMcpPackage *package, GObject *session) { + package->priv->session = GM_MCP_SESSION(session); +} + +GObject * +gm_mcp_package_get_session(GmMcpPackage *package) { + return G_OBJECT(package->priv->session); +} + +void +gm_mcp_package_set_version(GmMcpPackage *package, gdouble version) { + package->priv->version = version; +} + +gdouble +gm_mcp_package_get_version(GmMcpPackage *package) { + return package->priv->version; +} + +gchar const * +gm_mcp_package_get_name(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->name; +} + +GList const * +gm_mcp_package_get_depends(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->depends; +} + +GList const * +gm_mcp_package_get_overrides(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->overrides; +} + +gdouble +gm_mcp_package_get_min_version(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->min_version; +} + +gdouble +gm_mcp_package_get_max_version(GmMcpPackage *package) { + return GM_MCP_PACKAGE_GET_CLASS(package)->max_version; +} diff --git a/gnoemoe/mcp/gm-mcp-package.h b/gnoemoe/mcp/gm-mcp-package.h new file mode 100644 index 0000000..871fae9 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-package.h @@ -0,0 +1,89 @@ +#ifndef __GM_MCP_PACKAGE_H__ +#define __GM_MCP_PACKAGE_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_PACKAGE (gm_mcp_package_get_type()) +#define GM_MCP_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackage)) +#define GM_MCP_PACKAGE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_PACKAGE, GmMcpPackage const)) +#define GM_MCP_PACKAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackageClass)) +#define GM_IS_MCP_PACKAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_PACKAGE)) +#define GM_IS_MCP_PACKAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_PACKAGE)) +#define GM_MCP_PACKAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_PACKAGE, GmMcpPackageClass)) +#define GM_MCP_PACKAGE_SESSION(obj) (GM_MCP_SESSION( \ + gm_mcp_package_get_session(GM_MCP_PACKAGE(obj)))) + +/* Private structure type */ +typedef struct _GmMcpPackagePrivate GmMcpPackagePrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpPackage GmMcpPackage; + +struct _GmMcpPackage { + GObject parent; + + /*< private > */ + GmMcpPackagePrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpPackageClass GmMcpPackageClass; + +struct _GmMcpPackageClass { + GObjectClass parent_class; + + gchar *name; + gdouble min_version; + gdouble max_version; + GList *depends; + GList *overrides; + + void (* handle_simple)(GmMcpPackage *package, gchar *suffix, GList *fields); + gboolean (* handle_multi)(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues); + + /* Signals + void (* proto) (GmMcpPackage *obj); */ +}; + +/* Public */ +GType gm_mcp_package_get_type(void) G_GNUC_CONST; +GmMcpPackage *gm_mcp_package_new(); +void gm_mcp_package_set_session(GmMcpPackage *package, GObject *session); + +void gm_mcp_package_handle_simple(GmMcpPackage *package, gchar *suffix, + GList *fields); +gboolean gm_mcp_package_can_handle_simple(GmMcpPackage *package); + +gboolean gm_mcp_package_handle_multi(GmMcpPackage *package, gchar *data_tag, + gchar *key, gchar *value, GList *allValues); +gboolean gm_mcp_package_can_handle_multi(GmMcpPackage *package); + +void gm_mcp_package_set_version(GmMcpPackage *package, gdouble version); +gdouble gm_mcp_package_get_version(GmMcpPackage *package); +GObject *gm_mcp_package_get_session(GmMcpPackage *package); + +/* Class getters */ +gchar const *gm_mcp_package_get_name(GmMcpPackage *package); +GList const *gm_mcp_package_get_depends(GmMcpPackage *package); +GList const *gm_mcp_package_get_overrides(GmMcpPackage *package); +gdouble gm_mcp_package_get_min_version(GmMcpPackage *package); +gdouble gm_mcp_package_get_max_version(GmMcpPackage *package); + +G_END_DECLS +#endif /* __GM_MCP_PACKAGE_H__ */ diff --git a/gnoemoe/mcp/gm-mcp-session.c b/gnoemoe/mcp/gm-mcp-session.c new file mode 100644 index 0000000..afa46e0 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-session.c @@ -0,0 +1,501 @@ +#include <glib-object.h> +#include <string.h> + +#include "gm-mcp-session.h" +#include "gm-mcp-classes.h" +#include "gm-mcp.h" +#include "../gm-world.h" +#include "../gm-support.h" +#include "../gm-debug.h" +#include "../gm-options.h" + +#define GM_MCP_SESSION_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_MCP_SESSION, GmMcpSessionPrivate)) + +struct _GmMcpSessionPrivate { + GmWorld *world; + gchar *authkey; + gdouble version; + + GList *packages; + GList *multiline; +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_mcp_session_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmMcpSession, gm_mcp_session, G_TYPE_OBJECT) + +static void +gm_mcp_session_finalize(GObject *object) { + //GmMcpSession *obj = GM_MCP_SESSION(object); + + G_OBJECT_CLASS(gm_mcp_session_parent_class)->finalize(object); +} + +static void +gm_mcp_session_class_init(GmMcpSessionClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_mcp_session_finalize; + klass->available_packages = gm_mcp_classes_initialize(); + + /*gm_mcp_session_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmMcpSessionClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + g_type_class_add_private(object_class, sizeof(GmMcpSessionPrivate)); +} + +static void +gm_mcp_session_init(GmMcpSession *obj) { + obj->priv = GM_MCP_SESSION_GET_PRIVATE(obj); + obj->priv->packages = NULL; + obj->priv->version = -1; + obj->priv->authkey = NULL; + obj->priv->multiline = NULL; +} + +void +gm_mcp_session_handle_open(GmMcpSession *session, gchar *line) { + GList *fields = gm_mcp_process_key_values(line); + GmMcpPackage *p; + GmMcpPackageClass *pklass; + gchar const *min_version = gm_mcp_find_value(fields, "version"); + gchar const *max_version = gm_mcp_find_value(fields, "to"); + //gchar *minc, *maxc; + gdouble min_v, max_v, version; + + if (!(min_version && max_version)) { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOpen: invalid opening, " + "min version or max_version not found!"); + return; + } + + //minc = gm_fix_decimal_point(g_strdup(min_version), strlen(min_version)); + //maxc = gm_fix_decimal_point(g_strdup(max_version), strlen(max_version)); + + min_v = g_ascii_strtod(min_version, NULL); + max_v = g_ascii_strtod(max_version, NULL); + + //g_free(minc); + //g_free(maxc); + + gm_debug_msg(DEBUG_MCP, "MinMaxVersion: %fd %fd", min_v, max_v); + + version = gm_mcp_get_version(MCP_MIN_VERSION, MCP_MAX_VERSION, + min_v, max_v); + + if (version > 0.0) { + session->priv->authkey = gm_mcp_generate_auth_key(); + session->priv->version = version; + + gm_mcp_session_send_simple(session, "mcp", "authentication-key", + session->priv->authkey, "version", "2.1", "to", "2.1", NULL); + + pklass = gm_mcp_session_find_package_class("mcp-negotiate"); + p = gm_mcp_session_create_package(session, pklass, + pklass->max_version); + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOpen: mcp server version " + "does not match client version!"); + } +} + +void +gm_mcp_session_invoke_multiline_handle(GmMcpSession *session, gchar *data_tag, + gchar *key, gchar *value) { + GList *elem; + GmMcpPackage *p; + McpMultilineInfo *minfo; + gboolean handle_all = FALSE; + + // Now find the data_tag and emit the handle_multiline + for (elem = session->priv->multiline; elem; elem = elem->next) { + minfo = (McpMultilineInfo *)(elem->data); + + if (strcmp(minfo->data_tag, data_tag) == 0) { + p = minfo->package; + + if (gm_mcp_package_can_handle_multi(p)) { + if (key) { + if (!gm_mcp_package_handle_multi(p, data_tag, key, value, + NULL)) { + // Not handled a single value, store it for later + minfo->data = g_list_append(minfo->data, + g_strdup(value)); + } + } else { + handle_all = gm_mcp_package_handle_multi(p, data_tag, NULL, + NULL, minfo->data); + } + } + + if (!key) { + // This is the end, remove the minfo + g_free(minfo->data_tag); + g_free(minfo->key); + + // Lines should only be freed when allValues is not handled! + if (!handle_all) { + g_list_free_simple(minfo->data); + } else { + g_list_free(minfo->data); + } + + session->priv->multiline = g_list_remove( + session->priv->multiline, minfo); + g_free(minfo); + } + + return; + } + } +} + +void +gm_mcp_session_handle_multiline(GmMcpSession *session, gchar *line) { + gchar *data_tag, *key, *value, *ptr = line, *key_start; + + gm_string_skip_nonspace(&ptr); + + if (*ptr == '\0') { + return; + } + + data_tag = g_strndup(line, ptr - line); + gm_string_skip_space(&ptr); + + key_start = ptr; + + gm_string_skip_nonspace(&ptr); + + if (*ptr) { + return; + } + + key = g_strndup(key_start, (ptr - key_start) - 1); + value = g_strdup(ptr + 1); + + gm_mcp_session_invoke_multiline_handle(session, data_tag, key, value); + + g_free(data_tag); + g_free(key); + g_free(value); +} + +void +gm_mcp_session_handle_multiline_end(GmMcpSession *session, gchar *line) { + gchar *ptr = line; + gchar *data_tag; + + while (*ptr != '\0' && g_unichar_isspace(g_utf8_get_char(ptr))) { + ptr = g_utf8_next_char(ptr); + } + + data_tag = g_strndup(line, ptr - line); + gm_mcp_session_invoke_multiline_handle(session, data_tag, NULL, NULL); + g_free(data_tag); +} + +/* Public */ +GmMcpSession * +gm_mcp_session_new(GObject *world) { + GmMcpSession *obj = GM_MCP_SESSION(g_object_new(GM_TYPE_MCP_SESSION, NULL)); + + obj->priv->world = GM_WORLD(world); + return obj; +} + +GList const * +gm_mcp_session_get_packages(GmMcpSession *session) { + return session->priv->packages; +} + +GmMcpPackageClass * +gm_mcp_session_package_class_for_each(GmPackageClassFunc func, + gpointer user_data) { + GmMcpSessionClass *klass = g_type_class_peek(GM_TYPE_MCP_SESSION); + GList *item; + GmMcpPackageClass *package_klass; + + for (item = klass->available_packages; item; item = item->next) { + package_klass = GM_MCP_PACKAGE_CLASS(item->data); + + if (func(package_klass, user_data)) { + return package_klass; + } + } + + return NULL; +} + +void +gm_mcp_session_send_multiline(GmMcpSession *session, gchar const *data_tag, + gchar const *key, GList const *lines) { + gchar *msg = g_strconcat("#$#* ", data_tag, " ", key, ": ", NULL); + gchar *msg_line = NULL; + + while (lines) { + msg_line = g_strconcat(msg, (gchar *)(lines->data), NULL); + gm_world_sendln(session->priv->world, msg_line); + gm_world_log(session->priv->world, LOG_MCP_OUT, msg_line); + + g_free(msg_line); + lines = lines->next; + } + + g_free(msg); + msg_line = g_strconcat("#$#: ", data_tag, NULL); + + gm_world_sendln(session->priv->world, msg_line); + gm_world_log(session->priv->world, LOG_MCP_OUT, msg_line); + + g_free(msg_line); +} + +void +gm_mcp_session_send_simple(GmMcpSession *session, gchar const *pname, + gchar const *first_key, ...) { + GString *msg = NULL; + va_list args; + gchar const *key, *value; + gchar *esvalue; + + msg = g_string_new("#$#"); + msg = g_string_append(msg, pname); + + if (strcasecmp(pname, "mcp") != 0) { + msg = g_string_append(msg, " "); + msg = g_string_append(msg, session->priv->authkey); + } + + msg = g_string_append(msg, " "); + + if (first_key != NULL) { + va_start(args, first_key); + key = first_key; + + while (key) { + value = va_arg(args, gchar *); + + if (value) { + msg = g_string_append(msg, key); + msg = g_string_append(msg, ": "); + esvalue = gm_mcp_escape_if_needed(value); + msg = g_string_append(msg, esvalue); + g_free(esvalue); + msg = g_string_append(msg, " "); + key = va_arg(args, gchar *); + } else { + key = NULL; + } + } + + va_end(args); + } + + gm_world_sendln(session->priv->world, msg->str); + gm_world_log(session->priv->world, LOG_MCP_OUT, msg->str); + g_string_free(msg, TRUE); +} + +GmMcpPackage * +gm_mcp_session_has_package_of_class(GmMcpSession *session, + GmMcpPackageClass *klass) { + GList *item; + GmMcpPackage *package; + GType klass_type = G_TYPE_FROM_CLASS(klass); + + for (item = session->priv->packages; item; item = item->next) { + package = (GmMcpPackage *)(item->data); + + if (G_TYPE_FROM_CLASS(GM_MCP_PACKAGE_GET_CLASS(package)) == + klass_type) { + return package; + } + } + + return NULL; +} + +GmMcpPackage * +gm_mcp_session_find_package(GmMcpSession *session, gchar const *pname) { + GList *elem; + GmMcpPackage *package; + GmMcpPackageClass *package_klass; + + for (elem = session->priv->packages; elem; elem = elem->next) { + package = (GmMcpPackage *) (elem->data); + package_klass = GM_MCP_PACKAGE_GET_CLASS(package); + + if (strncasecmp(pname, package_klass->name, + strlen(package_klass->name)) == 0 && (strlen(pname) == + strlen(package_klass->name) || + pname[strlen(package_klass->name)] == '-')) { + return package; + } + } + + return NULL; +} + +GmMcpPackage * +gm_mcp_session_create_package(GmMcpSession *session, GmMcpPackageClass *klass, + gdouble version) { + GmMcpPackage *package; + + if (!klass) { + return NULL; + } + + if (!gm_mcp_session_has_package_of_class(session, klass)) { + package = g_object_new(G_TYPE_FROM_CLASS(G_OBJECT_CLASS(klass)), NULL); + + gm_mcp_package_set_session(package, G_OBJECT(session)); + gm_mcp_package_set_version(package, version); + + session->priv->packages = g_list_append(session->priv->packages, + package); + return package; + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.CreatePackage: package (%s) is " + "already registered for %s", klass->name, + gm_options_get(gm_world_options(session->priv->world), "name")); + return NULL; + } +} + +gboolean +gm_mcp_session_find_package_class_real(GmMcpPackageClass *klass, + gpointer user_data) { + gchar *name = (gchar *)(user_data); + return strcmp(name, klass->name) == 0; +} + +GmMcpPackageClass * +gm_mcp_session_find_package_class(gchar const *name) { + return gm_mcp_session_package_class_for_each( + &gm_mcp_session_find_package_class_real, (gpointer)(name)); +} + +void +gm_mcp_session_handle_oob(GmMcpSession *session, gchar *line) { + McpMessageInfo info; + GmMcpPackage *package; + McpMultilineInfo *minfo; + gchar *suffix; + gchar const *value, *full; + + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: %s", line); + + info.fields = NULL; + info.authkey = NULL; + info.name = NULL; + + if (strncmp(line, "mcp ", 4) == 0 && session->priv->version == -1) { + gm_mcp_session_handle_open(session, line + 3); + } else if (session->priv->version == -1) { + // No session, drop + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: No session found for %s, " + "ignoring out of bound command!", gm_options_get( + gm_world_options(session->priv->world), "name")); + } else if (strncmp(line, "* ", 2) == 0) { + // Multiline + gm_mcp_session_handle_multiline(session, line + 2); + } else if (strncmp(line, ": ", 2) == 0) { + // Multiline end + gm_mcp_session_handle_multiline_end(session, line + 2); + } else { + if (gm_mcp_parse_line(line, &info)) { + // Line is valid mcp + if (strcmp(info.authkey, session->priv->authkey) == 0) { + // Valid authentication + if ((package = gm_mcp_session_find_package(session, + info.name))) { + // Find package + if ((value = gm_mcp_find_multiline_tag(info.fields))) { + // This is multiline start! + if (gm_mcp_find_value(info.fields, + "_data-tag")) { + // This package requests a multiline opening! + minfo = g_new(McpMultilineInfo, 1); + minfo->key = g_strdup(value); + minfo->data_tag = g_strdup( + gm_mcp_find_value(info.fields, + "_data-tag")); + minfo->data = NULL; + minfo->package = package; + + session->priv->multiline = g_list_append( + session->priv->multiline, minfo); + + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: multiline " + "opening detected and stored " + "(data_tag = %s, key = %s)", + minfo->data_tag, + minfo->key); + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: multiline " + "opening detected, but no _data-tag specified, " + "ignore message"); + } + } + + if (gm_mcp_package_can_handle_simple(package)) { + full = gm_mcp_package_get_name(package); + if (strlen(full) < strlen(info.name)) { + suffix = info.name + (strlen(full) + 1); + } else { + suffix = NULL; + } + + gm_mcp_package_handle_simple(package, suffix, + info.fields); + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: can't handle simple message!"); + } + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: unsupported package " + "%s, ignore message", info.name); + } + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: invalid authentication " + "key %s instead of %s, ignore message", info.authkey, + session->priv->authkey); + } + } else { + gm_debug_msg(DEBUG_MCP, "GmMcpSession.HandleOob: invalid message " + "(could not parse), ignore message"); + } + + // Free the info + if (info.authkey) { + g_free(info.authkey); + } + + if (info.name) { + g_free(info.name); + } + if (info.fields) { + gm_mcp_destroy_fields(info.fields); + } + } +} + +GObject *gm_mcp_session_world(GmMcpSession *session) { + return G_OBJECT(session->priv->world); +} diff --git a/gnoemoe/mcp/gm-mcp-session.h b/gnoemoe/mcp/gm-mcp-session.h new file mode 100644 index 0000000..c10ef5e --- /dev/null +++ b/gnoemoe/mcp/gm-mcp-session.h @@ -0,0 +1,89 @@ +#ifndef __GM_MCP_SESSION_H__ +#define __GM_MCP_SESSION_H__ + +#include <glib-object.h> +#include "gm-mcp-package.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_MCP_SESSION (gm_mcp_session_get_type()) +#define GM_MCP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_MCP_SESSION, GmMcpSession)) +#define GM_MCP_SESSION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_MCP_SESSION, GmMcpSession const)) +#define GM_MCP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_MCP_SESSION, GmMcpSessionClass)) +#define GM_IS_MCP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_MCP_SESSION)) +#define GM_IS_MCP_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_MCP_SESSION)) +#define GM_MCP_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_MCP_SESSION, GmMcpSessionClass)) +#define GM_MCP_SESSION_WORLD(obj) (GM_WORLD(gm_mcp_session_world(obj))) + +typedef struct _McpMultilineInfo { + gchar *key; + gchar *data_tag; + GList *data; + GmMcpPackage *package; +} McpMultilineInfo; + +typedef gboolean (* GmPackageClassFunc) (GmMcpPackageClass *klass, + gpointer user_data); + +/* Private structure type */ +typedef struct _GmMcpSessionPrivate GmMcpSessionPrivate; + +/* + * Main object structure + */ +typedef struct _GmMcpSession GmMcpSession; + +struct _GmMcpSession { + GObject parent; + + /*< private > */ + GmMcpSessionPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmMcpSessionClass GmMcpSessionClass; + +struct _GmMcpSessionClass { + GObjectClass parent_class; + GList *available_packages; + + /* Signals + void (* proto) (GmMcpSession *obj); */ +}; + +GType gm_mcp_session_get_type(void) G_GNUC_CONST; +GmMcpSession *gm_mcp_session_new(GObject *world); + +GObject *gm_mcp_session_world(GmMcpSession *session); +GList const *gm_mcp_session_get_packages(GmMcpSession *session); +void gm_mcp_session_handle_oob(GmMcpSession *session, gchar *line); + +GmMcpPackage *gm_mcp_session_has_package_of_class(GmMcpSession *session, + GmMcpPackageClass *klass); +GmMcpPackageClass *gm_mcp_session_find_package_class(gchar const *name); +GmMcpPackage *gm_mcp_session_find_package(GmMcpSession *session, + gchar const *pname); +GmMcpPackageClass *gm_mcp_session_package_class_for_each( + GmPackageClassFunc func, gpointer user_data); + +GmMcpPackage *gm_mcp_session_create_package(GmMcpSession *session, + GmMcpPackageClass *klass, gdouble version); + +void gm_mcp_session_send_simple(GmMcpSession *session, gchar const *pname, + gchar const *first_key, ...); +void gm_mcp_session_send_multiline(GmMcpSession *session, gchar const *data_tag, + gchar const *key, GList const *lines); + +G_END_DECLS +#endif /* __GM_MCP_SESSION_H__ */ diff --git a/gnoemoe/mcp/gm-mcp.c b/gnoemoe/mcp/gm-mcp.c new file mode 100644 index 0000000..bb9b7c9 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp.c @@ -0,0 +1,263 @@ +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#include "gm-mcp.h" +#include "../gm-support.h" + +gdouble +gm_mcp_get_version(gdouble client_min, gdouble client_max, gdouble server_min, + gdouble server_max) { + if (client_max >= server_min && server_max >= client_min) { + if (client_max < server_max) { + return server_max; + } else { + return client_max; + } + } else { + return 0.0; + } +} + +gchar const * +gm_mcp_find_value(GList const *fields, gchar const *key) { + GmKeyValuePair *tmp; + + while (fields) { + tmp = (GmKeyValuePair *)(fields->data); + + if (strcasecmp(key, tmp->key) == 0) { + return tmp->value; + } + + fields = fields->next; + } + + return NULL; +} + +gchar * +gm_mcp_escape_if_needed(gchar const *line) { + GString *new_line; + gchar const *ptr = line; + gchar *result; + + if (*line == '\0') { + return g_strdup("\"\""); + } + + if (g_utf8_strchr(line, -1, ' ') || g_utf8_strchr(line, -1, '"') || + g_utf8_strchr(line, -1, '\\')) { + new_line = g_string_new("\""); + + while (*ptr != '\0') { + if (*ptr == '"' || *ptr == '\\') { + new_line = g_string_append_c(new_line, '\\'); + } + + new_line = g_string_append_c(new_line, *ptr); + ++ptr; + } + + new_line = g_string_append_c(new_line, '"'); + result = new_line->str; + g_string_free(new_line, FALSE); + + return result; + } else { + return g_strdup(line); + } +} + +gchar * +gm_mcp_un_escape(gchar *line) { + gchar *ptri = line, *ptrj = line; + gboolean shifted = FALSE; + + while (*ptri != '\0') { + if (*ptri == '\\') { + shifted = TRUE; + ++ptri; + } + + if (shifted) { + *ptrj = *ptri; + } + + ++ptrj; + ++ptri; + } + + *ptrj = '\0'; + return line; +} + +gchar * +gm_mcp_process_keyval(GList **info, gchar *line) { + gchar *keystart; + gchar *valuestart; + int keylen, valuelen; + GmKeyValuePair *newKeyValue; + + if (*line != ' ' || line[1] == '\0' || line[1] == ' ') { + return NULL; + } + + keystart = ++line; + gm_string_skip_till(&line, ": "); + + if (*line != ':') { + return NULL; + } + + keylen = line - keystart; + line = g_utf8_next_char(line); + + if (line[1] == '\0' || *line != ' ') { + return NULL; + } + + line = g_utf8_next_char(line); + + if (*line == ' ') { + return NULL; + } + + valuestart = line; + + if (*line == '"') { + ++valuestart; + ++line; + + while (*line != '\0' && *line != '"') { + if (*line == '\\') { + ++line; + } + + ++line; + } + + valuelen = line - valuestart; + + if (*line != '"') { + --valuelen; + } else { + ++line; + } + } else { + gm_string_skip_nonspace(&line); + valuelen = line - valuestart; + } + + newKeyValue = g_new(GmKeyValuePair, 1); + newKeyValue->key = g_strndup(keystart, keylen); + newKeyValue->value = gm_mcp_un_escape(g_strndup(valuestart, valuelen)); + + *info = g_list_append(*info, newKeyValue); + + return line; +} + +GList * +gm_mcp_process_key_values(gchar *line) { + GList *result = NULL; + + while (line && *line != '\0') { + line = gm_mcp_process_keyval(&result, line); + } + + return result; +} + +gboolean +gm_mcp_parse_line(gchar *line, McpMessageInfo *info) { + gchar *p = g_utf8_strchr(line, -1, ' '); + + if (!p || p == line) { + return FALSE; + } + + info->name = g_strndup(line, (p - line)); + line = p + 1; + + // Now there should be a authentication key! + p = g_utf8_strchr(line, -1, ' '); + + if (!p) { + p = line + strlen(line); + } + + info->authkey = g_strndup(line, (p - line)); + + // Now process the keyvals + info->fields = gm_mcp_process_key_values(p); + + return TRUE; +} + +gchar * +gm_mcp_generate_key(gint len) { + gchar *s = g_malloc(len + 1); + gchar ref[] = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; + gint n = strlen(ref); + gint i; + + srand((int) time(NULL)); + + for (i = 0; i < len; i++) { + s[i] = ref[rand() % n]; + } + + s[i] = 0; + return s; +} + +gchar * +gm_mcp_generate_data_tag() { + return gm_mcp_generate_key(10); +} + +gchar * +gm_mcp_generate_auth_key() { + return gm_mcp_generate_key(6); +} + +gchar const * +gm_mcp_find_multiline_tag(GList const *fields) { + GmKeyValuePair *data; + gunichar last; + + while (fields) { + data = (GmKeyValuePair *)(fields->data); + last = g_utf8_get_char(g_utf8_prev_char(data->key + strlen(data->key))); + + if (last == '*') { + return data->key; + } + + fields = fields->next; + } + + return NULL; +} + +void +gm_mcp_destroy_fields(GList *fields) { + GmKeyValuePair *tmp; + GList *elem = fields; + + while (elem) { + tmp = (GmKeyValuePair *) (elem->data); + + g_free(tmp->key); + g_free(tmp->value); + + g_free(elem->data); + elem->data = NULL; + + elem = elem->next; + } + + g_list_free(fields); +} diff --git a/gnoemoe/mcp/gm-mcp.h b/gnoemoe/mcp/gm-mcp.h new file mode 100644 index 0000000..821f8d0 --- /dev/null +++ b/gnoemoe/mcp/gm-mcp.h @@ -0,0 +1,28 @@ +#ifndef __GM_MCP_H__ +#define __GM_MCP_H__ + +#include <glib.h> +#include "gm-mcp.h" + +#define MCP_MIN_VERSION 2.1 +#define MCP_MAX_VERSION 2.1 + +typedef struct _McpMessageInfo { + gchar *name; + gchar *authkey; + GList *fields; +} McpMessageInfo; + +gdouble gm_mcp_get_version(gdouble client_min, gdouble client_max, + gdouble server_min, gdouble server_max); +gchar const *gm_mcp_find_value(GList const *fields, gchar const *key); +gchar *gm_mcp_escape_if_needed(gchar const *line); +gchar *gm_mcp_un_escape(gchar *line); +GList *gm_mcp_process_key_values(gchar *line); +gboolean gm_mcp_parse_line(gchar *line, McpMessageInfo *info); +gchar *gm_mcp_generate_data_tag(); +gchar *gm_mcp_generate_auth_key(); +gchar const *gm_mcp_find_multiline_tag(GList const *fields); +void gm_mcp_destroy_fields(GList *fields); + +#endif /* __GM_MCP_H__ */ diff --git a/gnoemoe/mcp/mcpinit.rb b/gnoemoe/mcp/mcpinit.rb new file mode 100755 index 0000000..1787e09 --- /dev/null +++ b/gnoemoe/mcp/mcpinit.rb @@ -0,0 +1,34 @@ +#!/usr/bin/ruby + +begin + defs = File.readlines(ARGV[0]) + defs.collect! {|elem| elem.chomp} + + fout = File.open(ARGV[1] + '.c', 'w') + + includes = ['<glib-object.h>'] + content = "GList *\ngm_mcp_classes_initialize() {\n\tGList *result = NULL;\n\n" + + defs.each do |line| + ucase = line.gsub(/[A-Z]+[a-z]+/) do |s| + s.upcase + '_' + end + + ucase.chop! + mcase = ucase.downcase.gsub('_', '-') + + includes << '"gm-mcp-' + mcase + '.h"' + content += "\tresult = g_list_append(result, \n\t\t\tg_type_class_ref(GM_TYPE_MCP_" + ucase + "));\n" + end + + includes.each {|inc| fout.write('#include ' + inc + "\n")} + fout.write("\n" + content + "\treturn result;\n}\n") + fout.close + + fout = File.open(ARGV[1] + '.h', 'w') + fout.write("GList *gm_mcp_classes_initialize();\n") + fout.close +rescue StandardError => boom + p boom + exit(1) +end diff --git a/gnoemoe/mcp/packages.defs b/gnoemoe/mcp/packages.defs new file mode 100644 index 0000000..fc2dfc8 --- /dev/null +++ b/gnoemoe/mcp/packages.defs @@ -0,0 +1,2 @@ +Negotiate +AwnsStatus diff --git a/gnoemoe/template.c b/gnoemoe/template.c new file mode 100644 index 0000000..9dbacca --- /dev/null +++ b/gnoemoe/template.c @@ -0,0 +1,59 @@ +#include <glib-object.h> +#include "gm-{template-}.h" + +#define GM_{TEMPLATE}_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Private)) + +struct _Gm{Template}Private { + +}; + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_{template_}_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(Gm{Template}, gm_{template_}, G_TYPE_OBJECT) + +static void +gm_{template_}_finalize(GObject *object) { + //Gm{Template} *obj = GM_{TEMPLATE}(object); + + G_OBJECT_CLASS(gm_{template_}_parent_class)->finalize(object); +} + +static void +gm_{template_}_class_init(Gm{Template}Class *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_{template_}_finalize; + + /*gm_{template_}_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(Gm{Template}Class, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + g_type_class_add_private(object_class, sizeof(Gm{Template}Private)); +} + +static void +gm_{template_}_init(Gm{Template} *obj) { + obj->priv = GM_{TEMPLATE}_GET_PRIVATE(obj); +} + +Gm{Template} * +gm_{template_}_new() { + Gm{Template} *obj = GM_{TEMPLATE}(g_object_new(GM_TYPE_{TEMPLATE}, NULL)); + + return obj; +} diff --git a/gnoemoe/template.h b/gnoemoe/template.h new file mode 100644 index 0000000..4123178 --- /dev/null +++ b/gnoemoe/template.h @@ -0,0 +1,56 @@ +#ifndef __GM_{TEMPLATE}_H__ +#define __GM_{TEMPLATE}_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_{TEMPLATE} (gm_{template_}_get_type()) +#define GM_{TEMPLATE}(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + GM_TYPE_{TEMPLATE}, Gm{Template})) +#define GM_{TEMPLATE}_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + GM_TYPE_{TEMPLATE}, Gm{Template} const)) +#define GM_{TEMPLATE}_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Class)) +#define GM_IS_{TEMPLATE}(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + GM_TYPE_{TEMPLATE})) +#define GM_IS_{TEMPLATE}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GM_TYPE_{TEMPLATE})) +#define GM_{TEMPLATE}_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + GM_TYPE_{TEMPLATE}, Gm{Template}Class)) + +/* Private structure type */ +typedef struct _Gm{Template}Private Gm{Template}Private; + +/* + * Main object structure + */ +typedef struct _Gm{Template} Gm{Template}; + +struct _Gm{Template} { + GObject parent; + + /*< private > */ + Gm{Template}Private *priv; +}; + +/* + * Class definition + */ +typedef struct _Gm{Template}Class Gm{Template}Class; + +struct _Gm{Template}Class { + GObjectClass parent_class; + + /* Signals + void (* proto) (Gm{Template} *obj); */ +}; + +GType gm_{template_}_get_type(void) G_GNUC_CONST; +Gm{Template} *gm_{template_}_new(void); + +G_END_DECLS +#endif /* __GM_{TEMPLATE}_H__ */ diff --git a/gnoemoe/widgets/Makefile.include b/gnoemoe/widgets/Makefile.include new file mode 100644 index 0000000..87fd9be --- /dev/null +++ b/gnoemoe/widgets/Makefile.include @@ -0,0 +1,10 @@ +## Process this file with automake to produce Makefile.in +widgetsdir = widgets + +gnoemoe_SOURCES += \ + $(widgetsdir)/gm-app-view.c $(widgetsdir)/gm-app-view.h \ + $(widgetsdir)/gm-world-view.c $(widgetsdir)/gm-world-view.h \ + $(widgetsdir)/gm-world-text-view.c $(widgetsdir)/gm-world-text-view.h \ + $(widgetsdir)/gm-world-input-view.c $(widgetsdir)/gm-world-input-view.h \ + $(widgetsdir)/gm-world-tab.c $(widgetsdir)/gm-world-tab.h \ + $(widgetsdir)/gm-text-scroller.c $(widgetsdir)/gm-text-scroller.h diff --git a/gnoemoe/widgets/gm-app-view.c b/gnoemoe/widgets/gm-app-view.c new file mode 100644 index 0000000..2b05ad8 --- /dev/null +++ b/gnoemoe/widgets/gm-app-view.c @@ -0,0 +1,1405 @@ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <libgnomeui/libgnomeui.h> +#include <libgnome/gnome-url.h> +#include <vte/vte.h> + +#include "../gm-debug.h" +#include "gm-app-view.h" +#include "../gm-ui.h" +#include "gm-world-view.h" +#include "../gm-world.h" +#include "../dialogs/gm-world-info-dialog.h" +#include "../dialogs/gm-worlds-list-dialog.h" +#include "../dialogs/gm-world-logs-dialog.h" +#include "../dialogs/gm-world-properties-dialog.h" +#include "../dialogs/gm-scripts-dialog.h" +#include "../dialogs/gm-preferences-dialog.h" +#include "gm-world-tab.h" + +//#include "if_world.h" +//#include "if_worlds_listing.h" +//#include "if_preferences.h" +//#include "if_mcpconsole.h" + +#include "../gm-pixbuf.h" +#include "../gm-support.h" +//#include "term.h" +//#include "world.h" +//#include "net.h" +//#include "main.h" + +void on_gm_app_view_destroy(GtkWidget * caller, gpointer user_data); +gboolean on_gm_app_view_focus_in(GtkWidget *widget, GdkEventFocus *event, + gpointer user_data); + +void on_gm_app_view_entry_find_activate(GtkEntry *entry, GmAppView *view); +void on_gm_app_view_entry_find_changed(GtkEditable *editable, + GmAppView *view); +gboolean on_gm_app_view_entry_find_key_press(GtkWidget *widget, + GdkEventKey *event, GmAppView *view); +void on_gm_app_view_button_find_close_clicked(GtkButton *button, + GmAppView *view); + +void on_gm_app_view_notebook_switch_page(GtkNotebook * notebook, + GtkNotebookPage * page, guint page_num, GmAppView *view); +gboolean on_gm_app_view_notebook_button_press(GtkNotebook *notebook, + GdkEventButton *event, GmAppView *view); +gboolean on_gm_app_view_notebook_button_release(GtkNotebook *notebook, + GdkEventButton *event, GmAppView *view); +void on_gm_app_view_check_button_search_direction_toggled( + GtkToggleButton *button, GmAppView *view); + +typedef struct _AccelInfo AccelInfo; +struct _AccelInfo { + int num; + GmAppView *view; +}; + +void on_gm_app_view_accel_switch_page(GtkAccelGroup * accelgroup, + GObject * arg1, guint arg2, GdkModifierType arg3, AccelInfo *info); +void on_gm_app_view_accel_switch_edit(GtkAccelGroup * accelgroup, + GObject * arg1, guint arg2, GdkModifierType arg3, AccelInfo *info); +void on_gm_app_view_accel_cycle_page(GtkAccelGroup * accelgroup, + GObject * arg1, guint arg2, GdkModifierType arg3, AccelInfo *info); + +void on_gm_app_view_world_added(GmApp *app, GmWorld *world, GmAppView *view); +void on_gm_app_view_world_removed(GmApp *app, GmWorld *world, GmAppView *view); +void on_gm_app_view_world_activate(GtkAction * action, GmWorld *world); +void on_gm_app_view_world_load(GmWorld *world, GmAppView *view); +void on_gm_app_view_world_unload(GmWorld *world, GmAppView *view); +void on_gm_app_view_world_activate_request(GmWorld *world, GmAppView *view); +void on_gm_app_view_world_name_changed(GmWorld *world, const gchar *name, + GmAppView *view); +void on_gm_app_view_world_state_changing(GmWorld *world, GmNetState state, + GmAppView *view); +void on_gm_app_view_world_active_changed(GmWorld *world, gboolean active, + GmAppView *view); + +#define GM_APP_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_APP_VIEW, GmAppViewPrivate)) + +typedef struct _GmAppViewWorldMenuItem GmAppViewWorldMenuItem; +struct _GmAppViewWorldMenuItem { + guint merge_id; + GtkAction *action; +}; + +void gm_app_view_destroy_world_menu_item(GmAppViewWorldMenuItem *item); + +struct _GmAppViewPrivate { + GmApp *application; + GtkUIManager *manager; + GtkActionGroup *sensitive_action_group; + GtkActionGroup *action_group; + GtkActionGroup *worlds_action_group; + GHashTable *world_menu_items; + + GtkNotebook *notebook; + GmWorld *active_world; + GtkMenuBar *menu; + + GtkEntry *entry_find; + GtkVBox *vbox_find; + GtkCheckButton *check_button_search_direction; + + gboolean drag_in_progress; + gint motion_notify_handler_id; + gint x_start; + gint y_start; + GdkCursor *cursor; +}; + +/* Signals */ + +/*enum { + NUM_SIGNALS +}; + +static guint app_view_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmAppView, gm_app_view, GTK_TYPE_WINDOW) + +static void +gm_app_view_finalize(GObject *object) { + GmAppView *view = GM_APP_VIEW(object); + + gm_scripts_dialog_fini(); + + g_hash_table_destroy(view->priv->world_menu_items); + G_OBJECT_CLASS(gm_app_view_parent_class)->finalize(object); +} + + +static void +gm_app_view_class_init(GmAppViewClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_app_view_finalize; + + g_type_class_add_private(object_class, sizeof(GmAppViewPrivate)); +} + +GtkWidget * +gm_app_view_create_menu(GmAppView *view) { + GError *error = NULL; + GtkActionGroup *action_group; + + view->priv->manager = gtk_ui_manager_new(); + + gtk_window_add_accel_group(GTK_WINDOW(view), + gtk_ui_manager_get_accel_group(view->priv->manager)); + + gtk_ui_manager_add_ui_from_file(view->priv->manager, + PACKAGE_DATA_DIR "/" PACKAGE "/ui/gm-ui.xml", &error); + + if (error) { + gm_debug_msg(DEBUG_DEFAULT, "Could not merge UI file"); + g_error_free(error); + } + + action_group = gtk_action_group_new("GmAppViewSensitiveActions"); + gtk_action_group_set_translation_domain(action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions(action_group, gm_sensitive_menu_entries, + G_N_ELEMENTS(gm_sensitive_menu_entries), view); + + gtk_ui_manager_insert_action_group(view->priv->manager, action_group, 0); + view->priv->sensitive_action_group = action_group; + + gtk_action_group_set_sensitive(action_group, TRUE); + + action_group = gtk_action_group_new("GmAppViewActions"); + gtk_action_group_set_translation_domain(action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions(action_group, gm_menu_entries, + G_N_ELEMENTS(gm_menu_entries), view); + + gtk_ui_manager_insert_action_group(view->priv->manager, action_group, 0); + view->priv->action_group = action_group; + + gtk_action_group_set_sensitive(action_group, FALSE); + + view->priv->worlds_action_group = + gtk_action_group_new("GmAppViewWorldsActions"); + gtk_action_group_set_translation_domain(view->priv->worlds_action_group, + GETTEXT_PACKAGE); + gtk_ui_manager_insert_action_group(view->priv->manager, + view->priv->worlds_action_group, 0); + + #ifndef HAVE_RUBY + gtk_widget_set_sensitive(gtk_ui_manager_get_widget(view->priv->manager, + "/MenuBar/ViewMenu/ViewScriptsMenu"), FALSE); + #endif + + return gtk_ui_manager_get_widget(view->priv->manager, "/MenuBar"); +} + +GtkWidget * +gm_app_view_create_search_box(GmAppView *view) { + GtkWidget *vbox = gtk_vbox_new(FALSE, 3); + GtkWidget *hbox = gtk_hbox_new(FALSE, 6); + GtkWidget *lbl = gtk_label_new(_("Find:")); + GtkWidget *entry = gtk_entry_new(); + GtkWidget *button = gtk_button_new(); + GtkWidget *hbox_button = gtk_hbox_new(FALSE, 2); + GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0.0, 0.0); + GtkWidget *check_button = + gtk_check_button_new_with_label(_("Search backwards")); + GtkWidget *button_close = gtk_button_new_from_stock(GTK_STOCK_CLOSE); + GtkWidget *separator = gtk_hseparator_new(); + + gtk_container_set_border_width(GTK_CONTAINER(hbox), 3); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), separator, FALSE, TRUE, 0); + + gtk_container_add(GTK_CONTAINER(button), align); + gtk_container_add(GTK_CONTAINER(align), hbox_button); + gtk_box_pack_start(GTK_BOX(hbox_button), gtk_image_new_from_stock( + GTK_STOCK_FIND, GTK_ICON_SIZE_BUTTON), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox_button), + gtk_label_new(_("Find next")), FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(hbox), lbl, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), check_button, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(hbox), button_close, FALSE, FALSE, 0); + + view->priv->vbox_find = GTK_VBOX(vbox); + view->priv->entry_find = GTK_ENTRY(entry); + view->priv->check_button_search_direction = GTK_CHECK_BUTTON(check_button); + + gtk_widget_show_all(vbox); + gtk_widget_hide(vbox); + + g_signal_connect(entry, "activate", + G_CALLBACK(on_gm_app_view_entry_find_activate), view); + g_signal_connect(entry, "changed", + G_CALLBACK(on_gm_app_view_entry_find_changed), view); + g_signal_connect(entry, "key_press_event", + G_CALLBACK(on_gm_app_view_entry_find_key_press), view); + + g_signal_connect(button_close, "clicked", + G_CALLBACK(on_gm_app_view_button_find_close_clicked), view); + g_signal_connect(check_button, "toggled", + G_CALLBACK(on_gm_app_view_check_button_search_direction_toggled), + view); + + return vbox; +} + +void +gm_app_view_create_keybindings(GmAppView *view) { + GtkAccelGroup *grp; + GClosure *closure; + int i; + gchar num[2] = { 0, 0 }; + AccelInfo *info; + + grp = gtk_accel_group_new(); + + // Setting up Alt-1/Ctrl-1 -> Alt-9/Ctrl-9 accelerators + for (i = 0; i < 9; i++) { + info = g_new0(AccelInfo, 1); + info->num = i; + info->view = view; + + num[0] = '1' + i; + closure = g_cclosure_new(G_CALLBACK(on_gm_app_view_accel_switch_page), + info, NULL); + gtk_accel_group_connect(grp, gdk_keyval_from_name(num), GDK_MOD1_MASK, + GTK_ACCEL_VISIBLE, closure); + g_closure_unref(closure); + + closure = g_cclosure_new(G_CALLBACK(on_gm_app_view_accel_switch_edit), + info, NULL); + gtk_accel_group_connect(grp, gdk_keyval_from_name(num), + GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE, closure); + g_closure_unref(closure); + } + + info = g_new0(AccelInfo, 1); + info->num = 1; + info->view = view; + + // Next page + closure = g_cclosure_new(G_CALLBACK(on_gm_app_view_accel_cycle_page), + info, NULL); + gtk_accel_group_connect(grp, GDK_Page_Up, GDK_CONTROL_MASK, + GTK_ACCEL_VISIBLE, closure); + g_closure_unref(closure); + + info = g_new0(AccelInfo, 1); + info->num = -1; + info->view = view; + + // Previous page + closure = g_cclosure_new(G_CALLBACK(on_gm_app_view_accel_cycle_page), + info, NULL); + gtk_accel_group_connect(grp, GDK_Page_Down, GDK_CONTROL_MASK, + GTK_ACCEL_VISIBLE, closure); + g_closure_unref(closure); + + gtk_window_add_accel_group(GTK_WINDOW(view), grp); +} + + +static void +gm_app_view_init(GmAppView *view) { + GtkWidget *menu; + GtkWidget *search; + GtkWidget *vbox; + GtkWidget *note; + + view->priv = GM_APP_VIEW_GET_PRIVATE(view); + + vbox = gtk_vbox_new(FALSE, 3); + + gtk_widget_show(vbox); + gtk_container_add(GTK_CONTAINER(view), vbox); + + menu = gm_app_view_create_menu(view); + gtk_widget_show(menu); + gtk_box_pack_start(GTK_BOX(vbox), menu, FALSE, TRUE, 0); + + search = gm_app_view_create_search_box(view); + gtk_box_pack_start(GTK_BOX(vbox), search, FALSE, TRUE, 0); + + note = gtk_notebook_new(); + gtk_widget_show(note); + gtk_container_set_border_width(GTK_CONTAINER(note), 3); + gtk_notebook_set_show_border(GTK_NOTEBOOK(note), FALSE); + GTK_WIDGET_UNSET_FLAGS(note, GTK_CAN_FOCUS); + gtk_widget_add_events(GTK_WIDGET(note), GDK_BUTTON1_MOTION_MASK); + gtk_box_pack_start(GTK_BOX(vbox), note, TRUE, TRUE, 0); + gm_app_view_create_keybindings(view); + + view->priv->menu = GTK_MENU_BAR(menu); + view->priv->notebook = GTK_NOTEBOOK(note); + + gtk_window_set_title(GTK_WINDOW(view), "GnoeMoe"); + gtk_window_set_icon(GTK_WINDOW(view), gm_pixbuf_get("gnoemoe_logo.svg")); + + g_signal_connect(view, "destroy", G_CALLBACK(on_gm_app_view_destroy), NULL); + g_signal_connect(view, "focus_in_event", + G_CALLBACK(on_gm_app_view_focus_in), NULL); + + /* Signals for tab reordering */ + g_signal_connect_after(note, "switch-page", + G_CALLBACK(on_gm_app_view_notebook_switch_page), view); + g_signal_connect(note, "button-press-event", + G_CALLBACK(on_gm_app_view_notebook_button_press), view); + g_signal_connect(note, "button-release-event", + G_CALLBACK(on_gm_app_view_notebook_button_release), view); + + view->priv->world_menu_items = g_hash_table_new_full(g_direct_hash, + g_direct_equal, NULL, + (GDestroyNotify)gm_app_view_destroy_world_menu_item); + + view->priv->drag_in_progress = FALSE; + view->priv->motion_notify_handler_id = 0; + view->priv->x_start = 0; + view->priv->y_start = 0; + view->priv->cursor = NULL; + + gm_scripts_dialog_init(); +} + + +// Public functions +GmAppView * +gm_app_view_new(GmApp *application) { + GmAppView *view = GM_APP_VIEW(g_object_new(GM_TYPE_APP_VIEW, NULL)); + gboolean dir; + + view->priv->application = application; + view->priv->active_world = NULL; + + // Toggle search direction + dir = gm_options_get_int(gm_app_options(application), "search_direction") + == 1; + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( + view->priv->check_button_search_direction), dir); + + g_signal_connect(application, "world_added", + G_CALLBACK(on_gm_app_view_world_added), view); + g_signal_connect(application, "world_removed", + G_CALLBACK(on_gm_app_view_world_removed), view); + + return view; +} + +const GmApp * +gm_app_view_application(GmAppView *view) { + return view->priv->application; +} + +void +gm_app_view_destroy_world_menu_item(GmAppViewWorldMenuItem *item) { + g_object_unref(item->action); + g_free(item); +} + +void +on_gm_app_view_world_removed(GmApp *app, GmWorld *world, GmAppView *view) { + GmAppViewWorldMenuItem *item = (GmAppViewWorldMenuItem *) + g_hash_table_lookup(view->priv->world_menu_items, world); + + gtk_ui_manager_remove_ui(view->priv->manager, item->merge_id); + gtk_action_group_remove_action(view->priv->worlds_action_group, + item->action); + + g_hash_table_remove(view->priv->world_menu_items, world); +} + +void +gm_app_view_set_sensitivity(GmAppView *view, gboolean sens) { + gtk_action_group_set_sensitive(view->priv->action_group, sens); + + if (sens) { + /* TODO: set world info menu item not sensitive if no world info avail + First fix MCP + gtk_widget_set_sensitive(if_main_get_widget("mnuWorldInfo"), sens); */ + } else { + gtk_widget_hide(GTK_WIDGET(view->priv->vbox_find)); + } +} + +GmWorldView * +gm_app_view_active_world_view(GmAppView *view) { + gint n; + + n = gtk_notebook_get_current_page(view->priv->notebook); + + if (n == -1) { + return NULL; + } + + return GM_WORLD_VIEW(gtk_notebook_get_nth_page(view->priv->notebook, n)); +} + +GmWorld * +gm_app_view_active_world(GmAppView *view) { + GmWorldView *world_view = gm_app_view_active_world_view(view); + + if (world_view != NULL) { + return gm_world_view_world(world_view); + } else { + return NULL; + } +} + +void +gm_app_view_update_title(GmAppView *view) { + gchar *title; + GmWorld *world = gm_app_view_active_world(view); + + if (world == NULL) { + gtk_window_set_title(GTK_WINDOW(view), "GnoeMoe"); + } else { + title = g_strconcat(gm_world_name(world), " - GnoeMoe", NULL); + gtk_window_set_title(GTK_WINDOW(view), title); + g_free(title); + } +} + +GtkWidget * +gm_app_view_container_item(GtkContainer *cnt, GType type) { + GList *childFirst = gtk_container_get_children(cnt); + GList *child; + GtkWidget *result = NULL; + + for (child = childFirst; child; child = child->next) { + if (G_TYPE_CHECK_INSTANCE_TYPE(child->data, type)) { + result = GTK_WIDGET(child->data); + break; + } else if (GTK_IS_CONTAINER(child->data)) { + if ((result = gm_app_view_container_item( + GTK_CONTAINER(child->data), type))) { + break; + } + } + } + + g_list_free(childFirst); + return result; +} + +void +gm_app_view_update_connect_button(GmAppView *view, gboolean connected) { + GtkImageMenuItem *img = GTK_IMAGE_MENU_ITEM( + gtk_ui_manager_get_widget(view->priv->manager, + "/MenuBar/WorldMenu/WorldConnectMenu")); + GtkLabel *label = GTK_LABEL(gm_app_view_container_item(GTK_CONTAINER(img), + GTK_TYPE_LABEL)); + GtkWidget *im; + + if (!connected) { + im = gtk_image_new_from_stock("gtk-network", GTK_ICON_SIZE_MENU); + gtk_label_set_text(label, _("Connect")); + } else { + im = gtk_image_new_from_stock("gtk-stop", GTK_ICON_SIZE_MENU); + gtk_label_set_text(label, _("Disconnect")); + } + + gtk_image_menu_item_set_image(img, im); + gtk_widget_show(im); +} + +void +gm_app_view_worlds_unloaded(GmAppView *view) { + gm_app_view_set_sensitivity(view, FALSE); + gm_app_view_update_connect_button(view, FALSE); + gm_app_view_update_title(view); +} + +void +gm_app_view_worlds_loaded(GmAppView *view) { + gm_app_view_set_sensitivity(view, TRUE); +} + +/* TODO: move to custom widget thingie, and put control in gm-app instead + of gm-app-view +static gboolean +have_tray(void) { + Screen *xscreen = DefaultScreenOfDisplay(gdk_display); + Atom selection_atom; + char *selection_atom_name; + + selection_atom_name = g_strdup_printf("_NET_SYSTEM_TRAY_S%d", + XScreenNumberOfScreen(xscreen)); + selection_atom = XInternAtom(DisplayOfScreen(xscreen), + selection_atom_name, False); + g_free(selection_atom_name); + + if (XGetSelectionOwner(DisplayOfScreen(xscreen), selection_atom)) { + return TRUE; + } else { + return FALSE; + } +} + +static gboolean +tray_flash_restore_func(gpointer data) { + switch (app_tray_info.iconnr) { + case TRAY_ICON_ACTIVE: + gtk_image_set_from_pixbuf(GTK_IMAGE(app_tray_info.image), + gnoe_pixbuf_get("tray/active.svg")); + break; + case TRAY_ICON_DEFAULT: + gtk_image_set_from_pixbuf(GTK_IMAGE(app_tray_info.image), + gnoe_pixbuf_get("tray/default.svg")); + break; + case TRAY_ICON_NOTIFY: + gtk_image_set_from_pixbuf(GTK_IMAGE(app_tray_info.image), + gnoe_pixbuf_get("tray/notify.svg")); + break; + } + + app_tray_info.flash_timeout = -1; + return FALSE; +} + +void +tray_activate() { + if (app_tray_info.flash_timeout != 0) { + g_source_remove(app_tray_info.flash_timeout); + } + + gtk_image_set_from_pixbuf(GTK_IMAGE(app_tray_info.image), + gnoe_pixbuf_get("tray/activate.svg")); + app_tray_info.flash_timeout = g_timeout_add(1000, tray_flash_restore_func, NULL); +} + +void +tray_set_notify(gchar *text) { + gboolean active = gtk_window_is_active(GTK_WINDOW(wndMain)); + + if (!active) { + app_tray_info.iconnr = TRAY_ICON_NOTIFY; + tray_flash_restore_func(NULL); + + egg_tray_icon_send_message(app_tray_info.icon, 2000, text, g_utf8_strlen(text, -1)); + gtk_tooltips_set_tip(app_tray_info.tooltips, app_tray_info.event_box, text, text); + } +} + +void +tray_update() { + GList *wlds; + world *wld; + gchar *tmp = NULL, *tmp2 = NULL; + gboolean active = gtk_window_is_active(GTK_WINDOW(wndMain)); + + if (!active) { + for (wlds = world_get_worlds(); wlds; wlds = wlds->next) { + wld = (world *)(wlds->data); + if (wld->loaded && wld->inactive > 0) { + if (tmp == NULL) { + tmp = g_strdup_printf(_("Activity in:\n%s (%d)"), options_get_str( + wld->settings, "name"), wld->inactive); + } else { + tmp2 = g_strdup_printf("%s, %s (%d)", tmp, options_get_str( + wld->settings, "name"), wld->inactive); + g_free(tmp); + tmp = g_strdup(tmp2); + g_free(tmp2); + tmp2 = NULL; + } + } + } + + if (tmp) { + app_tray_info.iconnr = TRAY_ICON_ACTIVE; + tray_activate(); + } else if (app_tray_info.iconnr != TRAY_ICON_NOTIFY) { + app_tray_info.iconnr = TRAY_ICON_DEFAULT; + tray_flash_restore_func(NULL); + } + + if (app_tray_info.iconnr != TRAY_ICON_NOTIFY) { + gtk_tooltips_set_tip(app_tray_info.tooltips, app_tray_info.event_box, tmp, tmp); + } + + g_free(tmp); + } else { + app_tray_info.iconnr = TRAY_ICON_DEFAULT; + tray_flash_restore_func(NULL); + gtk_tooltips_set_tip(app_tray_info.tooltips, app_tray_info.event_box, NULL, NULL); + } +} + +void +if_main_show_hide(gboolean show) { + world *wld; + + if (show || !gtk_window_is_active(GTK_WINDOW(wndMain))) { + gtk_widget_show(wndMain); + gtk_window_present(GTK_WINDOW(wndMain)); + wld = world_get_active(); + + if (wld) { + wld->inactive = 0; + } + + tray_update(); + } else { + gtk_widget_hide(wndMain); + } +}*/ + +void +gm_app_view_update_world_info(gboolean hasInfo) { + /* TODO: do this some other way */ + /*GtkWidget *image; + world *wld = world_get_active(); + gtk_widget_set_sensitive(if_main_get_widget("mnuWorldInfo"), hasInfo); + + if (options_get_str(wld->settings, "logo")) { + image = gtk_image_new_from_pixbuf(gnoe_pixbuf_get_at_size(options_get_str(wld->settings, "logo"), 16, 16)); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(wld->widgets.menuItem), image); + }*/ +} + +GmWorldView * +gm_app_view_world_view_from_world(GmAppView *view, GmWorld *world) { + GtkNotebook *book = view->priv->notebook; + GmWorldView *current_view; + gint i; + + for (i = 0; i < gtk_notebook_get_n_pages(book); i++) { + current_view = GM_WORLD_VIEW(gtk_notebook_get_nth_page(book, i)); + if (gm_world_view_world(current_view) == world) { + return current_view; + } + } + + return NULL; +} + +/* Callbacks */ + +void +on_gm_app_view_destroy(GtkWidget * caller, gpointer user_data) { + gtk_main_quit(); +} + +void +on_gm_app_view_world_close(GtkMenuItem * menuitem, GmAppView *view) { + GmWorld *world = gm_app_view_active_world(view); + + if (world) { + gm_world_unload(world); + } +} + +void +on_gm_app_view_world_close_from_tab(GmWorldTab *tab, GmWorld *world) { + gm_world_unload(world); +} + +void +on_gm_app_view_world_name_changed(GmWorld *world, const gchar *name, + GmAppView *view) { + GmAppViewWorldMenuItem *item = (GmAppViewWorldMenuItem *) + g_hash_table_lookup(view->priv->world_menu_items, world); + g_object_set(G_OBJECT(item->action), "label", name, NULL); + + if (world == gm_app_view_active_world(view)) { + gm_app_view_update_title(view); + } +} + +void +on_gm_app_view_world_added(GmApp *app, GmWorld *world, GmAppView *view) { + static guint id = 0; + GmAppViewWorldMenuItem *item = g_new0(GmAppViewWorldMenuItem, 1); + gchar *name = g_strdup_printf("WorldItem%d", id); + gchar *tooltip = g_strconcat(_("Open world "), gm_world_name(world), NULL); + + id++; + + /* TODO: add custom icon from logo */ + item->merge_id = gtk_ui_manager_new_merge_id(view->priv->manager); + item->action = gtk_action_new(name, gm_world_name(world), tooltip, NULL); + + gtk_action_group_add_action(view->priv->worlds_action_group, item->action); + gtk_ui_manager_add_ui(view->priv->manager, item->merge_id, + "/MenuBar/WorldMenu/WorldMenuAdditions", name, name, + GTK_UI_MANAGER_MENUITEM, FALSE); + + g_signal_connect(item->action, "activate", + G_CALLBACK(on_gm_app_view_world_activate), world); + g_signal_connect(world, "load", + G_CALLBACK(on_gm_app_view_world_load), view); + g_signal_connect(world, "unload", + G_CALLBACK(on_gm_app_view_world_unload), view); + g_signal_connect(world, "activate_request", + G_CALLBACK(on_gm_app_view_world_activate_request), view); + g_signal_connect(world, "name_changed", + G_CALLBACK(on_gm_app_view_world_name_changed), view); + g_signal_connect(world, "state_changing", + G_CALLBACK(on_gm_app_view_world_state_changing), view); + g_signal_connect(world, "active_changed", + G_CALLBACK(on_gm_app_view_world_active_changed), view); + + g_hash_table_insert(view->priv->world_menu_items, world, item); + g_free(name); + g_free(tooltip); +} + +void +on_gm_app_view_world_state_changing(GmWorld *world, GmNetState state, + GmAppView *view) { + if (world == gm_app_view_active_world(view)) { + switch (state) { + case GM_NET_STATE_CONNECTED: + gm_app_view_update_connect_button(view, TRUE); + break; + case GM_NET_STATE_DISCONNECTED: + gm_app_view_update_connect_button(view, FALSE); + break; + default: + break; + } + } +} + +void +on_gm_app_view_world_load(GmWorld *world, GmAppView *view) { + GtkWidget *world_view = gm_world_view_new(world); + GmWorldTab *tab = gm_world_tab_new(world); + + g_signal_connect(tab, "close", + G_CALLBACK(on_gm_app_view_world_close_from_tab), world); + + gtk_notebook_append_page(view->priv->notebook, world_view, + GTK_WIDGET(tab)); + gtk_widget_show_all(world_view); + + gm_world_view_set_userlist_width(GM_WORLD_VIEW(world_view), + gm_options_get_int(gm_world_options(world), "pane_position")); + + if (gtk_notebook_get_n_pages(view->priv->notebook) == 1) { + gm_app_view_worlds_loaded(view); + } +} + +void +on_gm_app_view_world_unload(GmWorld *world, GmAppView *view) { + GmWorldView *world_view = gm_app_view_world_view_from_world(view, world); + + gtk_notebook_remove_page(view->priv->notebook, + gtk_notebook_page_num(view->priv->notebook, GTK_WIDGET(world_view))); + + if (gtk_notebook_get_n_pages(view->priv->notebook) == 0) { + gm_app_view_worlds_unloaded(view); + } +} + +void +on_gm_app_view_world_activate_request(GmWorld *world, GmAppView *view) { + gm_world_set_active(world, TRUE); +} + +void +on_gm_app_view_world_quit(GtkMenuItem * menuitem, GmAppView *view) { + gtk_widget_destroy(GTK_WIDGET(view)); +} + +void +on_gm_app_view_edit_worlds(GtkMenuItem * menuitem, GmAppView *view) { + gm_worlds_list_dialog_run(); +} + +void +on_gm_app_view_accel_switch_page(GtkAccelGroup * accelgroup, GObject * arg1, + guint arg2, GdkModifierType arg3, AccelInfo *info) { + + gtk_notebook_set_current_page(info->view->priv->notebook, info->num); +} + +void +on_gm_app_view_accel_switch_edit(GtkAccelGroup * accelgroup, GObject * arg1, + guint arg2, GdkModifierType arg3, AccelInfo *info) { + GmWorld *world = gm_app_view_active_world(info->view); + + if (world) { + gtk_notebook_set_current_page(info->view->priv->notebook, info->num); + } +} + +void +on_gm_app_view_accel_cycle_page(GtkAccelGroup * accelgroup, GObject * arg1, + guint arg2, GdkModifierType arg3, AccelInfo *info) { + GtkNotebook *note = info->view->priv->notebook; + int p = gtk_notebook_get_current_page(note) + info->num; + + if (p < 0) { + gtk_notebook_set_current_page(note, gtk_notebook_get_n_pages(note) - 1); + } else if (p > gtk_notebook_get_n_pages(note) - 1) { + gtk_notebook_set_current_page(note, 0); + } else { + gtk_notebook_set_current_page(note, p); + } +} + +void +on_gm_app_view_world_activate(GtkAction * action, GmWorld *world) { + gm_world_load(world); +} + +GmWorldViewSearchFlags +gm_app_view_search_flags(GmAppView *view) { + gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + view->priv->check_button_search_direction)); + + if (active) { + return GM_WORLD_VIEW_SEARCH_BACKWARDS; + } else { + return GM_WORLD_VIEW_SEARCH_FORWARDS; + } +} + +gboolean +gm_app_view_find_first(GmAppView *view) { + GmWorldView *world_view = gm_app_view_active_world_view(view); + GdkColor red; + GtkEntry *entry = view->priv->entry_find; + + if (world_view) { + gdk_color_parse ("#ff6666", &red); + //gdk_color_parse ("white", &white); + + if (g_utf8_strlen(gtk_entry_get_text(entry), -1) == 0 || + gm_world_view_find_first(world_view, gtk_entry_get_text(entry), + gm_app_view_search_flags(view))) { + gtk_widget_modify_base(GTK_WIDGET(entry), GTK_STATE_NORMAL, NULL); + } else { + gtk_widget_modify_base(GTK_WIDGET(entry), GTK_STATE_NORMAL, &red); + } + } + + return FALSE; +} + +void +on_gm_app_view_entry_find_changed(GtkEditable *editable, GmAppView *view) { + g_idle_add((GSourceFunc)gm_app_view_find_first, view); +} + +void +on_gm_app_view_entry_find_activate(GtkEntry *entry, GmAppView *view) { + on_gm_app_view_edit_find_next(NULL, view); +} + +void +gm_app_view_show_find_box(GmAppView *view) { + GmWorldView *world_view; + int i, n; + + if (!GTK_WIDGET_VISIBLE(view->priv->vbox_find)) { + n = gtk_notebook_get_n_pages(view->priv->notebook); + + for (i = 0; i < gtk_notebook_get_n_pages(view->priv->notebook); i++) { + world_view = GM_WORLD_VIEW(gtk_notebook_get_nth_page( + view->priv->notebook, i)); + + gm_world_view_scroll_end_prepare(world_view); + } + + gtk_widget_show(GTK_WIDGET(view->priv->vbox_find)); + gm_do_events(); + + for (i = 0; i < gtk_notebook_get_n_pages(view->priv->notebook); i++) { + world_view = GM_WORLD_VIEW(gtk_notebook_get_nth_page( + view->priv->notebook, i)); + + gm_world_view_scroll_end(world_view); + } + } +} + +void +on_gm_app_view_edit_find(GtkMenuItem * menuitem, GmAppView *view) { + GtkWidget *entry = GTK_WIDGET(view->priv->entry_find); + + gm_app_view_show_find_box(view); + gtk_widget_grab_focus(entry); + gtk_widget_modify_base(entry, GTK_STATE_NORMAL, NULL); +} + +void +on_gm_app_view_edit_find_next(GtkMenuItem * menuitem, GmAppView *view) { + gm_app_view_show_find_box(view); + + gm_world_view_find_next(gm_app_view_active_world_view(view), + gtk_entry_get_text(view->priv->entry_find), + gm_app_view_search_flags(view)); +} + +void +on_gm_app_view_button_find_clicked(GtkButton *button, gpointer user_data) { + on_gm_app_view_edit_find_next(NULL, NULL); +} + +gboolean +on_gm_app_view_entry_find_key_press(GtkWidget *widget, GdkEventKey *event, + GmAppView *view) { + if (event->keyval == GDK_Escape) { + on_gm_app_view_button_find_close_clicked(NULL, view); + gtk_widget_grab_focus(GTK_WIDGET(gm_app_view_active_world_view(view))); + return TRUE; + } else { + return FALSE; + } +} + +void +on_gm_app_view_button_find_close_clicked(GtkButton *button, + GmAppView *view) { + GmWorldView *world_view = gm_app_view_active_world_view(view); + + gtk_widget_hide(GTK_WIDGET(view->priv->vbox_find)); + + if (world_view) { + gm_world_view_set_focus(world_view); + } +} + +void +on_gm_app_view_check_button_search_direction_toggled(GtkToggleButton *button, + GmAppView *view) { + gboolean active = gtk_toggle_button_get_active(button); + + gm_options_set_int(gm_app_options(view->priv->application), + "search_direction", active ? 1 : 0); +} + +void +on_gm_app_view_edit_preferences(GtkMenuItem * menuitem, GmAppView *view) { + gm_preferences_dialog_run(); +} + +/* Tab moving from gedit */ + +gint +gm_app_view_find_tab_num_at_pos(GmAppView *view, gint abs_x, + gint abs_y) { + GtkNotebook *notebook = view->priv->notebook; + GtkPositionType tab_pos; + int page_num = 0; + GtkWidget *page; + + tab_pos = gtk_notebook_get_tab_pos(notebook); + + if (notebook->first_tab == NULL) { + return -1; + } + + /* For some reason unfullscreen + quick click can + cause a wrong click event to be reported to the tab */ + /*if (!is_in_notebook_window(notebook, abs_x, abs_y)) { + return -1; + }*/ + + while ((page = gtk_notebook_get_nth_page(notebook, page_num)) != NULL) { + GtkWidget *tab; + gint max_x, max_y; + gint x_root, y_root; + + tab = gtk_notebook_get_tab_label(notebook, page); + g_return_val_if_fail(tab != NULL, -1); + + if (!GTK_WIDGET_MAPPED(GTK_WIDGET(tab))) { + ++page_num; + continue; + } + + gdk_window_get_origin(GDK_WINDOW(tab->window), &x_root, &y_root); + max_x = x_root + tab->allocation.x + tab->allocation.width; + max_y = y_root + tab->allocation.y + tab->allocation.height; + + if (((tab_pos == GTK_POS_TOP) || (tab_pos == GTK_POS_BOTTOM)) && + (abs_x <= max_x)) { + return page_num; + } else if (((tab_pos == GTK_POS_LEFT) || (tab_pos == GTK_POS_RIGHT)) && + (abs_y <= max_y)) { + return page_num; + } + + ++page_num; + } + + return -1; +} + +void +gm_app_view_drag_stop(GmAppView *view) { + view->priv->drag_in_progress = FALSE; + + if (view->priv->motion_notify_handler_id != 0) { + g_signal_handler_disconnect(G_OBJECT(view->priv->notebook), + view->priv->motion_notify_handler_id); + + view->priv->motion_notify_handler_id = 0; + } +} + +void +gm_app_view_drag_start(GmAppView *view, guint32 time) { + view->priv->drag_in_progress = TRUE; + + /* get a new cursor, if necessary */ + /* FIXME multi-head */ + if (view->priv->cursor == NULL) + view->priv->cursor = gdk_cursor_new(GDK_FLEUR); + + /* grab the pointer */ + gtk_grab_add(GTK_WIDGET(view->priv->notebook)); + + /* FIXME multi-head */ + if (!gdk_pointer_is_grabbed()) { + gdk_pointer_grab(GTK_WIDGET(view->priv->notebook)->window, + FALSE, GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, + view->priv->cursor, time); + } +} + +void +gm_app_view_move_current_tab(GmAppView *view, gint dest_position) { + gint cur_page_num; + cur_page_num = gtk_notebook_get_current_page(view->priv->notebook); + + if (dest_position != cur_page_num) { + GtkWidget *cur_tab; + + cur_tab = gtk_notebook_get_nth_page(view->priv->notebook, cur_page_num); + gtk_notebook_reorder_child(view->priv->notebook, cur_tab, dest_position); + } +} + +gboolean +on_gm_app_view_motion_notify(GmAppView *view, GdkEventMotion *event, + gpointer data) { + gint page_num; + + if (view->priv->drag_in_progress == FALSE) { + if (gtk_drag_check_threshold(GTK_WIDGET(view->priv->notebook), + view->priv->x_start, + view->priv->y_start, + event->x_root, + event->y_root)) { + gm_app_view_drag_start(view, event->time); + return TRUE; + } + + return FALSE; + } + + page_num = gm_app_view_find_tab_num_at_pos(view, event->x_root, + event->y_root); + + if (page_num != -1) { + gm_app_view_move_current_tab(view, page_num); + } + + return FALSE; +} + +gboolean +on_gm_app_view_notebook_button_press(GtkNotebook *notebook, + GdkEventButton *event, GmAppView *view) { + gint tab_clicked; + + if (view->priv->drag_in_progress) + return TRUE; + + tab_clicked = gm_app_view_find_tab_num_at_pos(view, event->x_root, + event->y_root); + + if ((event->button == 1) && (event->type == GDK_BUTTON_PRESS) && + (tab_clicked >= 0)) { + view->priv->x_start = event->x_root; + view->priv->y_start = event->y_root; + + view->priv->motion_notify_handler_id = g_signal_connect(G_OBJECT(notebook), + "motion-notify-event", G_CALLBACK(on_gm_app_view_motion_notify), NULL); + } else if ((event->type == GDK_BUTTON_PRESS) && (event->button == 3)) { + /* Switch to the page the mouse is over, but don't consume the event */ + //gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), tab_clicked); + } + + return FALSE; +} + +gboolean +on_gm_app_view_notebook_button_release(GtkNotebook *notebook, + GdkEventButton *event, GmAppView *view) { + if (view->priv->drag_in_progress) { + gint cur_page_num; + GtkWidget *cur_page; + + cur_page_num = gtk_notebook_get_current_page(notebook); + cur_page = gtk_notebook_get_nth_page(notebook, cur_page_num); + + /* ungrab the pointer if it's grabbed */ + if (gdk_pointer_is_grabbed()) { + gdk_pointer_ungrab(event->time); + } + + gtk_grab_remove(GTK_WIDGET(notebook)); + } + + /* This must be called even if a drag isn't happening */ + gm_app_view_drag_stop(view); + + return FALSE; +} + +void +on_gm_app_view_notebook_switch_page(GtkNotebook * notebook, + GtkNotebookPage * page, guint page_num, GmAppView *view) { + GmWorld *world = gm_app_view_active_world(view); + + if (view->priv->active_world == world) { + return; + } + + if (view->priv->active_world) { + gm_world_set_active(view->priv->active_world, FALSE); + } + + if (world) { + view->priv->active_world = world; + gm_world_set_active(view->priv->active_world, TRUE); + } +} + +static const gchar *authors[] = { + N_("Jesse van den Kieboom"), + N_("Sjoerd Simons (debian package)"), + NULL +}; + +static const gchar *artists[] = { + N_("Simon Gijsen"), + NULL +}; + +void +on_gm_app_view_help_about(GtkMenuItem *menuitem, GmAppView *view) { + gtk_show_about_dialog(GTK_WINDOW(view), + "name", _("GnoeMoe"), + "version", IVERSION, + "copyright", _("(C) 2004-2005 Icecrew.nl"), + "comments", _("GnoeMoe Gnome MOO Client"), + "authors", authors, + "artists", artists, + "logo", gm_pixbuf_get("gnoemoe_logo.svg"), + NULL); +} + +void +on_gm_app_view_edit_cut(GtkMenuItem * menuitem, GmAppView *view) { + GtkWidget *active = gtk_window_get_focus(GTK_WINDOW(view)); + GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + GtkTextBuffer *buf; + + if (active && GTK_IS_TEXT_VIEW(active)) { + buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(active)); + gtk_text_buffer_cut_clipboard(buf, clip, TRUE); + } +} + +void +on_gm_app_view_edit_copy(GtkMenuItem * menuitem, GmAppView *view) { + GtkWidget *active = gtk_window_get_focus(GTK_WINDOW(view)); + GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + GtkTextBuffer *buf; + GtkTextIter start, end; + GmWorldView *world_view; + + world_view = gm_app_view_active_world_view(view); + + if (world_view && active == GTK_WIDGET(gm_world_view_input(world_view))) { + buf = gm_world_view_buffer(world_view); + if (gtk_text_buffer_get_selection_bounds(buf, &start, &end)) { + gtk_text_buffer_copy_clipboard(buf, clip); + return; + } + } + + if (active && GTK_IS_TEXT_VIEW(active)) { + buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(active)); + gtk_text_buffer_copy_clipboard(buf, clip); + } +} + +void +on_gm_app_view_edit_paste(GtkMenuItem * menuitem, GmAppView *view) { + GtkWidget *active = gtk_window_get_focus(GTK_WINDOW(view)); + GtkClipboard *clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + GtkTextBuffer *buf; + + if (active && GTK_IS_TEXT_VIEW(active)) { + buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(active)); + gtk_text_buffer_paste_clipboard(buf, clip, NULL, TRUE); + } +} + +void +on_gm_app_view_world_new(GtkMenuItem * menuitem, GmAppView *view) { + gm_world_properties_dialog_run_new(NULL); +} + +void +on_gm_app_view_edit_world(GtkMenuItem * menuitem, GmAppView *view) { + GmWorld *active = gm_app_view_active_world(view); + + gm_world_properties_dialog_run(active); +} + +void +on_gm_app_view_world_connect(GtkMenuItem * menuitem, GmAppView *view) { + GmWorld *world = gm_app_view_active_world(view); + + if (world != NULL) { + if (gm_world_connected(world)) { + gm_world_disconnect(world); + } else if (gm_world_disconnected(world)) { + gm_world_connect(world); + } + } +} + +void +on_gm_app_view_world_info(GtkMenuItem *menuitem, GmAppView *view) { + GmWorld *world = gm_app_view_active_world(view); + + if (world) { + gm_world_info_dialog_new(gm_world_info(world)); + } else { + gm_app_view_update_world_info(FALSE); + } +} + + +void +on_gm_app_view_view_mcp(GtkMenuItem * menuitem, GmAppView *view) { + /* TODO: implementation */ + //if_mcpconsole_create(); +} + +void +on_gm_app_view_view_scripts(GtkMenuItem * menuitem, GmAppView *view) { +#ifdef HAVE_RUBY + gm_scripts_dialog_run(view); +#endif +} + +void +gm_app_view_open_log_progress(long bytes_read, long bytes_total, gchar *buf, + GtkProgressBar *progress) { + gtk_progress_bar_set_fraction(progress, (double)bytes_read / (double)bytes_total); + gm_do_events(); +} + +void +on_gm_app_view_world_logs(GtkMenuItem * menuitem, GmAppView *view) { + GtkDialog *dlg; + GtkTreeView *tview; + GtkTreeModel *smodel; + gchar *tmp, *tmp2; + GtkTreeIter iter; + gboolean done = FALSE; + static GdkCursor *wait_cursor = NULL; + GmWorld *world = gm_app_view_active_world(view); + GtkProgressBar *pgs; + + dlg = gm_world_logs_dialog_new(gm_app_view_active_world(view), &tview, &pgs); + + if (dlg != NULL) { + smodel = gtk_tree_view_get_model(tview); + + while (!done) { + done = TRUE; + switch (gtk_dialog_run(dlg)) { + case GTK_RESPONSE_OK: + if (gtk_tree_selection_get_selected( + gtk_tree_view_get_selection(tview), &smodel, &iter)) { + gtk_tree_model_get(smodel, &iter, 0, &tmp, -1); + tmp2 = g_strconcat(gm_world_path(world), tmp, NULL); + gtk_widget_set_sensitive(GTK_WIDGET(dlg), FALSE); + + if (wait_cursor == NULL) { + wait_cursor = gdk_cursor_new(GDK_WATCH); + } + + gdk_window_set_cursor(GTK_WIDGET(dlg)->window, wait_cursor); + gtk_widget_show(GTK_WIDGET(pgs)); + gm_world_view_open_log(gm_app_view_active_world_view(view), tmp2, + (OpenLogProgress)gm_app_view_open_log_progress, + pgs); + + gtk_widget_hide(GTK_WIDGET(pgs)); + + gdk_window_set_cursor(GTK_WIDGET(dlg)->window, NULL); + g_free(tmp2); + g_free(tmp); + } else { + gm_error_dialog(_("You didn't select a log file"), NULL); + done = FALSE; + } + break; + default: + break; + } + } + + gtk_widget_destroy(GTK_WIDGET(dlg)); + } +} + +void +on_gm_app_view_world_active_changed(GmWorld *world, gboolean active, + GmAppView *view) { + GmWorldView *world_view = gm_app_view_world_view_from_world(view, world); + + if (active) { + gtk_notebook_set_current_page(view->priv->notebook, + gtk_notebook_page_num(view->priv->notebook, + GTK_WIDGET(world_view))); + gm_app_view_update_title(view); + } +} + +gboolean +on_gm_app_view_focus_in(GtkWidget *widget, GdkEventFocus *event, + gpointer user_data) { + /* TODO: implementation + world *wld; + + wld = world_get_active(); + + if (wld) { + world_activated(wld); + } + + app_tray_info.iconnr = TRAY_ICON_DEFAULT; + tray_flash_restore_func(NULL);*/ + + return FALSE; +} diff --git a/gnoemoe/widgets/gm-app-view.h b/gnoemoe/widgets/gm-app-view.h new file mode 100644 index 0000000..b06d6e6 --- /dev/null +++ b/gnoemoe/widgets/gm-app-view.h @@ -0,0 +1,78 @@ +#ifndef __GM_APP_VIEW_H__ +#define __GM_APP_VIEW_H__ + +#include <gtk/gtk.h> +#include <glib.h> +#include <glade/glade.h> +#include "../gm-world.h" +#include "../gm-app.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_APP_VIEW (gm_app_view_get_type()) +#define GM_APP_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_APP_VIEW, GmAppView)) +#define GM_APP_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_APP_VIEW, GmAppView const)) +#define GM_APP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_APP_VIEW, GmAppViewClass)) +#define GM_IS_APP_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_APP_VIEW)) +#define GM_IS_APP_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_APP_VIEW)) +#define GM_APP_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_APP_VIEW, GmAppViewClass)) + +/* Private structure type */ +typedef struct _GmAppViewPrivate GmAppViewPrivate; + +/* + * Main object structure + */ +typedef struct _GmAppView GmAppView; + +struct _GmAppView { + GtkWindow window; + + /*< private > */ + GmAppViewPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmAppViewClass GmAppViewClass; + +struct _GmAppViewClass { + GtkWindowClass parent_class; + + /* Signals */ +}; + +GType gm_app_view_get_type(void) G_GNUC_CONST; +GmAppView *gm_app_view_new(GmApp *application); +const GmApp *gm_app_view_application(GmAppView *view); + +/* Callbacks */ +void on_gm_app_view_world_new(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_world_connect(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_world_logs(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_world_info(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_world_close(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_world_quit(GtkMenuItem * menuitem, GmAppView *view); + +void on_gm_app_view_edit_cut(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_copy(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_paste(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_worlds(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_world(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_find(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_find_next(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_edit_preferences( + GtkMenuItem * menuitem, GmAppView *view); + +void on_gm_app_view_view_mcp(GtkMenuItem * menuitem, GmAppView *view); +void on_gm_app_view_view_scripts(GtkMenuItem * menuitem, GmAppView *view); + +void on_gm_app_view_help_about(GtkMenuItem * menuitem, GmAppView *view); + +G_END_DECLS + +#endif /* __GM_APP_VIEW__ */ diff --git a/gnoemoe/widgets/gm-text-scroller.c b/gnoemoe/widgets/gm-text-scroller.c new file mode 100644 index 0000000..f13eba2 --- /dev/null +++ b/gnoemoe/widgets/gm-text-scroller.c @@ -0,0 +1,259 @@ +#include <glib-object.h> +#include <gtk/gtk.h> +#include <string.h> +#include "gm-text-scroller.h" +#include "../gm-debug.h" +#include "../gm-support.h" + +#define GM_TEXT_SCROLLER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_TEXT_SCROLLER, GmTextScrollerPrivate)) + +struct _GmTextScrollerPrivate { + GtkTextView *text_view; + GtkTextBuffer *text_buffer; + GtkScrolledWindow *scrolled_window; + + gint character_height; + gboolean end_scrolled; + gint idle_handler; +}; + +void on_gm_text_scroller_text_view_style_set(GtkTextView *view, + GtkStyle *previous_style, GmTextScroller *scroller); +void on_gm_text_scroller_text_buffer_changed(GtkTextBuffer *text_buffer, + GmTextScroller *scroller); +void on_gm_text_scroller_text_view_notify(GtkTextView *text_view, GParamSpec *arg1, + GmTextScroller *scroller); +void on_gm_text_scroller_text_view_destroy(GtkTextView *text_view, + GmTextScroller *scroller); + +/* Signals + +enum { + PROTO + NUM_SIGNALS +}; + +static guint gm_text_scroller_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmTextScroller, gm_text_scroller, G_TYPE_OBJECT) + +static void +gm_text_scroller_finalize(GObject *object) { + GmTextScroller *obj = GM_TEXT_SCROLLER(object); + + if (obj->priv->text_buffer != NULL) { + g_signal_handlers_disconnect_by_func(obj->priv->text_buffer, + G_CALLBACK(on_gm_text_scroller_text_buffer_changed), + obj); + + g_object_unref(obj->priv->text_buffer); + } + + g_signal_handlers_disconnect_by_func(obj->priv->text_view, + G_CALLBACK(on_gm_text_scroller_text_view_notify), obj); + g_signal_handlers_disconnect_by_func(obj->priv->text_view, + G_CALLBACK(on_gm_text_scroller_text_view_style_set), obj); + g_signal_handlers_disconnect_by_func(obj->priv->text_view, + G_CALLBACK(on_gm_text_scroller_text_view_destroy), obj); + + G_OBJECT_CLASS(gm_text_scroller_parent_class)->finalize(object); +} + +static void +gm_text_scroller_class_init(GmTextScrollerClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_text_scroller_finalize; + + /*gm_text_scroller_signals[PROTO] = + g_signal_new("proto", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmTextScrollerClass, proto), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0);*/ + + g_type_class_add_private(object_class, sizeof(GmTextScrollerPrivate)); +} + +static void +gm_text_scroller_init(GmTextScroller *obj) { + obj->priv = GM_TEXT_SCROLLER_GET_PRIVATE(obj); + + obj->priv->character_height = 10; + obj->priv->end_scrolled = FALSE; +} + +void +gm_text_scroller_update_text_buffer(GmTextScroller *scroller) { + if (scroller->priv->text_buffer != NULL) { + g_signal_handlers_disconnect_by_func(scroller->priv->text_buffer, + G_CALLBACK(on_gm_text_scroller_text_buffer_changed), + scroller); + + g_object_unref(scroller->priv->text_buffer); + } + + scroller->priv->text_buffer = + gtk_text_view_get_buffer(scroller->priv->text_view); + + if (scroller->priv->text_buffer != NULL) { + g_object_ref(scroller->priv->text_buffer); + + g_signal_connect(scroller->priv->text_buffer, "changed", + G_CALLBACK(on_gm_text_scroller_text_buffer_changed), scroller); + } + +} + +void +gm_text_scroller_scroll_end(GmTextScroller *scroller) { + GtkTextMark *mark; + GtkTextIter iter; + + mark = gtk_text_buffer_get_mark(scroller->priv->text_buffer, + "end-of-buffer"); + + if (mark == NULL) { + gtk_text_buffer_get_end_iter(scroller->priv->text_buffer, &iter); + mark = gtk_text_buffer_create_mark(scroller->priv->text_buffer, + "end-of-buffer", &iter, FALSE); + } + + gtk_text_view_scroll_to_mark(scroller->priv->text_view, mark, 0.0, + TRUE, 1.0, 1.0); +} + +void +gm_text_scroller_scroll_begin(GmTextScroller *scroller) { + GtkTextMark *mark; + GtkTextIter iter; + + mark = gtk_text_buffer_get_mark(scroller->priv->text_buffer, + "begin-of-buffer"); + + if (mark == NULL) { + gtk_text_buffer_get_start_iter(scroller->priv->text_buffer, &iter); + mark = gtk_text_buffer_create_mark(scroller->priv->text_buffer, + "begin-of-buffer", &iter, TRUE); + } + + gtk_text_view_scroll_to_mark(scroller->priv->text_view, mark, 0.0, + TRUE, 0.0, 0.0); +} + +gboolean +gm_text_scroller_scroll_end_idle(GmTextScroller *scroller) { + scroller->priv->idle_handler = 0; + gm_text_scroller_scroll_end(scroller); + scroller->priv->end_scrolled = FALSE; + + return FALSE; +} + + +void +gm_text_scroller_prepare(GmTextScroller *scroller) { + GtkAdjustment *ad = gtk_scrolled_window_get_vadjustment( + scroller->priv->scrolled_window); + + scroller->priv->end_scrolled = scroller->priv->end_scrolled || + ((ad->page_size + ad->value) >= ad->upper - + (double)(scroller->priv->character_height)); + + if (scroller->priv->idle_handler == 0 && scroller->priv->end_scrolled) { + scroller->priv->idle_handler = g_idle_add((GSourceFunc) + gm_text_scroller_scroll_end_idle, scroller); + } +} + +void +gm_text_scroller_update_character_height(GmTextScroller *scroller) { + GtkRcStyle *style = gtk_widget_get_modifier_style(GTK_WIDGET( + scroller->priv->text_view)); + PangoContext *pc = gtk_widget_create_pango_context(GTK_WIDGET( + scroller->priv->text_view)); + PangoLayout *pl; + gint cwidth, cheight; + + if (style->font_desc != NULL) { + pango_context_set_font_description(pc, style->font_desc); + pl = pango_layout_new(pc); + + pango_layout_set_text(pl, "G", 1); + pango_layout_get_pixel_size(pl, &(cwidth), + &(cheight)); + + if (scroller->priv->character_height != cheight) { + scroller->priv->character_height = cheight; + } + + g_object_unref(pl); + } + + g_object_unref(pc); +} + +GmTextScroller * +gm_text_scroller_new(GtkTextView *text_view) { + GmTextScroller *obj; + GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(text_view)); + + if (parent == NULL || !GTK_IS_SCROLLED_WINDOW(parent)) { + return NULL; + } + + obj = GM_TEXT_SCROLLER(g_object_new(GM_TYPE_TEXT_SCROLLER, + NULL)); + + obj->priv->text_view = text_view; + obj->priv->scrolled_window = GTK_SCROLLED_WINDOW(parent); + + gm_text_scroller_update_text_buffer(obj); + gm_text_scroller_update_character_height(obj); + + g_signal_connect(text_view, "notify", + G_CALLBACK(on_gm_text_scroller_text_view_notify), obj); + g_signal_connect(text_view, "style-set", + G_CALLBACK(on_gm_text_scroller_text_view_style_set), obj); + g_signal_connect(text_view, "destroy", + G_CALLBACK(on_gm_text_scroller_text_view_destroy), obj); + + obj->priv->idle_handler = g_idle_add((GSourceFunc) + gm_text_scroller_scroll_end_idle, obj); + + return obj; +} + +// Callbacks + +void +on_gm_text_scroller_text_view_notify(GtkTextView *text_view, GParamSpec *arg1, + GmTextScroller *scroller) { + if (strcmp(arg1->name, "buffer") == 0) { + // Buffer changed + gm_text_scroller_update_text_buffer(scroller); + } +} + +void +on_gm_text_scroller_text_view_style_set(GtkTextView *view, + GtkStyle *previous_style, GmTextScroller *scroller) { + gm_text_scroller_update_character_height(scroller); +} + +void +on_gm_text_scroller_text_buffer_changed(GtkTextBuffer *text_buffer, + GmTextScroller *scroller) { + // Changed... + gm_text_scroller_prepare(scroller); +} + +void +on_gm_text_scroller_text_view_destroy(GtkTextView *text_view, + GmTextScroller *scroller) { + // Remove ourselfs when the text view dies + g_object_unref(scroller); +} diff --git a/gnoemoe/widgets/gm-text-scroller.h b/gnoemoe/widgets/gm-text-scroller.h new file mode 100644 index 0000000..c995ffd --- /dev/null +++ b/gnoemoe/widgets/gm-text-scroller.h @@ -0,0 +1,52 @@ +#ifndef __GM_TEXT_SCROLLER_H__ +#define __GM_TEXT_SCROLLER_H__ + +#include <glib-object.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_TEXT_SCROLLER (gm_text_scroller_get_type()) +#define GM_TEXT_SCROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TEXT_SCROLLER, GmTextScroller)) +#define GM_TEXT_SCROLLER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_TEXT_SCROLLER, GmTextScroller const)) +#define GM_TEXT_SCROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_TEXT_SCROLLER, GmTextScrollerClass)) +#define GM_IS_TEXT_SCROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_TEXT_SCROLLER)) +#define GM_IS_TEXT_SCROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_TEXT_SCROLLER)) +#define GM_TEXT_SCROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_TEXT_SCROLLER, GmTextScrollerClass)) + +/* Private structure type */ +typedef struct _GmTextScrollerPrivate GmTextScrollerPrivate; + +/* + * Main object structure + */ +typedef struct _GmTextScroller GmTextScroller; + +struct _GmTextScroller { + GObject parent; + + /*< private > */ + GmTextScrollerPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmTextScrollerClass GmTextScrollerClass; + +struct _GmTextScrollerClass { + GObjectClass parent_class; + + /* Signals + void (* proto) (GmTextScroller *obj); */ +}; + +GType gm_text_scroller_get_type(void) G_GNUC_CONST; +GmTextScroller *gm_text_scroller_new(GtkTextView *text_view); +void gm_text_scroller_scroll_end(GmTextScroller *scroller); +void gm_text_scroller_scroll_begin(GmTextScroller *scroller); + +G_END_DECLS +#endif /* __GM_TEXT_SCROLLER_H__ */ diff --git a/gnoemoe/widgets/gm-world-input-view.c b/gnoemoe/widgets/gm-world-input-view.c new file mode 100644 index 0000000..11c8b8e --- /dev/null +++ b/gnoemoe/widgets/gm-world-input-view.c @@ -0,0 +1,392 @@ +#include <string.h> +#include <gtk/gtk.h> +#include <gdk/gdk.h> +#include <gdk/gdkkeysyms.h> + +#include "gm-world-input-view.h" +#include "gm-world-view.h" +#include "../gm-world.h" +#include "../gm-color-table.h" +#include "../gm-debug.h" + +#define GM_WORLD_INPUT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputViewPrivate)) + +void on_gm_world_input_view_color_table_color_changed(GmColorTable *table, + gchar *name, GmWorldInputView *view); +void on_gm_world_input_view_color_table_font_changed(GmColorTable *table, + gchar *font_description, GmWorldInputView *view); + +gboolean on_gm_world_input_view_key_pressed(GtkWidget *widget, + GdkEventKey * event, gpointer userdata); +gboolean on_gm_world_input_view_key_released(GtkWidget *widget, + GdkEventKey *event, gpointer userdata); + +struct _GmWorldInputViewPrivate { + GmColorTable *color_table; + GList **history; + GList *position; + gchar *prefix; +}; + +/* Signals */ + +enum { + TEXT_ACTIVATE, + NUM_SIGNALS +}; + +static guint world_input_view_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmWorldInputView, gm_world_input_view, GTK_TYPE_TEXT_VIEW) + +/* Private functions */ +static void +gm_world_input_view_finalize(GObject *object) { + GmWorldInputView *view = GM_WORLD_INPUT_VIEW(object); + + gm_world_input_view_set_color_table(view, NULL); + + G_OBJECT_CLASS(gm_world_input_view_parent_class)->finalize(object); +} + +static void +gm_world_input_view_class_init(GmWorldInputViewClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_world_input_view_finalize; + + world_input_view_signals[TEXT_ACTIVATE] = + g_signal_new("text_activate", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldInputViewClass, text_activate), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + g_type_class_add_private(object_class, sizeof(GmWorldInputViewPrivate)); +} + +static void +gm_world_input_view_init(GmWorldInputView *view) { + view->priv = GM_WORLD_INPUT_VIEW_GET_PRIVATE(view); + + view->priv->color_table = NULL; + view->priv->history = NULL; + view->priv->position = NULL; + view->priv->prefix = NULL; + + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR); + gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(view), TRUE); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 3); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 3); + gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(view), 1); + gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(view), 1); + + g_signal_connect(view, "key_press_event", + G_CALLBACK(on_gm_world_input_view_key_pressed), NULL); + g_signal_connect(view, "key_release_event", + G_CALLBACK(on_gm_world_input_view_key_released), NULL); +} + +gchar * +gm_world_input_view_str_new_value(gchar *old, gchar *new) { + g_free(old); + + if (new) { + return g_strdup(new); + } else { + return NULL; + } +} + +void +gm_world_input_view_set_text(GmWorldInputView *view, gchar *text, gint len) { + GtkTextIter end; + GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); + + gtk_text_buffer_set_text(buffer, text, len); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_place_cursor(buffer, &end); +} + +void +gm_world_input_view_update_colors(GmWorldInputView *view) { + /*GdkColor col; + + if (view->priv->color_table != NULL) { + if (gm_color_table_get(view->priv->color_table, "bg_default", &col)) { + gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL, + &col); + } + if (gm_color_table_get(view->priv->color_table, "fg_default", &col)) { + gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL, + &col); + } + }*/ +} + +void +gm_world_input_view_update_font(GmWorldInputView *view) { + PangoFontDescription *f = pango_font_description_from_string( + gm_color_table_font_description(view->priv->color_table)); + + if (f != NULL) { + gtk_widget_modify_font(GTK_WIDGET(view), f); + pango_font_description_free(f); + } +} + +/* Public functions */ +GtkWidget * +gm_world_input_view_new() { + GtkWidget *result; + GmColorTable *table = gm_color_table_new(); + result = gm_world_input_view_new_with_color_table(table); + g_object_unref(table); + + return result; +} + +GtkWidget * +gm_world_input_view_new_with_color_table(GmColorTable *color_table) { + GmWorldInputView *view = GM_WORLD_INPUT_VIEW( + g_object_new(GM_TYPE_WORLD_INPUT_VIEW, NULL)); + + gm_world_input_view_set_color_table(view, color_table); + + return GTK_WIDGET(view); +} + +void +gm_world_input_view_set_history(GmWorldInputView *view, GList **history) { + view->priv->history = history; + + view->priv->position = NULL; + view->priv->prefix = NULL; +} + +GList ** +gm_world_input_view_history(GmWorldInputView *view) { + return view->priv->history; +} + +void +gm_world_input_view_set_color_table(GmWorldInputView *view, + GmColorTable *color_table) { + if (view->priv->color_table != NULL) { + g_signal_handlers_disconnect_by_func(view->priv->color_table, + on_gm_world_input_view_color_table_color_changed, view); + g_signal_handlers_disconnect_by_func(view->priv->color_table, + on_gm_world_input_view_color_table_font_changed, view); + g_object_unref(view->priv->color_table); + } + + if (color_table != NULL) { + view->priv->color_table = g_object_ref(color_table); + g_signal_connect(view->priv->color_table, "color_changed", + G_CALLBACK(on_gm_world_input_view_color_table_color_changed), view); + g_signal_connect(view->priv->color_table, "font_changed", + G_CALLBACK(on_gm_world_input_view_color_table_font_changed), view); + + gm_world_input_view_update_colors(view); + gm_world_input_view_update_font(view); + } else { + view->priv->color_table = NULL; + } +} + +GmColorTable * +gm_world_input_view_color_table(GmWorldInputView *view) { + return view->priv->color_table; +} + +/* Callbacks */ +void +on_gm_world_input_view_color_table_color_changed(GmColorTable *table, + gchar *name, GmWorldInputView *view) { + if (strcmp(name, "fg_default") == 0 || strcmp(name, "bg_default") == 0) { + gm_world_input_view_update_colors(view); + } +} + +void +on_gm_world_input_view_color_table_font_changed(GmColorTable *table, + gchar *font_description, GmWorldInputView *view) { + gm_world_input_view_update_font(view); +} + +gboolean +on_gm_world_input_view_key_released(GtkWidget *widget, GdkEventKey *event, + gpointer userdata) { + GmWorldInputView *view = GM_WORLD_INPUT_VIEW(widget); + GtkTextBuffer *buffer; + GtkTextIter start, end; + gchar *text; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + + switch (event->keyval) { + case GDK_Delete: case GDK_BackSpace: + if (text[0] == '\0') { + // Reset position to the last item in the history + view->priv->position = g_list_last(*view->priv->history); + + // Reset prefix to NULL + view->priv->prefix = gm_world_input_view_str_new_value( + view->priv->prefix, NULL); + } + break; + } + + g_free(text); + return FALSE; +} + +gboolean +on_gm_world_input_view_key_pressed(GtkWidget *widget, GdkEventKey *event, + gpointer userdata) { + GmWorldInputView *view = GM_WORLD_INPUT_VIEW(widget); + gchar *text; + gboolean result = TRUE, isUp; + GtkTextBuffer *buf; + GtkTextIter start, end, cursor; + GtkTextMark *insert; + GList *item, *found = NULL; + gint line; + + buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); + + gtk_text_buffer_get_bounds(buf, &start, &end); + text = gtk_text_buffer_get_text(buf, &start, &end, FALSE); + + switch (event->keyval) { + case GDK_Up: case GDK_Down: + isUp = event->keyval == GDK_Up; + + if (!view->priv->history) { + break; + } + + insert = gtk_text_buffer_get_insert(buf); + gtk_text_buffer_get_iter_at_mark(buf, &cursor, insert); + line = gtk_text_iter_get_line(&cursor); + + if ((isUp && line != 0) || (!isUp && line != + gtk_text_buffer_get_line_count(buf) - 1)) { + g_free(text); + return FALSE; + } + + // If the current position is empty then append a new history item + if (!view->priv->position) { + *view->priv->history = g_list_append(*view->priv->history, + g_strdup(text)); + view->priv->position = g_list_last(*view->priv->history); + } + + // If there is nowhere to move, don't even bother + if ((isUp && !view->priv->position->prev) + || (!isUp && !view->priv->position->next)) { + break; + } + + // If the current prefix is NULL then we set the new prefix to + // the current text + if (view->priv->prefix == NULL) { + view->priv->prefix = g_strdup(text); + } + + // If the prefix is an empty string then simply advance to the + // next item + if (view->priv->prefix[0] == '\0') { + if (isUp) { + found = view->priv->position->prev; + } else { + found = view->priv->position->next; + } + } else { + // Else find the closest matching history line + item = isUp ? view->priv->position->prev : + view->priv->position->next; + while (item) { + if (strncmp((gchar *)item->data, view->priv->prefix, + strlen(view->priv->prefix)) == 0) { + // Change current position to the matched item + found = item; + break; + } + + item = isUp ? item->prev : item->next; + } + } + + // If a match is found then set this history text + if (found) { + // Change the data of the current position to the text + // now in the buffer. + view->priv->position->data = + gm_world_input_view_str_new_value( + view->priv->position->data, text); + gm_world_input_view_set_text(view, (gchar *)found->data, -1); + view->priv->position = found; + + if (found == g_list_last(*view->priv->history)) { + view->priv->prefix = gm_world_input_view_str_new_value( + view->priv->prefix, NULL); + } + } + break; + case GDK_Return: + // Emit the text_activate signal + if (!(event->state & GDK_CONTROL_MASK) && + !(event->state & GDK_SHIFT_MASK)) { + + g_signal_emit(view, world_input_view_signals[TEXT_ACTIVATE], 0, + text); + + if (view->priv->history) { + item = g_list_last(*view->priv->history); + + if (item) { + item->data = gm_world_input_view_str_new_value( + (gchar *)(item->data), text); + } else { + *view->priv->history = + g_list_append(*view->priv->history, + g_strdup(text)); + } + } + + // TODO: manage history length + + // Append new empty history item which will become our new + // current item + if (view->priv->history) { + *view->priv->history = + g_list_append(*view->priv->history, g_strdup("")); + view->priv->position = g_list_last(*view->priv->history); + } + + // Reset prefix to NULL + view->priv->prefix = gm_world_input_view_str_new_value( + view->priv->prefix, NULL); + + // Set textview text to an empty string + gm_world_input_view_set_text(view, "", 0); + } else { + result = FALSE; + } + break; + default: + result = FALSE; + break; + } + + g_free(text); + return result; +} diff --git a/gnoemoe/widgets/gm-world-input-view.h b/gnoemoe/widgets/gm-world-input-view.h new file mode 100644 index 0000000..94c2201 --- /dev/null +++ b/gnoemoe/widgets/gm-world-input-view.h @@ -0,0 +1,57 @@ +#ifndef __GM_WORLD_INPUT_VIEW_H__ +#define __GM_WORLD_INPUT_VIEW_H__ + +#include <gtk/gtk.h> +#include "../gm-color-table.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_WORLD_INPUT_VIEW (gm_world_input_view_get_type()) +#define GM_WORLD_INPUT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputView)) +#define GM_WORLD_INPUT_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputView const)) +#define GM_WORLD_INPUT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputViewClass)) +#define GM_IS_WORLD_INPUT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_WORLD_INPUT_VIEW)) +#define GM_IS_WORLD_INPUT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_WORLD_INPUT_VIEW)) +#define GM_WORLD_INPUT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_WORLD_INPUT_VIEW, GmWorldInputViewClass)) + +/* Private structure type */ +typedef struct _GmWorldInputViewPrivate GmWorldInputViewPrivate; + +/* + * Main object structure + */ +typedef struct _GmWorldInputView GmWorldInputView; + +struct _GmWorldInputView { + GtkTextView textview; + + /*< private > */ + GmWorldInputViewPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmWorldInputViewClass GmWorldInputViewClass; + +struct _GmWorldInputViewClass { + GtkTextViewClass parent_class; + + /* Signals */ + void (* text_activate) (GmWorldInputView *view, const gchar *text); +}; + +GType gm_world_input_view_get_type(void) G_GNUC_CONST; +GtkWidget *gm_world_input_view_new(); +GtkWidget *gm_world_input_view_new_with_color_table(GmColorTable *table); +void gm_world_input_view_set_color_table(GmWorldInputView *view, + GmColorTable *color_table); +GmColorTable *gm_world_input_view_color_table(GmWorldInputView *view); +void gm_world_input_view_set_history(GmWorldInputView *view, GList **history); +GList **gm_world_input_view_history(GmWorldInputView *view); + +G_END_DECLS +#endif /* __GM_WORLD_INPUT_VIEW_H__ */ diff --git a/gnoemoe/widgets/gm-world-tab.c b/gnoemoe/widgets/gm-world-tab.c new file mode 100644 index 0000000..cd75330 --- /dev/null +++ b/gnoemoe/widgets/gm-world-tab.c @@ -0,0 +1,196 @@ +#include <gtk/gtk.h> +#include "../gm-world.h" +#include "gm-world-tab.h" +#include "../gm-support.h" +#include "../gm-pixbuf.h" +#include "../gm-debug.h" + +#define GM_WORLD_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_WORLD_TAB, GmWorldTabPrivate)) + +void on_gm_world_tab_button_clicked(GtkButton *button, GmWorldTab *tab); +void on_gm_world_tab_world_activity_changed(GmWorld *world, gint activity, + GmWorldTab *tab); +void on_gm_world_tab_world_name_changed(GmWorld *world, gchar *text, + GmWorldTab *tab); + +struct _GmWorldTabPrivate { + GmWorld *world; + + GtkImage *image; + GtkLabel *label; + GtkButton *button; + + guint source_id; +}; + +// Signals + +enum { + CLOSE, + NUM_SIGNALS +}; + +static guint gm_world_tab_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmWorldTab, gm_world_tab, GTK_TYPE_HBOX) + +static void +gm_world_tab_stop_recolor(GmWorldTab *tab) { + if (tab->priv->source_id != 0) { + g_source_remove(tab->priv->source_id); + tab->priv->source_id = 0; + } +} + +static void +gm_world_tab_finalize(GObject *object) { + GmWorldTab *obj = GM_WORLD_TAB(object); + + gm_world_tab_stop_recolor(obj); + + if (obj->priv->world) { + g_signal_handlers_disconnect_by_func(obj->priv->world, + G_CALLBACK(on_gm_world_tab_world_activity_changed), obj); + g_signal_handlers_disconnect_by_func(obj->priv->world, + G_CALLBACK(on_gm_world_tab_world_name_changed), obj); + g_object_unref(obj->priv->world); + } + + G_OBJECT_CLASS(gm_world_tab_parent_class)->finalize(object); +} + +static void +gm_world_tab_class_init(GmWorldTabClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_world_tab_finalize; + + gm_world_tab_signals[CLOSE] = + g_signal_new("close", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldTabClass, close), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + g_type_class_add_private(object_class, sizeof(GmWorldTabPrivate)); +} + +static void +gm_world_tab_create_interface(GmWorldTab *obj) { + GtkWidget *image, *label, *button, *image_button; + + image = gtk_image_new(); + label = gtk_label_new(""); + button = gtk_button_new(); + + image_button = gtk_image_new_from_stock(GTK_STOCK_CLOSE, + GTK_ICON_SIZE_BUTTON); + gtk_widget_set_size_request(button, 16, 16); + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + gtk_container_add(GTK_CONTAINER(button), image_button); + + gtk_box_pack_start(GTK_BOX(obj), image, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(obj), label, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(obj), button, FALSE, FALSE, 0); + + gtk_box_set_spacing(GTK_BOX(obj), 6); + + obj->priv->image = GTK_IMAGE(image); + obj->priv->label = GTK_LABEL(label); + obj->priv->button = GTK_BUTTON(button); +} + +static void +gm_world_tab_init(GmWorldTab *obj) { + obj->priv = GM_WORLD_TAB_GET_PRIVATE(obj); + obj->priv->source_id = 0; + obj->priv->world = NULL; + + gm_world_tab_create_interface(obj); + + g_signal_connect(obj->priv->button, "clicked", + G_CALLBACK(on_gm_world_tab_button_clicked), obj); + + gtk_widget_show_all(GTK_WIDGET(obj)); + gtk_widget_hide(GTK_WIDGET(obj)); +} + +gboolean +gm_world_tab_update_color(GmWorldTab *tab) { + gchar *text; + gint activity = gm_world_activity(tab->priv->world); + + text = g_strdup_printf("<span color=\"#0000%02x\">%s [%i]</span>", + CALC_COLOR_RANGE(activity), + gm_world_name(tab->priv->world), activity); + + gtk_label_set_markup(tab->priv->label, text); + g_free(text); + + tab->priv->source_id = 0; + return FALSE; +} + +static void +gm_world_tab_update(GmWorldTab *tab) { + gint activity = gm_world_activity(tab->priv->world); + gchar *text; + + gm_world_tab_stop_recolor(tab); + + if (activity) { + text = g_strdup_printf("<span color=\"#%02x0000\">%s [%i]</span>", + CALC_COLOR_RANGE(activity), + gm_world_name(tab->priv->world), activity); + gtk_image_set_from_pixbuf(tab->priv->image, + gm_pixbuf_get_at_size("world_activity.svg", 16, 16)); + tab->priv->source_id = g_idle_add((GSourceFunc) + gm_world_tab_update_color, tab); + } else { + text = g_strdup(gm_world_name(tab->priv->world)); + gtk_image_set_from_pixbuf(tab->priv->image, + gm_pixbuf_get_at_size("world.svg", 16, 16)); + } + + gtk_label_set_markup(tab->priv->label, text); + g_free(text); +} + +// Public + +GmWorldTab * +gm_world_tab_new(GmWorld *world) { + GmWorldTab *obj = GM_WORLD_TAB(g_object_new(GM_TYPE_WORLD_TAB, NULL)); + + obj->priv->world = g_object_ref(world); + + g_signal_connect(world, "activity_changed", + G_CALLBACK(on_gm_world_tab_world_activity_changed), obj); + g_signal_connect(world, "name_changed", + G_CALLBACK(on_gm_world_tab_world_name_changed), obj); + + gm_world_tab_update(obj); + return obj; +} + +// Callbacks + +void +on_gm_world_tab_button_clicked(GtkButton *button, GmWorldTab *tab) { + g_signal_emit(tab, gm_world_tab_signals[CLOSE], 0); +} + +void +on_gm_world_tab_world_activity_changed(GmWorld *world, gint activity, + GmWorldTab *tab) { + gm_world_tab_update(tab); +} + +void +on_gm_world_tab_world_name_changed(GmWorld *world, gchar *text, + GmWorldTab *tab) { + gm_world_tab_update(tab); +} diff --git a/gnoemoe/widgets/gm-world-tab.h b/gnoemoe/widgets/gm-world-tab.h new file mode 100644 index 0000000..de165ad --- /dev/null +++ b/gnoemoe/widgets/gm-world-tab.h @@ -0,0 +1,50 @@ +#ifndef __GM_WORLD_TAB_H__ +#define __GM_WORLD_TAB_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_WORLD_TAB (gm_world_tab_get_type()) +#define GM_WORLD_TAB(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_TAB, GmWorldTab)) +#define GM_WORLD_TAB_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_TAB, GmWorldTab const)) +#define GM_WORLD_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_WORLD_TAB, GmWorldTabClass)) +#define GM_IS_WORLD_TAB(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_WORLD_TAB)) +#define GM_IS_WORLD_TAB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_WORLD_TAB)) +#define GM_WORLD_TAB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_WORLD_TAB, GmWorldTabClass)) + +/* Private structure type */ +typedef struct _GmWorldTabPrivate GmWorldTabPrivate; + +/* + * Main object structure + */ +typedef struct _GmWorldTab GmWorldTab; + +struct _GmWorldTab { + GtkHBox parent; + + /*< private > */ + GmWorldTabPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmWorldTabClass GmWorldTabClass; + +struct _GmWorldTabClass { + GtkHBoxClass parent_class; + + // Signals + void (* close) (GmWorldTab *obj); +}; + +GType gm_world_tab_get_type(void) G_GNUC_CONST; +GmWorldTab *gm_world_tab_new(GmWorld *world); + +G_END_DECLS +#endif /* __GM_WORLD_TAB_H__ */ diff --git a/gnoemoe/widgets/gm-world-text-view.c b/gnoemoe/widgets/gm-world-text-view.c new file mode 100644 index 0000000..40492ba --- /dev/null +++ b/gnoemoe/widgets/gm-world-text-view.c @@ -0,0 +1,1009 @@ +#include <gtk/gtk.h> +#include <string.h> + +#include "gm-world-text-view.h" +#include "../gm-color-table.h" +#include "../gm-marshal.h" +#include "../ansi.h" +#include "../gm-debug.h" +#include "../gm-support.h" + +/* Callback definitions */ +void on_gm_world_text_view_style_set(GmWorldTextView *view, + GtkStyle *previous_style, gpointer user_data); +gboolean on_gm_world_text_view_url_event(GtkTextTag *tag, GObject *object, + GdkEvent *event, GtkTextIter *iter, GmWorldTextView *view); +gboolean on_gm_world_text_view_event(GmWorldTextView *view, + GdkEventMotion *event, GtkTextTag *tag); + +/*void on_gm_world_text_view_color_table_bold_toggled(GmColorTable *table, + gboolean bold, GmWorldTextView *view);*/ +void on_gm_world_text_view_color_table_color_changed(GmColorTable *table, + gchar *name, GmWorldTextView *view); +void on_gm_world_text_view_color_table_font_changed(GmColorTable *table, + gchar *font_description, GmWorldTextView *view); + +void gm_world_text_view_create_tags(GmWorldTextView *view); +void gm_world_text_view_init_tags(GmWorldTextView *view); +void gm_world_text_view_update_font(GmWorldTextView *view); + +#define GM_WORLD_TEXT_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextViewPrivate)) +#define GM_WORLD_TEXT_VIEW_BUFFER(view) gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)) + +typedef struct _GmWorldTextViewInsertInfo GmWorldTextViewInsertInfo; + +struct _GmWorldTextViewInsertInfo { + GList *tags; + gboolean bold; + gboolean inverse; + gchar *text; +}; + +struct _GmWorldTextViewPrivate { + gboolean is_hand; + + gint character_width; + gint character_height; + gint max_lines; + GmWorldTextViewInsertInfo last_info; + GmColorTable *color_table; +}; + +/* Signals */ + +enum { + URL_ACTIVATE, + CHARACTER_SIZE_CHANGED, + NUM_SIGNALS +}; + +static guint world_text_view_signals[NUM_SIGNALS] = {0}; + +G_DEFINE_TYPE(GmWorldTextView, gm_world_text_view, GTK_TYPE_TEXT_VIEW) + +/* Class object functions etc */ +static void +gm_world_text_view_finalize(GObject *object) { + GmWorldTextView *view = GM_WORLD_TEXT_VIEW(object); + + gm_world_text_view_set_color_table(view, NULL); + g_list_free(view->priv->last_info.tags); + g_free(view->priv->last_info.text); + + G_OBJECT_CLASS(gm_world_text_view_parent_class)->finalize(object); +} + +static void +gm_world_text_view_class_init(GmWorldTextViewClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_world_text_view_finalize; + + world_text_view_signals[URL_ACTIVATE] = + g_signal_new("url_activate", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldTextViewClass, url_activate), + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + world_text_view_signals[CHARACTER_SIZE_CHANGED] = + g_signal_new("character_size_changed", + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GmWorldTextViewClass, character_size_changed), + NULL, NULL, + gm_marshal_VOID__INT_INT, + G_TYPE_NONE, + 2, + G_TYPE_INT, + G_TYPE_INT); + + g_type_class_add_private(object_class, sizeof(GmWorldTextViewPrivate)); +} + +static void +gm_world_text_view_init(GmWorldTextView *view) { + view->priv = GM_WORLD_TEXT_VIEW_GET_PRIVATE(view); + + // This view can't focus + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(view), GTK_CAN_FOCUS); + + // Therefore we need the active state to be the selected state + gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_ACTIVE, + &(GTK_WIDGET(view)->style->base[GTK_STATE_SELECTED])); + gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_ACTIVE, + &(GTK_WIDGET(view)->style->text[GTK_STATE_SELECTED])); + + // Margins + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 3); + gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 3); + + // Set default wrapping mode + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR); + + view->priv->is_hand = FALSE; + view->priv->color_table = NULL; + + view->priv->max_lines = 2000; + view->priv->last_info.bold = FALSE; + view->priv->last_info.inverse = FALSE; + view->priv->last_info.tags = NULL; + view->priv->last_info.text = NULL; + + // Connect style set signal + g_signal_connect(view, "style-set", + G_CALLBACK(on_gm_world_text_view_style_set), NULL); +} + +/* Private functions */ +void +gm_world_text_view_update_color_tag(GmWorldTextView *view, const gchar *name, + GtkTextTag *tag) { + GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view); + GdkColor col; + + if (tag == NULL) { + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buf), name); + } + + if (view->priv->color_table != NULL) { + gm_color_table_get(view->priv->color_table, name, &col); + } + + if (name[0] == 'f') { + g_object_set(G_OBJECT(tag), "foreground-gdk", &col, NULL); + + /*if (name[strlen(name) - 1] == 'h') { + // Color tag for bold colors + //g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL); + if (view->priv->color_table == NULL || + !gm_color_table_bold(view->priv->color_table)) { + g_object_set(G_OBJECT(tag), "foreground-set", FALSE, NULL); + } + }*/ + } else { + g_object_set(G_OBJECT(tag), "background-gdk", &col, NULL); + } +} + +void +gm_world_text_view_check_buffer_size(GmWorldTextView *view) { + GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view); + GtkTextIter start, end; + int d = gtk_text_buffer_get_line_count(buf) - view->priv->max_lines; + + if (d > 0) { + gtk_text_buffer_get_iter_at_line(buf, &start, 0); + gtk_text_buffer_get_iter_at_line(buf, &end, d); + gtk_text_buffer_delete(buf, &start, &end); + } +} + +void +gm_world_text_view_create_tags(GmWorldTextView *view) { + /* Create all the tags */ + int i; + GtkTextTag *tag; + GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view); + GdkColor col; + + /* Url tag */ + tag = gtk_text_buffer_create_tag(buf, "url", "foreground", "steelblue", + "underline", PANGO_UNDERLINE_SINGLE, NULL); + g_signal_connect(tag, "event", G_CALLBACK(on_gm_world_text_view_url_event), + view); + g_signal_connect(view, "event", G_CALLBACK(on_gm_world_text_view_event), + tag); + + for (i = 0; i < (int)(sizeof(ansi_colors) / sizeof(ansinamepair)); i++) { + tag = gtk_text_buffer_create_tag(buf, ansi_colors[i].name, NULL); + gm_world_text_view_update_color_tag(view, ansi_colors[i].name, tag); + } + + if (view->priv->color_table != NULL) { + gm_color_table_get(view->priv->color_table, "fg_default", &col); + } + + gtk_text_buffer_create_tag(buf, "inverse_bg", "background-gdk", &col, NULL); + + if (view->priv->color_table != NULL) { + gm_color_table_get(view->priv->color_table, "bg_default", &col); + } + + gtk_text_buffer_create_tag(buf, "inverse_fg", "background-gdk", &col, NULL); + + for (i = 0; i < (int)(sizeof(ansi_styles) / sizeof(ansinamepair)); i++) { + tag = gtk_text_buffer_create_tag(buf, ansi_styles[i].name, NULL); + + switch (ansi_styles[i].code) { + case A_BOLD: + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD, + NULL); + break; + case A_BOLD_OFF: + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, + NULL); + break; + case A_FAINT: + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRALIGHT, + NULL); + break; + case A_UNDERLINE: + g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_SINGLE, + NULL); + break; + case A_DOUBLE_UNDERLINE: + g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_DOUBLE, + NULL); + break; + case A_UNDERLINE_OFF: + g_object_set(G_OBJECT(tag), "underline", PANGO_UNDERLINE_NONE, + NULL); + break; + case A_CROSSOUT: + g_object_set(G_OBJECT(tag), "strikethrough", TRUE, NULL); + break; + case A_CROSSOUT_OFF: + g_object_set(G_OBJECT(tag), "strikethrough", FALSE, NULL); + break; + case A_ITALIC: + g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL); + break; + case A_ITALIC_OFF: + g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL); + break; + case A_INVISIBLE: + g_object_set(G_OBJECT(tag), "invisible", TRUE, NULL); + break; + case A_INVISIBLE_OFF: + g_object_set(G_OBJECT(tag), "invisible", FALSE, NULL); + break; + case A_NOWRAP: + g_object_set(G_OBJECT(tag), "wrap-mode", GTK_WRAP_NONE, NULL); + break; + default: + gtk_text_tag_table_remove(gtk_text_buffer_get_tag_table(buf), + tag); + break; + } + } +} + +void +gm_world_text_view_update_tags(GmWorldTextView *view) { + int i; + GtkTextTag *tag; + + for (i = 0; i < (int)(sizeof(ansi_colors) / sizeof(ansinamepair)); i++) { + gm_world_text_view_update_color_tag(view, ansi_colors[i].name, NULL); + } + + tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table( + GM_WORLD_TEXT_VIEW_BUFFER(view)), "bold"); + + /*if (gm_color_table_bold(view->priv->color_table)) { + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL); + } else {*/ + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD, NULL); + //} +} + +void +gm_world_text_view_init_tags(GmWorldTextView *view) { + GdkColor col; + + gm_color_table_get(view->priv->color_table, "fg_default", &col); + gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL, &col); + + gm_color_table_get(view->priv->color_table, "bg_default", &col); + gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL, &col); + + gm_world_text_view_update_tags(view); +} + +void +gm_world_text_view_update_font(GmWorldTextView *view) { + PangoFontDescription *f = pango_font_description_from_string( + gm_color_table_font_description(view->priv->color_table)); + + if (f != NULL) { + gtk_widget_modify_font(GTK_WIDGET(view), f); + pango_font_description_free(f); + } +} + +gboolean +g_utf8_toint(gchar *str, guint *result) { + gunichar c; + *result = 0; + + while ((c = g_utf8_get_char(str)) != '\0') { + if (g_unichar_isdigit(c)) { + *result = (*result * 10) + g_unichar_digit_value(c); + } else { + return FALSE; + } + str = g_utf8_next_char(str); + } + + return TRUE; +} + +void +gm_world_text_view_insert_text(GmWorldTextView *view, const gchar *text, + GList *tags) { + GtkTextIter end_iter, start_iter; + GtkTextBuffer *tb = GM_WORLD_TEXT_VIEW_BUFFER(view); + gint start_offset; + gchar *name; + + gtk_text_buffer_get_end_iter(tb, &end_iter); + start_offset = gtk_text_iter_get_offset(&end_iter); + + gtk_text_buffer_insert(tb, &end_iter, text, -1); + + if (tags) { + gtk_text_buffer_get_iter_at_offset(tb, &start_iter, start_offset); + gtk_text_buffer_get_end_iter(tb, &end_iter); + + for (; tags; tags = tags->next) { + g_object_get(GTK_TEXT_TAG(tags->data), "name", &name, NULL); + g_free(name); + gtk_text_buffer_apply_tag(tb, GTK_TEXT_TAG(tags->data), &start_iter, + &end_iter); + } + } +} + +const gchar * +gm_world_text_view_tagname_from_code(guint code) { + int i, len; + + len = (sizeof(ansi_colors) / sizeof(ansinamepair)); + + for (i = 0; i < len; i++) { + if (ansi_colors[i].code == code) { + return ansi_colors[i].name; + } + } + + len = sizeof(ansi_styles) / sizeof(ansinamepair); + for (i = 0; i < len; i++) { + if (ansi_styles[i].code == code) { + return ansi_styles[i].name; + } + } + + return NULL; +} + +GList * +gm_world_text_view_tags_fix_for_bold(GmWorldTextView *view, GList *tags) { + GList *t, *item; + GtkTextTag *tag, *htag; + gboolean fg_set; + gchar *name, *hname; + gboolean added = FALSE; + GtkTextTagTable *tag_table = gtk_text_buffer_get_tag_table( + GM_WORLD_TEXT_VIEW_BUFFER(view)); + + t = g_list_copy(tags); + + for (item = t; item; item = item->next) { + tag = GTK_TEXT_TAG(item->data); + + g_object_get(G_OBJECT(tag), "foreground-set", &fg_set, NULL); + g_object_get(G_OBJECT(tag), "name", &name, NULL); + + if (fg_set) { + hname = g_strconcat(name, "_h", NULL); + htag = gtk_text_tag_table_lookup(tag_table, hname); + + if (htag) { + if (!g_list_find(tags, htag)) { + tags = g_list_append(tags, htag); + } + added = TRUE; + } + + g_free(hname); + } + + g_free(name); + } + + /* If there is no high color added than this means we have a default color */ + if (!added) { + tags = g_list_append(tags, gtk_text_tag_table_lookup(tag_table, + "fg_default_h")); + } + + g_list_free(t); + return tags; +} + +GList * +gm_world_text_view_tags_fix_for_inverse(GmWorldTextView *view, GList *tags) { + gboolean bold = view->priv->last_info.bold; + gboolean fg_changed = FALSE, bg_changed = FALSE; + GList *new_tags = NULL, *item; + GtkTextTag *tag; + gchar *name, *base, *tagname; + int i; + GtkTextTagTable *tab = gtk_text_buffer_get_tag_table( + GM_WORLD_TEXT_VIEW_BUFFER(view)); + + for (item = tags; item; item = item->next) { + tag = GTK_TEXT_TAG(item->data); + + g_object_get(G_OBJECT(tag), "name", &name, NULL); + + if (strncmp(name, "fg_", 3) == 0) { + base = name + 3; + bg_changed = TRUE; + i = 0; + + while (base[i] != '_' && base[i] != '\0') + i++; + + base[i] = '\0'; + tagname = g_strconcat("bg_", base, NULL); + + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + tagname)); + g_free(tagname); + } else if (strncmp(name, "bg_", 3) == 0) { + base = name + 3; + fg_changed = TRUE; + + if (bold) { + tagname = g_strconcat("fg_", base, "_h", NULL); + } else { + tagname = g_strconcat("fg_", base, NULL); + } + + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + tagname)); + g_free(tagname); + } else if (strcmp(name, "inverse_bg") == 0) { + fg_changed = TRUE; + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + "fg_default")); + } else if (strcmp(name, "inverse_fg") == 0) { + bg_changed = TRUE; + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + "bg_default")); + } else { + new_tags = g_list_append(new_tags, tag); + } + + g_free(name); + } + + if (!bg_changed) { + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + "inverse_bg")); + } + if (!fg_changed) { + new_tags = g_list_append(new_tags, gtk_text_tag_table_lookup(tab, + "inverse_fg")); + } + + g_list_free(tags); + return new_tags; +} + +static const gchar * tag_checks[] = { + "foreground-set", + "background-set", + "weight-set", + "underline-set", + "style-set", + "wrap-mode-set", + "invisible-set", + NULL +}; + +gboolean +gm_world_text_view_tags_overlap(GtkTextTag *t1, GtkTextTag *t2) { + int i = 0; + gboolean val1, val2; + + while (tag_checks[i]) { + g_object_get(G_OBJECT(t1), tag_checks[i], &val1, NULL); + g_object_get(G_OBJECT(t2), tag_checks[i], &val2, NULL); + + if (val1 && val2) { + return TRUE; + } + + i++; + } + + return FALSE; +} + +GList * +gm_world_text_view_tags_remove_obsolete(GmWorldTextView *view, GList *tags, + GtkTextTag *tag) { + GList *t = g_list_copy(tags), *item; + GtkTextTag *tag2; + + for (item = t; item; item = item->next) { + tag2 = GTK_TEXT_TAG(item->data); + + if (gm_world_text_view_tags_overlap(tag, tag2)) { + tags = g_list_remove(tags, tag2); + } + } + + g_list_free(t); + return tags; +} + +GList * +gm_world_text_view_tags_remove_high(GmWorldTextView *view, GList *tags) { + GList *t = g_list_copy(tags), *item; + gchar *name; + + for (item = t; item; item = item->next) { + g_object_get(G_OBJECT(item->data), "name", &name, NULL); + + if (strncmp(name, "fg_", 3) == 0 && name[strlen(name) - 1] == 'h') { + tags = g_list_remove(tags, item->data); + } + g_free(name); + } + + g_list_free(t); + return tags; +} + +GList * +gm_world_text_view_tags_add(GmWorldTextView *view, GList *tags, GtkTextTag *tag, + gboolean inverse) { + gboolean weight; + gboolean fg_set; + gchar *name, *nameh; + GtkTextTag *tagh; + + tags = gm_world_text_view_tags_remove_obsolete(view, tags, tag); + + g_object_get(G_OBJECT(tag), "foreground-set", &fg_set, NULL); + + if (fg_set) { + tags = gm_world_text_view_tags_remove_high(view, tags); + g_object_get(G_OBJECT(tag), "name", &name, NULL); + nameh = g_strconcat(name, "_h", NULL); + + tagh = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table( + GM_WORLD_TEXT_VIEW_BUFFER(view)), nameh); + if (tagh) { + tags = g_list_append(tags, tagh); + } + + g_free(name); + g_free(nameh); + } + + g_object_get(G_OBJECT(tag), "weight-set", &weight, NULL); + + if (weight) { + tags = gm_world_text_view_tags_fix_for_bold(view, tags); + } + + return g_list_append(tags, tag); +} + +/* Public/exported functions */ +GtkWidget * +gm_world_text_view_new() { + GtkWidget *result; + GmColorTable *table = gm_color_table_new(); + result = gm_world_text_view_new_with_color_table(table); + g_object_unref(table); + + return result; +} + +GtkWidget * +gm_world_text_view_new_with_color_table(GmColorTable *color_table) { + GmWorldTextView *view = GM_WORLD_TEXT_VIEW(g_object_new( + GM_TYPE_WORLD_TEXT_VIEW, NULL)); + + gm_world_text_view_create_tags(view); + gm_world_text_view_set_color_table(view, color_table); + + return GTK_WIDGET(view); +} + +void +gm_world_text_view_set_color_table(GmWorldTextView *view, + GmColorTable *color_table) { + if (view->priv->color_table != NULL) { + g_signal_handlers_disconnect_by_func(view->priv->color_table, + on_gm_world_text_view_color_table_color_changed, view); + g_signal_handlers_disconnect_by_func(view->priv->color_table, + on_gm_world_text_view_color_table_font_changed, view); + g_object_unref(view->priv->color_table); + } + + if (color_table != NULL) { + view->priv->color_table = g_object_ref(color_table); + g_signal_connect(view->priv->color_table, "color_changed", + G_CALLBACK(on_gm_world_text_view_color_table_color_changed), + view); + g_signal_connect(view->priv->color_table, "font_changed", + G_CALLBACK(on_gm_world_text_view_color_table_font_changed), + view); + + gm_world_text_view_init_tags(view); + gm_world_text_view_update_font(view); + } else { + view->priv->color_table = NULL; + } +} + +GmColorTable * +gm_world_text_view_color_table(GmWorldTextView *view) { + return view->priv->color_table; +} + +void +gm_world_text_view_tag_urls(GmWorldTextView *view, gchar *text, + GtkTextIter *started, GtkTextIter *ended) { + gint num_matches, i; + GArray *start, *end; + gint s = 0, e = 0; + GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view); + GtkTextTag *url_tag = gtk_text_tag_table_lookup( + gtk_text_buffer_get_tag_table(buffer), "url"); + GtkTextIter urlstart, urlend; + + start = g_array_new(FALSE, FALSE, sizeof(gint)); + end = g_array_new(FALSE, FALSE, sizeof(gint)); + + num_matches = gm_url_regex_match(text, strlen(text), start, end); + + for (i = 0; i < num_matches; i++) { + urlstart = *started; + urlend = *started; + + s = g_array_index(start, gint, i); + e = g_array_index(end, gint, i); + + gtk_text_iter_forward_cursor_positions(&urlstart, + g_utf8_pointer_to_offset(text, text + s)); + gtk_text_iter_forward_cursor_positions(&urlend, + g_utf8_pointer_to_offset(text, text + e)); + + gtk_text_buffer_apply_tag(buffer, url_tag, &urlstart, &urlend); + } + + g_array_free(start, TRUE); + g_array_free(end, TRUE); +} + +gchar * +gm_world_text_view_insert(GmWorldTextView *view, const gchar *text) { + gchar *ptr, *ansi_start, *ansi_stop, **ansis; + gchar *name; + int i; + guint seq; + GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view); + GtkTextTagTable *tab = gtk_text_buffer_get_tag_table(buffer); + GmWorldTextViewInsertInfo *info = &(view->priv->last_info); + GString *new_line = NULL; + GtkTextIter started, ended; + GtkTextMark *start_mark; + GtkTextTag *tag; + gboolean skip; + + g_return_val_if_fail(g_utf8_validate(text, -1, NULL), NULL); + + if (info->text != NULL) { + new_line = g_string_new(info->text); + g_free(info->text); + info->text = NULL; + } else { + new_line = g_string_new(""); + } + + g_string_append(new_line, text); + + if ((ptr = g_utf8_strchr(new_line->str, -1, '\x07'))) { + gdk_beep(); + + while (ptr) { + g_string_erase(new_line, g_utf8_pointer_to_offset(new_line->str, ptr), 1); + ptr = g_utf8_strchr(new_line->str, -1, '\x07'); + } + } + + gm_debug_msg(DEBUG_DEFAULT, "GmWorldTextView.Insert: %s", new_line->str); + gtk_text_buffer_get_end_iter(buffer, &started); + start_mark = gtk_text_buffer_create_mark(buffer, "last-insertion", + &started, TRUE); + + while (new_line != NULL && + (ansi_start = strstr(new_line->str, "\x1B["))) { + i = g_utf8_pointer_to_offset(new_line->str, ansi_start); + + if (i != 0) { + ptr = g_strndup(new_line->str, ansi_start - new_line->str); + + gm_world_text_view_insert_text(view, ptr, info->tags); + g_free(ptr); + } + + if ((ansi_stop = g_utf8_strchr(ansi_start, -1, 'm'))) { + /* Advance to the ansi sequence */ + ansi_start = ansi_start + 2; + ptr = g_strndup(ansi_start, ansi_stop - ansi_start); + ansis = g_strsplit(ptr, ";", -1); + g_free(ptr); + + for (i = 0; ansis[i] != NULL; i++) { + if (ansis[i][0] != '\0' && g_utf8_toint(ansis[i], &seq)) { + skip = FALSE; + switch (seq) { + case A_FG_DEFAULT: case A_FG_BLACK: case A_FG_RED: case A_FG_GREEN: + case A_FG_YELLOW: case A_FG_BLUE: case A_FG_PURPLE: case A_FG_CYAN: + case A_FG_WHITE: + if (info->inverse) { + seq += 10; + } + break; + case A_BG_DEFAULT: case A_BG_BLACK: case A_BG_RED: case A_BG_GREEN: + case A_BG_YELLOW: case A_BG_BLUE: case A_BG_PURPLE: case A_BG_CYAN: + case A_BG_WHITE: + if (info->inverse) { + seq -= 10; + } + break; + case A_BOLD: + info->bold = TRUE; + + if (info->inverse) + skip = TRUE; + break; + case A_BOLD_OFF: + info->bold = FALSE; + + if (info->inverse) + skip = TRUE; + break; + case A_INVERSE: + info->inverse = !(info->inverse); + info->tags = gm_world_text_view_tags_fix_for_inverse(view, info->tags); + break; + case A_INVERSE_OFF: + if (info->inverse) { + info->tags = gm_world_text_view_tags_fix_for_inverse(view, info->tags); + info->inverse = FALSE; + } + break; + case A_DEFAULT: + g_list_free(info->tags); + info->tags = NULL; + info->bold = FALSE; + info->inverse = FALSE; + break; + } + + name = (gchar *)gm_world_text_view_tagname_from_code(seq); + + if (name != NULL && !skip) { + tag = gtk_text_tag_table_lookup(tab, name); + + if (tag != NULL) { + info->tags = gm_world_text_view_tags_add(view, info->tags, tag, + info->inverse); + } + } + } + } + + g_strfreev(ansis); + g_string_erase(new_line, 0, (ansi_stop - new_line->str) + 1); + } else { + info->text = g_strdup(ansi_start); + g_string_free(new_line, TRUE); + new_line = NULL; + } + } + + if (new_line && new_line->len != 0) { + gm_world_text_view_insert_text(view, new_line->str, info->tags); + } + + if (new_line) { + g_string_free(new_line, TRUE); + } + + gm_world_text_view_check_buffer_size(view); + + gtk_text_buffer_get_iter_at_mark(buffer, &started, start_mark); + gtk_text_buffer_get_end_iter(buffer, &ended); + gtk_text_buffer_delete_mark(buffer, start_mark); + + ptr = gtk_text_buffer_get_text(buffer, &started, &ended, FALSE); + gm_world_text_view_tag_urls(view, ptr, &started, &ended); + + return ptr; +} + +/* Callbacks */ +void +on_gm_world_text_view_style_set(GmWorldTextView *view, GtkStyle *previous_style, + gpointer user_data) { + GtkRcStyle *style = gtk_widget_get_modifier_style(GTK_WIDGET(view)); + PangoContext *pc = gtk_widget_create_pango_context(GTK_WIDGET(view)); + PangoLayout *pl; + gint cwidth, cheight; + + if (style->font_desc != NULL) { + pango_context_set_font_description(pc, style->font_desc); + pl = pango_layout_new(pc); + + pango_layout_set_text(pl, "G", 1); + pango_layout_get_pixel_size(pl, &(cwidth), + &(cheight)); + + if (view->priv->character_width != cwidth || + view->priv->character_height != cheight) { + view->priv->character_width = cwidth; + view->priv->character_height = cheight; + + g_signal_emit(view, world_text_view_signals[CHARACTER_SIZE_CHANGED], 0, + cwidth, cheight); + } + + g_object_unref(pl); + } + + g_object_unref(pc); +} + +gboolean +on_gm_world_text_view_url_event(GtkTextTag *tag, GObject *object, + GdkEvent *event, GtkTextIter *iter, GmWorldTextView *view) { + GtkTextIter start, end; + gchar *str; + GtkTextBuffer *buffer = GM_WORLD_TEXT_VIEW_BUFFER(view); + + /* If the link is being selected, don't do anything. */ + gtk_text_buffer_get_selection_bounds(buffer, &start, &end); + + if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) { + return FALSE; + } + + if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) { + start = end = *iter; + + if (gtk_text_iter_backward_to_tag_toggle(&start, tag) && + gtk_text_iter_forward_to_tag_toggle(&end, tag)) { + str = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + g_signal_emit(view, world_text_view_signals[URL_ACTIVATE], 0, str); + g_free(str); + } + } + + return FALSE; +} + +gboolean +on_gm_world_text_view_event(GmWorldTextView *view, GdkEventMotion *event, + GtkTextTag *tag) { + static GdkCursor *hand = NULL; + static GdkCursor *beam = NULL; + GtkTextWindowType type; + GtkTextIter iter; + GdkWindow *win; + gint x, y, buf_x, buf_y; + gboolean has_tag; + + type = gtk_text_view_get_window_type(GTK_TEXT_VIEW(view), event->window); + + if (type != GTK_TEXT_WINDOW_TEXT) { + return FALSE; + } + + /* Get where the pointer really is. */ + win = gtk_text_view_get_window(GTK_TEXT_VIEW(view), type); + gdk_window_get_pointer(win, &x, &y, NULL); + + /* Get the iter where the cursor is at */ + gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(view), type, + x, y, &buf_x, &buf_y); + + gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(view), &iter, buf_x, buf_y); + + if (!hand) { + hand = gdk_cursor_new(GDK_FLEUR); + beam = gdk_cursor_new(GDK_XTERM); + } + + has_tag = gtk_text_iter_has_tag(&iter, tag); + + if (has_tag && !view->priv->is_hand) { + view->priv->is_hand = TRUE; + gdk_window_set_cursor(win, hand); + } else if (!has_tag && view->priv->is_hand) { + view->priv->is_hand = FALSE; + gdk_window_set_cursor(win, beam); + } + + return FALSE; +} + +void +gm_world_text_view_color_table_fix_high_color(GtkTextTag *tag, gpointer data) { + gboolean bold = (gboolean)(GPOINTER_TO_INT(data)); + gchar *name; + GdkColor *col; + + g_object_get(G_OBJECT(tag), "name", &name, NULL); + + if (strncmp(name, "fg_", 3) == 0 && name[strlen(name) - 1] == 'h') { + g_object_get(G_OBJECT(tag), "foreground-gdk", &col, NULL); + + if (bold) { + g_object_set(G_OBJECT(tag), "foreground-gdk", col, NULL); + } else { + g_object_set(G_OBJECT(tag), "foreground-set", FALSE, NULL); + } + } + + g_free(name); +} + +/*void +on_gm_world_text_view_color_table_bold_toggled(GmColorTable *table, + gboolean bold, GmWorldTextView *view) { + GtkTextTag *tag; + GtkTextBuffer *buf = GM_WORLD_TEXT_VIEW_BUFFER(view); + GtkTextTagTable *tb = gtk_text_buffer_get_tag_table(buf); + + // Fix bold tag + tag = gtk_text_tag_table_lookup(tb, "bold"); + + if (tag != NULL) { + if (bold) { + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_NORMAL, NULL); + } else { + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_ULTRABOLD, NULL); + } + + // Fix high color tags + gtk_text_tag_table_foreach(tb, gm_world_text_view_color_table_fix_high_color, + GINT_TO_POINTER(bold)); + } +}*/ + +void +on_gm_world_text_view_color_table_color_changed(GmColorTable *table, + gchar *name, GmWorldTextView *view) { + GdkColor col; + + gm_world_text_view_update_color_tag(view, name, NULL); + + if (strcmp(name, "fg_default") == 0) { + gm_color_table_get(view->priv->color_table, "fg_default", &col); + gtk_widget_modify_text(GTK_WIDGET(view), GTK_STATE_NORMAL, &col); + } else if (strcmp(name, "bg_default") == 0) { + gm_color_table_get(view->priv->color_table, "bg_default", &col); + gtk_widget_modify_base(GTK_WIDGET(view), GTK_STATE_NORMAL, &col); + } +} + +void +on_gm_world_text_view_color_table_font_changed(GmColorTable *table, + gchar *font_description, GmWorldTextView *view) { + gm_world_text_view_update_font(view); +} diff --git a/gnoemoe/widgets/gm-world-text-view.h b/gnoemoe/widgets/gm-world-text-view.h new file mode 100644 index 0000000..69de070 --- /dev/null +++ b/gnoemoe/widgets/gm-world-text-view.h @@ -0,0 +1,59 @@ +#ifndef __GM_WORLD_TEXT_VIEW_H__ +#define __GM_WORLD_TEXT_VIEW_H__ + +#include <gtk/gtk.h> +#include "../gm-color-table.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_WORLD_TEXT_VIEW (gm_world_text_view_get_type()) +#define GM_WORLD_TEXT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextView)) +#define GM_WORLD_TEXT_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextView const)) +#define GM_WORLD_TEXT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextViewClass)) +#define GM_IS_WORLD_TEXT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_WORLD_TEXT_VIEW)) +#define GM_IS_WORLD_TEXT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_WORLD_TEXT_VIEW)) +#define GM_WORLD_TEXT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_WORLD_TEXT_VIEW, GmWorldTextViewClass)) + +/* Private structure type */ +typedef struct _GmWorldTextViewPrivate GmWorldTextViewPrivate; + +/* + * Main object structure + */ +typedef struct _GmWorldTextView GmWorldTextView; + +struct _GmWorldTextView { + GtkTextView textview; + + /*< private > */ + GmWorldTextViewPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmWorldTextViewClass GmWorldTextViewClass; + +struct _GmWorldTextViewClass { + GtkTextViewClass parent_class; + + /* Signals */ + void (* url_activate) (GmWorldTextView *view, const gchar *url); + void (* character_size_changed) (GmWorldTextView *view, gint width, + gint height); +}; + +GType gm_world_text_view_get_type(void) G_GNUC_CONST; +GtkWidget *gm_world_text_view_new(void); +GtkWidget *gm_world_text_view_new_with_color_table(GmColorTable *color_table); +gchar *gm_world_text_view_insert(GmWorldTextView *view, const gchar *text); +GmColorTable *gm_world_text_view_color_table(GmWorldTextView *view); +void gm_world_text_view_set_color_table(GmWorldTextView *view, + GmColorTable *color_table); + +G_END_DECLS +#endif /* __GM_WORLD_TEXT_VIEW_H__ */ + diff --git a/gnoemoe/widgets/gm-world-view.c b/gnoemoe/widgets/gm-world-view.c new file mode 100644 index 0000000..a3d0462 --- /dev/null +++ b/gnoemoe/widgets/gm-world-view.c @@ -0,0 +1,567 @@ +#include <gdk/gdkkeysyms.h> + +#include "../gm-app.h" +#include "gm-world-view.h" +#include "gm-world-text-view.h" +#include "gm-world-input-view.h" +#include "gm-text-scroller.h" +#include "../gm-debug.h" +#include "../gm-color-table.h" + +#define GM_WORLD_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GM_TYPE_WORLD_VIEW, GmWorldViewPrivate)) + +struct _GmWorldViewPrivate { + GmWorld *world; + + GtkStatusbar *statusbar; + GtkHPaned *hpaned; + GtkTreeView *tree_view_userlist; + GtkTreeModel *tree_model_userlist; + GmWorldTextView *text_view_world; + GmWorldInputView *text_view_input; + GmTextScroller *text_scroller_world; +}; + +enum { + GM_WORLD_VIEW_USERLIST_ICON, + GM_WORLD_VIEW_USERLIST_NAME, + GM_WORLD_VIEW_USERLIST_OBJ, + GM_WORLD_VIEW_USERLIST_SORT, + GM_WORLD_VIEW_USERLIST_N_COLUMNS +}; + +void on_gm_world_view_world_text_received(GmWorld *world, gchar *text, + GmWorldView *view); +void on_gm_world_view_world_error(GmWorld *world, gchar *text, gint code, + GmWorldView *view); +void on_gm_world_input_view_world_text_activate(GmWorldInputView *iview, + gchar *text, GmWorldView *view); +void on_gm_world_view_world_state_changing(GmWorld *world, guint state, + GmWorldView *view); +void on_gm_world_view_world_active_changed(GmWorld *world, gboolean active, + GmWorldView *view); +void on_gm_world_view_world_status_changed(GmWorld *world, gchar const *text, + GmWorldView *view); + +gboolean on_gm_world_view_world_text_view_scroll_event(GmWorldView *view, + GdkEventScroll *event, GmWorldTextView *text); +gboolean on_gm_world_view_world_input_view_key_pressed(GtkWidget *widget, + GdkEventKey *event, GmWorldView *view); + +/* Signals */ + +/*enum { + NUM_SIGNALS +}; + +static guint world_view_signals[NUM_SIGNALS] = {0};*/ + +G_DEFINE_TYPE(GmWorldView, gm_world_view, GTK_TYPE_NOTEBOOK) + +static void +gm_world_view_finalize(GObject *object) { + GmWorldView *view = GM_WORLD_VIEW(object); + + g_signal_handlers_disconnect_by_func(view->priv->world, + G_CALLBACK(on_gm_world_view_world_text_received), view); + g_signal_handlers_disconnect_by_func(view->priv->world, + G_CALLBACK(on_gm_world_view_world_error), view); + g_signal_handlers_disconnect_by_func(view->priv->world, + G_CALLBACK(on_gm_world_view_world_state_changing), view); + g_signal_handlers_disconnect_by_func(view->priv->world, + G_CALLBACK(on_gm_world_view_world_active_changed), view); + g_signal_handlers_disconnect_by_func(view->priv->world, + G_CALLBACK(on_gm_world_view_world_status_changed), view); + + g_object_unref(view->priv->world); + G_OBJECT_CLASS(gm_world_view_parent_class)->finalize(object); +} + +static void +gm_world_view_class_init(GmWorldViewClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gm_world_view_finalize; + + g_type_class_add_private(object_class, sizeof(GmWorldViewPrivate)); +} + +GtkTreeModel * +gm_world_view_userlist_model_new(GmWorldView *view) { + GtkListStore *store = + gtk_list_store_new(GM_WORLD_VIEW_USERLIST_N_COLUMNS, GDK_TYPE_PIXBUF, + G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING); + GtkTreeModel *model = gtk_tree_model_sort_new_with_model( + GTK_TREE_MODEL(store)); + + view->priv->tree_model_userlist = model; + return model; +} + +GtkWidget * +gm_world_view_userlist_new(GmWorldView *view) { + GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); + GtkTreeModel *model = gm_world_view_userlist_model_new(view); + GtkWidget *tree_view = gtk_tree_view_new_with_model(model); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_SHADOW_IN); + gtk_widget_set_size_request(scrolled_window, 150, -1); + gtk_widget_set_size_request(tree_view, 150, -1); + + gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view); + + renderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes(_("I"), renderer, "pixbuf", + GM_WORLD_VIEW_USERLIST_ICON, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); + gtk_tree_view_column_set_min_width(column, 30); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, + "text", GM_WORLD_VIEW_USERLIST_NAME, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); + + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), + GM_WORLD_VIEW_USERLIST_SORT, GTK_SORT_ASCENDING); + + GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(tree_view), GTK_CAN_FOCUS); + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree_view)); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE); + view->priv->tree_view_userlist = GTK_TREE_VIEW(tree_view); + + return scrolled_window; +} + +GtkWidget * +gm_world_view_create_input_text_view(GmWorldView *view) { + GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); + GtkWidget *input_text_view = gm_world_input_view_new_with_color_table( + gm_app_color_table(gm_app_instance())); + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scrolled_window), input_text_view); + + view->priv->text_view_input = GM_WORLD_INPUT_VIEW(input_text_view); + + g_signal_connect(input_text_view, "key_press_event", + G_CALLBACK(on_gm_world_view_world_input_view_key_pressed), view); + return scrolled_window; +} + +GtkWidget * +gm_world_view_create_world_text_view(GmWorldView *view) { + GtkWidget *world_text_view = gm_world_text_view_new_with_color_table( + gm_app_color_table(gm_app_instance())); + GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); + + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), + GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(scrolled_window), world_text_view); + + view->priv->text_view_world = GM_WORLD_TEXT_VIEW(world_text_view); + + // Create new text scroller, this object will take care of itself and will + // destroy itself when the view dies, neat! + view->priv->text_scroller_world = + gm_text_scroller_new(GTK_TEXT_VIEW(view->priv->text_view_world)); + + g_signal_connect(world_text_view, "scroll_event", + G_CALLBACK(on_gm_world_view_world_text_view_scroll_event), view); + return scrolled_window; +} + +void +gm_world_view_update_status(GmWorldView *view, gchar const *status) { + gtk_statusbar_pop(view->priv->statusbar, 0); + + if (status == NULL) { + gtk_statusbar_push(view->priv->statusbar, 0, + _("Welcome to GnoeMoe, explorer of new worlds!")); + } else { + gtk_statusbar_push(view->priv->statusbar, 0, + status); + } +} + +GtkWidget * +gm_world_view_world_page_new(GmWorldView *view) { + GtkWidget *vbox = gtk_vbox_new(FALSE, 0); + GtkWidget *hpaned = gtk_hpaned_new(); + GtkWidget *status = gtk_statusbar_new(); + GtkWidget *vbox_world = gtk_vbox_new(FALSE, 3); + + gtk_box_pack_start(GTK_BOX(vbox_world), + gm_world_view_create_world_text_view(view), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox_world), + gm_world_view_create_input_text_view(view), FALSE, FALSE, 0); + + gtk_container_set_border_width(GTK_CONTAINER(vbox_world), 3); + gtk_paned_pack1(GTK_PANED(hpaned), vbox_world, TRUE, TRUE); + gtk_paned_pack2(GTK_PANED(hpaned), gm_world_view_userlist_new(view), + FALSE, TRUE); + + gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(status), FALSE); + + gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0); + + view->priv->statusbar = GTK_STATUSBAR(status); + view->priv->hpaned = GTK_HPANED(hpaned); + + gm_world_view_update_status(view, NULL); + + return vbox; +} + +static void +gm_world_view_init(GmWorldView *view) { + GtkWidget *label; + GmLabelInfo info; + + view->priv = GM_WORLD_VIEW_GET_PRIVATE(view); + + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(view), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(view), FALSE); + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(view), GTK_POS_BOTTOM); + gtk_notebook_set_scrollable(GTK_NOTEBOOK(view), TRUE); + + label = gm_create_tab_label("world.svg", _("World"), FALSE, &info); + gtk_notebook_append_page(GTK_NOTEBOOK(view), + gm_world_view_world_page_new(view), label); +} + +void +on_gm_world_view_destroy(GmWorldView *view, gpointer user_data) { + gm_options_set_int(gm_world_options(view->priv->world), "pane_position", + GTK_WIDGET(view->priv->hpaned)->allocation.width - + gtk_paned_get_position(GTK_PANED(view->priv->hpaned))); + + gm_options_save(gm_world_options(view->priv->world)); +} + +void +on_gm_world_view_show(GmWorldView *view, gpointer user_data) { + gm_do_events(); + + gtk_paned_set_position(GTK_PANED(view->priv->hpaned), + GTK_WIDGET(view->priv->hpaned)->allocation.width + - gm_options_get_int(gm_world_options(view->priv->world), + "pane_position")); +} + +GtkWidget * +gm_world_view_new(GmWorld *world) { + GmWorldView *view = GM_WORLD_VIEW(g_object_new(GM_TYPE_WORLD_VIEW, NULL)); + + view->priv->world = g_object_ref(world); + + gm_world_input_view_set_history(view->priv->text_view_input, + gm_world_history(view->priv->world)); + + g_signal_connect(world, "text_received", + G_CALLBACK(on_gm_world_view_world_text_received), view); + g_signal_connect(world, "world_error", + G_CALLBACK(on_gm_world_view_world_error), view); + g_signal_connect(world, "state_changing", + G_CALLBACK(on_gm_world_view_world_state_changing), view); + g_signal_connect(world, "active_changed", + G_CALLBACK(on_gm_world_view_world_active_changed), view); + g_signal_connect(world, "status_changed", + G_CALLBACK(on_gm_world_view_world_status_changed), view); + + g_signal_connect(view->priv->text_view_input, "text_activate", + G_CALLBACK(on_gm_world_input_view_world_text_activate), view); + g_signal_connect(view, "destroy", + G_CALLBACK(on_gm_world_view_destroy), NULL); + g_signal_connect(view, "show", + G_CALLBACK(on_gm_world_view_show), NULL); + + return GTK_WIDGET(view); +} + +gboolean +gm_world_view_find_first(GmWorldView *view, const gchar *str, + GmWorldViewSearchFlags flags) { + GtkTextView *tview = GTK_TEXT_VIEW(view->priv->text_view_world); + GtkTextIter iter; + GtkTextBuffer *buffer; + + if (tview) { + buffer = gtk_text_view_get_buffer(tview); + + if (buffer) { + if (flags & GM_WORLD_VIEW_SEARCH_BACKWARDS) { + gtk_text_buffer_get_end_iter(buffer, &iter); + } else { + gtk_text_buffer_get_start_iter(buffer, &iter); + } + + gtk_text_buffer_place_cursor(buffer, &iter); + + return gm_world_view_find_next(view, str, flags); + } + } + + return FALSE; +} + +gboolean +gm_world_view_find_next(GmWorldView *view, const gchar *str, + GmWorldViewSearchFlags flags) { + GtkTextBuffer *buffer = NULL; + GtkTextView *tview = NULL; + GtkTextIter end, start, matchStart, matchEnd; + gboolean found = FALSE; + + if (*str == '\0') { + return FALSE; + } + + tview = GTK_TEXT_VIEW(view->priv->text_view_world); + + if (tview) { + buffer = gtk_text_view_get_buffer(tview); + } + + if (buffer) { + if (!gtk_text_buffer_get_selection_bounds(buffer, &start, &end)) { + if (flags & GM_WORLD_VIEW_SEARCH_BACKWARDS) { + gtk_text_buffer_get_end_iter(buffer, &end); + } else { + gtk_text_buffer_get_start_iter(buffer, &end); + } + + start = end; + } + + if (flags & GM_WORLD_VIEW_SEARCH_FORWARDS) { + found = gtk_text_iter_forward_search(&end, str, + GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, + &matchStart, &matchEnd, NULL); + } else { + found = gtk_text_iter_backward_search(&start, str, + GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY, + &matchStart, &matchEnd, NULL); + } + + if (found) { + gtk_text_buffer_place_cursor(buffer, &matchStart); + gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", + &matchEnd); + gtk_text_view_scroll_to_iter(tview, &matchStart, + 0.0, FALSE, 0.0, 0.0); + } + } + + return found; +} + +gboolean +gm_world_view_text_active(GmWorldView *view) { + return gtk_notebook_get_current_page(GTK_NOTEBOOK(view)) == 0; +} + +void +gm_world_view_scroll_end_prepare(GmWorldView *view) { + // TODO +} + +void +gm_world_view_scroll_end(GmWorldView *view) { + // TODO +} + +GmWorld * +gm_world_view_world(GmWorldView *view) { + return view->priv->world; +} + +GmWorldInputView * +gm_world_view_input(GmWorldView *view) { + return view->priv->text_view_input; +} + +GtkTextBuffer * +gm_world_view_buffer(GmWorldView *view) { + return gtk_text_view_get_buffer(GTK_TEXT_VIEW(view->priv->text_view_world)); +} + +void +gm_world_view_open_log(GmWorldView *view, const gchar *filename, + OpenLogProgress callback, gpointer user_data) { +} + +void +gm_world_view_set_userlist_width(GmWorldView *view, gint width) { + gtk_paned_set_position(GTK_PANED(view->priv->hpaned), + GTK_WIDGET(view->priv->hpaned)->allocation.width - width); +} + +void +gm_world_view_set_focus(GmWorldView *view) { + gtk_widget_grab_focus(GTK_WIDGET(view->priv->text_view_input)); +} + +void +gm_world_view_change_font_size(GmWorldView *view, gint size_change) { + GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(view)); + PangoFontDescription *desc = style->font_desc; + PangoFontDescription *copy = pango_font_description_copy(desc); + gchar *new_font; + + pango_font_description_set_size(copy, + pango_font_description_get_size(copy) + + (size_change * PANGO_SCALE)); + new_font = pango_font_description_to_string(copy); + gm_color_table_set_font_description(gm_app_color_table(gm_app_instance()), + new_font); + + pango_font_description_free(copy); + g_free(new_font); +} + +/* Callbacks */ +void +on_gm_world_input_view_world_text_activate(GmWorldInputView *iview, gchar *text, + GmWorldView *view) { + gm_world_process_input(view->priv->world, text); + gm_text_scroller_scroll_end(view->priv->text_scroller_world); +} + +void +on_gm_world_view_world_text_received(GmWorld *world, gchar *text, + GmWorldView *view) { + gchar *inserted = + gm_world_text_view_insert(view->priv->text_view_world, text); + + g_free(inserted); +} + +void +on_gm_world_view_world_error(GmWorld *world, gchar *text, gint code, + GmWorldView *view) { + gchar *line; + + switch (code) { + case GM_NET_ERROR_CONNECTING: + line = g_strdup_printf(_("# Connect failed: %s"), text); + break; + case GM_NET_ERROR_DISCONNECTED: + line = g_strdup_printf(_("# Connection lost... (%s)"), text); + break; + default: + line = g_strdup_printf(_("# Error: %s"), text); + break; + } + + gm_world_writeln(world, line); + g_free(line); +} + +void +on_gm_world_view_world_state_changing(GmWorld *world, guint state, + GmWorldView *view) { + gchar *line = NULL; + GmNetState pstate = gm_world_state(world); + + switch (state) { + case GM_NET_STATE_TRY_ADDRESS: + line = g_strdup_printf(_("# Trying %s port %s"), + gm_world_current_host(world), gm_world_current_port(world)); + break; + case GM_NET_STATE_CONNECTING: + line = g_strdup_printf(_("# Connecting to %s port %s"), + gm_world_current_host(world), gm_world_current_port(world)); + break; + case GM_NET_STATE_CONNECTED: + line = g_strdup(_("# Connected")); + break; + case GM_NET_STATE_DISCONNECTED: + if (pstate == GM_NET_STATE_CONNECTED || + pstate == GM_NET_STATE_DISCONNECTING) { + line = g_strdup(_("# Disconnected")); + } + break; + case GM_NET_STATE_DISCONNECTING: + line = g_strdup(_("# Disconnecting")); + break; + default: + break; + } + + if (line) { + gm_world_writeln(world, line); + g_free(line); + } +} + +void +on_gm_world_view_world_active_changed(GmWorld *world, gboolean active, + GmWorldView *view) { + if (active) { + gtk_widget_grab_focus(GTK_WIDGET(view->priv->text_view_input)); + } +} + +void +on_gm_world_view_world_status_changed(GmWorld *world, gchar const *text, + GmWorldView *view) { + gm_world_view_update_status(view, text); +} + +gboolean on_gm_world_view_world_text_view_scroll_event(GmWorldView *view, + GdkEventScroll *event, GmWorldTextView *text) { + if (event->state & GDK_CONTROL_MASK) { + switch (event->direction) { + case GDK_SCROLL_UP: + // Decrease font size + gm_world_view_change_font_size(view, -1); + break; + case GDK_SCROLL_DOWN: + // Increase font size + gm_world_view_change_font_size(view, 1); + break; + default: + break; + } + return TRUE; + } else { + return FALSE; + } + + return FALSE; +} + +gboolean +on_gm_world_view_world_input_view_key_pressed(GtkWidget *widget, + GdkEventKey *event, GmWorldView *view) { + switch (event->keyval) { + case GDK_Home: case GDK_End: + if ((event->state | GDK_CONTROL_MASK) == event->state) { + if (event->keyval == GDK_End) { + gm_text_scroller_scroll_end( + view->priv->text_scroller_world); + } else { + gm_text_scroller_scroll_begin( + view->priv->text_scroller_world); + } + } + return TRUE; + break; + default: + break; + } + + return FALSE; +} diff --git a/gnoemoe/widgets/gm-world-view.h b/gnoemoe/widgets/gm-world-view.h new file mode 100644 index 0000000..6f82606 --- /dev/null +++ b/gnoemoe/widgets/gm-world-view.h @@ -0,0 +1,75 @@ +#ifndef __GM_WORLD_VIEW_H__ +#define __GM_WORLD_VIEW_H__ + +#include <gtk/gtk.h> +#include "../gm-support.h" +#include "../gm-world.h" +#include "gm-world-input-view.h" + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GM_TYPE_WORLD_VIEW (gm_world_view_get_type()) +#define GM_WORLD_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_VIEW, GmWorldView)) +#define GM_WORLD_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GM_TYPE_WORLD_VIEW, GmWorldView const)) +#define GM_WORLD_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GM_TYPE_WORLD_VIEW, GmWorldViewClass)) +#define GM_IS_WORLD_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GM_TYPE_WORLD_VIEW)) +#define GM_IS_WORLD_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GM_TYPE_WORLD_VIEW)) +#define GM_WORLD_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GM_TYPE_WORLD_VIEW, GmWorldViewClass)) + +/* Private structure type */ +typedef struct _GmWorldViewPrivate GmWorldViewPrivate; + +/* + * Main object structure + */ +typedef struct _GmWorldView GmWorldView; + +struct _GmWorldView { + GtkNotebook notebook; + + /*< private > */ + GmWorldViewPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GmWorldViewClass GmWorldViewClass; + +struct _GmWorldViewClass { + GtkNotebookClass parent_class; + + /* Signals */ +}; + +typedef enum _GmWorldViewSearchFlags { + GM_WORLD_VIEW_SEARCH_NONE = 0, + GM_WORLD_VIEW_SEARCH_FORWARDS = 1 << 0, + GM_WORLD_VIEW_SEARCH_BACKWARDS = 1 << 1 +} GmWorldViewSearchFlags; + +GType gm_world_view_get_type(void) G_GNUC_CONST; +GtkWidget *gm_world_view_new(GmWorld *world); + +gboolean gm_world_view_find_first(GmWorldView *view, const gchar *str, + GmWorldViewSearchFlags flags); +gboolean gm_world_view_find_next(GmWorldView *view, const gchar *str, + GmWorldViewSearchFlags flags); + +void gm_world_view_scroll_end_prepare(GmWorldView *view); +void gm_world_view_scroll_end(GmWorldView *view); +GmWorld *gm_world_view_world(GmWorldView *view); +GmWorldInputView *gm_world_view_input(GmWorldView *view); +GtkTextBuffer *gm_world_view_buffer(GmWorldView *view); +gboolean gm_world_view_text_active(GmWorldView *view); + +void gm_world_view_open_log(GmWorldView *view, const gchar *filename, + OpenLogProgress callback, gpointer user_data); +void gm_world_view_set_userlist_width(GmWorldView *view, gint width); +void gm_world_view_set_focus(GmWorldView *view); + +G_END_DECLS +#endif /* __GM_WORLD_VIEW_H__ */