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 19 kB image not shown  

Quelle  range-editor.c

  Sprache: C
 

/*
 * Copyright © 2025 Red Hat, Inc
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * Authors: Matthias Clasen <mclasen@redhat.com>
 */


#include "range-editor.h"

/* {{{ Gizmo */

#define GIZMO_TYPE (gizmo_get_type ())
G_DECLARE_FINAL_TYPE (Gizmo, gizmo, GIZ, MO, GtkWidget)

typedef void    (* GizmoMeasureFunc)   (Gizmo          *gizmo,
                                        GtkOrientation  orientation,
                                        int             for_size,
                                        int            *minimum,
                                        int            *natural,
                                        int            *minimum_baseline,
                                        int            *natural_baseline);
typedef void    (* GizmoAllocateFunc)  (Gizmo          *gizmo,
                                        int             width,
                                        int             height,
                                        int             baseline);
typedef void    (* GizmoSnapshotFunc)  (Gizmo          *gizmo,
                                        GtkSnapshot    *snapshot);

struct _Gizmo
{
  GtkWidget parent_instance;

  GizmoMeasureFunc   measure_func;
  GizmoAllocateFunc  allocate_func;
  GizmoSnapshotFunc  snapshot_func;
};

struct _GizmoClass
{
  GtkWidgetClass parent_class;
};

G_DEFINE_TYPE (Gizmo, gizmo, GTK_TYPE_WIDGET);

static void
gizmo_measure (GtkWidget      *widget,
               GtkOrientation  orientation,
               int             for_size,
               int            *minimum,
               int            *natural,
               int            *minimum_baseline,
               int            *natural_baseline)
{
  Gizmo *self = GIZ_MO (widget);

  if (self->measure_func)
    self->measure_func (self, orientation, for_size,
                        minimum, natural,
                        minimum_baseline, natural_baseline);
}

static void
gizmo_size_allocate (GtkWidget *widget,
                     int        width,
                     int        height,
                     int        baseline)
{
  Gizmo *self = GIZ_MO (widget);

  if (self->allocate_func)
    self->allocate_func (self, width, height, baseline);
}

static void
gizmo_snapshot (GtkWidget   *widget,
                GtkSnapshot *snapshot)
{
  Gizmo *self = GIZ_MO (widget);

  if (self->snapshot_func)
    self->snapshot_func (self, snapshot);
  else
    GTK_WIDGET_CLASS (gizmo_parent_class)->snapshot (widget, snapshot);
}

static void
gizmo_finalize (GObject *object)
{
  Gizmo *self = GIZ_MO (object);
  GtkWidget *child;

  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))) != NULL)
    gtk_widget_unparent (child);

  G_OBJECT_CLASS (gizmo_parent_class)->finalize (object);
}

static void
gizmo_class_init (GizmoClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->finalize = gizmo_finalize;

  widget_class->measure = gizmo_measure;
  widget_class->size_allocate = gizmo_size_allocate;
  widget_class->snapshot = gizmo_snapshot;
}

static void
gizmo_init (Gizmo *self)
{
}

static GtkWidget *
gizmo_new (const char        *css_name,
           GizmoMeasureFunc   measure_func,
           GizmoAllocateFunc  allocate_func,
           GizmoSnapshotFunc  snapshot_func)
{
  Gizmo *gizmo;

  gizmo = g_object_new (GIZMO_TYPE,
                        "css-name", css_name,
                        "accessible-role", GTK_ACCESSIBLE_ROLE_NONE,
                        NULL);

  gizmo->measure_func  = measure_func;
  gizmo->allocate_func = allocate_func;
  gizmo->snapshot_func = snapshot_func;

  return GTK_WIDGET (gizmo);
}

/* }}} */

struct _RangeEditor
{
  GtkWidget parent_instance;

  float lower;
  float upper;
  float value1;
  float value2;

  GtkWidget *trough;
  GtkWidget *highlight;
  GtkWidget *slider1;
  GtkWidget *slider2;

  GtkGesture *drag_gesture;
  GtkWidget *mouse_location;
};

enum
{
  PROP_LOWER = 1,
  PROP_UPPER,
  PROP_VALUE1,
  PROP_VALUE2,
  NUM_PROPERTIES,
};

static GParamSpec *properties[NUM_PROPERTIES];

struct _RangeEditorClass
{
  GtkWidgetClass parent_class;
};

G_DEFINE_TYPE (RangeEditor, range_editor, GTK_TYPE_WIDGET)

/* {{{ Setters */

static void
range_editor_set_values (RangeEditor *self,
                         float        value1,
                         float        value2)
{
  g_return_if_fail (self->lower <= value1);
  g_return_if_fail (value1 <= value2);
  g_return_if_fail (value2 <= self->upper);

  if (self->value1 == value1 && self->value2 == value2)
    return;

  g_object_freeze_notify (G_OBJECT (self));

  if (self->value1 != value1)
    {
      self->value1 = value1;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE1]);
    }

  if (self->value2 != value2)
    {
      self->value2 = value2;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE2]);
    }

  g_object_thaw_notify (G_OBJECT (self));
  gtk_widget_queue_resize (self->highlight);
  gtk_widget_queue_resize (self->slider1);
  gtk_widget_queue_resize (self->slider2);
}

static void
range_editor_set_limits (RangeEditor *self,
                         float        lower,
                         float        upper)
{
  g_return_if_fail (lower <= upper);

  if (self->lower == lower && self->upper == upper)
    return;

  g_object_freeze_notify (G_OBJECT (self));

  if (self->lower != lower)
    {
      self->lower = lower;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOWER]);
    }

  if (self->upper != upper)
    {
      self->upper = upper;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPPER]);
    }

  range_editor_set_values (self,
                           CLAMP (self->value1, self->lower, self->upper),
                           CLAMP (self->value2, self->lower, self->upper));

  g_object_thaw_notify (G_OBJECT (self));
  gtk_widget_queue_resize (self->highlight);
}

/* }}} */
/* {{{ Utilities */

static void
measure_widget (GtkWidget      *widget,
                GtkOrientation  orientation,
                int            *min,
                int            *nat)
{
  gtk_widget_measure (widget, orientation, -1, min, nat, NULL, NULL);
}

/* }}} */
/* {{{ Trough */

static void
measure_trough (Gizmo          *gizmo,
                GtkOrientation  orientation,
                int             for_size,
                int            *minimum,
                int            *natural,
                int            *minimum_baseline,
                int            *natural_baseline)
{
  RangeEditor *self = RANGE_EDITOR (gtk_widget_get_parent (GTK_WIDGET (gizmo)));
  int min, nat;

  measure_widget (self->highlight, orientation, minimum, natural);
  measure_widget (self->slider1, orientation, &min, &nat);

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      *minimum += min;
      *natural += nat;
    }
  else
    {
      *minimum = MAX (*minimum, min);
      *natural = MAX (*natural, nat);
    }

  measure_widget (self->slider2, orientation, &min, &nat);

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      *minimum += min;
      *natural += nat;
    }
  else
    {
      *minimum = MAX (*minimum, min);
      *natural = MAX (*natural, nat);
    }
}

static void
allocate_trough (Gizmo *gizmo,
                 int    width,
                 int    height,
                 int    baseline)
{
  RangeEditor *self = RANGE_EDITOR (gtk_widget_get_parent (GTK_WIDGET (gizmo)));
  GtkAllocation alloc;
  float x1, x2;
  int slider_width, slider_height;

  x1 = width * (self->value1 - self->lower) / (self->upper - self->lower);
  x2 = width * (self->value2 - self->lower) / (self->upper - self->lower);

  alloc.x = x1;
  alloc.y = 0;
  alloc.width = x2 - x1;
  alloc.height = height;

  gtk_widget_size_allocate (self->highlight, &alloc, -1);

  measure_widget (self->slider1, GTK_ORIENTATION_HORIZONTAL, &slider_width, NULL);
  measure_widget (self->slider1, GTK_ORIENTATION_VERTICAL, &slider_height, NULL);

  alloc.x = x1 - slider_width;
  alloc.y = (height - slider_height) / 2;
  alloc.width = slider_width;
  alloc.height = slider_height;

  gtk_widget_size_allocate (self->slider1, &alloc, -1);

  measure_widget (self->slider2, GTK_ORIENTATION_HORIZONTAL, &slider_width, NULL);
  measure_widget (self->slider2, GTK_ORIENTATION_VERTICAL, &slider_height, NULL);

  alloc.x = x2;
  alloc.y = (height - slider_height) / 2;
  alloc.width = slider_width;
  alloc.height = slider_height;

  gtk_widget_size_allocate (self->slider2, &alloc, -1);
}

static void
render_trough (Gizmo       *gizmo,
               GtkSnapshot *snapshot)
{
  RangeEditor *self = RANGE_EDITOR (gtk_widget_get_parent (GTK_WIDGET (gizmo)));

  gtk_widget_snapshot_child (self->trough, self->highlight, snapshot);
  gtk_widget_snapshot_child (self->trough, self->slider1, snapshot);
  gtk_widget_snapshot_child (self->trough, self->slider2, snapshot);
}

/* }}} */
/* {{{ Input */

static void
drag_gesture_begin (GtkGestureDrag *gesture,
                    double          offset_x,
                    double          offset_y,
                    RangeEditor    *self)
{
  self->mouse_location = gtk_widget_pick (GTK_WIDGET (self), offset_x, offset_y, 0);

  if (self->mouse_location == self->slider1 ||
      self->mouse_location == self->slider2)
    gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
}

static void
drag_gesture_end (GtkGestureDrag *gesture,
                  double          offset_x,
                  double          offset_y,
                  RangeEditor    *self)
{
  self->mouse_location = NULL;
}

static void
drag_gesture_update (GtkGestureDrag *gesture,
                     double          offset_x,
                     double          offset_y,
                     RangeEditor    *self)
{
  double start_x, start_y;

  if (self->mouse_location == self->slider1 ||
      self->mouse_location == self->slider2)
    {
      int mouse_x;
      float v;
      graphene_rect_t bounds;

      gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
      mouse_x = start_x + offset_x;

      if (!gtk_widget_compute_bounds (self->trough, GTK_WIDGET (self), &bounds))
        g_assert_not_reached ();

      v = self->lower + (mouse_x - bounds.origin.x) / bounds.size.width * (self->upper - self->lower);

      if (self->mouse_location == self->slider1)
        {
          v = CLAMP (v, self->lower, self->upper);
          range_editor_set_values (self, MIN (v, self->value2), self->value2);
        }
      else if (self->mouse_location == self->slider2)
        {
          v = CLAMP (v, self->lower, self->upper);
          range_editor_set_values (self, self->value1, MAX (self->value1, v));
        }
    }
}

/* }}} */
/* {{{ GtkWidget implementation */

static void
range_editor_measure (GtkWidget      *widget,
                      GtkOrientation  orientation,
                      int             for_size,
                      int            *minimum,
                      int            *natural,
                      int            *minimum_baseline,
                      int            *natural_baseline)
{
  RangeEditor *self = RANGE_EDITOR (widget);

  measure_widget (self->trough, orientation, minimum, natural);
}

static void
range_editor_size_allocate (GtkWidget *widget,
                            int        width,
                            int        height,
                            int        baseline)
{
  RangeEditor *self = RANGE_EDITOR (widget);
  GtkAllocation alloc;
  int min_height;
  int slider1_width, slider2_width;

  measure_widget (self->slider1, GTK_ORIENTATION_HORIZONTAL, &slider1_width, NULL);
  measure_widget (self->slider2, GTK_ORIENTATION_HORIZONTAL, &slider2_width, NULL);
  measure_widget (self->trough, GTK_ORIENTATION_VERTICAL, &min_height, NULL);

  alloc.x = slider1_width;
  alloc.y = (height - min_height) / 2;
  alloc.width = width - slider1_width - slider2_width;
  alloc.height = min_height;

  gtk_widget_size_allocate (self->trough, &alloc, -1);
}

/* }}} */
/* {{{ GObject boilerplate */

static void
range_editor_init (RangeEditor *self)
{
  self->lower = 0;
  self->upper = 0;
  self->value1 = 0;
  self->value2 = 0;

  self->trough = gizmo_new ("trough",
                            measure_trough,
                            allocate_trough,
                            render_trough);
  gtk_widget_set_parent (self->trough, GTK_WIDGET (self));

  self->highlight = gizmo_new ("highlight", NULL, NULL, NULL);
  gtk_widget_set_parent (self->highlight, self->trough);

  self->slider1 = gizmo_new ("slider", NULL, NULL, NULL);
  gtk_widget_set_parent (self->slider1, self->trough);
  gtk_widget_add_css_class (self->slider1, "left");

  self->slider2 = gizmo_new ("slider", NULL, NULL, NULL);
  gtk_widget_set_parent (self->slider2, self->trough);
  gtk_widget_add_css_class (self->slider2, "right");

  self->drag_gesture = gtk_gesture_drag_new ();
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->drag_gesture), 0);
  g_signal_connect (self->drag_gesture, "drag-begin",
                    G_CALLBACK (drag_gesture_begin), self);
  g_signal_connect (self->drag_gesture, "drag-update",
                    G_CALLBACK (drag_gesture_update), self);
  g_signal_connect (self->drag_gesture, "drag-end",
                    G_CALLBACK (drag_gesture_end), self);

  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->drag_gesture));
}

static void
range_editor_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  RangeEditor *self = RANGE_EDITOR (object);
  float v;

  switch (prop_id)
    {
    case PROP_LOWER:
      v = g_value_get_float (value);
      range_editor_set_limits (self, v, MAX (v, self->upper));
      break;

    case PROP_UPPER:
      v = g_value_get_float (value);
      range_editor_set_limits (self, MIN (v, self->lower), v);
      break;

    case PROP_VALUE1:
      v = g_value_get_float (value);
      range_editor_set_values (self, v, MAX (v, self->value2));
      break;

    case PROP_VALUE2:
      v = g_value_get_float (value);
      range_editor_set_values (self, MIN (v, self->value1), v);
      break;

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

static void
range_editor_get_property (GObject    *object,
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  RangeEditor *self = RANGE_EDITOR (object);

  switch (prop_id)
    {
    case PROP_LOWER:
      g_value_set_float (value, self->lower);
      break;

    case PROP_UPPER:
      g_value_set_float (value, self->upper);
      break;

    case PROP_VALUE1:
      g_value_set_float (value, self->value1);
      break;

    case PROP_VALUE2:
      g_value_set_float (value, self->value2);
      break;

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

static void
range_editor_dispose (GObject *object)
{
  RangeEditor *self = RANGE_EDITOR (object);

  g_clear_pointer (&self->highlight, gtk_widget_unparent);
  g_clear_pointer (&self->slider1, gtk_widget_unparent);
  g_clear_pointer (&self->slider2, gtk_widget_unparent);
  g_clear_pointer (&self->trough, gtk_widget_unparent);

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

static void
range_editor_finalize (GObject *object)
{
//  RangeEditor *self = RANGE_EDITOR (object);

  G_OBJECT_CLASS (range_editor_parent_class)->finalize (object);
}

static void
range_editor_class_init (RangeEditorClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

  object_class->set_property = range_editor_set_property;
  object_class->get_property = range_editor_get_property;
  object_class->dispose = range_editor_dispose;
  object_class->finalize = range_editor_finalize;

  widget_class->measure = range_editor_measure;
  widget_class->size_allocate = range_editor_size_allocate;

  properties[PROP_LOWER] =
    g_param_spec_float ("lower", NULL, NULL,
                        -G_MAXFLOAT, G_MAXFLOAT, 0,
                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_UPPER] =
    g_param_spec_float ("upper", NULL, NULL,
                        -G_MAXFLOAT, G_MAXFLOAT, 0,
                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_VALUE1] =
    g_param_spec_float ("value1", NULL, NULL,
                        -G_MAXFLOAT, G_MAXFLOAT, 0,
                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_VALUE2] =
    g_param_spec_float ("value2", NULL, NULL,
                        -G_MAXFLOAT, G_MAXFLOAT, 0,
                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);

  gtk_widget_class_set_css_name (widget_class, "rangeeditor");
}

/* }}} */
/* {{{ Public API */

RangeEditor *
range_editor_new (void)
{
  return g_object_new (RANGE_EDITOR_TYPE, NULL);
}

void
range_editor_get_limits (RangeEditor *self,
                         float       *lower,
                         float       *upper)
{
  if (lower)
    *lower = self->lower;

  if (upper)
    *upper = self->upper;
}

void
range_editor_get_values (RangeEditor *self,
                         float       *value1,
                         float       *value2)
{
  if (value1)
    *value1 = self->value1;

  if (value2)
    *value2 = self->value2;
}

void
range_editor_configure (RangeEditor *self,
                        float        lower,
                        float        upper,
                        float        value1,
                        float        value2)
{
  g_return_if_fail (lower <= value1);
  g_return_if_fail (value1 <= value2);
  g_return_if_fail (value2 <= upper);

  g_object_freeze_notify (G_OBJECT (self));

  if (self->lower != lower)
    {
      self->lower = lower;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOWER]);
    }

  if (self->upper != upper)
    {
      self->upper = upper;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPPER]);
    }

  if (self->value1 != value1)
    {
      self->value1 = value1;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE1]);
    }

  if (self->value2 != value2)
    {
      self->value2 = value2;
      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE2]);
    }

  g_object_thaw_notify (G_OBJECT (self));

  gtk_widget_queue_resize (self->highlight);
  gtk_widget_queue_resize (self->slider1);
  gtk_widget_queue_resize (self->slider2);
}

/* }}} */

/* vim:set foldmethod=marker: */

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

¤ Dauer der Verarbeitung: 0.14 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.