Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Gnome/demos/gtk-demo/   (Gnome Linux Desktop Version 4.23.2©)  Datei vom 30.5.2026 mit Größe 33 kB image not shown  

Quelle  suggestionentry.c

  Sprache: C
 

#include "suggestionentry.h"

struct _MatchObject
{
  GObject parent_instance;

  GObject *item;
  char *string;
  guint match_start;
  guint match_end;
  guint score;
};

typedef struct
{
  GObjectClass parent_class;
} MatchObjectClass;

enum
{
  PROP_ITEM = 1,
  PROP_STRING,
  PROP_MATCH_START,
  PROP_MATCH_END,
  PROP_SCORE,
  N_MATCH_PROPERTIES
};

static GParamSpec *match_properties[N_MATCH_PROPERTIES];

G_DEFINE_TYPE (MatchObject, match_object, G_TYPE_OBJECT)

static void
match_object_init (MatchObject *object)
{
}

static void
match_object_get_property (GObject    *object,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  MatchObject *self = MATCH_OBJECT (object);

  switch (property_id)
    {
    case PROP_ITEM:
      g_value_set_object (value, self->item);
      break;

    case PROP_STRING:
      g_value_set_string (value, self->string);
      break;

    case PROP_MATCH_START:
      g_value_set_uint (value, self->match_start);
      break;

    case PROP_MATCH_END:
      g_value_set_uint (value, self->match_end);
      break;

    case PROP_SCORE:
      g_value_set_uint (value, self->score);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
match_object_set_property (GObject      *object,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  MatchObject *self = MATCH_OBJECT (object);

  switch (property_id)
    {
    case PROP_ITEM:
      self->item = g_value_get_object (value);
      break;

    case PROP_STRING:
      self->string = g_value_dup_string (value);
      break;

    case PROP_MATCH_START:
      if (self->match_start != g_value_get_uint (value))
        {
          self->match_start = g_value_get_uint (value);
          g_object_notify_by_pspec (object, pspec);
        }
      break;

    case PROP_MATCH_END:
      if (self->match_end != g_value_get_uint (value))
        {
          self->match_end = g_value_get_uint (value);
          g_object_notify_by_pspec (object, pspec);
        }
      break;

    case PROP_SCORE:
      if (self->score != g_value_get_uint (value))
        {
          self->score = g_value_get_uint (value);
          g_object_notify_by_pspec (object, pspec);
        }
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
match_object_dispose (GObject *object)
{
  MatchObject *self = MATCH_OBJECT (object);

  g_clear_object (&self->item);
  g_clear_pointer (&self->string, g_free);

  G_OBJECT_CLASS (match_object_parent_class)->dispose (object);
}

static void
match_object_class_init (MatchObjectClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->dispose = match_object_dispose;
  object_class->get_property = match_object_get_property;
  object_class->set_property = match_object_set_property;

  match_properties[PROP_ITEM]
      = g_param_spec_object ("item", NULL, NULL,
                             G_TYPE_OBJECT,
                             G_PARAM_READWRITE |
                             G_PARAM_CONSTRUCT_ONLY |
                             G_PARAM_STATIC_NAME);
  match_properties[PROP_STRING]
      = g_param_spec_string ("string", NULL, NULL,
                             NULL,
                             G_PARAM_READWRITE |
                             G_PARAM_CONSTRUCT_ONLY |
                             G_PARAM_STATIC_NAME);
  match_properties[PROP_MATCH_START]
      = g_param_spec_uint ("match-start", NULL, NULL,
                           0, G_MAXUINT, 0,
                           G_PARAM_READWRITE |
                           G_PARAM_EXPLICIT_NOTIFY |
                           G_PARAM_STATIC_NAME);
  match_properties[PROP_MATCH_END]
      = g_param_spec_uint ("match-end", NULL, NULL,
                           0, G_MAXUINT, 0,
                           G_PARAM_READWRITE |
                           G_PARAM_EXPLICIT_NOTIFY |
                           G_PARAM_STATIC_NAME);
  match_properties[PROP_SCORE]
      = g_param_spec_uint ("score", NULL, NULL,
                           0, G_MAXUINT, 0,
                           G_PARAM_READWRITE |
                           G_PARAM_EXPLICIT_NOTIFY |
                           G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, N_MATCH_PROPERTIES, match_properties);
}

static MatchObject *
match_object_new (gpointer    item,
                  const char *string)
{
  return g_object_new (MATCH_TYPE_OBJECT,
                       "item", item,
                       "string", string,
                       NULL);
}

gpointer
match_object_get_item (MatchObject *object)
{
  return object->item;
}

const char *
match_object_get_string (MatchObject *object)
{
  return object->string;
}

guint
match_object_get_match_start (MatchObject *object)
{
  return object->match_start;
}

guint
match_object_get_match_end (MatchObject *object)
{
  return object->match_end;
}

guint
match_object_get_score (MatchObject *object)
{
  return object->score;
}

void
match_object_set_match (MatchObject *object,
                        guint           start,
                        guint           end,
                        guint           score)
{
  g_object_freeze_notify (G_OBJECT (object));

  g_object_set (object,
                "match-start", start,
                "match-end", end,
                "score", score,
                NULL);

  g_object_thaw_notify (G_OBJECT (object));
}

/* ---- */

struct _SuggestionEntry
{
  GtkWidget parent_instance;

  GListModel *model;
  GtkListItemFactory *factory;
  GtkExpression *expression;

  GtkFilter *filter;
  GtkMapListModel *map_model;
  GtkSingleSelection *selection;

  GtkWidget *entry;
  GtkWidget *arrow;
  GtkWidget *popup;
  GtkWidget *list;

  char *search;

  SuggestionEntryMatchFunc match_func;
  gpointer match_data;
  GDestroyNotify destroy;

  gulong changed_id;

  guint use_filter : 1;
  guint show_arrow : 1;
};

typedef struct _SuggestionEntryClass SuggestionEntryClass;

struct _SuggestionEntryClass
{
  GtkWidgetClass parent_class;
};

enum
{
  PROP_0,
  PROP_MODEL,
  PROP_FACTORY,
  PROP_EXPRESSION,
  PROP_PLACEHOLDER_TEXT,
  PROP_POPUP_VISIBLE,
  PROP_USE_FILTER,
  PROP_SHOW_ARROW,

  N_PROPERTIES,
};

static void suggestion_entry_set_popup_visible (SuggestionEntry *self,
                                                gboolean         visible);

static GtkEditable *
suggestion_entry_get_delegate (GtkEditable *editable)
{
  return GTK_EDITABLE (SUGGESTION_ENTRY (editable)->entry);
}

static void
suggestion_entry_editable_init (GtkEditableInterface *iface)
{
  iface->get_delegate = suggestion_entry_get_delegate;
}

G_DEFINE_TYPE_WITH_CODE (SuggestionEntry, suggestion_entry, GTK_TYPE_WIDGET,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
                                                suggestion_entry_editable_init))

static GParamSpec *properties[N_PROPERTIES] = { NULL, };

static void
suggestion_entry_dispose (GObject *object)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (object);

  g_clear_signal_handler (&self->changed_id, self->entry);
  g_clear_pointer (&self->entry, gtk_widget_unparent);
  g_clear_pointer (&self->arrow, gtk_widget_unparent);
  g_clear_pointer (&self->popup, gtk_widget_unparent);

  g_clear_pointer (&self->expression, gtk_expression_unref);
  g_clear_object (&self->factory);

  g_clear_object (&self->model);
  g_clear_object (&self->map_model);
  g_clear_object (&self->selection);

  g_clear_pointer (&self->search, g_free);

  if (self->destroy)
    self->destroy (self->match_data);

  G_OBJECT_CLASS (suggestion_entry_parent_class)->dispose (object);
}

static void
suggestion_entry_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (object);

  if (gtk_editable_delegate_get_property (object, property_id, value, pspec))
    return;

  switch (property_id)
    {
    case PROP_MODEL:
      g_value_set_object (value, suggestion_entry_get_model (self));
      break;

    case PROP_FACTORY:
      g_value_set_object (value, suggestion_entry_get_factory (self));
      break;

    case PROP_EXPRESSION:
      gtk_value_set_expression (value, suggestion_entry_get_expression (self));
      break;

    case PROP_PLACEHOLDER_TEXT:
      g_value_set_string (value, gtk_text_get_placeholder_text (GTK_TEXT (self->entry)));
      break;

    case PROP_POPUP_VISIBLE:
      g_value_set_boolean (value, self->popup && gtk_widget_get_visible (self->popup));
      break;

    case PROP_USE_FILTER:
      g_value_set_boolean (value, suggestion_entry_get_use_filter (self));
      break;

    case PROP_SHOW_ARROW:
      g_value_set_boolean (value, suggestion_entry_get_show_arrow (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
suggestion_entry_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (object);

  if (gtk_editable_delegate_set_property (object, property_id, value, pspec))
    return;

  switch (property_id)
    {
    case PROP_MODEL:
      suggestion_entry_set_model (self, g_value_get_object (value));
      break;

    case PROP_FACTORY:
      suggestion_entry_set_factory (self, g_value_get_object (value));
      break;

    case PROP_EXPRESSION:
      suggestion_entry_set_expression (self, gtk_value_get_expression (value));
      break;

    case PROP_PLACEHOLDER_TEXT:
      gtk_text_set_placeholder_text (GTK_TEXT (self->entry), g_value_get_string (value));
      break;

    case PROP_POPUP_VISIBLE:
      suggestion_entry_set_popup_visible (self, g_value_get_boolean (value));
      break;

    case PROP_USE_FILTER:
      suggestion_entry_set_use_filter (self, g_value_get_boolean (value));
      break;

    case PROP_SHOW_ARROW:
      suggestion_entry_set_show_arrow (self, g_value_get_boolean (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
suggestion_entry_measure (GtkWidget      *widget,
                          GtkOrientation  orientation,
                          int             for_size,
                          int            *minimum,
                          int            *natural,
                          int            *minimum_baseline,
                          int            *natural_baseline)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (widget);
  int arrow_min = 0, arrow_nat = 0;

  gtk_widget_measure (self->entry, orientation, for_size,
                      minimum, natural,
                      minimum_baseline, natural_baseline);

  if (self->arrow && gtk_widget_get_visible (self->arrow))
    gtk_widget_measure (self->arrow, orientation, for_size,
                        &arrow_min, &arrow_nat,
                        NULL, NULL);
}

static void
suggestion_entry_size_allocate (GtkWidget *widget,
                                int        width,
                                int        height,
                                int        baseline)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (widget);
  int arrow_min = 0, arrow_nat = 0;

  if (self->arrow && gtk_widget_get_visible (self->arrow))
    gtk_widget_measure (self->arrow, GTK_ORIENTATION_HORIZONTAL, -1,
                        &arrow_min, &arrow_nat,
                        NULL, NULL);

  gtk_widget_size_allocate (self->entry,
                            &(GtkAllocation) { 00, width - arrow_nat, height },
                            baseline);

  if (self->arrow && gtk_widget_get_visible (self->arrow))
    gtk_widget_size_allocate (self->arrow,
                              &(GtkAllocation) { width - arrow_nat, 0, arrow_nat, height },
                              baseline);

  gtk_widget_set_size_request (self->popup, gtk_widget_get_width (GTK_WIDGET (self)), -1);
  gtk_widget_queue_resize (self->popup);

  gtk_popover_present (GTK_POPOVER (self->popup));
}

static gboolean
suggestion_entry_grab_focus (GtkWidget *widget)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (widget);

  return gtk_widget_grab_focus (self->entry);
}

static void
suggestion_entry_class_init (SuggestionEntryClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = suggestion_entry_dispose;
  object_class->get_property = suggestion_entry_get_property;
  object_class->set_property = suggestion_entry_set_property;

  widget_class->measure = suggestion_entry_measure;
  widget_class->size_allocate = suggestion_entry_size_allocate;
  widget_class->grab_focus = suggestion_entry_grab_focus;

  properties[PROP_MODEL] =
    g_param_spec_object ("model",
                         NULL, NULL,
                         G_TYPE_LIST_MODEL,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  properties[PROP_FACTORY] =
    g_param_spec_object ("factory",
                         NULL, NULL,
                         GTK_TYPE_LIST_ITEM_FACTORY,
                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  properties[PROP_EXPRESSION] =
    gtk_param_spec_expression ("expression",
                               NULL, NULL,
                               G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  properties[PROP_PLACEHOLDER_TEXT] =
      g_param_spec_string ("placeholder-text",
                           NULL, NULL,
                           NULL,
                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME);

  properties[PROP_POPUP_VISIBLE] =
      g_param_spec_boolean ("popup-visible",
                            NULL, NULL,
                            FALSE,
                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  properties[PROP_USE_FILTER] =
      g_param_spec_boolean ("use-filter",
                            NULL, NULL,
                            TRUE,
                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  properties[PROP_SHOW_ARROW] =
      g_param_spec_boolean ("show-arrow",
                            NULL, NULL,
                            FALSE,
                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
  gtk_editable_install_properties (object_class, N_PROPERTIES);

  gtk_widget_class_install_property_action (widget_class, "popup.show""popup-visible");

  gtk_widget_class_add_binding_action (widget_class,
                                       GDK_KEY_Down, GDK_ALT_MASK,
                                       "popup.show", NULL);

  gtk_widget_class_set_css_name (widget_class, "entry");
}

static void
setup_item (GtkSignalListItemFactory *factory,
            GtkListItem              *list_item,
            gpointer                  data)
{
  GtkWidget *label;

  label = gtk_label_new (NULL);
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
  gtk_list_item_set_child (list_item, label);
}

static void
bind_item (GtkSignalListItemFactory *factory,
           GtkListItem              *list_item,
           gpointer                  data)
{
  gpointer item;
  GtkWidget *label;
  GValue value = G_VALUE_INIT;

  item = gtk_list_item_get_item (list_item);
  label = gtk_list_item_get_child (list_item);

  gtk_label_set_label (GTK_LABEL (label), match_object_get_string (MATCH_OBJECT (item)));
  g_value_unset (&value);
}

static void
suggestion_entry_set_popup_visible (SuggestionEntry *self,
                                    gboolean         visible)
{
  if (gtk_widget_get_visible (self->popup) == visible)
    return;

  if (g_list_model_get_n_items (G_LIST_MODEL (self->selection)) == 0)
    return;

  if (visible)
    {
      if (!gtk_widget_has_focus (self->entry))
        gtk_text_grab_focus_without_selecting (GTK_TEXT (self->entry));

      gtk_single_selection_set_selected (self->selection, GTK_INVALID_LIST_POSITION);
      gtk_popover_popup (GTK_POPOVER (self->popup));
    }
  else
    {
      gtk_popover_popdown (GTK_POPOVER (self->popup));
    }

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_POPUP_VISIBLE]);
}

static void update_map (SuggestionEntry *self);

static void
text_changed_idle (gpointer data)
{
  SuggestionEntry *self = data;
  const char *text;
  guint matches;

  if (!self->map_model)
    return;

  text = gtk_editable_get_text (GTK_EDITABLE (self->entry));

  g_free (self->search);
  self->search = g_strdup (text);

  update_map (self);

  matches = g_list_model_get_n_items (G_LIST_MODEL (self->selection));

  suggestion_entry_set_popup_visible (self, matches > 0);
}

static void
text_changed (GtkEditable        *editable,
              GParamSpec         *pspec,
              SuggestionEntry *self)
{
  /* We need to defer to an idle since GtkText sets selection bounds
   * after notify::text
   */

  g_idle_add_once (text_changed_idle, self);
}

static void
accept_current_selection (SuggestionEntry *self)
{
  gpointer item;

  item = gtk_single_selection_get_selected_item (self->selection);
  if (!item)
    return;

  g_signal_handler_block (self->entry, self->changed_id);

  gtk_editable_set_text (GTK_EDITABLE (self->entry),
                         match_object_get_string (MATCH_OBJECT (item)));

  gtk_editable_set_position (GTK_EDITABLE (self->entry), -1);

  g_signal_handler_unblock (self->entry, self->changed_id);
}

static void
suggestion_entry_row_activated (GtkListView     *listview,
                                guint            position,
                                SuggestionEntry *self)
{
  suggestion_entry_set_popup_visible (self, FALSE);
  accept_current_selection (self);
}

static inline gboolean
keyval_is_cursor_move (guint keyval)
{
  if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up)
    return TRUE;

  if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
    return TRUE;

  if (keyval == GDK_KEY_Page_Up || keyval == GDK_KEY_Page_Down)
    return TRUE;

  return FALSE;
}

#define PAGE_STEP 10

static gboolean
suggestion_entry_key_pressed (GtkEventControllerKey *controller,
                              guint                  keyval,
                              guint                  keycode,
                              GdkModifierType        state,
                              SuggestionEntry       *self)
{
  guint matches;
  guint selected;

  if (state & (GDK_SHIFT_MASK | GDK_ALT_MASK | GDK_CONTROL_MASK))
    return FALSE;

  if (keyval == GDK_KEY_Return ||
      keyval == GDK_KEY_KP_Enter ||
      keyval == GDK_KEY_ISO_Enter)
    {
      suggestion_entry_set_popup_visible (self, FALSE);
      accept_current_selection (self);
      g_free (self->search);
      self->search = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->entry)));
      update_map (self);

      return TRUE;
    }
  else if (keyval == GDK_KEY_Escape)
    {
      if (gtk_widget_get_mapped (self->popup))
        {
          suggestion_entry_set_popup_visible (self, FALSE);

          g_signal_handler_block (self->entry, self->changed_id);

          gtk_editable_set_text (GTK_EDITABLE (self->entry), self->search ? self->search : "");

          gtk_editable_set_position (GTK_EDITABLE (self->entry), -1);

          g_signal_handler_unblock (self->entry, self->changed_id);
          return TRUE;
       }
    }
  else if (keyval == GDK_KEY_Right ||
           keyval == GDK_KEY_KP_Right)
    {
      gtk_editable_set_position (GTK_EDITABLE (self->entry), -1);
      return TRUE;
    }
  else if (keyval == GDK_KEY_Left ||
           keyval == GDK_KEY_KP_Left)
    {
      return FALSE;
    }
  else if (keyval == GDK_KEY_Tab ||
           keyval == GDK_KEY_KP_Tab ||
           keyval == GDK_KEY_ISO_Left_Tab)
    {
      suggestion_entry_set_popup_visible (self, FALSE);
      return FALSE/* don't disrupt normal focus handling */
    }

  matches = g_list_model_get_n_items (G_LIST_MODEL (self->selection));
  selected = gtk_single_selection_get_selected (self->selection);

  if (keyval_is_cursor_move (keyval))
    {
      if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up)
        {
          if (selected == 0)
            selected = GTK_INVALID_LIST_POSITION;
          else if (selected == GTK_INVALID_LIST_POSITION)
            selected = matches - 1;
          else
            selected--;
        }
      else if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
        {
          if (selected == matches - 1)
            selected = GTK_INVALID_LIST_POSITION;
          else if (selected == GTK_INVALID_LIST_POSITION)
            selected = 0;
          else
            selected++;
        }
      else if (keyval == GDK_KEY_Page_Up)
        {
          if (selected == 0)
            selected = GTK_INVALID_LIST_POSITION;
          else if (selected == GTK_INVALID_LIST_POSITION)
            selected = matches - 1;
          else if (selected >= PAGE_STEP)
            selected -= PAGE_STEP;
          else
            selected = 0;
        }
      else if (keyval == GDK_KEY_Page_Down)
        {
          if (selected == matches - 1)
            selected = GTK_INVALID_LIST_POSITION;
          else if (selected == GTK_INVALID_LIST_POSITION)
            selected = 0;
          else if (selected + PAGE_STEP < matches)
            selected += PAGE_STEP;
          else
            selected = matches - 1;
        }

      gtk_list_view_scroll_to (GTK_LIST_VIEW (self->list), selected, GTK_LIST_SCROLL_SELECT, NULL);
      return TRUE;
    }

  return FALSE;
}

static void
suggestion_entry_focus_out (GtkEventController *controller,
                            SuggestionEntry    *self)
{
  if (!gtk_widget_get_mapped (self->popup))
    return;

  suggestion_entry_set_popup_visible (self, FALSE);
  accept_current_selection (self);
}

static void
set_default_factory (SuggestionEntry *self)
{
  GtkListItemFactory *factory;

  factory = gtk_signal_list_item_factory_new ();

  g_signal_connect (factory, "setup", G_CALLBACK (setup_item), self);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_item), self);

  suggestion_entry_set_factory (self, factory);

  g_object_unref (factory);
}

static void default_match_func (MatchObject *object,
                                const char  *search,
                                gpointer     data);

static void
suggestion_entry_init (SuggestionEntry *self)
{
  GtkWidget *sw;
  GtkEventController *controller;

  if (!g_object_get_data (G_OBJECT (gdk_display_get_default ()), "suggestion-style"))
    {
      GtkCssProvider *provider;

      provider = gtk_css_provider_new ();
      gtk_css_provider_load_from_resource (provider, "/listview_selections/suggestionentry.css");
      gtk_style_context_add_provider_for_display (gdk_display_get_default (),
                                                  GTK_STYLE_PROVIDER (provider),
                                                  800);
      g_object_set_data (G_OBJECT (gdk_display_get_default ()), "suggestion-style", provider);
      g_object_unref (provider);
    }

  self->use_filter = TRUE;
  self->show_arrow = FALSE;

  self->match_func = default_match_func;
  self->match_data = NULL;
  self->destroy = NULL;

  gtk_widget_add_css_class (GTK_WIDGET (self), "suggestion");

  self->entry = gtk_text_new ();
  gtk_widget_set_parent (self->entry, GTK_WIDGET (self));
  gtk_widget_set_hexpand (self->entry, TRUE);
  gtk_editable_init_delegate (GTK_EDITABLE (self));
  self->changed_id = g_signal_connect (self->entry, "notify::text", G_CALLBACK (text_changed), self);

  self->popup = gtk_popover_new ();
  gtk_popover_set_position (GTK_POPOVER (self->popup), GTK_POS_BOTTOM);
  gtk_popover_set_autohide (GTK_POPOVER (self->popup), FALSE);
  gtk_popover_set_has_arrow (GTK_POPOVER (self->popup), FALSE);
  gtk_widget_set_halign (self->popup, GTK_ALIGN_START);
  gtk_widget_add_css_class (self->popup, "menu");
  gtk_widget_set_parent (self->popup, GTK_WIDGET (self));
  sw = gtk_scrolled_window_new ();
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                  GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (sw), 400);
  gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);

  gtk_popover_set_child (GTK_POPOVER (self->popup), sw);
  self->list = gtk_list_view_new (NULL, NULL);
  gtk_list_view_set_single_click_activate (GTK_LIST_VIEW (self->list), TRUE);
  g_signal_connect (self->list, "activate",
                    G_CALLBACK (suggestion_entry_row_activated), self);
  gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), self->list);

  set_default_factory (self);

  controller = gtk_event_controller_key_new ();
  gtk_event_controller_set_name (controller, "gtk-suggestion-entry");
  g_signal_connect (controller, "key-pressed",
                    G_CALLBACK (suggestion_entry_key_pressed), self);
  gtk_widget_add_controller (self->entry, controller);

  controller = gtk_event_controller_focus_new ();
  gtk_event_controller_set_name (controller, "gtk-suggestion-entry");
  g_signal_connect (controller, "leave",
                    G_CALLBACK (suggestion_entry_focus_out), self);
  gtk_widget_add_controller (self->entry, controller);
}

GtkWidget *
suggestion_entry_new (void)
{
  return g_object_new (SUGGESTION_TYPE_ENTRY, NULL);
}

GListModel *
suggestion_entry_get_model (SuggestionEntry *self)
{
  g_return_val_if_fail (SUGGESTION_IS_ENTRY (self), NULL);

  return self->model;
}

static void
selection_changed (GtkSingleSelection *selection,
                   GParamSpec         *pspec,
                   SuggestionEntry    *self)
{
  accept_current_selection (self);
}

static gboolean
filter_func (gpointer item, gpointer user_data)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (user_data);
  guint min_score;

  if (self->use_filter)
    min_score = 1;
  else
    min_score = 0;

  return match_object_get_score (MATCH_OBJECT (item)) >= min_score;
}

static void
default_match_func (MatchObject *object,
                    const char  *search,
                    gpointer     data)
{
  char *tmp1, *tmp2, *tmp3, *tmp4;

  tmp1 = g_utf8_normalize (match_object_get_string (object), -1, G_NORMALIZE_ALL);
  tmp2 = g_utf8_casefold (tmp1, -1);

  tmp3 = g_utf8_normalize (search, -1, G_NORMALIZE_ALL);
  tmp4 = g_utf8_casefold (tmp3, -1);

  if (g_str_has_prefix (tmp2, tmp4))
    match_object_set_match (object, 0, g_utf8_strlen (search, -1), 1);
  else
    match_object_set_match (object, 000);

  g_free (tmp1);
  g_free (tmp2);
  g_free (tmp3);
  g_free (tmp4);
}

static gpointer
map_func (gpointer item, gpointer user_data)
{
  SuggestionEntry *self = SUGGESTION_ENTRY (user_data);
  GValue value = G_VALUE_INIT;
  gpointer obj;

  if (self->expression)
    {
      gtk_expression_evaluate (self->expression, item, &value);
    }
  else if (GTK_IS_STRING_OBJECT (item))
    {
      g_object_get_property (G_OBJECT (item), "string", &value);
    }
  else
    {
      g_critical ("Either SuggestionEntry:expression must be set "
                  "or SuggestionEntry:model must be a GtkStringList");
      g_value_set_static_string (&value, "No value");
    }

  obj = match_object_new (item, g_value_get_string (&value));

  g_value_unset (&value);

  if (self->search && self->search[0])
    self->match_func (obj, self->search, self->match_data);
  else
    match_object_set_match (obj, 001);

  return obj;
}

static void
update_map (SuggestionEntry *self)
{
  gtk_map_list_model_set_map_func (self->map_model, map_func, self, NULL);
}

void
suggestion_entry_set_model (SuggestionEntry *self,
                            GListModel      *model)
{
  g_return_if_fail (SUGGESTION_IS_ENTRY (self));
  g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));

  if (!g_set_object (&self->model, model))
    return;

  if (self->selection)
    g_signal_handlers_disconnect_by_func (self->selection, selection_changed, self);

  if (model == NULL)
    {
      gtk_list_view_set_model (GTK_LIST_VIEW (self->list), NULL);
      g_clear_object (&self->selection);
      g_clear_object (&self->map_model);
      g_clear_object (&self->filter);
    }
  else
    {
      GtkMapListModel *map_model;
      GtkFilterListModel *filter_model;
      GtkFilter *filter;
      GtkSortListModel *sort_model;
      GtkSingleSelection *selection;
      GtkSorter *sorter;

      map_model = gtk_map_list_model_new (g_object_ref (model), NULL, NULL, NULL);
      g_set_object (&self->map_model, map_model);

      update_map (self);

      filter = GTK_FILTER (gtk_custom_filter_new (filter_func, self, NULL));
      filter_model = gtk_filter_list_model_new (G_LIST_MODEL (self->map_model), filter);
      g_set_object (&self->filter, filter);

      sorter = GTK_SORTER (gtk_numeric_sorter_new (gtk_property_expression_new (MATCH_TYPE_OBJECT, NULL, "score")));
      gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING);
      sort_model = gtk_sort_list_model_new (G_LIST_MODEL (filter_model), sorter);

      update_map (self);

      selection = gtk_single_selection_new (G_LIST_MODEL (sort_model));
      gtk_single_selection_set_autoselect (selection, FALSE);
      gtk_single_selection_set_can_unselect (selection, TRUE);
      gtk_single_selection_set_selected (selection, GTK_INVALID_LIST_POSITION);
      g_set_object (&self->selection, selection);
      gtk_list_view_set_model (GTK_LIST_VIEW (self->list), GTK_SELECTION_MODEL (selection));
      g_object_unref (selection);
    }

  if (self->selection)
    {
      g_signal_connect (self->selection, "notify::selected",
                        G_CALLBACK (selection_changed), self);
      selection_changed (self->selection, NULL, self);
    }

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}

GtkListItemFactory *
suggestion_entry_get_factory (SuggestionEntry *self)
{
  g_return_val_if_fail (SUGGESTION_IS_ENTRY (self), NULL);

  return self->factory;
}

void
suggestion_entry_set_factory (SuggestionEntry    *self,
                              GtkListItemFactory *factory)
{
  g_return_if_fail (SUGGESTION_IS_ENTRY (self));
  g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));

  if (!g_set_object (&self->factory, factory))
    return;

  if (self->list)
    gtk_list_view_set_factory (GTK_LIST_VIEW (self->list), factory);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
}

void
suggestion_entry_set_expression (SuggestionEntry *self,
                                 GtkExpression   *expression)
{
  g_return_if_fail (SUGGESTION_IS_ENTRY (self));
  g_return_if_fail (expression == NULL ||
                    gtk_expression_get_value_type (expression) == G_TYPE_STRING);

  if (self->expression == expression)
    return;

  if (self->expression)
    gtk_expression_unref (self->expression);

  self->expression = expression;

  if (self->expression)
    gtk_expression_ref (self->expression);

  update_map (self);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]);
}

GtkExpression *
suggestion_entry_get_expression (SuggestionEntry *self)
{
  g_return_val_if_fail (SUGGESTION_IS_ENTRY (self), NULL);

  return self->expression;
}

void
suggestion_entry_set_use_filter (SuggestionEntry *self,
                                 gboolean         use_filter)
{
  g_return_if_fail (SUGGESTION_IS_ENTRY (self));

  if (self->use_filter == use_filter)
    return;

  self->use_filter = use_filter;

  gtk_filter_changed (self->filter, GTK_FILTER_CHANGE_DIFFERENT);

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USE_FILTER]);
}

gboolean
suggestion_entry_get_use_filter (SuggestionEntry *self)
{
  g_return_val_if_fail (SUGGESTION_IS_ENTRY (self), TRUE);

  return self->use_filter;
}

static void
suggestion_entry_arrow_clicked (SuggestionEntry *self)
{
  gboolean visible;

  visible = gtk_widget_get_visible (self->popup);
  suggestion_entry_set_popup_visible (self, !visible);
}

void
suggestion_entry_set_show_arrow (SuggestionEntry *self,
                                 gboolean         show_arrow)
{
  g_return_if_fail (SUGGESTION_IS_ENTRY (self));

  if (self->show_arrow == show_arrow)
    return;

  self->show_arrow = show_arrow;

  if (show_arrow)
    {
      GtkGesture *press;

      self->arrow = gtk_image_new_from_icon_name ("pan-down-symbolic");
      gtk_widget_set_tooltip_text (self->arrow, "Show suggestions");
      gtk_widget_set_parent (self->arrow, GTK_WIDGET (self));

      press = gtk_gesture_click_new ();
      g_signal_connect_swapped (press, "released",
                                G_CALLBACK (suggestion_entry_arrow_clicked), self);
      gtk_widget_add_controller (self->arrow, GTK_EVENT_CONTROLLER (press));

    }
  else
    {
      g_clear_pointer (&self->arrow, gtk_widget_unparent);
    }

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_ARROW]);
}

gboolean
suggestion_entry_get_show_arrow (SuggestionEntry *self)
{
  g_return_val_if_fail (SUGGESTION_IS_ENTRY (self), FALSE);

  return self->show_arrow;
}

void
suggestion_entry_set_match_func (SuggestionEntry          *self,
                                 SuggestionEntryMatchFunc  match_func,
                                 gpointer                  user_data,
                                 GDestroyNotify            destroy)
{
  if (self->destroy)
    self->destroy (self->match_data);
  self->match_func = match_func;
  self->match_data = user_data;
  self->destroy = destroy;
}

Messung V0.5 in Prozent
C=98 H=95 G=96

¤ Dauer der Verarbeitung: 0.15 Sekunden  (vorverarbeitet am  2026-07-02) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.