#include #include #include "gm-mcp-package.h" #include "gm-mcp-userlist-view.h" #include "gm-cell-renderer-text.h" #include "gm-iuserlist.h" #include "widgets/gm-world-view.h" #include "gm-pixbuf.h" #include "gm-app.h" #include "gm-debug.h" #define GM_USERLIST_ICON_SIZE 22 typedef struct _GmMcpUserlistView { GmWorldView *view; GmMcpPackage *package; GtkTreeModel *model; GtkListStore *store; GtkTreeView *tree_view; GtkWidget *label; GtkScrolledWindow *scrolled_window; GtkWidget *popup_menu; GtkWidget *vbox; gboolean initializing; GmUserlistSortType sort_type; gboolean show_object; gboolean show_status; gboolean use_state_icon; gint num_players; gint num_active; GdkColor status_color; } GmMcpUserlistView; static void on_gm_mcp_userlist_view_weak_notify(gpointer data, GObject *obj); static void on_gm_mcp_userlist_view_player_added(GmMcpPackage *package, gint id, GmMcpUserlistView *view); static void on_gm_mcp_userlist_view_player_removed(GmMcpPackage *package, gint id, GmMcpUserlistView *view); static void on_gm_mcp_userlist_view_name_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view); static void on_gm_mcp_userlist_view_rank_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view); static void on_gm_mcp_userlist_view_state_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view); static gboolean on_gm_mcp_userlist_view_popup_menu(GtkWidget *widget, GmMcpUserlistView *view); static gboolean on_gm_mcp_userlist_view_button_press(GtkWidget *widget, GdkEventButton *event, GmMcpUserlistView *view); static void on_gm_mcp_userlist_view_option_changed(GmOptions *options, gchar const *key, GmMcpUserlistView *view); typedef struct _SortInfo { gchar *name; gint rank_priority; gint state_priority; } SortInfo; static gint gm_mcp_userlist_view_sort_state_rank_name(SortInfo *info1, SortInfo *info2); static gint gm_mcp_userlist_view_sort_rank_name(SortInfo *info1, SortInfo *info2); static gint gm_mcp_userlist_view_sort_state_name(SortInfo *info1, SortInfo *info2); static gint gm_mcp_userlist_view_sort_name(SortInfo *info1, SortInfo *info2); typedef gint (*GmUserlistCompareFunc) (SortInfo *info1, SortInfo *info2); static GmUserlistCompareFunc compare_functions[] = { gm_mcp_userlist_view_sort_state_rank_name, gm_mcp_userlist_view_sort_rank_name, gm_mcp_userlist_view_sort_state_name, gm_mcp_userlist_view_sort_name }; GtkTreeModel * gm_mcp_userlist_view_model_create(GmMcpUserlistView *view) { GtkListStore *store = gtk_list_store_new(GM_USERLIST_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); GtkTreeModel *model = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL( store)); view->store = store; view->model = model; return model; } gint gm_userlist_view_strcmp_safe(gchar const *str1, gchar const *str2) { gchar *u1, *u2; gint result; if (str1 == NULL || str2 == NULL) { if (str1 == NULL && str2 == NULL) { result = 0; } else if (str1 == NULL) { result = -1; } else { result = 1; } } else { u1 = g_utf8_casefold(str1, -1); u2 = g_utf8_casefold(str2, -1); result = g_utf8_collate(u1, u2); g_free(u1); g_free(u2); } return result; } static gint gm_mcp_userlist_view_sort_state_rank_name(SortInfo *info1, SortInfo *info2) { // Sort by 'icon' (so first by state, then if state is avail sort by rank // and then by name, otherwise sort by name) if (info1->state_priority == info2->state_priority) { // The same state, if state is 0 then go by rank and then name, // otherwise go by name if (info1->state_priority == 0) { if (info1->rank_priority == info2->rank_priority) { return gm_userlist_view_strcmp_safe(info1->name, info2->name); } else { return info1->rank_priority - info2->rank_priority; } } else { return gm_userlist_view_strcmp_safe(info1->name, info2->name); } } else { return info1->state_priority - info2->state_priority; } } static gint gm_mcp_userlist_view_sort_rank_name(SortInfo *info1, SortInfo *info2) { // First sort by rank, then sort by name if (info1->rank_priority == info2->rank_priority) { return gm_userlist_view_strcmp_safe(info1->name, info2->name); } else { return info1->rank_priority - info2->rank_priority; } } static gint gm_mcp_userlist_view_sort_state_name(SortInfo *info1, SortInfo *info2) { // First sort by state, then sort by name if (info1->state_priority == info2->state_priority) { return gm_userlist_view_strcmp_safe(info1->name, info2->name); } else { return info1->state_priority - info2->state_priority; } } static gint gm_mcp_userlist_view_sort_name(SortInfo *info1, SortInfo *info2) { // Sort by name return gm_userlist_view_strcmp_safe(info1->name, info2->name); } gint gm_mcp_userlist_view_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, GmMcpUserlistView *view) { /* a < b => -1 a == b => 0 a > b => 1 */ SortInfo info1, info2; gint result; gtk_tree_model_get(model, a, GM_USERLIST_RANK_PRIORITY, &(info1.rank_priority), GM_USERLIST_STATE_PRIORITY, &(info1.state_priority), GM_USERLIST_NAME, &(info1.name), -1); gtk_tree_model_get(model, b, GM_USERLIST_RANK_PRIORITY, &(info2.rank_priority), GM_USERLIST_STATE_PRIORITY, &(info2.state_priority), GM_USERLIST_NAME, &(info2.name), -1); result = (* compare_functions[view->sort_type]) (&info1, &info2); g_free(info1.name); g_free(info2.name); return result; } static void gm_mcp_userlist_view_update_label(GmMcpUserlistView *view) { gchar *label; label = g_strdup_printf(_("Players: %d, active: %d"), view->num_players, view->num_active); gtk_label_set_label(GTK_LABEL(view->label), label); g_free(label); } GtkWidget * gm_mcp_userlist_view_create_label(GmMcpUserlistView *view) { view->label = gtk_label_new(NULL); gm_mcp_userlist_view_update_label(view); return view->label; } GtkWidget * gm_mcp_userlist_view_create_userlist(GmMcpUserlistView *view) { GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); GtkTreeModel *model = gm_mcp_userlist_view_model_create(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_AUTOMATIC, 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_USERLIST_ICON, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_min_width(column, 30); renderer = gm_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "name", GM_USERLIST_NAME, "status", GM_USERLIST_STATUS, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(model), (GtkTreeIterCompareFunc)gm_mcp_userlist_view_sort_func, view, NULL); GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(tree_view), GTK_CAN_FOCUS); gtk_widget_modify_base(GTK_WIDGET(tree_view), GTK_STATE_ACTIVE, &(GTK_WIDGET(tree_view)->style->base[GTK_STATE_SELECTED])); gtk_widget_modify_text(GTK_WIDGET(tree_view), GTK_STATE_ACTIVE, &(GTK_WIDGET(tree_view)->style->text[GTK_STATE_SELECTED])); gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree_view)); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE); view->tree_view = GTK_TREE_VIEW(tree_view); view->scrolled_window = GTK_SCROLLED_WINDOW(scrolled_window); return scrolled_window; } void gm_mcp_userlist_view_new(GmMcpPackage *package, GObject *parent) { GmMcpUserlistView *view; GtkPaned *paned; GtkWidget *vbox, *child; GmOptions *options; if (!GM_IS_WORLD_VIEW(parent)) { return; } view = g_new0(GmMcpUserlistView, 1); view->view = GM_WORLD_VIEW(parent); view->package = package; paned = GTK_PANED(gm_world_view_hpaned(view->view)); vbox = gtk_paned_get_child2(paned); if (vbox == NULL) { vbox = gtk_vbox_new(FALSE, 3); view->vbox = vbox; gtk_paned_pack2(paned, view->vbox, FALSE, FALSE); } else { vbox = view->vbox; } child = gm_mcp_userlist_view_create_userlist(view); gtk_box_pack_end(GTK_BOX(vbox), child, TRUE, TRUE, 0); gtk_box_pack_end(GTK_BOX(vbox), gm_mcp_userlist_view_create_label(view), FALSE, FALSE, 0); gtk_widget_show_all(vbox); g_signal_connect(view->tree_view, "popup-menu", G_CALLBACK(on_gm_mcp_userlist_view_popup_menu), view); g_signal_connect(view->tree_view, "button-press-event", G_CALLBACK(on_gm_mcp_userlist_view_button_press), view); g_signal_connect(package, "player_added", G_CALLBACK(on_gm_mcp_userlist_view_player_added), view); g_signal_connect(package, "player_removed", G_CALLBACK(on_gm_mcp_userlist_view_player_removed), view); g_signal_connect(package, "name_changed", G_CALLBACK(on_gm_mcp_userlist_view_name_changed), view); g_signal_connect(package, "state_changed", G_CALLBACK(on_gm_mcp_userlist_view_state_changed), view); g_signal_connect(package, "rank_changed", G_CALLBACK(on_gm_mcp_userlist_view_rank_changed), view); // Userlist options options = gm_app_options(gm_app_instance()); view->show_object = gm_options_get_int(options, "userlist_show_object_number"); view->sort_type = gm_options_get_int(options, "userlist_sort_type"); view->show_status = gm_options_get_int(options, "userlist_show_status"); view->use_state_icon = gm_options_get_int(options, "userlist_use_state_icon"); if (!gm_options_get_int(options, "show_userlist")) gtk_widget_hide(vbox); g_signal_connect(options, "option-changed", G_CALLBACK(on_gm_mcp_userlist_view_option_changed), view); g_object_weak_ref(G_OBJECT(package), on_gm_mcp_userlist_view_weak_notify, view); } static gchar * gm_mcp_userlist_view_get_status(GmMcpUserlistView *view, gint id) { if (!view->show_status || !gm_iuserlist_supports_status(GM_IUSERLIST(view->package))) { return NULL; } return gm_iuserlist_get_status(GM_IUSERLIST(view->package), id); } static gchar * gm_mcp_userlist_view_get_name(GmMcpUserlistView *view, gint id) { gchar const *name; name = gm_iuserlist_get_name(GM_IUSERLIST(view->package), id); if (view->show_object) { return g_strdup_printf("%s (#%d)", name, id); } else { return g_strdup(name); } } static void gm_mcp_userlist_view_update_sort_type(GmMcpUserlistView *view, GmUserlistSortType sort_type) { if (sort_type == view->sort_type) { return; } if (sort_type < 0 || sort_type >= GM_USERLIST_SORT_TYPE_NUM) { return; } view->sort_type = sort_type; gtk_tree_model_sort_reset_default_sort_func(GTK_TREE_MODEL_SORT( view->model)); gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(view->model), (GtkTreeIterCompareFunc)gm_mcp_userlist_view_sort_func, view, NULL); } static void gm_mcp_userlist_view_update_names(GmMcpUserlistView *view) { GtkTreeModel *model = GTK_TREE_MODEL(view->store); gint iterid; GtkTreeIter iter; gchar *name; gchar *status; if (gtk_tree_model_get_iter_first(model, &iter)) { do { gtk_tree_model_get(model, &iter, GM_USERLIST_ID, &iterid, -1); name = gm_mcp_userlist_view_get_name(view, iterid); status = gm_mcp_userlist_view_get_status(view, iterid); gtk_list_store_set(view->store, &iter, GM_USERLIST_NAME, name, GM_USERLIST_STATUS, status, -1); g_free(name); g_free(status); } while (gtk_tree_model_iter_next(model, &iter)); } } static void gm_mcp_userlist_view_update_show_object_number(GmMcpUserlistView *view, gboolean show_object) { if (view->show_object == show_object) { return; } view->show_object = show_object; gm_mcp_userlist_view_update_names(view); } static void gm_mcp_userlist_view_update_show_status(GmMcpUserlistView *view, gboolean show_status) { if (view->show_status == show_status) { return; } view->show_status = show_status; gm_mcp_userlist_view_update_names(view); } static void gm_mcp_userlist_view_update_use_state_icon(GmMcpUserlistView *view, gboolean use_state_icon) { gchar const *icon; GtkTreeModel *model = GTK_TREE_MODEL(view->store); GtkTreeIter iter; gint iterid; if (view->use_state_icon == use_state_icon) { return; } view->use_state_icon = use_state_icon; // Update icons for all players if (gtk_tree_model_get_iter_first(model, &iter)) { do { gtk_tree_model_get(model, &iter, GM_USERLIST_ID, &iterid, -1); icon = gm_iuserlist_get_icon(GM_IUSERLIST(view->package), iterid, use_state_icon); gtk_list_store_set(view->store, &iter, GM_USERLIST_ICON, gm_pixbuf_get_at_size(icon, GM_USERLIST_ICON_SIZE, GM_USERLIST_ICON_SIZE), -1); } while (gtk_tree_model_iter_next(model, &iter)); } } static gboolean gm_mcp_userlist_view_player_active(GmMcpUserlistView *view, GtkTreeIter *iter) { gint state; gtk_tree_model_get(GTK_TREE_MODEL(view->store), iter, GM_USERLIST_STATE_PRIORITY, &state, -1); return (state == 0); } /* Callbacks */ static void on_gm_mcp_userlist_view_weak_notify(gpointer data, GObject *obj) { GmMcpUserlistView *view = (GmMcpUserlistView *)(data); GtkWidget *vbox; GList *children; if (GM_IS_WORLD_VIEW(view->view)) { gtk_widget_destroy(GTK_WIDGET(view->scrolled_window)); gtk_widget_destroy(view->label); vbox = gtk_paned_get_child2(GTK_PANED(gm_world_view_hpaned( view->view))); if (vbox != NULL) { children = gtk_container_get_children(GTK_CONTAINER(vbox)); if (children == NULL) { gtk_widget_destroy(vbox); } g_list_free(children); } } g_object_unref(G_OBJECT(view->store)); g_object_unref(G_OBJECT(view->model)); g_signal_handlers_disconnect_by_func(gm_app_options(gm_app_instance()), G_CALLBACK(on_gm_mcp_userlist_view_option_changed), view); g_free(data); } static void on_gm_mcp_userlist_view_option_changed(GmOptions *options, gchar const *key, GmMcpUserlistView *view) { if (strcmp(key, "userlist_sort_type") == 0) { gm_mcp_userlist_view_update_sort_type(view, gm_options_get_int(options, "userlist_sort_type")); } else if (strcmp(key, "userlist_show_status") == 0) { gm_mcp_userlist_view_update_show_status(view, gm_options_get_int(options, "userlist_show_status")); } else if (strcmp(key, "userlist_use_state_icon") == 0) { gm_mcp_userlist_view_update_use_state_icon(view, gm_options_get_int(options, "userlist_use_state_icon")); } else if (strcmp(key, "userlist_show_object_number") == 0) { gm_mcp_userlist_view_update_show_object_number(view, gm_options_get_int(options, "userlist_show_object_number")); } else if (strcmp(key, "show_userlist") == 0) { if (gm_options_get_int(options, "show_userlist")) gtk_widget_show(view->vbox); else gtk_widget_hide(view->vbox); } } static gboolean gm_mcp_userlist_view_find(GmMcpUserlistView *view, gint id, GtkTreeIter *iter) { GtkTreeModel *model = GTK_TREE_MODEL(view->store); gint iterid; if (gtk_tree_model_get_iter_first(model, iter)) { do { gtk_tree_model_get(model, iter, GM_USERLIST_ID, &iterid, -1); if (iterid == id) { return TRUE; } } while (gtk_tree_model_iter_next(model, iter)); } return FALSE; } static void on_gm_mcp_userlist_view_player_added(GmMcpPackage *package, gint id, GmMcpUserlistView *view) { GtkTreeIter iter; gchar *name, *status; GmIUserlist *userlist = GM_IUSERLIST(package); if (gm_mcp_userlist_view_find(view, id, &iter)) { gm_debug_msg(DEBUG_MCP, "GmMcpUserlistView.OnPlayerAdded: player %d " "is already in the list, can't be added twice!", id); return; } name = gm_mcp_userlist_view_get_name(view, id); status = gm_mcp_userlist_view_get_status(view, id); gtk_list_store_insert_with_values(view->store, &iter, gtk_tree_model_iter_n_children(GTK_TREE_MODEL(view->store), NULL), GM_USERLIST_ID, id, GM_USERLIST_ICON, gm_pixbuf_get_at_size( gm_iuserlist_get_icon(userlist, id, view->use_state_icon), GM_USERLIST_ICON_SIZE, GM_USERLIST_ICON_SIZE), GM_USERLIST_NAME, name, GM_USERLIST_STATUS, status, GM_USERLIST_RANK_PRIORITY, gm_iuserlist_get_rank_priority(userlist, id), GM_USERLIST_STATE_PRIORITY, gm_iuserlist_get_state_priority(userlist, id), -1); g_free(name); g_free(status); view->num_players = view->num_players + 1; if (gm_mcp_userlist_view_player_active(view, &iter)) { view->num_active = view->num_active + 1; } gm_mcp_userlist_view_update_label(view); } static void on_gm_mcp_userlist_view_player_removed(GmMcpPackage *package, gint id, GmMcpUserlistView *view) { GtkTreeIter iter; if (!gm_mcp_userlist_view_find(view, id, &iter)) { gm_debug_msg(DEBUG_MCP, "GmMcpUserlistView.OnPlayerRemoved: player %d " "is not in the list, can't be removed!", id); return; } view->num_players = view->num_players - 1; if (gm_mcp_userlist_view_player_active(view, &iter)) { view->num_active = view->num_active - 1; } gm_mcp_userlist_view_update_label(view); gtk_list_store_remove(view->store, &iter); } static void on_gm_mcp_userlist_view_name_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view) { GtkTreeIter iter; gchar *name; if (!gm_mcp_userlist_view_find(view, id, &iter)) { gm_debug_msg(DEBUG_MCP, "GmMcpUserlistView.OnNameChanged: player %d " "is not in the list!", id); return; } name = gm_mcp_userlist_view_get_name(view, id); if (name != NULL) { gtk_list_store_set(view->store, &iter, GM_USERLIST_NAME, name, -1); g_free(name); } } static void on_gm_mcp_userlist_view_rank_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view) { GtkTreeIter iter; gchar const *icon; if (!gm_mcp_userlist_view_find(view, id, &iter)) { gm_debug_msg(DEBUG_MCP, "GmMcpUserlistView.OnRankChanged: player %d " "is not in the list!", id); return; } icon = gm_iuserlist_get_icon(GM_IUSERLIST(package), id, view->use_state_icon); gtk_list_store_set(view->store, &iter, GM_USERLIST_ICON, gm_pixbuf_get_at_size(icon, GM_USERLIST_ICON_SIZE, GM_USERLIST_ICON_SIZE), GM_USERLIST_RANK_PRIORITY, gm_iuserlist_get_rank_priority( GM_IUSERLIST(package), id), -1); } static void on_gm_mcp_userlist_view_state_changed(GmMcpPackage *package, gint id, GmMcpUserlistView *view) { GtkTreeIter iter; gchar *name, *status; gchar const *icon; gboolean active; if (!gm_mcp_userlist_view_find(view, id, &iter)) { gm_debug_msg(DEBUG_MCP, "GmMcpUserlistView.OnStateChanged: player %d " "is not in the list!", id); return; } active = gm_mcp_userlist_view_player_active(view, &iter); name = gm_mcp_userlist_view_get_name(view, id); status = gm_mcp_userlist_view_get_status(view, id); icon = gm_iuserlist_get_icon(GM_IUSERLIST(package), id, view->use_state_icon); gtk_list_store_set(view->store, &iter, GM_USERLIST_ICON, gm_pixbuf_get_at_size(icon, GM_USERLIST_ICON_SIZE, GM_USERLIST_ICON_SIZE), GM_USERLIST_NAME, name, GM_USERLIST_STATUS, status, GM_USERLIST_STATE_PRIORITY, gm_iuserlist_get_state_priority( GM_IUSERLIST(package), id), -1); g_free(name); g_free(status); if (gm_mcp_userlist_view_player_active(view, &iter) != active) { if (active) { // no longer active view->num_active = view->num_active - 1; } else { // now active view->num_active = view->num_active + 1; } } gm_mcp_userlist_view_update_label(view); } static void popup_menu_detach(GtkWidget *attach_widget, GtkMenu *menu) { GmMcpUserlistView *view = (GmMcpUserlistView *)g_object_get_data( G_OBJECT(menu), "McpUserlistView"); view->popup_menu = NULL; } static void on_gm_mcp_userlist_view_menuitem_activate(GtkMenuItem *item, GmMcpUserlistView *view) { gchar *action = (gchar *)g_object_get_data(G_OBJECT(item), "UserlistAction"); if (action) { gm_world_sendln(GM_MCP_SESSION_WORLD(GM_MCP_PACKAGE_SESSION( view->package)), action); } } static gboolean gm_mcp_userlist_view_do_popup(GmMcpUserlistView *view, GdkEventButton *event) { GList *menu; GList *item; GmKeyValuePair *pair; GtkWidget *menuitem; GtkTreeSelection *selection = gtk_tree_view_get_selection(view->tree_view); gint id; GtkTreeModel *model; GtkTreeIter iter; GtkTreePath *path; if (!event) { if (!gtk_tree_selection_get_selected(selection, &model, &iter)) { return FALSE; } gtk_tree_model_get(model, &iter, GM_USERLIST_ID, &id, -1); } else { if (!gtk_tree_view_get_path_at_pos(view->tree_view, event->x, event->y, &path, NULL, NULL, NULL)) { return FALSE; } else { gtk_tree_model_get_iter(view->model, &iter, path); gtk_tree_path_free(path); gtk_tree_model_get(view->model, &iter, GM_USERLIST_ID, &id, -1); } } menu = gm_iuserlist_get_menu(GM_IUSERLIST(view->package), id); if (view->popup_menu) { gtk_widget_destroy(view->popup_menu); } if (!menu) { return FALSE; } view->popup_menu = gtk_menu_new(); g_object_set_data(G_OBJECT(view->popup_menu), "McpUserlistView", view); gtk_menu_attach_to_widget(GTK_MENU(view->popup_menu), GTK_WIDGET(view->tree_view), popup_menu_detach); for (item = menu; item; item = item->next) { pair = (GmKeyValuePair *)(item->data); if (pair->key == NULL) { menuitem = gtk_separator_menu_item_new(); g_free(pair->value); } else { menuitem = gtk_menu_item_new_with_mnemonic(pair->key); g_object_set_data_full(G_OBJECT(menuitem), "UserlistAction", pair->value, g_free); g_signal_connect(menuitem, "activate", G_CALLBACK(on_gm_mcp_userlist_view_menuitem_activate), view); } gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(view->popup_menu), menuitem); g_free(pair->key); g_free(pair); } g_list_free(menu); if (!event) { gtk_menu_popup(GTK_MENU(view->popup_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } else { gtk_menu_popup(GTK_MENU(view->popup_menu), NULL, NULL, NULL, NULL, event->button, event->time); } return TRUE; } static gboolean on_gm_mcp_userlist_view_popup_menu(GtkWidget *widget, GmMcpUserlistView *view) { return gm_mcp_userlist_view_do_popup(view, NULL); } static gboolean on_gm_mcp_userlist_view_button_press(GtkWidget *widget, GdkEventButton *event, GmMcpUserlistView *view) { if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { gm_mcp_userlist_view_do_popup(view, event); } return FALSE; }