#include "dashboard-widget.h" /** * Implementation of list model * */ struct _DiyaDashboardListModel { GObject parent_instance; GList *appinfos; }; static guint diya_dashboard_list_model_get_n_items(GListModel *list) { DiyaDashboardListModel *self = DIYA_DASHBOARD_LIST_MODEL(list); return g_list_length(self->appinfos); } static gpointer diya_dashboard_list_model_get_item(GListModel *list, guint position) { DiyaDashboardListModel *self = DIYA_DASHBOARD_LIST_MODEL(list); return g_object_ref(g_list_nth_data(self->appinfos, position)); } static GType diya_dashboard_list_model_get_item_type(GListModel *list) { (void)list; // Unused parameter return G_TYPE_APP_INFO; } static void diya_dashboard_list_model_interface_init(GListModelInterface *iface) { iface->get_n_items = diya_dashboard_list_model_get_n_items; iface->get_item = diya_dashboard_list_model_get_item; iface->get_item_type = diya_dashboard_list_model_get_item_type; } G_DEFINE_TYPE_WITH_CODE(DiyaDashboardListModel, diya_dashboard_list_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL, diya_dashboard_list_model_interface_init)) static void diya_dashboard_list_model_finalize(GObject *object) { g_debug("diya_dashboard_list_model_finalize"); DiyaDashboardListModel *self = DIYA_DASHBOARD_LIST_MODEL(object); g_list_free_full(self->appinfos, g_object_unref); G_OBJECT_CLASS(diya_dashboard_list_model_parent_class)->finalize(object); } static void diya_dashboard_list_model_dispose(GObject *object) { (void)object; g_debug("diya_dashboard_list_model_dispose"); G_OBJECT_CLASS(diya_dashboard_list_model_parent_class)->dispose(object); } static void diya_dashboard_list_model_init(DiyaDashboardListModel *self) { self->appinfos = NULL; } static void diya_dashboard_list_model_class_init(DiyaDashboardListModelClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS(class); gobject_class->finalize = diya_dashboard_list_model_finalize; gobject_class->dispose = diya_dashboard_list_model_dispose; } void diya_dashboard_list_model_append(DiyaDashboardListModel *self, GAppInfo *info) { self->appinfos = g_list_append(self->appinfos, g_object_ref(info)); // self->appinfos = g_list_reverse(self->appinfos); g_list_model_items_changed(G_LIST_MODEL(self), g_list_length(self->appinfos) - 1, 0, 1); } static gint diya_dashboard_app_info_cmd(GAppInfo *a, GAppInfo *b) { return !g_app_info_equal(a, b); } gboolean diya_dashboard_list_model_contain(DiyaDashboardListModel *self, GAppInfo *info) { GList *found = NULL; found = g_list_find_custom(self->appinfos, info, (GCompareFunc)diya_dashboard_app_info_cmd); return found != NULL; } /** * Implement of Dashboard widget * */ struct _DiyaDashboardWidget { DiyaShellWindow parent; GtkWidget *revealer; GtkWidget *search_entry; GtkWidget *app_list_box; DiyaDashboardListModel *list_model; GtkSortListModel *sort_model; GtkFilterListModel *proxy_model; GtkSorter *sorter; GtkFilter *filter; bool active; }; enum { NO_PROP, PROP_ACTIVE, N_PROPERTIES }; static GParamSpec *g_prop[N_PROPERTIES] = {0}; G_DEFINE_TYPE(DiyaDashboardWidget, diya_dashboard_widget, DIYA_TYPE_SHELL_WINDOW) static void diya_dashboard_widget_dispose(GObject *object) { DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(object); g_debug("diya_dashboard_widget_dispose"); if (self->proxy_model) { g_object_unref(self->proxy_model); } // verify if the list model + sorter is also unref G_OBJECT_CLASS(diya_dashboard_widget_parent_class)->dispose(object); } static void diya_dashboard_app_launch(GtkFlowBox *box, GtkFlowBoxChild *child, gpointer user_data) { (void)box; // Unused parameter DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(user_data); int current_index = gtk_flow_box_child_get_index(child); if (current_index < 0) { g_warning("Invalid child index: %d", current_index); return; } GAppInfo *app = G_APP_INFO(g_list_model_get_item(G_LIST_MODEL(self->proxy_model), current_index)); assert(app); diya_shell_launch(diya_shell_window_get_shell(DIYA_SHELL_WINDOW(self)), app); g_object_set(self, DIYA_PROP_DASHBOARD_ACTIVE, false, NULL); g_object_unref(app); } static void diya_dashboard_app_icon_clicked(GtkWidget *widget, gpointer user_data) { // relay signal to the GTK_FLOW_BOX_CHILD parent g_signal_emit_by_name(gtk_widget_get_parent(widget), "activate", user_data); } static GtkWidget *diya_dashboard_create_app_icon_widget(void *item, void *user_data) { (void)user_data; // Unused parameter GAppInfo *app_info = G_APP_INFO(item); const gchar *name = g_app_info_get_display_name(app_info); GIcon *icon = g_app_info_get_icon(app_info); GtkWidget *widget = gtk_button_new_with_label(name); if (icon) { g_debug("app_info %s has icon %s", name, g_icon_to_string(icon)); // gtk_button_set_icon_name(GTK_BUTTON(widget), g_icon_to_string(icon)); } g_signal_connect(widget, "clicked", G_CALLBACK(diya_dashboard_app_icon_clicked), user_data); return widget; } static int diya_dashboard_app_info_cmp(GAppInfo *a, GAppInfo *b, gpointer user_data) { (void)user_data; // Unused parameter const gchar *name_a = g_app_info_get_id(a); const gchar *name_b = g_app_info_get_id(b); return g_strcmp0(name_a, name_b); } static gboolean diya_app_info_filter_func(GAppInfo *app_info, gpointer user_data) { DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(user_data); gchar *text = g_utf8_strdown(gtk_editable_get_text(GTK_EDITABLE(self->search_entry)), -1); if (strlen(text) == 0) { g_free(text); return true; } gchar *name = g_utf8_strdown(g_app_info_get_display_name(app_info), -1); if(!name) { g_free(text); return false; } gboolean result = g_str_has_prefix(name, text); g_free(text); g_free(name); return result; } static void diya_dashboard_search_text_changed(GtkEntry *entry, DiyaDashboardWidget *self) { (void)entry; // Unused parameter gtk_filter_changed(self->filter, GTK_FILTER_CHANGE_DIFFERENT); } static void diya_dashboard_widget_init(DiyaDashboardWidget *self) { g_debug("diya_dashboard_widget_init"); gtk_widget_init_template(GTK_WIDGET(self)); self->active = false; // int layer shell for window gtk_layer_init_for_window(GTK_WINDOW(self)); // anchor window to all edges gtk_layer_set_anchor(GTK_WINDOW(self), GTK_LAYER_SHELL_EDGE_LEFT, true); gtk_layer_set_anchor(GTK_WINDOW(self), GTK_LAYER_SHELL_EDGE_RIGHT, true); gtk_layer_set_anchor(GTK_WINDOW(self), GTK_LAYER_SHELL_EDGE_TOP, true); gtk_layer_set_anchor(GTK_WINDOW(self), GTK_LAYER_SHELL_EDGE_BOTTOM, true); gtk_layer_set_namespace(GTK_WINDOW(self), "diya-dashboard"); // set margin on window for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++) gtk_layer_set_margin(GTK_WINDOW(self), i, 0); gtk_layer_set_layer(GTK_WINDOW(self), GTK_LAYER_SHELL_LAYER_TOP); // gtk_layer_set_keyboard_mode (GTK_WINDOW(self), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); // the top launcher shall be exclusive // gtk_layer_auto_exclusive_zone_enable (GTK_WINDOW(self)); gtk_layer_set_keyboard_mode(GTK_WINDOW(self), GTK_LAYER_SHELL_KEYBOARD_MODE_ON_DEMAND); // gtk_widget_set_can_focus(GTK_WIDGET(self), true); // gtk_widget_set_name(GTK_WIDGET(dashboard),NAMESPACE); // gtk_window_set_default_size(GTK_WINDOW(dashboard), 48, 48); // event controller // GtkEventController *event_controller = gtk_event_controller_key_new(); // g_signal_connect(event_controller, "key-pressed", G_CALLBACK(on_diya_dashboard_key_press), self); // gtk_widget_add_controller(GTK_WIDGET(self), event_controller); self->list_model = g_object_new(DIYA_TYPE_DASHBOARD_LIST_MODEL, NULL); self->sorter = GTK_SORTER(gtk_custom_sorter_new((GCompareDataFunc)diya_dashboard_app_info_cmp, NULL, NULL)); self->sort_model = gtk_sort_list_model_new(G_LIST_MODEL(self->list_model), self->sorter); self->filter = GTK_FILTER(gtk_custom_filter_new((GtkCustomFilterFunc)diya_app_info_filter_func, self, NULL)); self->proxy_model = gtk_filter_list_model_new(G_LIST_MODEL(self->sort_model), self->filter); gtk_flow_box_bind_model(GTK_FLOW_BOX(self->app_list_box), G_LIST_MODEL(self->proxy_model), diya_dashboard_create_app_icon_widget, self, NULL); gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->app_list_box), GTK_SELECTION_SINGLE); gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->app_list_box), true); } static void diya_dashboard_show(DiyaDashboardWidget *self) { gtk_window_present(GTK_WINDOW(self)); // gtk_widget_set_visible(GTK_WIDGET(self), true); gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), true); gtk_window_set_focus(GTK_WINDOW(self), self->search_entry); // gtk_widget_grab_focus( GTK_WIDGET(self->search_entry)); GList *apps = g_app_info_get_all(); GList *l; for (l = apps; l != NULL; l = l->next) { GAppInfo *info = l->data; if (diya_dashboard_list_model_contain(self->list_model, info)) { continue; } g_debug("add AppInfo %s", g_app_info_get_id(info)); diya_dashboard_list_model_append(self->list_model, info); } g_list_free_full(apps, g_object_unref); gtk_sorter_changed(self->sorter, GTK_SORTER_CHANGE_INVERTED); } static void diya_dashboard_hide(DiyaDashboardWidget *self) { gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), false); gtk_widget_set_visible(GTK_WIDGET(self), false); } static void diya_dashboard_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(object); switch (property_id) { case PROP_ACTIVE: { gboolean active = g_value_get_boolean(value); if (self->active == active) { return; } self->active = active; if (self->active) { diya_dashboard_show(self); } else { diya_dashboard_hide(self); } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void diya_dashboard_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(object); switch (property_id) { case PROP_ACTIVE: g_value_set_boolean(value, self->active); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void diya_dashboard_widget_class_init(DiyaDashboardWidgetClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS(class); gobject_class->dispose = diya_dashboard_widget_dispose; gobject_class->set_property = diya_dashboard_set_property; gobject_class->get_property = diya_dashboard_get_property; // DiyaShellWindowClass* base_class = DIYA_SHELL_WINDOW_CLASS(class); // base_class->setup = diya_dashboard_widget_setup; gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), "/dev/iohub/diya/shell/dashboard.ui"); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaDashboardWidget, revealer); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaDashboardWidget, search_entry); gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS(class), DiyaDashboardWidget, app_list_box); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), diya_dashboard_app_launch); gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), diya_dashboard_search_text_changed); gtk_widget_class_set_css_name(GTK_WIDGET_CLASS(class), "diya-dashboard"); // gtk_widget_class_bind_template_child(GTK_WIDGET_CLASS (class), ExampleAppWindow, stack); // gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), search_text_changed); g_prop[PROP_ACTIVE] = g_param_spec_boolean(DIYA_PROP_DASHBOARD_ACTIVE, NULL, "Active the dashboard", false, G_PARAM_READWRITE); // g_object_class_install_properties(gobject_class, N_PROPERTIES, g_prop); } gboolean diya_dashboard_is_active(DiyaDashboardWidget *self) { return self->active; }