Blob Blame History Raw
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML
><HEAD
><TITLE
>Creating a widget from scratch</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINK
REL="HOME"
TITLE="GTK+ 2.0 Tutorial"
HREF="book1.html"><LINK
REL="UP"
TITLE="Writing Your Own Widgets"
HREF="c2180.html"><LINK
REL="PREVIOUS"
TITLE="Creating a Composite widget"
HREF="x2200.html"><LINK
REL="NEXT"
TITLE="Learning More"
HREF="x2419.html"></HEAD
><BODY
CLASS="SECT1"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="NAVHEADER"
><TABLE
SUMMARY="Header navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TH
COLSPAN="3"
ALIGN="center"
>GTK+ 2.0 Tutorial</TH
></TR
><TR
><TD
WIDTH="10%"
ALIGN="left"
VALIGN="bottom"
><A
HREF="x2200.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="80%"
ALIGN="center"
VALIGN="bottom"
>Writing Your Own Widgets</TD
><TD
WIDTH="10%"
ALIGN="right"
VALIGN="bottom"
><A
HREF="x2419.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
></TABLE
><HR
ALIGN="LEFT"
WIDTH="100%"></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="SEC-CREATINGAWIDGETFROMSCRATCH"
>Creating a widget from scratch</A
></H1
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2312"
>Introduction</A
></H2
><P
>In this section, we'll learn more about how widgets display themselves
on the screen and interact with events. As an example of this, we'll
create an analog dial widget with a pointer that the user can drag to
set the value.</P
><P
><SPAN
CLASS="INLINEMEDIAOBJECT"
><IMG
SRC="images/gtkdial.png"></SPAN
></P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2319"
>Displaying a widget on the screen</A
></H2
><P
>There are several steps that are involved in displaying on the screen.
After the widget is created with a call to <TT
CLASS="LITERAL"
>WIDGETNAME_new()</TT
>,
several more functions are needed:</P
><P
></P
><UL
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_realize()</TT
> is responsible for creating an X
window for the widget if it has one.</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_map()</TT
> is invoked after the user calls
<TT
CLASS="LITERAL"
>gtk_widget_show()</TT
>. It is responsible for making sure the widget
is actually drawn on the screen (<I
CLASS="EMPHASIS"
>mapped</I
>). For a container class,
it must also make calls to <TT
CLASS="LITERAL"
>map()</TT
> functions of any child widgets.</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_draw()</TT
> is invoked when <TT
CLASS="LITERAL"
>gtk_widget_draw()</TT
>
is called for the widget or one of its ancestors. It makes the actual
calls to the drawing functions to draw the widget on the screen. For
container widgets, this function must make calls to
<TT
CLASS="LITERAL"
>gtk_widget_draw()</TT
> for its child widgets.</P
></LI
><LI
><P
> <TT
CLASS="LITERAL"
>WIDGETNAME_expose()</TT
> is a handler for expose events for the
widget. It makes the necessary calls to the drawing functions to draw
the exposed portion on the screen. For container widgets, this
function must generate expose events for its child widgets which don't
have their own windows. (If they have their own windows, then X will
generate the necessary expose events.)</P
></LI
></UL
><P
>You might notice that the last two functions are quite similar - each
is responsible for drawing the widget on the screen. In fact many
types of widgets don't really care about the difference between the
two. The default <TT
CLASS="LITERAL"
>draw()</TT
> function in the widget class simply
generates a synthetic expose event for the redrawn area. However, some
types of widgets can save work by distinguishing between the two
functions. For instance, if a widget has multiple X windows, then
since expose events identify the exposed window, it can redraw only
the affected window, which is not possible for calls to <TT
CLASS="LITERAL"
>draw()</TT
>.</P
><P
>Container widgets, even if they don't care about the difference for
themselves, can't simply use the default <TT
CLASS="LITERAL"
>draw()</TT
> function because
their child widgets might care about the difference. However,
it would be wasteful to duplicate the drawing code between the two
functions. The convention is that such widgets have a function called
<TT
CLASS="LITERAL"
>WIDGETNAME_paint()</TT
> that does the actual work of drawing the
widget, that is then called by the <TT
CLASS="LITERAL"
>draw()</TT
> and <TT
CLASS="LITERAL"
>expose()</TT
>
functions.</P
><P
>In our example approach, since the dial widget is not a container
widget, and only has a single window, we can take the simplest
approach and use the default <TT
CLASS="LITERAL"
>draw()</TT
> function and only implement
an <TT
CLASS="LITERAL"
>expose()</TT
> function.</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2352"
>The origins of the Dial Widget</A
></H2
><P
>Just as all land animals are just variants on the first amphibian that
crawled up out of the mud, GTK widgets tend to start off as variants
of some other, previously written widget. Thus, although this section
is entitled "Creating a Widget from Scratch", the Dial widget really
began with the source code for the Range widget. This was picked as a
starting point because it would be nice if our Dial had the same
interface as the Scale widgets which are just specialized descendants
of the Range widget. So, though the source code is presented below in
finished form, it should not be implied that it was written, <I
CLASS="EMPHASIS"
>ab
initio</I
> in this fashion. Also, if you aren't yet familiar with
how scale widgets work from the application writer's point of view, it
would be a good idea to look them over before continuing.</P
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2356"
>The Basics</A
></H2
><P
>Quite a bit of our widget should look pretty familiar from the
Tictactoe widget. First, we have a header file:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__

#include &#60;gdk/gdk.h&#62;
#include &#60;gtk/gtkadjustment.h&#62;
#include &#60;gtk/gtkwidget.h&#62;


#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */


#define GTK_DIAL(obj)          GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj)       GTK_CHECK_TYPE (obj, gtk_dial_get_type ())


typedef struct _GtkDial        GtkDial;
typedef struct _GtkDialClass   GtkDialClass;

struct _GtkDial
{
  GtkWidget widget;

  /* update policy (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
  guint policy : 2;

  /* Button currently pressed or 0 if none */
  guint8 button;

  /* Dimensions of dial components */
  gint radius;
  gint pointer_width;

  /* ID of update timer, or 0 if none */
  guint32 timer;

  /* Current angle */
  gfloat angle;

  /* Old values from adjustment stored so we know when something changes */
  gfloat old_value;
  gfloat old_lower;
  gfloat old_upper;

  /* The adjustment object that stores the data for this dial */
  GtkAdjustment *adjustment;
};

struct _GtkDialClass
{
  GtkWidgetClass parent_class;
};


GtkWidget*     gtk_dial_new                    (GtkAdjustment *adjustment);
GtkType        gtk_dial_get_type               (void);
GtkAdjustment* gtk_dial_get_adjustment         (GtkDial      *dial);
void           gtk_dial_set_update_policy      (GtkDial      *dial,
						GtkUpdateType  policy);

void           gtk_dial_set_adjustment         (GtkDial      *dial,
						GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */


#endif /* __GTK_DIAL_H__ */</PRE
></TD
></TR
></TABLE
><P
>Since there is quite a bit more going on in this widget than the last
one, we have more fields in the data structure, but otherwise things
are pretty similar.</P
><P
>Next, after including header files and declaring a few constants,
we have some functions to provide information about the widget
and initialize it:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>#include &#60;math.h&#62;
#include &#60;stdio.h&#62;
#include &#60;gtk/gtkmain.h&#62;
#include &#60;gtk/gtksignal.h&#62;

#include "gtkdial.h"

#define SCROLL_DELAY_LENGTH  300
#define DIAL_DEFAULT_SIZE 100

/* Forward declarations */

[ omitted to save space ]

/* Local data */

static GtkWidgetClass *parent_class = NULL;

GtkType
gtk_dial_get_type ()
{
  static GtkType dial_type = 0;

  if (!dial_type)
    {
      static const GtkTypeInfo dial_info =
      {
	"GtkDial",
	sizeof (GtkDial),
	sizeof (GtkDialClass),
	(GtkClassInitFunc) gtk_dial_class_init,
	(GtkObjectInitFunc) gtk_dial_init,
	/* reserved_1 */ NULL,
	/* reserved_1 */ NULL,
	(GtkClassInitFunc) NULL
      };

      dial_type = gtk_type_unique (GTK_TYPE_WIDGET, &#38;dial_info);
    }

  return dial_type;
}

static void
gtk_dial_class_init (GtkDialClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

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

  parent_class = gtk_type_class (gtk_widget_get_type ());

  object_class-&#62;destroy = gtk_dial_destroy;

  widget_class-&#62;realize = gtk_dial_realize;
  widget_class-&#62;expose_event = gtk_dial_expose;
  widget_class-&#62;size_request = gtk_dial_size_request;
  widget_class-&#62;size_allocate = gtk_dial_size_allocate;
  widget_class-&#62;button_press_event = gtk_dial_button_press;
  widget_class-&#62;button_release_event = gtk_dial_button_release;
  widget_class-&#62;motion_notify_event = gtk_dial_motion_notify;
}

static void
gtk_dial_init (GtkDial *dial)
{
  dial-&#62;button = 0;
  dial-&#62;policy = GTK_UPDATE_CONTINUOUS;
  dial-&#62;timer = 0;
  dial-&#62;radius = 0;
  dial-&#62;pointer_width = 0;
  dial-&#62;angle = 0.0;
  dial-&#62;old_value = 0.0;
  dial-&#62;old_lower = 0.0;
  dial-&#62;old_upper = 0.0;
  dial-&#62;adjustment = NULL;
}

GtkWidget*
gtk_dial_new (GtkAdjustment *adjustment)
{
  GtkDial *dial;

  dial = gtk_type_new (gtk_dial_get_type ());

  if (!adjustment)
    adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);

  gtk_dial_set_adjustment (dial, adjustment);

  return GTK_WIDGET (dial);
}

static void
gtk_dial_destroy (GtkObject *object)
{
  GtkDial *dial;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_DIAL (object));

  dial = GTK_DIAL (object);

  if (dial-&#62;adjustment)
    gtk_object_unref (GTK_OBJECT (dial-&#62;adjustment));

  if (GTK_OBJECT_CLASS (parent_class)-&#62;destroy)
    (* GTK_OBJECT_CLASS (parent_class)-&#62;destroy) (object);
}</PRE
></TD
></TR
></TABLE
><P
>Note that this <TT
CLASS="LITERAL"
>init()</TT
> function does less than for the Tictactoe
widget, since this is not a composite widget, and the <TT
CLASS="LITERAL"
>new()</TT
>
function does more, since it now has an argument. Also, note that when
we store a pointer to the Adjustment object, we increment its
reference count, (and correspondingly decrement it when we no longer
use it) so that GTK can keep track of when it can be safely destroyed.</P
><P
>Also, there are a few function to manipulate the widget's options:</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>GtkAdjustment*
gtk_dial_get_adjustment (GtkDial *dial)
{
  g_return_val_if_fail (dial != NULL, NULL);
  g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);

  return dial-&#62;adjustment;
}

void
gtk_dial_set_update_policy (GtkDial      *dial,
			     GtkUpdateType  policy)
{
  g_return_if_fail (dial != NULL);
  g_return_if_fail (GTK_IS_DIAL (dial));

  dial-&#62;policy = policy;
}

void
gtk_dial_set_adjustment (GtkDial      *dial,
			  GtkAdjustment *adjustment)
{
  g_return_if_fail (dial != NULL);
  g_return_if_fail (GTK_IS_DIAL (dial));

  if (dial-&#62;adjustment)
    {
      gtk_signal_disconnect_by_data (GTK_OBJECT (dial-&#62;adjustment), (gpointer) dial);
      gtk_object_unref (GTK_OBJECT (dial-&#62;adjustment));
    }

  dial-&#62;adjustment = adjustment;
  gtk_object_ref (GTK_OBJECT (dial-&#62;adjustment));

  gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
		      (GtkSignalFunc) gtk_dial_adjustment_changed,
		      (gpointer) dial);
  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
		      (GtkSignalFunc) gtk_dial_adjustment_value_changed,
		      (gpointer) dial);

  dial-&#62;old_value = adjustment-&#62;value;
  dial-&#62;old_lower = adjustment-&#62;lower;
  dial-&#62;old_upper = adjustment-&#62;upper;

  gtk_dial_update (dial);
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2368"
><TT
CLASS="LITERAL"
>gtk_dial_realize()</TT
></A
></H2
><P
>Now we come to some new types of functions. First, we have a function
that does the work of creating the X window. Notice that a mask is
passed to the function <TT
CLASS="LITERAL"
>gdk_window_new()</TT
> which specifies which fields of
the GdkWindowAttr structure actually have data in them (the remaining
fields will be given default values). Also worth noting is the way the
event mask of the widget is created. We call
<TT
CLASS="LITERAL"
>gtk_widget_get_events()</TT
> to retrieve the event mask that the user
has specified for this widget (with <TT
CLASS="LITERAL"
>gtk_widget_set_events()</TT
>), and
add the events that we are interested in ourselves.</P
><P
>After creating the window, we set its style and background, and put a
pointer to the widget in the user data field of the GdkWindow. This
last step allows GTK to dispatch events for this window to the correct
widget.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static void
gtk_dial_realize (GtkWidget *widget)
{
  GtkDial *dial;
  GdkWindowAttr attributes;
  gint attributes_mask;

  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_DIAL (widget));

  gtk_widget_set_realized (widget, TRUE);
  dial = GTK_DIAL (widget);

  attributes.x = widget-&#62;allocation.x;
  attributes.y = widget-&#62;allocation.y;
  attributes.width = widget-&#62;allocation.width;
  attributes.height = widget-&#62;allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events (widget) | 
    GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | 
    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
    GDK_POINTER_MOTION_HINT_MASK;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
  widget-&#62;window = gdk_window_new (gtk_widget_get_parent_window (widget), &#38;attributes, attributes_mask);

  widget-&#62;style = gtk_style_attach (widget-&#62;style, widget-&#62;window);

  gdk_window_set_user_data (widget-&#62;window, widget);

  gtk_style_set_background (widget-&#62;style, widget-&#62;window, GTK_STATE_ACTIVE);
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2377"
>Size negotiation</A
></H2
><P
>Before the first time that the window containing a widget is
displayed, and whenever the layout of the window changes, GTK asks
each child widget for its desired size. This request is handled by the
function <TT
CLASS="LITERAL"
>gtk_dial_size_request()</TT
>. Since our widget isn't a
container widget, and has no real constraints on its size, we just
return a reasonable default value.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static void 
gtk_dial_size_request (GtkWidget      *widget,
		       GtkRequisition *requisition)
{
  requisition-&#62;width = DIAL_DEFAULT_SIZE;
  requisition-&#62;height = DIAL_DEFAULT_SIZE;
}</PRE
></TD
></TR
></TABLE
><P
>After all the widgets have requested an ideal size, the layout of the
window is computed and each child widget is notified of its actual
size. Usually, this will be at least as large as the requested size,
but if for instance the user has resized the window, it may
occasionally be smaller than the requested size. The size notification
is handled by the function <TT
CLASS="LITERAL"
>gtk_dial_size_allocate()</TT
>. Notice that
as well as computing the sizes of some component pieces for future
use, this routine also does the grunt work of moving the widget's X
window into the new position and size.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static void
gtk_dial_size_allocate (GtkWidget     *widget,
			GtkAllocation *allocation)
{
  GtkDial *dial;

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

  widget-&#62;allocation = *allocation;
  if (gtk_widget_get_realized (widget))
    {
      dial = GTK_DIAL (widget);

      gdk_window_move_resize (widget-&#62;window,
			      allocation-&#62;x, allocation-&#62;y,
			      allocation-&#62;width, allocation-&#62;height);

      dial-&#62;radius = MAX(allocation-&#62;width,allocation-&#62;height) * 0.45;
      dial-&#62;pointer_width = dial-&#62;radius / 5;
    }
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2385"
><TT
CLASS="LITERAL"
>gtk_dial_expose()</TT
></A
></H2
><P
>As mentioned above, all the drawing of this widget is done in the
handler for expose events. There's not much to remark on here except
the use of the function <TT
CLASS="LITERAL"
>gtk_draw_polygon</TT
> to draw the pointer with
three dimensional shading according to the colors stored in the
widget's style.</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static gboolean
gtk_dial_expose( GtkWidget      *widget,
		 GdkEventExpose *event )
{
  GtkDial *dial;
  GdkPoint points[3];
  gdouble s,c;
  gdouble theta;
  gint xc, yc;
  gint tick_length;
  gint i;

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

  if (event-&#62;count &#62; 0)
    return FALSE;
  
  dial = GTK_DIAL (widget);

  gdk_window_clear_area (widget-&#62;window,
			 0, 0,
			 widget-&#62;allocation.width,
			 widget-&#62;allocation.height);

  xc = widget-&#62;allocation.width/2;
  yc = widget-&#62;allocation.height/2;

  /* Draw ticks */

  for (i=0; i&#60;25; i++)
    {
      theta = (i*M_PI/18. - M_PI/6.);
      s = sin(theta);
      c = cos(theta);

      tick_length = (i%6 == 0) ? dial-&#62;pointer_width : dial-&#62;pointer_width/2;
      
      gdk_draw_line (widget-&#62;window,
		     widget-&#62;style-&#62;fg_gc[widget-&#62;state],
		     xc + c*(dial-&#62;radius - tick_length),
		     yc - s*(dial-&#62;radius - tick_length),
		     xc + c*dial-&#62;radius,
		     yc - s*dial-&#62;radius);
    }

  /* Draw pointer */

  s = sin(dial-&#62;angle);
  c = cos(dial-&#62;angle);


  points[0].x = xc + s*dial-&#62;pointer_width/2;
  points[0].y = yc + c*dial-&#62;pointer_width/2;
  points[1].x = xc + c*dial-&#62;radius;
  points[1].y = yc - s*dial-&#62;radius;
  points[2].x = xc - s*dial-&#62;pointer_width/2;
  points[2].y = yc - c*dial-&#62;pointer_width/2;

  gtk_draw_polygon (widget-&#62;style,
		    widget-&#62;window,
		    GTK_STATE_NORMAL,
		    GTK_SHADOW_OUT,
		    points, 3,
		    TRUE);
  
  return FALSE;
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2391"
>Event handling</A
></H2
><P
>The rest of the widget's code handles various types of events, and
isn't too different from what would be found in many GTK
applications. Two types of events can occur - either the user can
click on the widget with the mouse and drag to move the pointer, or
the value of the Adjustment object can change due to some external
circumstance.</P
><P
>When the user clicks on the widget, we check to see if the click was
appropriately near the pointer, and if so, store the button that the
user clicked with in the <TT
CLASS="LITERAL"
>button</TT
> field of the widget
structure, and grab all mouse events with a call to
<TT
CLASS="LITERAL"
>gtk_grab_add()</TT
>. Subsequent motion of the mouse causes the
value of the control to be recomputed (by the function
<TT
CLASS="LITERAL"
>gtk_dial_update_mouse</TT
>). Depending on the policy that has been
set, "value_changed" events are either generated instantly
(<TT
CLASS="LITERAL"
>GTK_UPDATE_CONTINUOUS</TT
>), after a delay in a timer added with
<TT
CLASS="LITERAL"
>g_timeout_add()</TT
> (<TT
CLASS="LITERAL"
>GTK_UPDATE_DELAYED</TT
>), or only when the
button is released (<TT
CLASS="LITERAL"
>GTK_UPDATE_DISCONTINUOUS</TT
>).</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static gboolean
gtk_dial_button_press( GtkWidget      *widget,
		       GdkEventButton *event )
{
  GtkDial *dial;
  gint dx, dy;
  double s, c;
  double d_parallel;
  double d_perpendicular;

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

  dial = GTK_DIAL (widget);

  /* Determine if button press was within pointer region - we 
     do this by computing the parallel and perpendicular distance of
     the point where the mouse was pressed from the line passing through
     the pointer */
  
  dx = event-&#62;x - widget-&#62;allocation.width / 2;
  dy = widget-&#62;allocation.height / 2 - event-&#62;y;
  
  s = sin(dial-&#62;angle);
  c = cos(dial-&#62;angle);
  
  d_parallel = s*dy + c*dx;
  d_perpendicular = fabs(s*dx - c*dy);
  
  if (!dial-&#62;button &#38;&#38;
      (d_perpendicular &#60; dial-&#62;pointer_width/2) &#38;&#38;
      (d_parallel &#62; - dial-&#62;pointer_width))
    {
      gtk_grab_add (widget);

      dial-&#62;button = event-&#62;button;

      gtk_dial_update_mouse (dial, event-&#62;x, event-&#62;y);
    }

  return FALSE;
}

static gboolean
gtk_dial_button_release( GtkWidget      *widget,
			 GdkEventButton *event )
{
  GtkDial *dial;

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

  dial = GTK_DIAL (widget);

  if (dial-&#62;button == event-&#62;button)
    {
      gtk_grab_remove (widget);

      dial-&#62;button = 0;

      if (dial-&#62;policy == GTK_UPDATE_DELAYED)
	g_source_remove (dial-&#62;timer);
      
      if ((dial-&#62;policy != GTK_UPDATE_CONTINUOUS) &#38;&#38;
	  (dial-&#62;old_value != dial-&#62;adjustment-&#62;value))
	gtk_signal_emit_by_name (GTK_OBJECT (dial-&#62;adjustment), "value_changed");
    }

  return FALSE;
}

static gboolean
gtk_dial_motion_notify( GtkWidget      *widget,
                        GdkEventMotion *event )
{
  GtkDial *dial;
  GdkModifierType mods;
  gint x, y, mask;

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

  dial = GTK_DIAL (widget);

  if (dial-&#62;button != 0)
    {
      x = event-&#62;x;
      y = event-&#62;y;

      if (event-&#62;is_hint || (event-&#62;window != widget-&#62;window))
	gdk_window_get_pointer (widget-&#62;window, &#38;x, &#38;y, &#38;mods);

      switch (dial-&#62;button)
	{
	case 1:
	  mask = GDK_BUTTON1_MASK;
	  break;
	case 2:
	  mask = GDK_BUTTON2_MASK;
	  break;
	case 3:
	  mask = GDK_BUTTON3_MASK;
	  break;
	default:
	  mask = 0;
	  break;
	}

      if (mods &#38; mask)
	gtk_dial_update_mouse (dial, x,y);
    }

  return FALSE;
}

static gboolean
gtk_dial_timer( GtkDial *dial )
{
  g_return_val_if_fail (dial != NULL, FALSE);
  g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);

  if (dial-&#62;policy == GTK_UPDATE_DELAYED)
    gtk_signal_emit_by_name (GTK_OBJECT (dial-&#62;adjustment), "value_changed");

  return FALSE;
}

static void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
  gint xc, yc;
  gfloat old_value;

  g_return_if_fail (dial != NULL);
  g_return_if_fail (GTK_IS_DIAL (dial));

  xc = GTK_WIDGET(dial)-&#62;allocation.width / 2;
  yc = GTK_WIDGET(dial)-&#62;allocation.height / 2;

  old_value = dial-&#62;adjustment-&#62;value;
  dial-&#62;angle = atan2(yc-y, x-xc);

  if (dial-&#62;angle &#60; -M_PI/2.)
    dial-&#62;angle += 2*M_PI;

  if (dial-&#62;angle &#60; -M_PI/6)
    dial-&#62;angle = -M_PI/6;

  if (dial-&#62;angle &#62; 7.*M_PI/6.)
    dial-&#62;angle = 7.*M_PI/6.;

  dial-&#62;adjustment-&#62;value = dial-&#62;adjustment-&#62;lower + (7.*M_PI/6 - dial-&#62;angle) *
    (dial-&#62;adjustment-&#62;upper - dial-&#62;adjustment-&#62;lower) / (4.*M_PI/3.);

  if (dial-&#62;adjustment-&#62;value != old_value)
    {
      if (dial-&#62;policy == GTK_UPDATE_CONTINUOUS)
	{
	  gtk_signal_emit_by_name (GTK_OBJECT (dial-&#62;adjustment), "value_changed");
	}
      else
	{
	  gtk_widget_draw (GTK_WIDGET(dial), NULL);

	  if (dial-&#62;policy == GTK_UPDATE_DELAYED)
	    {
	      if (dial-&#62;timer)
		g_source_remove (dial-&#62;timer);

	      dial-&#62;timer = g_timeout_add (SCROLL_DELAY_LENGTH,
				           (GtkFunction) gtk_dial_timer,
					   (gpointer) dial);
	    }
	}
    }
}</PRE
></TD
></TR
></TABLE
><P
>Changes to the Adjustment by external means are communicated to our
widget by the "changed" and "value_changed" signals. The handlers
for these functions call <TT
CLASS="LITERAL"
>gtk_dial_update()</TT
> to validate the
arguments, compute the new pointer angle, and redraw the widget (by
calling <TT
CLASS="LITERAL"
>gtk_widget_draw()</TT
>).</P
><TABLE
BORDER="0"
BGCOLOR="#E0E0E0"
WIDTH="100%"
><TR
><TD
><PRE
CLASS="PROGRAMLISTING"
>static void
gtk_dial_update (GtkDial *dial)
{
  gfloat new_value;
  
  g_return_if_fail (dial != NULL);
  g_return_if_fail (GTK_IS_DIAL (dial));

  new_value = dial-&#62;adjustment-&#62;value;
  
  if (new_value &#60; dial-&#62;adjustment-&#62;lower)
    new_value = dial-&#62;adjustment-&#62;lower;

  if (new_value &#62; dial-&#62;adjustment-&#62;upper)
    new_value = dial-&#62;adjustment-&#62;upper;

  if (new_value != dial-&#62;adjustment-&#62;value)
    {
      dial-&#62;adjustment-&#62;value = new_value;
      gtk_signal_emit_by_name (GTK_OBJECT (dial-&#62;adjustment), "value_changed");
    }

  dial-&#62;angle = 7.*M_PI/6. - (new_value - dial-&#62;adjustment-&#62;lower) * 4.*M_PI/3. /
    (dial-&#62;adjustment-&#62;upper - dial-&#62;adjustment-&#62;lower);

  gtk_widget_draw (GTK_WIDGET(dial), NULL);
}

static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
			      gpointer       data)
{
  GtkDial *dial;

  g_return_if_fail (adjustment != NULL);
  g_return_if_fail (data != NULL);

  dial = GTK_DIAL (data);

  if ((dial-&#62;old_value != adjustment-&#62;value) ||
      (dial-&#62;old_lower != adjustment-&#62;lower) ||
      (dial-&#62;old_upper != adjustment-&#62;upper))
    {
      gtk_dial_update (dial);

      dial-&#62;old_value = adjustment-&#62;value;
      dial-&#62;old_lower = adjustment-&#62;lower;
      dial-&#62;old_upper = adjustment-&#62;upper;
    }
}

static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
				    gpointer       data)
{
  GtkDial *dial;

  g_return_if_fail (adjustment != NULL);
  g_return_if_fail (data != NULL);

  dial = GTK_DIAL (data);

  if (dial-&#62;old_value != adjustment-&#62;value)
    {
      gtk_dial_update (dial);

      dial-&#62;old_value = adjustment-&#62;value;
    }
}</PRE
></TD
></TR
></TABLE
></DIV
><DIV
CLASS="SECT2"
><H2
CLASS="SECT2"
><A
NAME="AEN2407"
>Possible Enhancements</A
></H2
><P
>The Dial widget as we've described it so far runs about 670 lines of
code. Although that might sound like a fair bit, we've really
accomplished quite a bit with that much code, especially since much of
that length is headers and boilerplate. However, there are quite a few
more enhancements that could be made to this widget:</P
><P
></P
><UL
><LI
><P
> If you try this widget out, you'll find that there is some
flashing as the pointer is dragged around. This is because the entire
widget is erased every time the pointer is moved before being
redrawn. Often, the best way to handle this problem is to draw to an
offscreen pixmap, then copy the final results onto the screen in one
step. (The ProgressBar widget draws itself in this fashion.)</P
></LI
><LI
><P
> The user should be able to use the up and down arrow keys to
increase and decrease the value.</P
></LI
><LI
><P
> It would be nice if the widget had buttons to increase and
decrease the value in small or large steps. Although it would be
possible to use embedded Button widgets for this, we would also like
the buttons to auto-repeat when held down, as the arrows on a
scrollbar do. Most of the code to implement this type of behavior can
be found in the Range widget.</P
></LI
><LI
><P
> The Dial widget could be made into a container widget with a
single child widget positioned at the bottom between the buttons
mentioned above. The user could then add their choice of a label or
entry widget to display the current value of the dial.</P
></LI
></UL
></DIV
></DIV
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="x2200.html"
ACCESSKEY="P"
>&#60;&#60;&#60; Previous</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="book1.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="x2419.html"
ACCESSKEY="N"
>Next &#62;&#62;&#62;</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>Creating a Composite widget</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="c2180.html"
ACCESSKEY="U"
>Up</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>Learning More</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>