#include #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; }