diff --git a/resources/session-shell.css b/resources/session-shell.css index 48a694b..89795fd 100644 --- a/resources/session-shell.css +++ b/resources/session-shell.css @@ -7,11 +7,15 @@ diya-taskbar diya-dashboard { - background-color: blue; + background-color: lightgray; } #diya_shell_background { background-image:url("file:///etc/xdg/labwc/wpp.jpg"); background-size: cover; +} + +flowbox flowboxchild { + border: 5px solid black; } \ No newline at end of file diff --git a/resources/ui/dashboard.ui b/resources/ui/dashboard.ui index 399481c..64aa119 100644 --- a/resources/ui/dashboard.ui +++ b/resources/ui/dashboard.ui @@ -20,7 +20,7 @@ 1 - + @@ -31,17 +31,52 @@ - - 1 + 1 - content + 1 + + + app_list + Application list + + + never + + + 1 + + + + + + + + + + + search_list + Search + + + second stack child + + + + - + + + + stack + + + + diff --git a/src/widgets/dashboard-widget.c b/src/widgets/dashboard-widget.c index 747ca60..c58b482 100644 --- a/src/widgets/dashboard-widget.c +++ b/src/widgets/dashboard-widget.c @@ -1,10 +1,104 @@ #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; }; @@ -17,140 +111,235 @@ enum static GParamSpec *g_prop[N_PROPERTIES] = {0}; +G_DEFINE_TYPE(DiyaDashboardWidget, diya_dashboard_widget, DIYA_TYPE_SHELL_WINDOW) -G_DEFINE_TYPE (DiyaDashboardWidget, diya_dashboard_widget, DIYA_TYPE_SHELL_WINDOW) - -static void diya_dashboard_widget_dispose(GObject* object) +static void diya_dashboard_widget_dispose(GObject *object) { - (void) 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_widget_init(DiyaDashboardWidget * self) +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)); + gtk_widget_init_template(GTK_WIDGET(self)); self->active = false; - + // int layer shell for window - gtk_layer_init_for_window (GTK_WINDOW(self)); + 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_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_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); + // 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); + // 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); + DiyaDashboardWidget *self = DIYA_DASHBOARD_WIDGET(object); switch (property_id) { case PROP_ACTIVE: { gboolean active = g_value_get_boolean(value); - if(self->active == active) + if (self->active == active) { return; } self->active = active; - if(self->active) + if (self->active) { - 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)); + diya_dashboard_show(self); } else { - gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), false); - gtk_widget_set_visible(GTK_WIDGET(self), false); + diya_dashboard_hide(self); } break; } default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + 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); + 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); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } -static void diya_dashboard_launch(GtkButton* btn, DiyaDashboardWidget* self) -{ - g_debug("Clicked %d %d", DIYA_IS_DASHBOARD_WIDGET(self), GTK_IS_BUTTON(btn)); - - GError* error = NULL; - GAppInfo* app = g_app_info_create_from_commandline("gtk4example", "Gtk Example", G_APP_INFO_CREATE_NONE, &error); - if(error) - { - g_critical("Unable to create app info: %s", error->message); - g_error_free(error); - return; - } - diya_shell_launch(diya_shell_window_get_shell(DIYA_SHELL_WINDOW(self)), app); - g_object_set(self, DIYA_PROP_DASHBOARD_ACTIVE, false, NULL); - -} - -static void diya_dashboard_widget_class_init(DiyaDashboardWidgetClass* class) +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; + // 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_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_launch); + 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); + // 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); + 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) +gboolean diya_dashboard_is_active(DiyaDashboardWidget *self) { return self->active; } \ No newline at end of file diff --git a/src/widgets/dashboard-widget.h b/src/widgets/dashboard-widget.h index cf09e3f..4410e97 100644 --- a/src/widgets/dashboard-widget.h +++ b/src/widgets/dashboard-widget.h @@ -9,6 +9,9 @@ #define DIYA_TYPE_DASHBOARD_WIDGET (diya_dashboard_widget_get_type()) G_DECLARE_FINAL_TYPE (DiyaDashboardWidget, diya_dashboard_widget, DIYA, DASHBOARD_WIDGET, DiyaShellWindow) +#define DIYA_TYPE_DASHBOARD_LIST_MODEL (diya_dashboard_list_model_get_type()) +G_DECLARE_FINAL_TYPE (DiyaDashboardListModel, diya_dashboard_list_model, DIYA, DASHBOARD_LIST_MODEL, GObject) + gboolean diya_dashboard_is_active(DiyaDashboardWidget* self); #endif \ No newline at end of file