/* Lists/Selections
* # Keywords : suggestion , completion
*
* The GtkDropDown widget is a modern alternative to GtkComboBox .
* It uses list models instead of tree models , and the content is
* displayed using widgets instead of cell renderers .
*
* This example also shows a custom widget that can replace
* GtkEntryCompletion or GtkComboBoxText . It is not currently
* part of GTK .
*/
#include <gtk/gtk.h>
#include "suggestionentry.h"
#define STRING_TYPE_HOLDER (string_holder_get_type ())
G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject)
struct _StringHolder {
GObject parent_instance;
char *title;
char *icon;
char *description;
};
G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT);
static void
string_holder_init (StringHolder *holder)
{
}
static void
string_holder_finalize (GObject *object)
{
StringHolder *holder = STRING_HOLDER (object);
g_free (holder->title);
g_free (holder->icon);
g_free (holder->description);
G_OBJECT_CLASS (string_holder_parent_class)->finalize (object);
}
static void
string_holder_class_init (StringHolderClass *class )
{
GObjectClass *object_class = G_OBJECT_CLASS (class );
object_class->finalize = string_holder_finalize;
}
static StringHolder *
string_holder_new (const char *title, const char *icon, const char *description)
{
StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL);
holder->title = g_strdup (title);
holder->icon = g_strdup (icon);
holder->description = g_strdup (description);
return holder;
}
static void
strings_setup_item_single_line (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
GtkWidget *box, *image, *title;
GtkWidget *checkmark;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10 );
image = gtk_image_new ();
title = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (title), 0 .0 );
checkmark = gtk_image_new_from_icon_name ("object-select-symbolic" );
gtk_box_append (GTK_BOX (box), image);
gtk_box_append (GTK_BOX (box), title);
gtk_box_append (GTK_BOX (box), checkmark);
g_object_set_data (G_OBJECT (item), "title" , title);
g_object_set_data (G_OBJECT (item), "image" , image);
g_object_set_data (G_OBJECT (item), "checkmark" , checkmark);
gtk_list_item_set_child (item, box);
}
static void
strings_setup_item_full (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
GtkWidget *box, *box2, *image, *title, *description;
GtkWidget *checkmark;
image = gtk_image_new ();
title = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (title), 0 .0 );
description = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (description), 0 .0 );
gtk_widget_add_css_class (description, "dim-label" );
checkmark = gtk_image_new_from_icon_name ("object-select-symbolic" );
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10 );
box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 );
gtk_box_append (GTK_BOX (box), image);
gtk_box_append (GTK_BOX (box), box2);
gtk_box_append (GTK_BOX (box2), title);
gtk_box_append (GTK_BOX (box2), description);
gtk_box_append (GTK_BOX (box), checkmark);
g_object_set_data (G_OBJECT (item), "title" , title);
g_object_set_data (G_OBJECT (item), "image" , image);
g_object_set_data (G_OBJECT (item), "description" , description);
g_object_set_data (G_OBJECT (item), "checkmark" , checkmark);
gtk_list_item_set_child (item, box);
}
static void
selected_item_changed (GtkDropDown *dropdown,
GParamSpec *pspec,
GtkListItem *item)
{
GtkWidget *checkmark;
checkmark = g_object_get_data (G_OBJECT (item), "checkmark" );
if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item))
gtk_widget_set_opacity (checkmark, 1 .0 );
else
gtk_widget_set_opacity (checkmark, 0 .0 );
}
static void
strings_bind_item (GtkSignalListItemFactory *factory,
GtkListItem *item,
gpointer data)
{
GtkDropDown *dropdown = data;
GtkWidget *image, *title, *description;
GtkWidget *checkmark;
StringHolder *holder;
GtkWidget *popup;
holder = gtk_list_item_get_item (item);
title = g_object_get_data (G_OBJECT (item), "title" );
image = g_object_get_data (G_OBJECT (item), "image" );
description = g_object_get_data (G_OBJECT (item), "description" );
checkmark = g_object_get_data (G_OBJECT (item), "checkmark" );
gtk_label_set_label (GTK_LABEL (title), holder->title);
if (image)
{
gtk_image_set_from_icon_name (GTK_IMAGE (image), holder->icon);
gtk_widget_set_visible (image, holder->icon != NULL);
}
if (description)
{
gtk_label_set_label (GTK_LABEL (description), holder->description);
gtk_widget_set_visible (description , holder->description != NULL);
}
popup = gtk_widget_get_ancestor (title, GTK_TYPE_POPOVER);
if (popup && gtk_widget_is_ancestor (popup, GTK_WIDGET (dropdown)))
{
gtk_widget_set_visible (checkmark, TRUE );
g_signal_connect (dropdown, "notify::selected-item" ,
G_CALLBACK (selected_item_changed), item);
selected_item_changed (dropdown, NULL, item);
}
else
{
gtk_widget_set_visible (checkmark, FALSE );
}
}
static void
strings_unbind_item (GtkSignalListItemFactory *factory,
GtkListItem *list_item,
gpointer data)
{
GtkDropDown *dropdown = data;
g_signal_handlers_disconnect_by_func (dropdown, selected_item_changed, list_item);
}
static GtkListItemFactory *
strings_factory_new (gpointer data, gboolean full)
{
GtkListItemFactory *factory;
factory = gtk_signal_list_item_factory_new ();
if (full)
g_signal_connect (factory, "setup" , G_CALLBACK (strings_setup_item_full), data);
else
g_signal_connect (factory, "setup" , G_CALLBACK (strings_setup_item_single_line), data);
g_signal_connect (factory, "bind" , G_CALLBACK (strings_bind_item), data);
g_signal_connect (factory, "unbind" , G_CALLBACK (strings_unbind_item), data);
return factory;
}
static GListModel *
strings_model_new (const char *const *titles,
const char *const *icons,
const char *const *descriptions)
{
GListStore *store;
int i;
store = g_list_store_new (STRING_TYPE_HOLDER);
for (i = 0 ; titles[i]; i++)
{
StringHolder *holder = string_holder_new (titles[i],
icons ? icons[i] : NULL,
descriptions ? descriptions[i] : NULL);
g_list_store_append (store, holder);
g_object_unref (holder);
}
return G_LIST_MODEL (store);
}
static GtkWidget *
drop_down_new_from_strings (const char *const *titles,
const char *const *icons,
const char *const *descriptions)
{
GtkWidget *widget;
GListModel *model;
GtkListItemFactory *factory;
GtkListItemFactory *list_factory;
g_return_val_if_fail (titles != NULL, NULL);
g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL);
g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL);
model = strings_model_new (titles, icons, descriptions);
widget = g_object_new (GTK_TYPE_DROP_DOWN,
"model" , model,
NULL);
g_object_unref (model);
factory = strings_factory_new (widget, FALSE );
if (icons != NULL || descriptions != NULL)
list_factory = strings_factory_new (widget, TRUE );
else
list_factory = NULL;
g_object_set (widget,
"factory" , factory,
"list-factory" , list_factory,
NULL);
g_object_unref (factory);
if (list_factory)
g_object_unref (list_factory);
return widget;
}
static char *
get_family_name (gpointer item)
{
return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item)));
}
static char *
get_file_name (gpointer item)
{
return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item)));
}
static void
setup_item (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
GtkWidget *box;
GtkWidget *icon;
GtkWidget *label;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10 );
icon = gtk_image_new ();
label = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (label), 0 );
gtk_box_append (GTK_BOX (box), icon);
gtk_box_append (GTK_BOX (box), label);
gtk_list_item_set_child (item, box);
}
static void
bind_item (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
MatchObject *match = MATCH_OBJECT (gtk_list_item_get_item (item));
GFileInfo *info = G_FILE_INFO (match_object_get_item (match));
GtkWidget *box = gtk_list_item_get_child (item);
GtkWidget *icon = gtk_widget_get_first_child (box);
GtkWidget *label = gtk_widget_get_last_child (box);
gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info));
gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info));
}
static void
setup_highlight_item (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
GtkWidget *label;
label = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (label), 0 );
gtk_list_item_set_child (item, label);
}
static void
bind_highlight_item (GtkSignalListItemFactory *factory,
GtkListItem *item)
{
MatchObject *obj;
GtkWidget *label;
PangoAttrList *attrs;
PangoAttribute *attr;
const char *str;
obj = MATCH_OBJECT (gtk_list_item_get_item (item));
label = gtk_list_item_get_child (item);
str = match_object_get_string (obj);
gtk_label_set_label (GTK_LABEL (label), str);
attrs = pango_attr_list_new ();
attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
attr->start_index = match_object_get_match_start (obj);
attr->end_index = match_object_get_match_end (obj);
pango_attr_list_insert (attrs, attr);
gtk_label_set_attributes (GTK_LABEL (label), attrs);
pango_attr_list_unref (attrs);
}
static void
match_func (MatchObject *obj,
const char *search,
gpointer user_data)
{
char *tmp1, *tmp2;
char *p;
tmp1 = g_utf8_normalize (match_object_get_string (obj), -1 , G_NORMALIZE_ALL);
tmp2 = g_utf8_normalize (search, -1 , G_NORMALIZE_ALL);
if ((p = strstr (tmp1, tmp2)) != NULL)
match_object_set_match (obj,
p - tmp1,
(p - tmp1) + g_utf8_strlen (search, -1 ),
1 );
else
match_object_set_match (obj, 0 , 0 , 0 );
g_free (tmp1);
g_free (tmp2);
}
static void
setup_header (GtkSignalListItemFactory *factory,
GObject *list_item,
gpointer data)
{
GtkListHeader *self = GTK_LIST_HEADER (list_item);
GtkWidget *child;
child = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (child), 0 );
gtk_label_set_use_markup (GTK_LABEL (child), TRUE );
gtk_widget_set_margin_top (child, 10 );
gtk_widget_set_margin_bottom (child, 10 );
gtk_list_header_set_child (self, child);
}
static void
bind_header (GtkSignalListItemFactory *factory,
GObject *list_item,
gpointer data)
{
GtkListHeader *self = GTK_LIST_HEADER (list_item);
GtkWidget *child = gtk_list_header_get_child (self);
GObject *item = gtk_list_header_get_item (self);
if (strstr (gtk_string_object_get_string (GTK_STRING_OBJECT (item)), "hour" ))
gtk_label_set_label (GTK_LABEL (child), "<big><b>Hours</b></big>" );
else
gtk_label_set_label (GTK_LABEL (child), "<big><b>Minutes</b></big>" );
}
GtkWidget *
do_listview_selections (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
GtkWidget *button, *box, *spin, *check, *hbox, *label, *entry;
GListModel *model;
GtkExpression *expression;
GtkListItemFactory *factory;
const char * const times[] = { "1 minute" , "2 minutes" , "5 minutes" , "20 minutes" , NULL };
const char * const minutes[] = {
"1 minute" , "2 minutes" , "5 minutes" , "10 minutes" , "15 minutes" , "20 minutes" ,
"25 minutes" , "30 minutes" , "35 minutes" , "40 minutes" , "45 minutes" , "50 minutes" ,
"55 minutes" , NULL
};
const char * const hours[] = { "1 hour" , "2 hours" , "3 hours" , "5 hours" , "6 hours" , "7 hours" ,
"8 hours" , "9 hours" , "10 hours" , "11 hours" , "12 hours" , NULL
};
const char * const device_titles[] = { "Digital Output" , "Headphones" , "Digital Output" , "Analog Output" , NULL };
const char * const device_icons[] = { "audio-card-symbolic" , "audio-headphones-symbolic" , "audio-card-symbolic" , "audio-card-symbolic" , NULL };
const char * const device_descriptions[] = {
"Built-in Audio" , "Built-in audio" , "Thinkpad Tunderbolt 3 Dock USB Audio" , "Thinkpad Tunderbolt 3 Dock USB Audio" , NULL
};
char *cwd;
GFile *file;
GListModel *dir;
GtkStringList *strings;
if (!window)
{
GtkStringList *minutes_model, *hours_model;
GListStore *store;
GtkFlattenListModel *flat;
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
gtk_window_set_title (GTK_WINDOW (window), "Selections" );
gtk_window_set_resizable (GTK_WINDOW (window), FALSE );
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20 );
gtk_widget_set_margin_start (hbox, 20 );
gtk_widget_set_margin_end (hbox, 20 );
gtk_widget_set_margin_top (hbox, 20 );
gtk_widget_set_margin_bottom (hbox, 20 );
gtk_window_set_child (GTK_WINDOW (window), hbox);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10 );
gtk_box_append (GTK_BOX (hbox), box);
label = gtk_label_new ("Dropdowns" );
gtk_widget_add_css_class (label, "title-4" );
gtk_box_append (GTK_BOX (box), label);
/* A basic dropdown */
button = drop_down_new_from_strings (times, NULL, NULL);
gtk_box_append (GTK_BOX (box), button);
/* A dropdown using an expression to obtain strings */
minutes_model = gtk_string_list_new (minutes);
hours_model = gtk_string_list_new (hours);
store = g_list_store_new (G_TYPE_LIST_MODEL);
g_list_store_append (store, minutes_model);
g_list_store_append (store, hours_model);
g_object_unref (minutes_model);
g_object_unref (hours_model);
flat = gtk_flatten_list_model_new (G_LIST_MODEL (store));
expression = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT,
NULL,
"string" );
button = gtk_drop_down_new (G_LIST_MODEL (flat), expression);
gtk_drop_down_set_enable_search (GTK_DROP_DOWN (button), TRUE );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_header), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_header), NULL);
gtk_drop_down_set_header_factory (GTK_DROP_DOWN (button), factory);
g_object_unref (factory);
gtk_box_append (GTK_BOX (box), button);
button = gtk_drop_down_new (NULL, NULL);
model = G_LIST_MODEL (pango_cairo_font_map_get_default ());
gtk_drop_down_set_model (GTK_DROP_DOWN (button), model);
gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0 );
expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0 , NULL,
(GCallback)get_family_name,
NULL, NULL);
gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
gtk_expression_unref (expression);
gtk_box_append (GTK_BOX (box), button);
spin = gtk_spin_button_new_with_range (-1 , g_list_model_get_n_items (G_LIST_MODEL (model)), 1 );
gtk_widget_set_halign (spin, GTK_ALIGN_START);
gtk_widget_set_margin_start (spin, 20 );
g_object_bind_property (button, "selected" , spin, "value" , G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
gtk_box_append (GTK_BOX (box), spin);
check = gtk_check_button_new_with_label ("Enable search" );
gtk_widget_set_margin_start (check, 20 );
g_object_bind_property (button, "enable-search" , check, "active" , G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
gtk_box_append (GTK_BOX (box), check);
g_object_unref (model);
/* A dropdown with a separate list factory */
button = drop_down_new_from_strings (device_titles, device_icons, device_descriptions);
gtk_box_append (GTK_BOX (box), button);
gtk_box_append (GTK_BOX (hbox), gtk_separator_new (GTK_ORIENTATION_VERTICAL));
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10 );
gtk_box_append (GTK_BOX (hbox), box);
label = gtk_label_new ("Suggestions" );
gtk_widget_add_css_class (label, "title-4" );
gtk_box_append (GTK_BOX (box), label);
/* A basic suggestion entry */
entry = suggestion_entry_new ();
g_object_set (entry, "placeholder-text" , "Words with T or G…" , NULL);
strings = gtk_string_list_new ((const char *[]){
"GNOME" ,
"gnominious" ,
"Gnomonic projection" ,
"total" ,
"totally" ,
"toto" ,
"tottery" ,
"totterer" ,
"Totten trust" ,
"totipotent" ,
"totipotency" ,
"totemism" ,
"totem pole" ,
"Totara" ,
"totalizer" ,
"totalizator" ,
"totalitarianism" ,
"total parenteral nutrition" ,
"total hysterectomy" ,
"total eclipse" ,
"Totipresence" ,
"Totipalmi" ,
"Tomboy" ,
"zombie" ,
NULL});
suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
g_object_unref (strings);
gtk_box_append (GTK_BOX (box), entry);
/* A suggestion entry using a custom model, and no filtering */
entry = suggestion_entry_new ();
cwd = g_get_current_dir ();
file = g_file_new_for_path (cwd);
dir = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size" , file));
suggestion_entry_set_model (SUGGESTION_ENTRY (entry), dir);
g_object_unref (dir);
g_object_unref (file);
g_free (cwd);
expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
0 , NULL,
(GCallback)get_file_name,
NULL, NULL);
suggestion_entry_set_expression (SUGGESTION_ENTRY (entry), expression);
gtk_expression_unref (expression);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_item), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_item), NULL);
suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
g_object_unref (factory);
suggestion_entry_set_use_filter (SUGGESTION_ENTRY (entry), FALSE );
suggestion_entry_set_show_arrow (SUGGESTION_ENTRY (entry), TRUE );
gtk_box_append (GTK_BOX (box), entry);
/* A suggestion entry with match highlighting */
entry = suggestion_entry_new ();
g_object_set (entry, "placeholder-text" , "Destination" , NULL);
strings = gtk_string_list_new ((const char *[]){
"app-mockups" ,
"settings-mockups" ,
"os-mockups" ,
"software-mockups" ,
"mocktails" ,
NULL});
suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
g_object_unref (strings);
gtk_box_append (GTK_BOX (box), entry);
suggestion_entry_set_match_func (SUGGESTION_ENTRY (entry), match_func, NULL, NULL);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_highlight_item), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_highlight_item), NULL);
suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
g_object_unref (factory);
}
if (!gtk_widget_get_visible (window))
gtk_widget_set_visible (window, TRUE );
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
Messung V0.5 in Prozent C=99 H=94 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland