Logo Search packages:      
Sourcecode: gretl version File versions

obsbutton.c

/* 
 *  gretl -- Gnu Regression, Econometrics and Time-series Library
 *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program 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 General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

/*
 * A custon spin-button for gretl, based on gtkspinbutton.
 * Allin Cottrell, November 2003.
 */

#include "gretl.h"

#include <gdk/gdk.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkmain.h>

#include "obsbutton.h"

#define MIN_OBS_BUTTON_WIDTH              30
#define OBS_BUTTON_INITIAL_TIMER_DELAY    200
#define OBS_BUTTON_TIMER_DELAY            20
#define MAX_TIMER_CALLS                    5
#define EPSILON                            1e-10
#define MIN_ARROW_WIDTH                6

enum {
  PROP_0,
  PROP_VALUE
};

struct _ObsButton
{
    GtkEntry entry;
    GtkAdjustment *adjustment;
    const DATAINFO *pdinfo;
    GdkWindow *panel;
    guint32 timer;
    gdouble timer_step;
    guint in_child : 2;
    guint click_child : 2; /* valid: GTK_ARROW_UP=0, GTK_ARROW_DOWN=1 or 2=NONE/BOTH */
    guint button : 2;
    guint need_timer : 1;
    guint timer_calls : 3;
};

struct _ObsButtonClass
{
    GtkEntryClass parent_class;

    gint (*input)  (ObsButton *obs_button,
                gdouble       *new_value);
    gint (*output) (ObsButton *obs_button);
    void (*value_changed) (ObsButton *obs_button);

    /* Action signals for keybindings, do not connect to these */
    void (*change_value) (ObsButton *obs_button,
                    GtkScrollType scroll);
};

static void obs_button_class_init     (ObsButtonClass *klass);
static void obs_button_editable_init  (GtkEditableClass   *iface);
static void obs_button_init           (ObsButton      *obs_button);
static void obs_button_finalize       (GObject            *object);
static void obs_button_destroy        (GtkObject          *object);
static void obs_button_set_property   (GObject         *object,
                               guint            prop_id,
                               const GValue    *value,
                               GParamSpec      *pspec);
static void obs_button_get_property   (GObject         *object,
                               guint            prop_id,
                               GValue          *value,
                               GParamSpec      *pspec);
static void obs_button_map            (GtkWidget          *widget);
static void obs_button_unmap          (GtkWidget          *widget);
static void obs_button_realize        (GtkWidget          *widget);
static void obs_button_unrealize      (GtkWidget          *widget);
static void obs_button_size_request   (GtkWidget          *widget,
                               GtkRequisition     *requisition);
static void obs_button_size_allocate  (GtkWidget          *widget,
                               GtkAllocation      *allocation);
static gint obs_button_expose         (GtkWidget          *widget,
                               GdkEventExpose     *event);
static gint obs_button_button_press   (GtkWidget          *widget,
                               GdkEventButton     *event);
static gint obs_button_button_release (GtkWidget          *widget,
                               GdkEventButton     *event);
static gint obs_button_motion_notify  (GtkWidget          *widget,
                               GdkEventMotion     *event);
static gint obs_button_enter_notify   (GtkWidget          *widget,
                               GdkEventCrossing   *event);
static gint obs_button_leave_notify   (GtkWidget          *widget,
                               GdkEventCrossing   *event);
static gint obs_button_focus_out      (GtkWidget          *widget,
                               GdkEventFocus      *event);
static void obs_button_grab_notify    (GtkWidget          *widget,
                               gboolean            was_grabbed);
static void obs_button_state_changed  (GtkWidget          *widget,
                               GtkStateType        previous_state);
static void obs_button_draw_arrow     (ObsButton      *obs_button, 
                               GtkArrowType        arrow_type);
static gint obs_button_timer          (ObsButton      *obs_button);
static void obs_button_stop_spinning  (ObsButton      *spin);
static void obs_button_value_changed  (GtkAdjustment      *adjustment,
                               ObsButton      *obs_button); 
static gint obs_button_key_release    (GtkWidget          *widget,
                               GdkEventKey        *event);
static gint obs_button_scroll         (GtkWidget          *widget,
                               GdkEventScroll     *event);
static void obs_button_activate       (GtkEntry           *entry);
static void obs_button_insert_text    (GtkEditable        *editable,
                               const gchar        *new_text,
                               gint                new_text_length,
                               gint               *position);
static void obs_button_real_spin      (ObsButton      *obs_button,
                               gdouble             step);
static void obs_button_real_change_value (ObsButton   *spin,
                                GtkScrollType    scroll);

static gint obs_button_default_input  (ObsButton      *obs_button,
                               gdouble            *new_val);
static gint obs_button_default_output (ObsButton      *obs_button);

static gint obs_button_get_arrow_size     (ObsButton      *obs_button);
static gint obs_button_get_shadow_type    (ObsButton      *spin_button);
static void obs_button_redraw             (ObsButton      *obs_button);
static void obs_button_set_adjustment     (ObsButton      *obs_button,
                                 GtkAdjustment *adjustment);


static GtkEntryClass *parent_class = NULL;

#define NO_ARROW 2

GType obs_button_get_type (void)
{
    static GType obs_button_type = 0;

    if (!obs_button_type) {
      static const GTypeInfo obs_button_info = {
          sizeof (ObsButtonClass),
          NULL,         /* base_init */
          NULL,         /* base_finalize */
          (GClassInitFunc) obs_button_class_init,
          NULL,         /* class_finalize */
          NULL,         /* class_data */
          sizeof (ObsButton),
          0,            /* n_preallocs */
          (GInstanceInitFunc) obs_button_init,
          NULL
      };

      static const GInterfaceInfo editable_info = {
          (GInterfaceInitFunc) obs_button_editable_init, /* interface_init */
          NULL, /* interface_finalize */
          NULL  /* interface_data */
      };

      obs_button_type =
          g_type_register_static(GTK_TYPE_ENTRY, "ObsButton",
                           &obs_button_info, 0);

      g_type_add_interface_static(obs_button_type,
                            GTK_TYPE_EDITABLE,
                            &editable_info);
    }
    return obs_button_type;
}

static void
obs_button_class_init (ObsButtonClass *class)
{
  GObjectClass     *gobject_class = G_OBJECT_CLASS(class);
  GtkObjectClass   *object_class;
  GtkWidgetClass   *widget_class;
  GtkEntryClass    *entry_class;

  object_class = (GtkObjectClass*)   class;
  widget_class = (GtkWidgetClass*)   class;
  entry_class  = (GtkEntryClass*)    class;

  parent_class = g_type_class_peek_parent (class);

  gobject_class->finalize = obs_button_finalize;

  gobject_class->set_property = obs_button_set_property;
  gobject_class->get_property = obs_button_get_property;

  object_class->destroy = obs_button_destroy;

  widget_class->map = obs_button_map;
  widget_class->unmap = obs_button_unmap;
  widget_class->realize = obs_button_realize;
  widget_class->unrealize = obs_button_unrealize;
  widget_class->size_request = obs_button_size_request;
  widget_class->size_allocate = obs_button_size_allocate;
  widget_class->expose_event = obs_button_expose;
  widget_class->scroll_event = obs_button_scroll;
  widget_class->button_press_event = obs_button_button_press;
  widget_class->button_release_event = obs_button_button_release;
  widget_class->motion_notify_event = obs_button_motion_notify;
  widget_class->key_release_event = obs_button_key_release;
  widget_class->enter_notify_event = obs_button_enter_notify;
  widget_class->leave_notify_event = obs_button_leave_notify;
  widget_class->focus_out_event = obs_button_focus_out;
  widget_class->grab_notify = obs_button_grab_notify;
  widget_class->state_changed = obs_button_state_changed;

  entry_class->activate = obs_button_activate;

  class->input = NULL;
  class->output = NULL;
  class->change_value = obs_button_real_change_value;

  g_object_class_install_property (gobject_class,
                                   PROP_VALUE,
                                   g_param_spec_double ("value",
                                          _("Value"),
                                          _("Reads the current value, or sets a new value"),
                                          -G_MAXDOUBLE,
                                          G_MAXDOUBLE,
                                          0.0,
                                          G_PARAM_READWRITE));  
  
  gtk_widget_class_install_style_property_parser (widget_class,
                                      g_param_spec_enum ("shadow_type", "Shadow Type", NULL,
                                                     GTK_TYPE_SHADOW_TYPE,
                                                     GTK_SHADOW_IN,
                                                     G_PARAM_READABLE),
                                      gtk_rc_property_parse_enum);
}

static void
obs_button_editable_init (GtkEditableClass *iface)
{
    iface->insert_text = obs_button_insert_text;
}

static void
obs_button_set_property (GObject      *object,
                   guint         prop_id,
                   const GValue *value,
                   GParamSpec   *pspec)
{
    ObsButton *obs_button = OBS_BUTTON(object);

    if (prop_id == PROP_VALUE) {
      obs_button_set_value(obs_button, g_value_get_double(value));
    }
}

static void
obs_button_get_property (GObject      *object,
                   guint         prop_id,
                   GValue       *value,
                   GParamSpec   *pspec)
{
    ObsButton *obs_button = OBS_BUTTON (object);

    if (prop_id == PROP_VALUE) {
      g_value_set_double (value, obs_button->adjustment->value);
    } else {
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
obs_button_init (ObsButton *obs_button)
{
    obs_button->adjustment = NULL;
    obs_button->panel = NULL;
    obs_button->timer = 0;
    obs_button->timer_step = 0.0;
    obs_button->in_child = NO_ARROW;
    obs_button->click_child = NO_ARROW;
    obs_button->button = 0;
    obs_button->need_timer = FALSE;
    obs_button->timer_calls = 0;
    obs_button->adjustment = NULL;
}

static void
obs_button_finalize (GObject *object)
{
    obs_button_set_adjustment(OBS_BUTTON(object), NULL);
  
    G_OBJECT_CLASS(parent_class)->finalize(object);
}

static void
obs_button_destroy (GtkObject *object)
{
    obs_button_stop_spinning(OBS_BUTTON(object));
  
    GTK_OBJECT_CLASS(parent_class)->destroy(object);
}

static void
obs_button_map (GtkWidget *widget)
{
    if (GTK_WIDGET_REALIZED(widget) && !GTK_WIDGET_MAPPED(widget)) {
      GTK_WIDGET_CLASS(parent_class)->map(widget);
      gdk_window_show(OBS_BUTTON(widget)->panel);
    }
}

static void
obs_button_unmap (GtkWidget *widget)
{
    if (GTK_WIDGET_MAPPED(widget)) {
      gdk_window_hide(OBS_BUTTON(widget)->panel);
      GTK_WIDGET_CLASS(parent_class)->unmap(widget);
    }
}

static void
obs_button_realize (GtkWidget *widget)
{
    ObsButton *obs_button;
    GdkWindowAttr attributes;
    gint attributes_mask;
    guint real_width;
    gint arrow_size;

    obs_button = OBS_BUTTON(widget);
    arrow_size = obs_button_get_arrow_size(obs_button);

    real_width = widget->allocation.width;
    widget->allocation.width -= arrow_size + 2 * widget->style->xthickness;
    gtk_widget_set_events(widget, gtk_widget_get_events(widget) |
                    GDK_KEY_RELEASE_MASK);
    GTK_WIDGET_CLASS(parent_class)->realize(widget);

    widget->allocation.width = real_width;
  
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.visual = gtk_widget_get_visual(widget);
    attributes.colormap = gtk_widget_get_colormap(widget);
    attributes.event_mask = gtk_widget_get_events(widget);
    attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK 
      | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK 
      | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

    attributes.x = (widget->allocation.x +
                widget->allocation.width - arrow_size -
                2 * widget->style->xthickness);
    attributes.y = widget->allocation.y + (widget->allocation.height -
                                 widget->requisition.height) / 2;
    attributes.width = arrow_size + 2 * widget->style->xthickness;
    attributes.height = widget->requisition.height;
  
    obs_button->panel = gdk_window_new(gtk_widget_get_parent_window(widget), 
                               &attributes, attributes_mask);
    gdk_window_set_user_data(obs_button->panel, widget);

    gtk_style_set_background(widget->style, obs_button->panel, GTK_STATE_NORMAL);

    obs_button_default_output(obs_button);

    gtk_widget_queue_resize(GTK_WIDGET(obs_button));
}

static void
obs_button_unrealize (GtkWidget *widget)
{
    ObsButton *spin = OBS_BUTTON(widget);

    GTK_WIDGET_CLASS(parent_class)->unrealize(widget);

    if (spin->panel) {
      gdk_window_set_user_data(spin->panel, NULL);
      gdk_window_destroy(spin->panel);
      spin->panel = NULL;
    }
}

static void
obs_button_size_request (GtkWidget      *widget,
                   GtkRequisition *requisition)
{
    GtkEntry *entry;
    ObsButton *obs_button;
    gint arrow_size;

    entry = GTK_ENTRY (widget);
    obs_button = OBS_BUTTON (widget);
    arrow_size = obs_button_get_arrow_size (obs_button);
  
    GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);

    if (entry->width_chars < 0) {
      PangoContext *context;
      PangoFontMetrics *metrics;
      gint width;
      gint w;
      gint string_len;
      gint max_string_len;
      gint digit_width;
      gboolean interior_focus;
      gint focus_width;

      gtk_widget_style_get(widget,
                       "interior-focus", &interior_focus,
                       "focus-line-width", &focus_width,
                       NULL);

      context = gtk_widget_get_pango_context(widget);
      metrics = pango_context_get_metrics(context,
                                  widget->style->font_desc,
                                  pango_context_get_language(context));

      digit_width = pango_font_metrics_get_approximate_digit_width(metrics);
      digit_width = PANGO_SCALE *
          ((digit_width + PANGO_SCALE - 1) / PANGO_SCALE);

      pango_font_metrics_unref(metrics);
      
      /* Get max of MIN_OBS_BUTTON_WIDTH, size of upper, size of lower */
      
      width = MIN_OBS_BUTTON_WIDTH;
      max_string_len = OBSLEN;
      string_len = strlen(obs_button->pdinfo->endobs) + 1;
      w = PANGO_PIXELS(MIN(string_len, max_string_len) * digit_width);
      width = MAX(width, w);
      
      requisition->width = width + /* INNER_BORDER */ 2 * 2;
      if (!interior_focus)
          requisition->width += 2 * focus_width;
    }

    requisition->width += arrow_size + 2 * widget->style->xthickness;
}

static void
obs_button_size_allocate (GtkWidget     *widget,
                    GtkAllocation *allocation)
{
    ObsButton *spin;
    GtkAllocation entry_allocation;
    GtkAllocation panel_allocation;
    gint arrow_size;
    gint panel_width;

    g_return_if_fail(GTK_IS_OBS_BUTTON(widget));
    g_return_if_fail(allocation != NULL);

    spin = OBS_BUTTON(widget);
    arrow_size = obs_button_get_arrow_size(spin);
    panel_width = arrow_size + 2 * widget->style->xthickness;
  
    widget->allocation = *allocation;
  
    entry_allocation = *allocation;
    entry_allocation.width -= panel_width;

    if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) {
      entry_allocation.x += panel_width;
      panel_allocation.x = allocation->x;
    } else {
      panel_allocation.x = allocation->x + allocation->width - panel_width;
    }

    panel_allocation.width = panel_width;
    panel_allocation.height = MIN(widget->requisition.height, allocation->height);

    panel_allocation.y = allocation->y + (allocation->height -
                                panel_allocation.height) / 2;

    GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, &entry_allocation);

    if (GTK_WIDGET_REALIZED(widget)) {
      gdk_window_move_resize(OBS_BUTTON(widget)->panel, 
                         panel_allocation.x,
                         panel_allocation.y,
                         panel_allocation.width,
                         panel_allocation.height); 
    }

    obs_button_redraw(spin);
}

static gint
obs_button_expose (GtkWidget      *widget,
               GdkEventExpose *event)
{
    ObsButton *spin;

    g_return_val_if_fail(GTK_IS_OBS_BUTTON(widget), FALSE);
    g_return_val_if_fail(event != NULL, FALSE);

    spin = OBS_BUTTON(widget);

    if (GTK_WIDGET_DRAWABLE(widget)) {
      GtkShadowType shadow_type;
      GdkRectangle rect;

      if (event->window != spin->panel)
          GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);

      /* we redraw the panel even if it wasn't exposed. This is
       * because spin->panel is not a child window of widget->window,
       * so it will not be invalidated by eg. gtk_widget_queue_draw().
       */
      rect.x = 0;
      rect.y = 0;

      gdk_drawable_get_size(spin->panel, &rect.width, &rect.height);

      shadow_type = obs_button_get_shadow_type(spin);
      
      gdk_window_begin_paint_rect(spin->panel, &rect);      

      if (shadow_type != GTK_SHADOW_NONE) {
          gtk_paint_box(widget->style, spin->panel,
                    GTK_STATE_NORMAL, shadow_type,
                    NULL, widget, "spinbutton",
                    rect.x, rect.y, rect.width, rect.height);
      }

      obs_button_draw_arrow(spin, GTK_ARROW_UP);
      obs_button_draw_arrow(spin, GTK_ARROW_DOWN);

      gdk_window_end_paint(spin->panel);
    }
  
    return FALSE;
}

static gboolean
obs_button_at_limit (ObsButton *obs_button,
                     GtkArrowType   arrow)
{
    ObsButton *other;
    gpointer p;
    int minsep = 0;
    gdouble val;

    p = g_object_get_data(G_OBJECT(obs_button), "minsep");
    if (p != NULL) {
      minsep = GPOINTER_TO_INT(p);
    }

    other = g_object_get_data(G_OBJECT(obs_button), "startspin");
    if (other != NULL) {
      /* we're looking at the end spinner */
      val = obs_button_get_value(other);
      obs_button->adjustment->lower = val + minsep;
    } else {
      other = g_object_get_data(G_OBJECT(obs_button), "endspin");
      if (other != NULL) {
          /* we're looking at the start spinner */
          val = obs_button_get_value(other);
          obs_button->adjustment->upper = val - minsep;
      }
    }

    if (arrow == GTK_ARROW_UP &&
      (obs_button->adjustment->upper - obs_button->adjustment->value <= EPSILON))
      return TRUE;
  
    if (arrow == GTK_ARROW_DOWN &&
      (obs_button->adjustment->value - obs_button->adjustment->lower <= EPSILON))
      return TRUE;
  
    return FALSE;
}

static void
obs_button_draw_arrow (ObsButton *obs_button, 
                   GtkArrowType   arrow_type)
{
    GtkStateType state_type;
    GtkShadowType shadow_type;
    GtkWidget *widget;
    gint x;
    gint y;
    gint height;
    gint width;
    gint h, w;

    g_return_if_fail (GTK_IS_OBS_BUTTON (obs_button));
    g_return_if_fail (arrow_type == GTK_ARROW_UP || arrow_type == GTK_ARROW_DOWN);
  
    widget = GTK_WIDGET (obs_button);

    if (GTK_WIDGET_DRAWABLE (widget)) {
      width = obs_button_get_arrow_size (obs_button) + 2 * widget->style->xthickness;

      if (arrow_type == GTK_ARROW_UP) {
          x = 0;
          y = 0;

          height = widget->requisition.height / 2;
      } else {
          x = 0;
          y = widget->requisition.height / 2;

          height = (widget->requisition.height + 1) / 2;
      }

      if (obs_button_at_limit (obs_button, arrow_type)) {
          shadow_type = GTK_SHADOW_OUT;
          state_type = GTK_STATE_INSENSITIVE;
      } else {
          if (obs_button->click_child == arrow_type) {
            state_type = GTK_STATE_ACTIVE;
            shadow_type = GTK_SHADOW_IN;
          } else {
            if (obs_button->in_child == arrow_type &&
                obs_button->click_child == NO_ARROW) {
                state_type = GTK_STATE_PRELIGHT;
            } else {
                state_type = GTK_WIDGET_STATE (widget);
            }
            
            shadow_type = GTK_SHADOW_OUT;
          }
      }
      
      gtk_paint_box (widget->style, obs_button->panel,
                   state_type, shadow_type,
                   NULL, widget,
                   (arrow_type == GTK_ARROW_UP)? "spinbutton_up" : "spinbutton_down",
                   x, y, width, height);

      height = widget->requisition.height;

      if (arrow_type == GTK_ARROW_DOWN) {
          y = height / 2;
          height = height - y - 2;
      } else {
          y = 2;
          height = height / 2 - 2;
      }

      width -= 3;

      if (widget && gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
          x = 2;
      else
          x = 1;

      w = width / 2;
      w -= w % 2 - 1; /* force odd */
      h = (w + 1) / 2;
      
      x += (width - w) / 2;
      y += (height - h) / 2;
      
      height = h;
      width = w;

      gtk_paint_arrow (widget->style, obs_button->panel,
                   state_type, shadow_type, 
                   NULL, widget, "spinbutton",
                   arrow_type, TRUE, 
                   x, y, width, height);
    }
}

static gint
obs_button_enter_notify (GtkWidget        *widget,
                   GdkEventCrossing *event)
{
    ObsButton *spin = OBS_BUTTON(widget);

    if (event->window == spin->panel) {
      gint x;
      gint y;

      gdk_window_get_pointer(spin->panel, &x, &y, NULL);

      if (y <= widget->requisition.height / 2)
          spin->in_child = GTK_ARROW_UP;
      else
          spin->in_child = GTK_ARROW_DOWN;

      obs_button_redraw (spin);
    }
  
    return FALSE;
}

static gint
obs_button_leave_notify (GtkWidget        *widget,
                   GdkEventCrossing *event)
{
    ObsButton *spin = OBS_BUTTON(widget);

    spin->in_child = NO_ARROW;
    obs_button_redraw (spin);
  
    return FALSE;
}

static gint
obs_button_focus_out (GtkWidget     *widget,
                  GdkEventFocus *event)
{
    if (GTK_ENTRY(widget)->editable)
      obs_button_update(OBS_BUTTON(widget));

    return GTK_WIDGET_CLASS(parent_class)->focus_out_event(widget, event);
}

static void
obs_button_grab_notify (GtkWidget *widget,
                  gboolean   was_grabbed)
{
    ObsButton *spin = OBS_BUTTON(widget);

    if (!was_grabbed) {
      obs_button_stop_spinning(spin);
      obs_button_redraw(spin);
    }
}

static void
obs_button_state_changed (GtkWidget    *widget,
                    GtkStateType  previous_state)
{
    ObsButton *spin = OBS_BUTTON(widget);

    if (!GTK_WIDGET_IS_SENSITIVE(widget)) {
      obs_button_stop_spinning(spin);    
      obs_button_redraw(spin);
    }
}

static gint
obs_button_scroll (GtkWidget      *widget,
               GdkEventScroll *event)
{
    ObsButton *spin = OBS_BUTTON (widget);

    if (event->direction == GDK_SCROLL_UP) {
      if (!GTK_WIDGET_HAS_FOCUS (widget))
          gtk_widget_grab_focus (widget);
      obs_button_real_spin (spin, spin->adjustment->step_increment);
    }
    else if (event->direction == GDK_SCROLL_DOWN) {
      if (!GTK_WIDGET_HAS_FOCUS (widget))
          gtk_widget_grab_focus (widget);
      obs_button_real_spin (spin, -spin->adjustment->step_increment); 
    }
    else
      return FALSE;

    return TRUE;
}

static void
obs_button_stop_spinning (ObsButton *spin)
{
    if (spin->timer) {
      g_source_remove (spin->timer);
      spin->timer = 0;
      spin->timer_calls = 0;
      spin->need_timer = FALSE;
    }

    spin->button = 0;
    spin->timer = 0;
    spin->timer_step = spin->adjustment->step_increment;
    spin->timer_calls = 0;

    spin->click_child = NO_ARROW;
    spin->button = 0;
}

static void
start_spinning (ObsButton *spin,
            GtkArrowType   click_child,
            gdouble        step)
{
    g_return_if_fail (click_child == GTK_ARROW_UP || click_child == GTK_ARROW_DOWN);
  
    spin->click_child = click_child;
    obs_button_real_spin (spin, click_child == GTK_ARROW_UP ? step : -step);
  
    if (!spin->timer) {
      spin->timer_step = step;
      spin->need_timer = TRUE;
      spin->timer = g_timeout_add (OBS_BUTTON_INITIAL_TIMER_DELAY, 
                             (GSourceFunc) obs_button_timer, (gpointer) spin);
    }

    obs_button_redraw (spin);
}

static gint
obs_button_button_press (GtkWidget      *widget,
                   GdkEventButton *event)
{
    ObsButton *spin = OBS_BUTTON (widget);

    if (!spin->button) {
      if (event->window == spin->panel) {
          if (!GTK_WIDGET_HAS_FOCUS (widget))
            gtk_widget_grab_focus (widget);
          spin->button = event->button;
        
          if (GTK_ENTRY (widget)->editable)
            obs_button_update (spin);
        
          if (event->y <= widget->requisition.height / 2) {
            if (event->button == 1)
                start_spinning (spin, GTK_ARROW_UP, spin->adjustment->step_increment);
            else if (event->button == 2)
                start_spinning (spin, GTK_ARROW_UP, spin->adjustment->page_increment);
            else
                spin->click_child = GTK_ARROW_UP;
          }
          else {
            if (event->button == 1)
                start_spinning (spin, GTK_ARROW_DOWN, spin->adjustment->step_increment);
            else if (event->button == 2)
                start_spinning (spin, GTK_ARROW_DOWN, spin->adjustment->page_increment);
            else
                spin->click_child = GTK_ARROW_DOWN;
          }
          return TRUE;
      }
      else
          return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
    }
    return FALSE;
}

static gint
obs_button_button_release (GtkWidget      *widget,
                     GdkEventButton *event)
{
    ObsButton *spin = OBS_BUTTON (widget);
    gint arrow_size;

    arrow_size = obs_button_get_arrow_size (spin);

    if (event->button == spin->button) {
      int click_child = spin->click_child;

      obs_button_stop_spinning (spin);

      if (event->button == 3) {
          if (event->y >= 0 && event->x >= 0 && 
            event->y <= widget->requisition.height &&
            event->x <= arrow_size + 2 * widget->style->xthickness) {
            if (click_child == GTK_ARROW_UP &&
                event->y <= widget->requisition.height / 2) {
                gdouble diff;

                diff = spin->adjustment->upper - spin->adjustment->value;
                if (diff > EPSILON)
                  obs_button_real_spin (spin, diff);
            }
            else if (click_child == GTK_ARROW_DOWN &&
                   event->y > widget->requisition.height / 2) {
                gdouble diff;

                diff = spin->adjustment->value - spin->adjustment->lower;
                if (diff > EPSILON)
                  obs_button_real_spin (spin, -diff);
            }
          }
      }             
      obs_button_redraw (spin);

      return TRUE;
    }
    else
      return GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
}

static gint
obs_button_motion_notify (GtkWidget      *widget,
                    GdkEventMotion *event)
{
    ObsButton *spin = OBS_BUTTON (widget);

    if (spin->button)
      return FALSE;

    if (event->window == spin->panel) {
      gint y;
      
      gdk_window_get_pointer (spin->panel, NULL, &y, NULL);
  
      if (y <= widget->requisition.height / 2 && 
          spin->in_child == GTK_ARROW_DOWN) {
          spin->in_child = GTK_ARROW_UP;
          obs_button_redraw (spin);
      }
      else if (y > widget->requisition.height / 2 && 
             spin->in_child == GTK_ARROW_UP) {
          spin->in_child = GTK_ARROW_DOWN;
          obs_button_redraw (spin);
      }
      
      return FALSE;
    }
        
    return GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
}

static gboolean
obs_button_timer (ObsButton *obs_button)
{
    gboolean retval = FALSE;
  
    GDK_THREADS_ENTER ();

    if (obs_button->timer) {
      if (obs_button->click_child == GTK_ARROW_UP)
          obs_button_real_spin (obs_button, obs_button->timer_step);
      else
          obs_button_real_spin (obs_button, -obs_button->timer_step);

      if (obs_button->need_timer) {
          obs_button->need_timer = FALSE;
          obs_button->timer = g_timeout_add 
            (OBS_BUTTON_TIMER_DELAY, (GSourceFunc) obs_button_timer, 
             (gpointer) obs_button);
      } else {
          retval = TRUE;
      }
    }

    GDK_THREADS_LEAVE ();

    return retval;
}

static void
obs_button_value_changed (GtkAdjustment *adjustment,
                    ObsButton *obs_button)
{
    g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));

    obs_button_default_output(obs_button);

    obs_button_redraw(obs_button);
  
    g_object_notify(G_OBJECT(obs_button), "value");
}

static void
obs_button_real_change_value (ObsButton *spin,
                        GtkScrollType  scroll)
{
    /* We don't test whether the entry is editable, since
     * this key binding conceptually corresponds to changing
     * the value with the buttons using the mouse, which
     * we allow for non-editable spin buttons.
     */
    switch (scroll) {
    case GTK_SCROLL_STEP_BACKWARD:
    case GTK_SCROLL_STEP_DOWN:
    case GTK_SCROLL_STEP_LEFT:
      obs_button_real_spin (spin, -spin->timer_step);
      break;
      
    case GTK_SCROLL_STEP_FORWARD:
    case GTK_SCROLL_STEP_UP:
    case GTK_SCROLL_STEP_RIGHT:
      obs_button_real_spin (spin, spin->timer_step);
      break;
      
    case GTK_SCROLL_PAGE_BACKWARD:
    case GTK_SCROLL_PAGE_DOWN:
    case GTK_SCROLL_PAGE_LEFT:
      obs_button_real_spin (spin, -spin->adjustment->page_increment);
      break;
      
    case GTK_SCROLL_PAGE_FORWARD:
    case GTK_SCROLL_PAGE_UP:
    case GTK_SCROLL_PAGE_RIGHT:
      obs_button_real_spin (spin, spin->adjustment->page_increment);
      break;
      
    case GTK_SCROLL_START:
      {
          gdouble diff = spin->adjustment->value - spin->adjustment->lower;
          if (diff > EPSILON)
            obs_button_real_spin (spin, -diff);
          break;
      }
      
    case GTK_SCROLL_END:
      {
          gdouble diff = spin->adjustment->upper - spin->adjustment->value;
          if (diff > EPSILON)
            obs_button_real_spin (spin, diff);
          break;
      }
      
    default:
      g_warning ("Invalid scroll type %d for ObsButton::change-value", scroll);
      break;
    }
  
    obs_button_update (spin);
}

static gint
obs_button_key_release (GtkWidget   *widget,
                  GdkEventKey *event)
{
    ObsButton *spin = OBS_BUTTON(widget);

    /* We only get a release at the end of a key repeat run, so reset the timer_step */
    spin->timer_step = spin->adjustment->step_increment;
    spin->timer_calls = 0;
  
    return TRUE;
}

static void
obs_button_activate (GtkEntry *entry)
{
    if (entry->editable)
      obs_button_update(OBS_BUTTON(entry));

    /* Chain up so that entry->activates_default is honored */
    parent_class->activate(entry);
}

static void
obs_button_insert_text (GtkEditable *editable,
                  const gchar *new_text,
                  gint         new_text_length,
                  gint        *position)
{
    GtkEditableClass *parent_editable_iface = g_type_interface_peek(parent_class, GTK_TYPE_EDITABLE);
 
    parent_editable_iface->insert_text(editable, new_text,
                               new_text_length, position);
}

static void
obs_button_real_spin (ObsButton *obs_button,
                  gdouble        increment)
{
    GtkAdjustment *adj;
    gdouble new_value = 0.0;
  
    adj = obs_button->adjustment;

    new_value = adj->value + increment;

    if (increment > 0) {
      new_value = MIN(new_value, adj->upper);
    } else if (increment < 0) {
      new_value = MAX(new_value, adj->lower);
    }

    if (fabs (new_value - adj->value) > EPSILON)
      gtk_adjustment_set_value (adj, new_value);

    obs_button_redraw (obs_button);
}

static gint
obs_button_default_input (ObsButton *obs_button,
                    gdouble *new_val)
{
    *new_val = dateton(gtk_entry_get_text(GTK_ENTRY (obs_button)), 
                   obs_button->pdinfo);

    if (*new_val < 0)
      return GTK_INPUT_ERROR;
    else
      return FALSE;
}

extern gboolean update_obs_label (GtkEditable *entry, gpointer data);

static gint
obs_button_default_output (ObsButton *obs_button)
{
    int ival = obs_button->adjustment->value;
    gchar buf[OBSLEN];
    gpointer data;

    ntodate_full(buf, ival, obs_button->pdinfo);

    if (strcmp(buf, gtk_entry_get_text(GTK_ENTRY(obs_button))))
      gtk_entry_set_text(GTK_ENTRY(obs_button), buf);

    data = g_object_get_data(G_OBJECT(obs_button), "rset");
    if (data != NULL) {
      update_obs_label(NULL, data);
    }

    return FALSE;
}

/***********************************************************
 ***********************************************************
 ***                  Public interface                   ***
 ***********************************************************
 ***********************************************************/

GtkWidget *
obs_button_new (GtkAdjustment *adjustment, const DATAINFO *pdinfo)
{
    ObsButton *spin;

    if (adjustment)
      g_return_val_if_fail(GTK_IS_ADJUSTMENT(adjustment), NULL);

    spin = g_object_new(GTK_TYPE_OBS_BUTTON, NULL);

    spin->pdinfo = pdinfo;

    obs_button_set_adjustment(spin, adjustment);
    gtk_adjustment_value_changed(adjustment);

    return GTK_WIDGET(spin);
}

/* Callback used when the spin button's adjustment changes.  We need to redraw
 * the arrows when the adjustment's range changes, and reevaluate our size request.
 */
static void
adjustment_changed_cb (GtkAdjustment *adjustment, gpointer data)
{
    ObsButton *obs_button = OBS_BUTTON(data);

    obs_button->timer_step = obs_button->adjustment->step_increment;
    gtk_widget_queue_resize(GTK_WIDGET(obs_button));
}

/**
 * obs_button_set_adjustment:
 * @obs_button: a #ObsButton
 * @adjustment: a #GtkAdjustment to replace the existing adjustment
 * 
 * Replaces the #GtkAdjustment associated with @obs_button.
 **/
static void
obs_button_set_adjustment (ObsButton *obs_button,
                     GtkAdjustment *adjustment)
{
    g_return_if_fail (GTK_IS_OBS_BUTTON (obs_button));

    if (obs_button->adjustment != adjustment) {
      if (obs_button->adjustment) {
          g_signal_handlers_disconnect_by_func (obs_button->adjustment,
                                      obs_button_value_changed,
                                      obs_button);
          g_signal_handlers_disconnect_by_func (obs_button->adjustment,
                                      adjustment_changed_cb,
                                      obs_button);
          g_object_unref (obs_button->adjustment);
      }
      obs_button->adjustment = adjustment;
      if (adjustment) {
          g_object_ref (adjustment);
#if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 10)
          gtk_object_sink (GTK_OBJECT (adjustment));
#else
          g_object_ref_sink (GTK_OBJECT (adjustment));
#endif
          g_signal_connect (adjustment, "value-changed",
                        G_CALLBACK (obs_button_value_changed),
                        obs_button);
          g_signal_connect (adjustment, "changed",
                        G_CALLBACK (adjustment_changed_cb),
                        obs_button);
          obs_button->timer_step = obs_button->adjustment->step_increment;
      }

      gtk_widget_queue_resize (GTK_WIDGET (obs_button));
    }
}

/**
 * obs_button_get_value:
 * @obs_button: a #ObsButton
 * 
 * Get the value in the @obs_button.
 * 
 * Return value: the value of @obs_button
 **/
gdouble
obs_button_get_value (ObsButton *obs_button)
{
    g_return_val_if_fail (GTK_IS_OBS_BUTTON (obs_button), 0.0);

    return obs_button->adjustment->value;
}

/**
 * obs_button_set_value:
 * @obs_button: a #ObsButton
 * @value: the new value
 * 
 * Set the value of @obs_button.
 **/
void 
obs_button_set_value (ObsButton *obs_button, 
                  gdouble        value)
{
    g_return_if_fail (GTK_IS_OBS_BUTTON (obs_button));

    if (fabs(value - obs_button->adjustment->value) > EPSILON) {
      gtk_adjustment_set_value (obs_button->adjustment, value);
    } else {
      obs_button_default_output (obs_button);
    }
}

static gint
obs_button_get_arrow_size (ObsButton *obs_button)
{
    gint size = pango_font_description_get_size (GTK_WIDGET (obs_button)->style->font_desc);
    gint arrow_size;

    arrow_size = MAX (PANGO_PIXELS (size), MIN_ARROW_WIDTH);

    return arrow_size - arrow_size % 2; /* force even */
}

static gint
obs_button_get_shadow_type (ObsButton *spin_button)
{
    GtkShadowType rc_shadow_type;

    gtk_widget_style_get(GTK_WIDGET(spin_button), "shadow_type", &rc_shadow_type, NULL);

    return rc_shadow_type;
}

/**
 * obs_button_update:
 * @obs_button: a #ObsButton 
 * 
 * Manually force an update of the spin button.
 **/
void 
obs_button_update (ObsButton *obs_button)
{
    gdouble val;
    gint error = 0;
    gint return_val;

    g_return_if_fail(GTK_IS_OBS_BUTTON(obs_button));

    return_val = obs_button_default_input(obs_button, &val);
    error = (return_val == GTK_INPUT_ERROR);
    
    obs_button_redraw(obs_button);

    if (val < obs_button->adjustment->lower)
      val = obs_button->adjustment->lower;
    else if (val > obs_button->adjustment->upper)
      val = obs_button->adjustment->upper;

    if (fabs(val - obs_button->adjustment->value) > EPSILON) {
      gtk_adjustment_set_value(obs_button->adjustment, val);
    } else {
      obs_button_default_output(obs_button);
    }
}

static void
obs_button_redraw (ObsButton *obs_button)
{
    GtkWidget *widget;

    widget = GTK_WIDGET(obs_button);

    if (GTK_WIDGET_DRAWABLE(widget)) {
      gtk_widget_queue_draw(widget);

      /* We must invalidate the panel window ourselves, because it
       * is not a child of widget->window
       */
      gdk_window_invalidate_rect(obs_button->panel, NULL, TRUE);
    }
}        

Generated by  Doxygen 1.6.0   Back to index