Linux 版 (精华区)

发信人: netiscpu (平淡是真), 信区: Linux
标  题: GTK入门导引(20)
发信站: 紫 丁 香 (Mon Dec 14 14:50:10 1998), 转信


发信人: hey (吟风·悠游98), 信区: Unix
标  题: GTK入门导引(20)
发信站: 华南网木棉站 (Tue Nov 10 11:35:50 1998), 转信

20. 写出属于您自己的物件

20.1 概说 

虽然GTK的物件基本上是够用了, 但有时您还是需要产生自己所需要的物件型
态. 如果已经有一个既存的物件很接近您的需求, 那么您可以把程式改个几行
就可以达到您的需求了. 但在您决定要写一个新的物件之前, 先确认是否有人
已经写过了. 这会避免重复浪费资源, 并保持物件数量达到最少, 这会使程式及
介面比较统一一点. 另一方面, 一旦您写好您的物件, 要向全世界公告, 这样其它
人才会受益. 最好的公告地点大概就是gtk-list了. 

20.2 物件的解析 

为了要产生一个新的物件, 了解GTK的运作是很重要的. 这里只简单的说一下.
详细请参照reference documentation. 

GTK物件是以流行的物件导件的观念来设计的. 不过, 依然是以C来写的. 比起
用C++来说, 这可以大大改善可移植性及稳定性. 但同时, 这也意味著widget
writer需要小心许多实作上的问题. 所有同一类别的物件的一般资讯 (例如所有
的按钮物件)是放在 class structure. 只有一份这样的结构. 在这份结构中储存
类别信号的资讯. 要支撑这样的继承, 第一栏的资料结构必须是其父类别的资
料结构. 例如GtkButton的类别宣告看起来像这样: 

    struct _GtkButtonClass
    {
      GtkContainerClass parent_class;

      void (* pressed)  (GtkButton *button);
      void (* released) (GtkButton *button);
      void (* clicked)  (GtkButton *button);
      void (* enter)    (GtkButton *button);
      void (* leave)    (GtkButton *button);
    };

当一个按钮被看成是个container时(例如, 当它被缩放时), 其类别结构可被传到
GtkContainerClass, 而其相关的栏位被用来处理信号. 

对每个物件结构来说, 都有一些状况上的不同. 该结构都有一些资讯是不太一
样的. 我们称此结构为object structure. 如按钮一类, 看起来像这样: 

    struct _GtkButton
    {
      GtkContainer container;

      GtkWidget *child;

      guint in_button : 1;
      guint button_down : 1;
    };

可以看到, 第一栏是其父类别的物件资料结构, 因此该结构可以传到其父类别
的物件结构来处理. 

20.3 产生一个组合物件 

标头档

每个物件类别都有一个标头档来宣告其物件, 类别结构及其函数. 有些特性是
值得指出的. 要避免重复宣告, 我们将整个标头档包成: 

    #ifndef __TICTACTOE_H__
    #define __TICTACTOE_H__
    .
    .
    .
    #endif /* __TICTACTOE_H__ */

而且加入让C++程式不会抓狂的定义码: 

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

除了函数及结构外, 我们宣告了三个标准巨集在标头档中 
TICTACTOE(obj), TICTACTOE_CLASS(klass),
及IS_TICTACTOE(obj), 当我们传入一个指标到物件或类别结构
中, 它会检查是否是我们的tictactoe物件. 

这里是完整的标头档: 


    #ifndef __TICTACTOE_H__
    #define __TICTACTOE_H__

    #include <gdk/gdk.h>
    #include <gtk/gtkvbox.h>

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

    #define TICTACTOE(obj)          GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
    #define TICTACTOE_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
    #define IS_TICTACTOE(obj)       GTK_CHECK_TYPE (obj, tictactoe_get_type ())


    typedef struct _Tictactoe       Tictactoe;
    typedef struct _TictactoeClass  TictactoeClass;

    struct _Tictactoe
    {
      GtkVBox vbox;
      
      GtkWidget *buttons[3][3];
    };

    struct _TictactoeClass
    {
      GtkVBoxClass parent_class;

      void (* tictactoe) (Tictactoe *ttt);
    };

    guint          tictactoe_get_type        (void);
    GtkWidget*     tictactoe_new             (void);
    void           tictactoe_clear           (Tictactoe *ttt);

    #ifdef __cplusplus
    }
    #endif /* __cplusplus */

    #endif /* __TICTACTOE_H__ */

_get_type()函数.

我们现在来继续做我们的物件. 对每个物件来说, 都有一个重要的核心函数 
WIDGETNAME_get_type(). 这个函数, 当第一次被呼叫的时候,
会告诉GTK有关该物件类别, 并取得一个ID来辨视其物件类别. 在其后的呼叫
中, 它会返回该ID. 

    guint
    tictactoe_get_type ()
    {
      static guint ttt_type = 0;

      if (!ttt_type)
        {
          GtkTypeInfo ttt_info =
          {
            "Tictactoe",
            sizeof (Tictactoe),
            sizeof (TictactoeClass),
            (GtkClassInitFunc) tictactoe_class_init,
            (GtkObjectInitFunc) tictactoe_init,
            (GtkArgFunc) NULL,
          };

          ttt_type = gtk_type_unique (gtk_vbox_get_type (), &ttt_info);
        }

      return ttt_type;
    }

GtkTypeInfo结构有以下定义: 

    struct _GtkTypeInfo
    {
      gchar *type_name;
      guint object_size;
      guint class_size;
      GtkClassInitFunc class_init_func;
      GtkObjectInitFunc object_init_func;
      GtkArgFunc arg_func;
    };

这资料结构自我解释的很好. 在此, 我们将会忽略掉arg_func这一栏: 它
很重要, 可以允许用来给设定解译式语言来设定, 但大部份相关工作都还没有
完成. 一旦GTK被正确的填入该资料结构, 它会知道如何产生某一个特别的物
件类别. 

The _class_init() function

WIDGETNAME_class_init()函数启始设定该物件类别的资料,
并设定给该类别信号. 


    enum {
      TICTACTOE_SIGNAL,
      LAST_SIGNAL
    };

    static gint tictactoe_signals[LAST_SIGNAL] = { 0 };

    static void
    tictactoe_class_init (TictactoeClass *class)
    {
      GtkObjectClass *object_class;

      object_class = (GtkObjectClass*) class;
      
      tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
                                             GTK_RUN_FIRST,
                                             object_class->type,
                                             GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
                                             gtk_signal_default_marshaller, GTK_ARG_NONE, 0);


      gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);

      class->tictactoe = NULL;
    }

该函数只有一个信号, ``tictactoe''信号. 并非所有组合式物件都需要信号, 所以
如果这是您第一次读这里, 您可以跳到下一个, 因为这里有点复杂. 

    gint   gtk_signal_new                     (gchar               *name,
                                               GtkSignalRunType     run_type,
                                               gint                 object_type,
                                               gint                 function_offset,
                                               GtkSignalMarshaller  marshaller,
                                               GtkArgType           return_val,
                                               gint                 nparams,
                                               ...);

产生新讯号, 参数包含: 

    name: 信号名称. 
    run_type: 决定内定的处理器要在使用者的处理器之前处理或之
    后处理. 一般可以是GTK_RUN_FIRST, or
    GTK_RUN_LAST. 
    object_type: 物件的ID. 
    function_offset: 在类别结构中内定处理器函数位址值在
    记忆体中的偏移值. 
    marshaller: 用来触发信号处理器的函数. 对除了使用者资料外,
    没有额外参数的的信号处理器来说, 我们可以用内定的marshaller函数 
    gtk_signal_default_marshaller. 
    return_val: 返回值的型态. 
    nparams: 信号处理器的参数数量. (不同于以上所提的两个) 
    ...: 参数型态. 

当指定型态时, 可用GtkArgType: 

    typedef enum
    {
      GTK_ARG_INVALID,
      GTK_ARG_NONE,
      GTK_ARG_CHAR,
      GTK_ARG_SHORT,
      GTK_ARG_INT,
      GTK_ARG_LONG,
      GTK_ARG_POINTER,
      GTK_ARG_OBJECT,
      GTK_ARG_FUNCTION,
      GTK_ARG_SIGNAL
    } GtkArgType;

The _init() function.


    static void
    tictactoe_init (Tictactoe *ttt)
    {
      GtkWidget *table;
      gint i,j;
      
      table = gtk_table_new (3, 3, TRUE);
      gtk_container_add (GTK_CONTAINER(ttt), table);
      gtk_widget_show (table);

      for (i=0;i<3; i++)
        for (j=0;j<3; j++)
          {
            ttt->buttons[i][j] = gtk_toggle_button_new ();
            gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j], 
                                       i, i+1, j, j+1);
            gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
                                GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
            gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
            gtk_widget_show (ttt->buttons[i][j]);
          }
    }


    GtkWidget*
    tictactoe_new ()
    {
      return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
    }

    void           
    tictactoe_clear (Tictactoe *ttt)
    {
      int i,j;

      for (i=0;i<3;i++)
        for (j=0;j<3;j++)
          {
            gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
            gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
                                         FALSE);
            gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
          }
    }

    static void
    tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
    {
      int i,k;

      static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
                                 { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
                                 { 0, 1, 2 }, { 0, 1, 2 } };
      static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
                                 { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
                                 { 0, 1, 2 }, { 2, 1, 0 } };

      int success, found;

      for (k=0; k<8; k++)
        {
          success = TRUE;
          found = FALSE;

          for (i=0;i<3;i++)
            {
              success = success && 
                GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
              found = found ||
                ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
            }
          
          if (success && found)
            {
              gtk_signal_emit (GTK_OBJECT (ttt), 
                               tictactoe_signals[TICTACTOE_SIGNAL]);
              break;
            }
        }
    }

最后, 使用Tictactoe widget的范例程式: 

    #include <gtk/gtk.h>
    #include "tictactoe.h"

    /* Invoked when a row, column or diagonal is completed */
    void
    win (GtkWidget *widget, gpointer data)
    {
      g_print ("Yay!\n");
      tictactoe_clear (TICTACTOE (widget));
    }

    int 
    main (int argc, char *argv[])
    {
      GtkWidget *window;
      GtkWidget *ttt;
      
      gtk_init (&argc, &argv);

      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      
      gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
      
      gtk_signal_connect (GTK_OBJECT (window), "destroy",
                          GTK_SIGNAL_FUNC (gtk_exit), NULL);
      
      gtk_container_border_width (GTK_CONTAINER (window), 10);

      /* Create a new Tictactoe widget */
      ttt = tictactoe_new ();
      gtk_container_add (GTK_CONTAINER (window), ttt);
      gtk_widget_show (ttt);

      /* And attach to its "tictactoe" signal */
      gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
                          GTK_SIGNAL_FUNC (win), NULL);

      gtk_widget_show (window);
      
      gtk_main ();
      
      return 0;
    }

20.4 从草稿中产生物件. 

基本

我们的物件看起来会有点像Tictactoe物件. 

    /* 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 <gdk/gdk.h>
    #include <gtk/gtkadjustment.h>
    #include <gtk/gtkwidget.h>


    #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);
    guint          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__ */

在您产生视窗后, 我们设定其型态及背景, 并放指标到物件的GdkWindow使用
者资料栏上 最后一步允许GTK来分派事件给各别的物件. 

    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_FLAGS (widget, GTK_REALIZED);
      dial = GTK_DIAL (widget);

      attributes.x = widget->allocation.x;
      attributes.y = widget->allocation.y;
      attributes.width = widget->allocation.width;
      attributes.height = widget->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->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);

      widget->style = gtk_style_attach (widget->style, widget->window);

      gdk_window_set_user_data (widget->window, widget);

      gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
    }

大小的设定

在所有视窗被显示出来之前, GTK会先问每个子物件的大小. 该事件是由
gtk_dial_size_request()所处理的. 既然我们的物件不是
container物件, 而且没什么大小约束, 就用个合理的数字就行了. 

    static void 
    gtk_dial_size_request (GtkWidget      *widget,
                           GtkRequisition *requisition)
    {
      requisition->width = DIAL_DEFAULT_SIZE;
      requisition->height = DIAL_DEFAULT_SIZE;
    }

最后所有物件都有理想的大小. 一般会尽可能用原定大小, 但使用者会改变它
的大小. 大小的改变是由gtk_dial_size_allocate(). 

    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->allocation = *allocation;
      if (GTK_WIDGET_REALIZED (widget))
        {
          dial = GTK_DIAL (widget);

          gdk_window_move_resize (widget->window,
                                  allocation->x, allocation->y,
                                  allocation->width, allocation->height);

          dial->radius = MAX(allocation->width,allocation->height) * 0.45;
          dial->pointer_width = dial->radius / 5;
        }
    }

.. 

gtk_dial_expose()

就如之前所提到的一样, 所有物件的绘出都是由expose事件来处理. 没什么可
多提的, 除了用gtk_draw_polygon 来画出三维阴影. 

    static gint
    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->count > 0)
        return FALSE;
      
      dial = GTK_DIAL (widget);

      gdk_window_clear_area (widget->window,
                             0, 0,
                             widget->allocation.width,
                             widget->allocation.height);

      xc = widget->allocation.width/2;
      yc = widget->allocation.height/2;

      /* Draw ticks */

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

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

      /* Draw pointer */

      s = sin(dial->angle);
      c = cos(dial->angle);


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

      gtk_draw_polygon (widget->style,
                        widget->window,
                        GTK_STATE_NORMAL,
                        GTK_SHADOW_OUT,
                        points, 3,
                        TRUE);
      
      return FALSE;
    }

事件处理

最后一段程式处理各种事件, 跟我们之前所做的没有什么太大的不同. 有两种
事件会发生, 使用者滑鼠的动作及其它因素所造成的物件参数调整. 

当使用者在物件上按钮时, 我们检查是否靠近我们的指标, 如果是, 将资料存到
button一栏, 并用gtk_grab_add()将所有滑鼠事件抓住. 接下来
的滑鼠的动作将会被gtk_dial_update_mouse所接管.. 接下来
就看我们是如何做的, "value_changed"事件可以用
(GTK_UPDATE_CONTINUOUS)来产生, 或用
gtk_timeout_add()来延迟一下
(GTK_UPDATE_DELAYED), 或仅在按钮按下时反应
(GTK_UPDATE_DISCONTINUOUS). 

    static gint
    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->x - widget->allocation.width / 2;
      dy = widget->allocation.height / 2 - event->y;
      
      s = sin(dial->angle);
      c = cos(dial->angle);
      
      d_parallel = s*dy + c*dx;
      d_perpendicular = fabs(s*dx - c*dy);
      
      if (!dial->button &&
          (d_perpendicular < dial->pointer_width/2) &&
          (d_parallel > - dial->pointer_width))
        {
          gtk_grab_add (widget);

          dial->button = event->button;

          gtk_dial_update_mouse (dial, event->x, event->y);
        }

      return FALSE;
    }

    static gint
    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->button == event->button)
        {
          gtk_grab_remove (widget);

          dial->button = 0;

          if (dial->policy == GTK_UPDATE_DELAYED)
            gtk_timeout_remove (dial->timer);
          
          if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
              (dial->old_value != dial->adjustment->value))
            gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
        }

      return FALSE;
    }

    static gint
    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->button != 0)
        {
          x = event->x;
          y = event->y;

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

          switch (dial->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 & mask)
            gtk_dial_update_mouse (dial, x,y);
        }

      return FALSE;
    }

    static gint
    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->policy == GTK_UPDATE_DELAYED)
        gtk_signal_emit_by_name (GTK_OBJECT (dial->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)->allocation.width / 2;
      yc = GTK_WIDGET(dial)->allocation.height / 2;

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

      if (dial->angle < -M_PI/2.)
        dial->angle += 2*M_PI;

      if (dial->angle < -M_PI/6)
        dial->angle = -M_PI/6;

      if (dial->angle > 7.*M_PI/6.)
        dial->angle = 7.*M_PI/6.;

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

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

              if (dial->policy == GTK_UPDATE_DELAYED)
                {
                  if (dial->timer)
                    gtk_timeout_remove (dial->timer);

                  dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
                                                 (GtkFunction) gtk_dial_timer,
                                                 (gpointer) dial);
                }
            }
        }
    }

    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->adjustment->value;
      
      if (new_value < dial->adjustment->lower)
        new_value = dial->adjustment->lower;

      if (new_value > dial->adjustment->upper)
        new_value = dial->adjustment->upper;

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

      dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
        (dial->adjustment->upper - dial->adjustment->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->old_value != adjustment->value) ||
          (dial->old_lower != adjustment->lower) ||
          (dial->old_upper != adjustment->upper))
        {
          gtk_dial_update (dial);

          dial->old_value = adjustment->value;
          dial->old_lower = adjustment->lower;
          dial->old_upper = adjustment->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->old_value != adjustment->value)
        {
          gtk_dial_update (dial);

          dial->old_value = adjustment->value;
        }
    }

有可能的增强之处

这个Dial物件到目前为止有670行. 这看起来好像有不少了, 不过我们真正完成
的只有一点点, 因为大部份都是标头及模子. 还是有许多可以加强的地方: 

    如果您试过这个物件, 您会发现滑鼠指标会一闪一闪的. 这是因为整个
    物件每次都重画一次. 当然了最好的方式是在offscreen pixmap上画完
    以后, 然后整个复制到萤幕上. 
    使用者应该可以用up及down按键来增加或减少其值. 
    如果有个按钮来增加或减少其值, 那是再好不过的了. 虽然可也以用
    embedded Button widgets来做, 但我们会想要按钮有auto-repeat的功
    能. 所有要做这一类功能的程式可以在GtkRange物件中发现. 
    这个Dial物件可再做进一个container物件, 带有一个子物件, 位于按钮
    与最下面之间. 使用者可以增加一个标签或整个物件来显示目前的值. 

20.5 更多一点 

关于产生一个新的物件的细部资讯在以上被提供出来. 如果您想要写一个属于
自己的物件, 我想最好的范例就是GTK本身了. 

问问您自己一些关于您想要写的物件: 

它是否是个Container物件?
它是否有自己的视窗?
是否是个现有物件的修改?
找出一个相近的物件, 然后开始动工.

祝好运! 

20.6 版权 

This section on of the tutorial on writing widgets is Copyright (C) 1997 Owen
Taylor 

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 2 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, write to the Free Software Foundation, Inc., 675
Mass Ave, Cambridge, MA 02139, USA. 

--
        6m3m┌───────────────────────┐0m
        6m3m│     4m疾如风,徐如林,侵掠如火,不动如山       3m│4m 0m
        6m3m└───────────────────────┘0m4m 0m
          4m                                                 0m

m;32m※ 来源:.华南网木棉站 bbs.gznet.edu.cn.[FROM: 202.38.212.66]m
--
m;32m※ 转寄:.华南网木棉站 bbs.gznet.edu.cn.[FROM: mtlab.hit.edu.cn]
--

                              Enjoy Linux!
                          -----It's FREE!-----

※ 来源:.紫 丁 香 bbs.hit.edu.cn.[FROM: mtlab.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:427.275毫秒