selectable labels



Hi,

By popular demand, it's gtk_label_set_selectable()!

This exciting patch lets you copy from a label (if the programmer
turns it on). Still missing from the patch is a right-click menu,
though it will somewhat oddly have only the single item "copy" in it.

Will commit soon if no comments.

Havoc

Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gtk+/ChangeLog,v
retrieving revision 1.1719
diff -u -u -r1.1719 ChangeLog
--- ChangeLog	2001/02/13 16:22:09	1.1719
+++ ChangeLog	2001/02/13 23:59:23
@@ -1,3 +1,21 @@
+2001-02-13  Havoc Pennington  <hp redhat com>
+
+	* gtk/testgtk.c (create_labels): Add test for selectable
+
+	* gtk/gtkentry.c (gtk_entry_draw_text): Use new GDK API to draw
+	the selection stuff. This code is kind of broken since it doesn't 
+	use the theme engine.
+
+	* gdk/gdkpango.c (gdk_pango_layout_line_get_clip_region): 
+	fix infinite loop and y offset problem
+	(gdk_draw_layout_line_with_colors): fix foreground color handling
+
+        * gtk/gtklabel.h, gtk/gtklabel.c: Implement a "selectable" flag 
+	that makes the label selectable.
+	
+	* gtk/gtklabel.c (gtk_label_style_set): recreate the label's
+	layout when the style is set, since fonts etc. could have changed.	
+
 2001-02-13  Alexander Larsson  <alla lysator liu se>
 
 	* gdk/linux-fb/gdkdrawable-fb2.c (gdk_fb_clip_region):
Index: gdk/gdkpango.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/gdkpango.c,v
retrieving revision 1.13
diff -u -u -r1.13 gdkpango.c
--- gdk/gdkpango.c	2001/02/13 05:44:45	1.13
+++ gdk/gdkpango.c	2001/02/13 23:59:23
@@ -259,7 +259,7 @@
               tmp.green = foreground->green;
             }
           
-          fg_gc = gdk_pango_get_gc (context, fg_set ? &fg_color : NULL,
+          fg_gc = gdk_pango_get_gc (context, fg_set ? &tmp : NULL,
                                     stipple, gc);
         }
       else
@@ -696,16 +696,17 @@
       for (j=0; j < n_pixel_ranges; j++)
         {
           GdkRectangle rect;
-      
+          
           rect.x = x_origin + pixel_ranges[2*j] / PANGO_SCALE;
-          rect.y = y_origin - logical_rect.y / PANGO_SCALE;
+          rect.y = y_origin + logical_rect.y / PANGO_SCALE;
           rect.width = (pixel_ranges[2*j + 1] - pixel_ranges[2*j]) / PANGO_SCALE;
           rect.height = logical_rect.height / PANGO_SCALE;      
-      
+          
           gdk_region_union_with_rect (clip_region, &rect);
         }
 
       g_free (pixel_ranges);
+      ++i;
     }
 
   return clip_region;
Index: gtk/gtkentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkentry.c,v
retrieving revision 1.106
diff -u -u -r1.106 gtkentry.c
--- gtk/gtkentry.c	2001/02/13 05:44:47	1.106
+++ gtk/gtkentry.c	2001/02/13 23:59:23
@@ -1986,37 +1986,27 @@
 
       if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos))
 	{
-	  gint *ranges;
-	  gint n_ranges, i;
-	  gint start_index = g_utf8_offset_to_pointer (entry->text, start_pos) - entry->text;
-	  gint end_index = g_utf8_offset_to_pointer (entry->text, end_pos) - entry->text;
-	  GdkRegion *clip_region = gdk_region_new ();
+          gint range[2];
+          GdkRegion *clip_region;
+          
+	  range[0] = g_utf8_offset_to_pointer (entry->text, start_pos) - entry->text;
+	  range[1] = g_utf8_offset_to_pointer (entry->text, end_pos) - entry->text;
 
-	  pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);
+	  clip_region = gdk_pango_layout_get_clip_region (layout,
+                                                          INNER_BORDER - entry->scroll_offset, y_pos,
+                                                          range, 1);
 
-	  for (i=0; i < n_ranges; i++)
-	    {
-	      GdkRectangle rect;
-
-	      rect.x = INNER_BORDER - entry->scroll_offset + ranges[2*i] / PANGO_SCALE;
-	      rect.y = y_pos;
-	      rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE;
-	      rect.height = logical_rect.height / PANGO_SCALE;
-	      
-	      gdk_draw_rectangle (entry->text_area, widget->style->bg_gc [GTK_STATE_SELECTED], TRUE,
-				  rect.x, rect.y, rect.width, rect.height);
-
-	      gdk_region_union_with_rect (clip_region, &rect);
-	    }
-
 	  gdk_gc_set_clip_region (widget->style->fg_gc [GTK_STATE_SELECTED], clip_region);
-	  gdk_draw_layout (entry->text_area, widget->style->fg_gc [GTK_STATE_SELECTED], 
-			   INNER_BORDER - entry->scroll_offset, y_pos,
-			   layout);
+	  gdk_draw_layout_with_colors (entry->text_area,
+                                       widget->style->fg_gc [GTK_STATE_SELECTED], 
+                                       INNER_BORDER - entry->scroll_offset, y_pos,
+                                       layout,
+                                       &widget->style->fg[GTK_STATE_SELECTED],
+                                       &widget->style->bg[GTK_STATE_SELECTED]);
+
 	  gdk_gc_set_clip_region (widget->style->fg_gc [GTK_STATE_SELECTED], NULL);
 	  
 	  gdk_region_destroy (clip_region);
-	  g_free (ranges);
 	}
 
       g_object_unref (G_OBJECT (layout));
Index: gtk/gtklabel.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtklabel.c,v
retrieving revision 1.71
diff -u -u -r1.71 gtklabel.c
--- gtk/gtklabel.c	2001/02/13 04:56:06	1.71
+++ gtk/gtklabel.c	2001/02/13 23:59:24
@@ -27,9 +27,16 @@
 #include <string.h>
 #include "gtklabel.h"
 #include "gdk/gdkkeysyms.h"
+#include "gtkclipboard.h"
 #include "gdk/gdki18n.h"
 #include <pango/pango.h>
 
+struct _GtkLabelSelectionInfo
+{
+  GdkWindow *window;
+  gint selection_anchor;
+  gint selection_end;
+};
 
 enum {
   ARG_0,
@@ -50,15 +57,39 @@
 static void gtk_label_finalize          (GObject          *object);
 static void gtk_label_size_request      (GtkWidget        *widget,
 					 GtkRequisition   *requisition);
+static void gtk_label_size_allocate     (GtkWidget        *widget,
+                                         GtkAllocation    *allocation);
 static void gtk_label_style_set         (GtkWidget        *widget,
 					 GtkStyle         *previous_style);
 static void gtk_label_direction_changed (GtkWidget        *widget,
 					 GtkTextDirection  previous_dir);
 static gint gtk_label_expose            (GtkWidget        *widget,
 					 GdkEventExpose   *event);
+
+static void gtk_label_realize           (GtkWidget        *widget);
+static void gtk_label_unrealize         (GtkWidget        *widget);
+static void gtk_label_map               (GtkWidget        *widget);
+static void gtk_label_unmap             (GtkWidget        *widget);
+static gint gtk_label_button_press      (GtkWidget        *widget,
+                                         GdkEventButton   *event);
+static gint gtk_label_button_release    (GtkWidget        *widget,
+                                         GdkEventButton   *event);
+static gint gtk_label_motion            (GtkWidget        *widget,
+                                         GdkEventMotion   *event);
+
+static void gtk_label_create_window       (GtkLabel *label);
+static void gtk_label_destroy_window      (GtkLabel *label);
+static void gtk_label_clear_layout        (GtkLabel *label);
+static void gtk_label_ensure_layout       (GtkLabel *label,
+                                           gint     *widthp,
+                                           gint     *heightp);
+static void gtk_label_select_region_index (GtkLabel *label,
+                                           gint      anchor_index,
+                                           gint      end_index);
 
-static GtkMiscClass *parent_class = NULL;
 
+GtkMiscClass *parent_class = NULL;
+
 GtkType
 gtk_label_get_type (void)
 {
@@ -108,9 +139,17 @@
   object_class->get_arg = gtk_label_get_arg;
   
   widget_class->size_request = gtk_label_size_request;
+  widget_class->size_allocate = gtk_label_size_allocate;
   widget_class->style_set = gtk_label_style_set;
   widget_class->direction_changed = gtk_label_direction_changed;
   widget_class->expose_event = gtk_label_expose;
+  widget_class->realize = gtk_label_realize;
+  widget_class->unrealize = gtk_label_unrealize;
+  widget_class->map = gtk_label_map;
+  widget_class->unmap = gtk_label_unmap;
+  widget_class->button_press_event = gtk_label_button_press;
+  widget_class->button_release_event = gtk_label_button_release;
+  widget_class->motion_notify_event = gtk_label_motion;
 }
 
 static void
@@ -207,11 +246,10 @@
   g_free (label->label);
   
   label->label = str;
-  if (label->layout)
-    {
-      g_object_unref (G_OBJECT (label->layout));
-      label->layout = NULL;
-    }
+
+  gtk_label_clear_layout (label);  
+
+  gtk_label_select_region_index (label, 0, 0);
   
   gtk_widget_queue_resize (GTK_WIDGET (label));
 }
@@ -426,6 +464,8 @@
 
   if (label->attrs)
     pango_attr_list_unref (label->attrs);
+
+  g_free (label->select_info);
   
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -469,17 +509,26 @@
 }
 
 static void
-gtk_label_size_request (GtkWidget      *widget,
-			GtkRequisition *requisition)
+gtk_label_clear_layout (GtkLabel *label)
 {
-  GtkLabel *label;
+  if (label->layout)
+    {
+      g_object_unref (G_OBJECT (label->layout));
+      label->layout = NULL;
+    }
+}
+
+static void
+gtk_label_ensure_layout (GtkLabel *label,
+                         gint     *widthp,
+                         gint     *heightp)
+{
+  GtkWidget *widget;
   PangoRectangle logical_rect;
-  
-  g_return_if_fail (GTK_IS_LABEL (widget));
-  g_return_if_fail (requisition != NULL);
-  
-  label = GTK_LABEL (widget);
+  gint width, height;
 
+  widget = GTK_WIDGET (label);
+
   /*
    * There are a number of conditions which will necessitate re-filling
    * our text:
@@ -501,8 +550,8 @@
    * don't think it's really that slow.
    */
 
-  requisition->width = label->misc.xpad * 2;
-  requisition->height = label->misc.ypad * 2;
+  width = label->misc.xpad * 2;
+  height = label->misc.ypad * 2;
 
   if (!label->layout)
     {
@@ -512,7 +561,7 @@
       label->layout = gtk_widget_create_pango_layout (widget, label->label);
 
       /* FIXME move to a model where the pattern isn't stored
-       * permanently, and just modifes or creates the AttrList
+       * permanently, and just modifies or creates the AttrList
        */
       if (label->attrs)
 	attrs = pango_attr_list_copy (label->attrs);
@@ -521,7 +570,7 @@
 
       if (label->pattern)
 	gtk_label_pattern_to_attrs (label, attrs);
-
+      
       if (attrs)
 	{
 	  pango_layout_set_attributes (label->layout, attrs);
@@ -564,8 +613,8 @@
 	  pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE);
 	  pango_layout_get_extents (label->layout, NULL, &logical_rect);
 
-	  requisition->width += aux_info->width;
-	  requisition->height += PANGO_PIXELS (logical_rect.height);
+	  width += aux_info->width;
+	  height += PANGO_PIXELS (logical_rect.height);
 	}
       else
 	{
@@ -632,17 +681,61 @@
 	    }
 	  pango_layout_set_width (label->layout, width);
 
-	  requisition->width += PANGO_PIXELS (real_width);
-	  requisition->height += PANGO_PIXELS (height);
+	  width += PANGO_PIXELS (real_width);
+	  height += PANGO_PIXELS (height);
 	}
     }
   else				/* !label->wrap */
     {
       pango_layout_set_width (label->layout, -1);
       pango_layout_get_extents (label->layout, NULL, &logical_rect);
+
+      width += PANGO_PIXELS (logical_rect.width);
+      height += PANGO_PIXELS (logical_rect.height);
+    }
+
+  if (widthp)
+    *widthp = width;
 
-      requisition->width += PANGO_PIXELS (logical_rect.width);
-      requisition->height += PANGO_PIXELS (logical_rect.height);
+  if (heightp)
+    *heightp = height;
+}
+
+static void
+gtk_label_size_request (GtkWidget      *widget,
+			GtkRequisition *requisition)
+{
+  GtkLabel *label;
+  gint width, height;
+  
+  g_return_if_fail (GTK_IS_LABEL (widget));
+  g_return_if_fail (requisition != NULL);
+  
+  label = GTK_LABEL (widget);
+
+  gtk_label_ensure_layout (label, &width, &height);
+
+  requisition->width = width;
+  requisition->height = height;
+}
+
+static void
+gtk_label_size_allocate (GtkWidget     *widget,
+                         GtkAllocation *allocation)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+  
+  (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
+
+  if (label->select_info && label->select_info->window)
+    {
+      gdk_window_move_resize (label->select_info->window,
+                              allocation->x,
+                              allocation->y,
+                              allocation->width,
+                              allocation->height);
     }
 }
 
@@ -651,13 +744,13 @@
 		     GtkStyle  *previous_style)
 {
   GtkLabel *label;
-
+  
   g_return_if_fail (GTK_IS_LABEL (widget));
   
   label = GTK_LABEL (widget);
 
-  if (previous_style && label->layout)
-    pango_layout_context_changed (label->layout);
+  /* We have to clear the layout, fonts etc. may have changed */
+  gtk_label_clear_layout (label);
 }
 
 static void 
@@ -703,43 +796,59 @@
 }
 #endif
 
+static void
+get_layout_location (GtkLabel  *label,
+                     gint      *xp,
+                     gint      *yp)
+{
+  GtkMisc *misc;
+  GtkWidget *widget;
+  gfloat xalign;
+  gint x, y;
+  
+  misc = GTK_MISC (label);
+  widget = GTK_WIDGET (label);
+  
+  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+    xalign = misc->xalign;
+  else
+    xalign = 1.0 - misc->xalign;
+  
+  x = floor (widget->allocation.x + (gint)misc->xpad
+             + ((widget->allocation.width - widget->requisition.width) * xalign)
+             + 0.5);
+  
+  y = floor (widget->allocation.y + (gint)misc->ypad 
+             + ((widget->allocation.height - widget->requisition.height) * misc->yalign)
+             + 0.5);
+  
+
+  if (xp)
+    *xp = x;
+
+  if (yp)
+    *yp = y;
+}
+
 static gint
 gtk_label_expose (GtkWidget      *widget,
 		  GdkEventExpose *event)
 {
   GtkLabel *label;
-  GtkMisc *misc;
   gint x, y;
-  gfloat xalign;
   
   g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
   
   label = GTK_LABEL (widget);
 
-  /* if label->layout is NULL it means we got a set_text since
-   * our last size request, so a resize should be queued,
-   * which means a full expose is in the queue anyway.
-   */
+  gtk_label_ensure_layout (label, NULL, NULL);
+  
   if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
-      label->layout && label->label && (*label->label != '\0'))
+      label->label && (*label->label != '\0'))
     {
-      misc = GTK_MISC (widget);
+      get_layout_location (label, &x, &y);
       
-      if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
-	xalign = misc->xalign;
-      else
-	xalign = 1. - misc->xalign;
-      
-      x = floor (widget->allocation.x + (gint)misc->xpad
-		 + ((widget->allocation.width - widget->requisition.width) * xalign)
-		 + 0.5);
-      
-      y = floor (widget->allocation.y + (gint)misc->ypad 
-		 + ((widget->allocation.height - widget->requisition.height) * misc->yalign)
-		 + 0.5);
-
-      
       gtk_paint_layout (widget->style,
                         widget->window,
                         GTK_WIDGET_STATE (widget),
@@ -748,6 +857,45 @@
                         "label",
                         x, y,
                         label->layout);
+      
+      if (label->select_info &&
+          (label->select_info->selection_anchor !=
+           label->select_info->selection_end))
+        {
+          gint range[2];
+          GdkRegion *clip;
+          
+          range[0] = label->select_info->selection_anchor;
+          range[1] = label->select_info->selection_end;
+
+          if (range[0] > range[1])
+            {
+              gint tmp = range[0];
+              range[0] = range[1];
+              range[1] = tmp;
+            }
+
+          clip = gdk_pango_layout_get_clip_region (label->layout,
+                                                   x, y,
+                                                   range,
+                                                   1);
+
+          /* FIXME should use gtk_paint, but it can't use a clip
+           * region
+           */
+
+          gdk_gc_set_clip_region (widget->style->white_gc, clip);
+          
+          gdk_draw_layout_with_colors (widget->window,
+                                       widget->style->white_gc,
+                                       x, y,
+                                       label->layout,
+                                       &widget->style->fg[GTK_STATE_SELECTED],
+                                       &widget->style->bg[GTK_STATE_SELECTED]);
+
+          gdk_gc_set_clip_region (widget->style->white_gc, NULL);
+          gdk_region_destroy (clip);
+        }
     }
 
   return TRUE;
@@ -836,3 +984,430 @@
   
   return accel_key;
 }
+
+static void
+gtk_label_realize (GtkWidget *widget)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+  
+  (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+
+  if (label->select_info)
+    gtk_label_create_window (label);
+}
+
+static void
+gtk_label_unrealize (GtkWidget *widget)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+
+  if (label->select_info)
+    gtk_label_destroy_window (label);
+  
+  (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_label_map (GtkWidget *widget)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+  
+  (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
+  
+  if (label->select_info)
+    gdk_window_show (label->select_info->window);
+}
+
+static void
+gtk_label_unmap (GtkWidget *widget)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+
+  if (label->select_info)
+    gdk_window_hide (label->select_info->window);
+  
+  (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+}
+
+static void
+window_to_layout_coords (GtkLabel *label,
+                         gint     *x,
+                         gint     *y)
+{
+  gint lx, ly;
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (label);
+  
+  /* get layout location in widget->window coords */
+  get_layout_location (label, &lx, &ly);
+  
+  if (x)
+    {
+      *x += widget->allocation.x; /* go to widget->window */
+      *x -= lx;                   /* go to layout */
+    }
+
+  if (y)
+    {
+      *y += widget->allocation.y; /* go to widget->window */
+      *y -= ly;                   /* go to layout */
+    }
+}
+
+static void
+layout_to_window_coords (GtkLabel *label,
+                         gint     *x,
+                         gint     *y)
+{
+  gint lx, ly;
+  GtkWidget *widget;
+
+  widget = GTK_WIDGET (label);
+  
+  /* get layout location in widget->window coords */
+  get_layout_location (label, &lx, &ly);
+  
+  if (x)
+    {
+      *x += lx;                   /* go to widget->window */
+      *x -= widget->allocation.x; /* go to selection window */
+    }
+
+  if (y)
+    {
+      *y += ly;                   /* go to widget->window */
+      *y -= widget->allocation.y; /* go to selection window */
+    }
+}
+
+static void
+get_layout_index (GtkLabel *label,
+                  gint      x,
+                  gint      y,
+                  gint     *index)
+{
+  gint trailing = 0;
+  const gchar *cluster;
+  const gchar *cluster_end;
+
+  *index = 0;
+  
+  gtk_label_ensure_layout (label, NULL, NULL);
+  
+  window_to_layout_coords (label, &x, &y);
+
+  x *= PANGO_SCALE;
+  y *= PANGO_SCALE;
+  
+  pango_layout_xy_to_index (label->layout,
+                            x, y,
+                            index, &trailing);
+
+  
+  cluster = label->label + *index;
+  cluster_end = cluster;
+  while (trailing)
+    {
+      cluster_end = g_utf8_next_char (cluster_end);
+      --trailing;
+    }
+
+  *index += (cluster_end - cluster);
+}
+
+static gint
+gtk_label_button_press (GtkWidget      *widget,
+                        GdkEventButton *event)
+{
+  GtkLabel *label;
+  gint index = 0;
+  
+  label = GTK_LABEL (widget);
+
+  if (label->select_info == NULL)
+    return FALSE;
+
+  if (event->button != 1)
+    return FALSE;
+
+  get_layout_index (label, event->x, event->y, &index);
+  
+  if ((label->select_info->selection_anchor !=
+       label->select_info->selection_end) &&
+      (event->state & GDK_SHIFT_MASK))
+    {
+      /* extend (same as motion) */
+      if (index < label->select_info->selection_end)
+        gtk_label_select_region_index (label,
+                                       index,
+                                       label->select_info->selection_end);
+      else
+        gtk_label_select_region_index (label,
+                                       label->select_info->selection_anchor,
+                                       index);
+
+      /* ensure the anchor is opposite index */
+      if (index == label->select_info->selection_anchor)
+        {
+          gint tmp = label->select_info->selection_end;
+          label->select_info->selection_end = label->select_info->selection_anchor;
+          label->select_info->selection_anchor = tmp;
+        }
+    }
+  else
+    {
+      /* start a replacement */
+      gtk_label_select_region_index (label, index, index);
+    }
+  
+  return TRUE;
+}
+
+static gint
+gtk_label_button_release (GtkWidget      *widget,
+                          GdkEventButton *event)
+
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (widget);
+  
+  if (label->select_info == NULL)
+    return FALSE;
+  
+  if (event->button != 1)
+    return FALSE;
+  
+  /* The goal here is to return TRUE iff we ate the
+   * button press to start selecting.
+   */
+  
+  return TRUE;
+}
+
+static gint
+gtk_label_motion (GtkWidget      *widget,
+                  GdkEventMotion *event)
+{
+  GtkLabel *label;
+  gint index;
+  gint x, y;
+  
+  label = GTK_LABEL (widget);
+  
+  if (label->select_info == NULL)
+    return FALSE;  
+
+  if ((event->state & GDK_BUTTON1_MASK) == 0)
+    return FALSE;
+
+  gdk_window_get_pointer (label->select_info->window,
+                          &x, &y, NULL);
+  
+  get_layout_index (label, x, y, &index);
+
+  gtk_label_select_region_index (label,
+                                 label->select_info->selection_anchor,
+                                 index);
+  
+  return TRUE;
+}
+
+static void
+gtk_label_create_window (GtkLabel *label)
+{
+  GtkWidget *widget;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  
+  g_assert (label->select_info);
+  g_assert (GTK_WIDGET_REALIZED (label));
+  
+  if (label->select_info->window)
+    return;
+  
+  widget = GTK_WIDGET (label);
+
+  attributes.x = widget->allocation.x;
+  attributes.y = widget->allocation.y;
+  attributes.width = widget->allocation.width;
+  attributes.height = widget->allocation.height;
+  attributes.window_type = GDK_WINDOW_TEMP;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.override_redirect = TRUE;
+  attributes.event_mask = gtk_widget_get_events (widget) |
+    GDK_BUTTON_PRESS_MASK        |
+    GDK_BUTTON_RELEASE_MASK      |
+    GDK_BUTTON_MOTION_MASK;
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
+
+  label->select_info->window = gdk_window_new (widget->window,
+                                               &attributes, attributes_mask);
+  gdk_window_set_user_data (label->select_info->window, widget);  
+}
+
+static void
+gtk_label_destroy_window (GtkLabel *label)
+{
+  g_assert (label->select_info);
+
+  if (label->select_info->window == NULL)
+    return;
+  
+  gdk_window_set_user_data (label->select_info->window, NULL);
+  gdk_window_destroy (label->select_info->window);
+  label->select_info->window = NULL;
+}
+
+void
+gtk_label_set_selectable (GtkLabel *label,
+                          gboolean  setting)
+{
+  g_return_if_fail (GTK_IS_LABEL (label));
+  
+  setting = setting != FALSE;
+
+  if (setting)
+    {
+      if (label->select_info == NULL)
+        {
+          label->select_info = g_new (GtkLabelSelectionInfo, 1);
+
+          label->select_info->window = NULL;
+          label->select_info->selection_anchor = 0;
+          label->select_info->selection_end = 0;
+
+          if (GTK_WIDGET_REALIZED (label))
+            gtk_label_create_window (label);
+
+          if (GTK_WIDGET_MAPPED (label))
+            gdk_window_show (label->select_info->window);
+        }
+    }
+  else
+    {
+      if (label->select_info)
+        {
+          if (label->select_info->window)
+            gtk_label_destroy_window (label);
+
+          g_free (label->select_info);
+
+          label->select_info = NULL;
+        }
+    }
+}
+
+gboolean
+gtk_label_get_selectable (GtkLabel *label)
+{
+  g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
+
+  return label->select_info != NULL;
+}
+
+static void
+get_text_callback (GtkClipboard     *clipboard,
+                   GtkSelectionData *selection_data,
+                   guint             info,
+                   gpointer          user_data_or_owner)
+{
+  GtkLabel *label;
+  gchar *str;
+  
+  label = GTK_LABEL (user_data_or_owner);
+  
+  if ((label->select_info->selection_anchor !=
+       label->select_info->selection_end) &&
+      label->label)
+    {
+      gint start, end;
+      
+      start = MIN (label->select_info->selection_anchor,
+                   label->select_info->selection_end);
+      end = MAX (label->select_info->selection_anchor,
+                 label->select_info->selection_end);
+      
+      str = g_strndup (label->label + start,
+                       end - start);
+      
+      gtk_selection_data_set_text (selection_data, 
+                                   str);
+
+      g_free (str);
+    }
+}
+
+static void
+clear_text_callback (GtkClipboard     *clipboard,
+                     gpointer          user_data_or_owner)
+{
+  GtkLabel *label;
+
+  label = GTK_LABEL (user_data_or_owner);
+
+  if (label->select_info)
+    {
+      label->select_info->selection_anchor = 0;
+      label->select_info->selection_end = 0;
+      
+      gtk_label_clear_layout (label);
+      gtk_widget_queue_draw (GTK_WIDGET (label));
+    }
+}
+
+static void
+gtk_label_select_region_index (GtkLabel *label,
+                               gint      anchor_index,
+                               gint      end_index)
+{
+  static const GtkTargetEntry targets[] = {
+    { "STRING", 0, 0 },
+    { "TEXT",   0, 0 }, 
+    { "COMPOUND_TEXT", 0, 0 },
+    { "UTF8_STRING", 0, 0 }
+  };
+  
+  g_return_if_fail (GTK_IS_LABEL (label));
+  
+  if (label->select_info)
+    {
+      GtkClipboard *clipboard;
+
+      label->select_info->selection_anchor = anchor_index;
+      label->select_info->selection_end = end_index;
+
+      clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);      
+
+      gtk_clipboard_set_with_owner (clipboard,
+                                    targets,
+                                    G_N_ELEMENTS (targets),
+                                    get_text_callback,
+                                    clear_text_callback,
+                                    G_OBJECT (label));
+
+      gtk_label_clear_layout (label);
+      gtk_widget_queue_draw (GTK_WIDGET (label));
+    }
+}
+
+void
+gtk_label_select_region  (GtkLabel *label,
+                          gint      start_offset,
+                          gint      end_offset)
+{
+  /* FIXME */
+
+
+}
+
Index: gtk/gtklabel.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtklabel.h,v
retrieving revision 1.23
diff -u -u -r1.23 gtklabel.h
--- gtk/gtklabel.h	2001/02/03 01:09:40	1.23
+++ gtk/gtklabel.h	2001/02/13 23:59:24
@@ -47,19 +47,24 @@
 typedef struct _GtkLabel       GtkLabel;
 typedef struct _GtkLabelClass  GtkLabelClass;
 
+typedef struct _GtkLabelSelectionInfo GtkLabelSelectionInfo;
+
 struct _GtkLabel
 {
   GtkMisc misc;
 
+  /*< private >*/
+  
   gchar    *label;
   gchar    *pattern;
 
   guint   jtype : 2;
   guint   wrap : 1;
   
-  /*< private >*/
   PangoLayout *layout;
   PangoAttrList *attrs;
+
+  GtkLabelSelectionInfo *select_info;
 };
 
 struct _GtkLabelClass
@@ -94,6 +99,14 @@
  */
 guint       gtk_label_parse_uline   (GtkLabel         *label,
 				     const gchar      *string);
+
+void     gtk_label_set_selectable (GtkLabel *label,
+                                   gboolean  setting);
+gboolean gtk_label_get_selectable (GtkLabel *label);
+
+void     gtk_label_select_region  (GtkLabel *label,
+                                   gint      start_offset,
+                                   gint      end_offset);
 
 #ifndef	GTK_DISABLE_COMPAT_H
 #  define gtk_label_set				gtk_label_set_text
Index: gtk/testgtk.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/testgtk.c,v
retrieving revision 1.217
diff -u -u -r1.217 testgtk.c
--- gtk/testgtk.c	2001/02/13 05:44:47	1.217
+++ gtk/testgtk.c	2001/02/13 23:59:24
@@ -2004,6 +2004,59 @@
   return button;
 }
 
+static void
+set_selectable_recursive (GtkWidget *widget,
+                          gboolean   setting)
+{
+  if (GTK_IS_CONTAINER (widget))
+    {
+      GList *children;
+      GList *tmp;
+      
+      children = gtk_container_children (GTK_CONTAINER (widget));
+      tmp = children;
+      while (tmp)
+        {
+          set_selectable_recursive (tmp->data, setting);
+
+          tmp = tmp->next;
+        }
+      g_list_free (children);
+    }
+  else if (GTK_IS_LABEL (widget))
+    {
+      gtk_label_set_selectable (GTK_LABEL (widget), setting);
+    }
+}
+
+static void
+selectable_toggled (GtkWidget *toggle,
+                    GtkWidget *widget)
+{
+  set_selectable_recursive (widget,
+                            GTK_TOGGLE_BUTTON (toggle)->active);
+}
+
+static GtkWidget*
+create_selectable_control (GtkWidget *widget)
+{
+  GtkWidget *button;
+
+  button = gtk_toggle_button_new_with_label ("Selectable");  
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                FALSE);
+  
+  gtk_signal_connect (GTK_OBJECT (button),
+                      "toggled",
+                      GTK_SIGNAL_FUNC (selectable_toggled),
+                      widget);
+  
+  gtk_widget_show_all (button);
+
+  return button;
+}
+
 void create_labels (void)
 {
   static GtkWidget *window = NULL;
@@ -2032,6 +2085,10 @@
       gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
 
       button = create_sensitivity_control (hbox);
+
+      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
+
+      button = create_selectable_control (hbox);
 
       gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
       




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]