Re: Freeze break request: RANDR monitor labeling



On Fri, 2008-08-15 at 11:31 +0200, Andre Klapper wrote:

> I like the screenshots, but how could I get a boring old diff of the
> exact changes? (/me used to svn.gnome.org/viewvc where this is possible)

Ah, sorry... I guess Gitorious doesn't let one get diffs between
non-consecutive commits.  The diffs are attached to this mail.

  Federico
 libgnome-desktop/ChangeLog                     |   15 +
 libgnome-desktop/Makefile.am                   |    1 +
 libgnome-desktop/gnome-rr-labeler.c            |  366 ++++++++++++++++++++++++
 libgnome-desktop/libgnomeui/Makefile.am        |    3 +-
 libgnome-desktop/libgnomeui/gnome-rr-labeler.h |   53 ++++
 5 files changed, 437 insertions(+), 1 deletions(-)

diff --git a/libgnome-desktop/ChangeLog b/libgnome-desktop/ChangeLog
index 4394489..254ca8f 100644
--- a/libgnome-desktop/ChangeLog
+++ b/libgnome-desktop/ChangeLog
@@ -1,3 +1,18 @@
+2008-08-14  Federico Mena Quintero  <federico novell com>
+
+	* gnome-rr-labeler.c (create_label_window): Make the label bold,
+	for consistency with the tray icon's menu in gnome-settings-daemon.
+
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* gnome-rr-labeler.[ch]: New files with a GnomeRRLabeler object.
+	When created against a GnomeRRConfig, this lets the user see which
+	physical monitor coresponds to each element in the user
+	interface.  It displays a color-coded label window on
+	the upper-left corner of each monitor.  Also, it lets callers
+	access the color-coding information so that they can color their
+	own GUI to configure monitors.
+
 2008-08-11  Federico Mena Quintero  <federico novell com>
 
 	* gnome-rr.c (gnome_rr_screen_destroy): New public function.  We
diff --git a/libgnome-desktop/Makefile.am b/libgnome-desktop/Makefile.am
index f8516ca..772a918 100644
--- a/libgnome-desktop/Makefile.am
+++ b/libgnome-desktop/Makefile.am
@@ -24,6 +24,7 @@ libgnome_desktop_2_la_SOURCES = \
 	display-name.c		\
 	gnome-rr.c		\
 	gnome-rr-config.c	\
+	gnome-rr-labeler.c	\
 	edid-parse.c		\
 	edid.h
 
diff --git a/libgnome-desktop/gnome-rr-labeler.c b/libgnome-desktop/gnome-rr-labeler.c
new file mode 100644
index 0000000..a12caec
--- /dev/null
+++ b/libgnome-desktop/gnome-rr-labeler.c
@@ -0,0 +1,366 @@
+/* gnome-rr-labeler.c - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome 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.
+ *
+ * The Gnome 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 the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Federico Mena-Quintero <federico novell com>
+ */
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+
+#include <config.h>
+#include "libgnomeui/gnome-rr-labeler.h"
+#include <gtk/gtk.h>
+
+struct _GnomeRRLabeler {
+	GObject parent;
+
+	GnomeRRConfig *config;
+
+	int num_outputs;
+
+	GdkColor *palette;
+	GtkWidget **windows;
+};
+
+struct _GnomeRRLabelerClass {
+	GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (GnomeRRLabeler, gnome_rr_labeler, G_TYPE_OBJECT);
+
+static void gnome_rr_labeler_finalize (GObject *object);
+
+static void
+gnome_rr_labeler_init (GnomeRRLabeler *labeler)
+{
+	/* nothing */
+}
+
+static void
+gnome_rr_labeler_class_init (GnomeRRLabelerClass *class)
+{
+	GObjectClass *object_class;
+
+	object_class = (GObjectClass *) class;
+
+	object_class->finalize = gnome_rr_labeler_finalize;
+}
+
+static void
+gnome_rr_labeler_finalize (GObject *object)
+{
+	GnomeRRLabeler *labeler;
+
+	labeler = GNOME_RR_LABELER (object);
+
+	/* We don't destroy the labeler->config (a GnomeRRConfig*) here; let our
+	 * caller do that instead.
+	 */
+
+	if (labeler->windows != NULL) {
+		gnome_rr_labeler_hide (labeler);
+		g_free (labeler->windows);
+		labeler->windows = NULL;
+	}
+
+	g_free (labeler->palette);
+	labeler->palette = NULL;
+
+	G_OBJECT_CLASS (gnome_rr_labeler_parent_class)->finalize (object);
+}
+
+static int
+count_outputs (GnomeRRConfig *config)
+{
+	int i;
+
+	for (i = 0; config->outputs[i] != NULL; i++)
+		;
+
+	return i;
+}
+
+/* hsv_to_rgb() stolen from gtk+/gtk/gtkhsv.c, sigh. */
+
+#define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
+
+/* Converts from HSV to RGB */
+static void
+hsv_to_rgb (gdouble *h,
+	    gdouble *s,
+	    gdouble *v)
+{
+  gdouble hue, saturation, value;
+  gdouble f, p, q, t;
+
+  if (*s == 0.0)
+    {
+      *h = *v;
+      *s = *v;
+      *v = *v; /* heh */
+    }
+  else
+    {
+      hue = *h * 6.0;
+      saturation = *s;
+      value = *v;
+
+      if (hue == 6.0)
+	hue = 0.0;
+
+      f = hue - (int) hue;
+      p = value * (1.0 - saturation);
+      q = value * (1.0 - saturation * f);
+      t = value * (1.0 - saturation * (1.0 - f));
+
+      switch ((int) hue)
+	{
+	case 0:
+	  *h = value;
+	  *s = t;
+	  *v = p;
+	  break;
+
+	case 1:
+	  *h = q;
+	  *s = value;
+	  *v = p;
+	  break;
+
+	case 2:
+	  *h = p;
+	  *s = value;
+	  *v = t;
+	  break;
+
+	case 3:
+	  *h = p;
+	  *s = q;
+	  *v = value;
+	  break;
+
+	case 4:
+	  *h = t;
+	  *s = p;
+	  *v = value;
+	  break;
+
+	case 5:
+	  *h = value;
+	  *s = p;
+	  *v = q;
+	  break;
+
+	default:
+	  g_assert_not_reached ();
+	}
+    }
+}
+
+static void
+make_palette (GnomeRRLabeler *labeler)
+{
+	/* The idea is that we go around an hue color wheel.  We want to start
+	 * at red, go around to green/etc. and stop at blue --- because magenta
+	 * is evil.  Eeeeek, no magenta, please!
+	 *
+	 * Purple would be nice, though.  Remember that we are watered down
+	 * (i.e. low saturation), so that would be like Like berries with cream.
+	 * Mmmmm, berries.
+	 */
+	double start_hue;
+	double end_hue;
+	int i;
+
+	g_assert (labeler->num_outputs > 0);
+
+	labeler->palette = g_new (GdkColor, labeler->num_outputs);
+
+	start_hue = 0.0; /* red */
+	end_hue   = 2.0/3; /* blue */
+
+	for (i = 0; i < labeler->num_outputs; i++) {
+		double h, s, v;
+
+		h = start_hue + (end_hue - start_hue) / labeler->num_outputs * i;
+		s = 1.0 / 3;
+		v = 1.0;
+
+		hsv_to_rgb (&h, &s, &v);
+
+		labeler->palette[i].red   = (int) (65535 * h + 0.5);
+		labeler->palette[i].green = (int) (65535 * s + 0.5);
+		labeler->palette[i].blue  = (int) (65535 * v + 0.5);
+	}
+}
+
+#define LABEL_WINDOW_EDGE_THICKNESS 2
+#define LABEL_WINDOW_PADDING 12
+
+static gboolean
+label_window_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+	cairo_t *cr;
+	GdkColor *color;
+
+	color = g_object_get_data (G_OBJECT (widget), "color");
+
+	cr = gdk_cairo_create (widget->window);
+
+	/* edge outline */
+
+	cairo_set_source_rgb (cr, 0, 0, 0);
+	cairo_rectangle (cr,
+			 LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+			 LABEL_WINDOW_EDGE_THICKNESS / 2.0,
+			 widget->allocation.width - LABEL_WINDOW_EDGE_THICKNESS,
+			 widget->allocation.height - LABEL_WINDOW_EDGE_THICKNESS);
+	cairo_set_line_width (cr, LABEL_WINDOW_EDGE_THICKNESS);
+	cairo_stroke (cr);
+
+	/* fill */
+
+	gdk_cairo_set_source_color (cr, color);
+	cairo_rectangle (cr,
+			 LABEL_WINDOW_EDGE_THICKNESS,
+			 LABEL_WINDOW_EDGE_THICKNESS,
+			 widget->allocation.width - LABEL_WINDOW_EDGE_THICKNESS * 2,
+			 widget->allocation.height - LABEL_WINDOW_EDGE_THICKNESS * 2);
+	cairo_fill (cr);
+
+	cairo_destroy (cr);
+
+	return FALSE;
+}
+
+static GtkWidget *
+create_label_window (GnomeRRLabeler *labeler, GnomeOutputInfo *output, GdkColor *color)
+{
+	GtkWidget *window;
+	GtkWidget *widget;
+	char *str;
+
+	window = gtk_window_new (GTK_WINDOW_POPUP);
+	GTK_WIDGET_SET_FLAGS (window, GTK_APP_PAINTABLE);
+
+	gtk_container_set_border_width (GTK_CONTAINER (window), LABEL_WINDOW_PADDING + LABEL_WINDOW_EDGE_THICKNESS);
+
+	/* This is semi-dangerous.  The color is part of the labeler->palette
+	 * array.  Note that in gnome_rr_labeler_finalize(), we are careful to
+	 * free the palette only after we free the windows.
+	 */
+	g_object_set_data (G_OBJECT (window), "color", color);
+
+	g_signal_connect (window, "expose-event",
+			  G_CALLBACK (label_window_expose_event_cb), labeler);
+
+	str = g_strdup_printf ("<b>%s</b>", output->display_name);
+	widget = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (widget), str);
+	g_free (str);
+
+	gtk_container_add (GTK_CONTAINER (window), widget);
+
+	/* Should we center this at the top edge of the monitor, instead of using the upper-left corner? */
+	gtk_window_move (GTK_WINDOW (window), output->x, output->y);
+
+	gtk_widget_show_all (window);
+
+	return window;
+}
+
+static void
+create_label_windows (GnomeRRLabeler *labeler)
+{
+	int i;
+
+	/* FIXME: this doesn't handle cloned outputs yet */
+
+	labeler->windows = g_new (GtkWidget *, labeler->num_outputs);
+
+	for (i = 0; i < labeler->num_outputs; i++) {
+		if (labeler->config->outputs[i]->on) {
+			labeler->windows[i] = create_label_window (labeler, labeler->config->outputs[i], labeler->palette + i);
+		} else
+			labeler->windows[i] = NULL;
+	}
+}
+
+static void
+setup_from_config (GnomeRRLabeler *labeler)
+{
+	labeler->num_outputs = count_outputs (labeler->config);
+
+	make_palette (labeler);
+
+	create_label_windows (labeler);
+}
+
+GnomeRRLabeler *
+gnome_rr_labeler_new (GnomeRRConfig *config)
+{
+	GnomeRRLabeler *labeler;
+
+	g_return_val_if_fail (config != NULL, NULL);
+
+	labeler = g_object_new (GNOME_TYPE_RR_LABELER, NULL);
+	labeler->config = config;
+
+	setup_from_config (labeler);
+
+	return labeler;
+}
+
+void
+gnome_rr_labeler_hide (GnomeRRLabeler *labeler)
+{
+	int i;
+
+	g_return_if_fail (GNOME_IS_RR_LABELER (labeler));
+
+	for (i = 0; i < labeler->num_outputs; i++)
+		if (labeler->windows[i] != NULL) {
+			gtk_widget_destroy (labeler->windows[i]);
+			labeler->windows[i] = NULL;
+		}
+}
+
+void
+gnome_rr_labeler_get_color_for_output (GnomeRRLabeler *labeler, GnomeOutputInfo *output, GdkColor *color_out)
+{
+	int i;
+
+	g_return_if_fail (GNOME_IS_RR_LABELER (labeler));
+	g_return_if_fail (output != NULL);
+	g_return_if_fail (color_out != NULL);
+
+	for (i = 0; i < labeler->num_outputs; i++)
+		if (labeler->config->outputs[i] == output) {
+			*color_out = labeler->palette[i];
+			return;
+		}
+
+	g_warning ("trying to get the color for unknown GnomeOutputInfo %p; returning magenta!", output);
+
+	color_out->red   = 0xffff;
+	color_out->green = 0;
+	color_out->blue  = 0xffff;
+}
diff --git a/libgnome-desktop/libgnomeui/Makefile.am b/libgnome-desktop/libgnomeui/Makefile.am
index eb5b510..195bad2 100644
--- a/libgnome-desktop/libgnomeui/Makefile.am
+++ b/libgnome-desktop/libgnomeui/Makefile.am
@@ -4,4 +4,5 @@ libgnomeui_desktop_HEADERS =	\
 	gnome-hint.h		\
 	gnome-bg.h		\
 	gnome-rr.h		\
-	gnome-rr-config.h
+	gnome-rr-config.h	\
+	gnome-rr-labeler.h
diff --git a/libgnome-desktop/libgnomeui/gnome-rr-labeler.h b/libgnome-desktop/libgnomeui/gnome-rr-labeler.h
new file mode 100644
index 0000000..5b751cc
--- /dev/null
+++ b/libgnome-desktop/libgnomeui/gnome-rr-labeler.h
@@ -0,0 +1,53 @@
+/* gnome-rr-labeler.h - Utility to label monitors to identify them
+ * while they are being configured.
+ *
+ * Copyright 2008, Novell, Inc.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome 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.
+ *
+ * The Gnome 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 the Gnome Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Federico Mena-Quintero <federico novell com>
+ */
+
+#ifndef GNOME_RR_LABELER_H
+#define GNOME_RR_LABELER_H
+
+#ifndef GNOME_DESKTOP_USE_UNSTABLE_API
+#error    GnomeRR is unstable API. You must define GNOME_DESKTOP_USE_UNSTABLE_API before including gnomerr.h
+#endif
+
+#include <libgnomeui/gnome-rr-config.h>
+
+#define GNOME_TYPE_RR_LABELER            (gnome_rr_labeler_get_type ())
+#define GNOME_RR_LABELER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_RR_LABELER, GnomeRRLabeler))
+#define GNOME_RR_LABELER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GNOME_TYPE_RR_LABELER, GnomeRRLabelerClass))
+#define GNOME_IS_RR_LABELER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_RR_LABELER))
+#define GNOME_IS_RR_LABELER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GNOME_TYPE_RR_LABELER))
+#define GNOME_RR_LABELER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GNOME_TYPE_RR_LABELER, GnomeRRLabelerClass))
+
+typedef struct _GnomeRRLabeler GnomeRRLabeler;
+typedef struct _GnomeRRLabelerClass GnomeRRLabelerClass;
+
+GType gnome_rr_labeler_get_type (void);
+
+GnomeRRLabeler *gnome_rr_labeler_new (GnomeRRConfig *config);
+
+void gnome_rr_labeler_hide (GnomeRRLabeler *labeler);
+
+void gnome_rr_labeler_get_color_for_output (GnomeRRLabeler *labeler, GnomeOutputInfo *output, GdkColor *color_out);
+
+#endif
 ChangeLog                           |   21 ++
 plugins/xrandr/gsd-xrandr-manager.c |  417 ++++++++++++++++++++++++++++++++++-
 2 files changed, 428 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index e23f4e3..7b2217a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* plugins/xrandr/gsd-xrandr-manager.c (status_icon_popup_menu):
+	Create the RANDR configuration and labeler before the menu items.
+
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* plugins/xrandr/gsd-xrandr-manager.c (status_icon_popup_menu):
+	Add a separator to the menu before "Configure display settings".
+
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* plugins/xrandr/gsd-xrandr-manager.c (status_icon_popup_menu):
+	When the menu comes up, create a GnomeRRLabeler so that the user
+	can identify which physical monitors we are talking about.  This
+	will actually be visible in the popup menu once we implement the
+	rotation commands.
+	(status_icon_popup_menu_selection_done_cb): Hide and destroy the
+	GnomeRRLabeler.
+	(struct GsdXrandrManagerPrivate): New field "labeler".
+
 2008-08-12  Federico Mena Quintero  <federico novell com>
 
 	* configure.ac: For LIBSOUNDS, check for libgnomeui, not just libgnome.
diff --git a/plugins/xrandr/gsd-xrandr-manager.c b/plugins/xrandr/gsd-xrandr-manager.c
index 62010fe..4d8ce2f 100644
--- a/plugins/xrandr/gsd-xrandr-manager.c
+++ b/plugins/xrandr/gsd-xrandr-manager.c
@@ -42,6 +42,7 @@
 
 #include <libgnomeui/gnome-rr-config.h>
 #include <libgnomeui/gnome-rr.h>
+#include <libgnomeui/gnome-rr-labeler.h>
 
 #ifdef HAVE_RANDR
 #include <X11/extensions/Xrandr.h>
@@ -76,6 +77,9 @@ struct GsdXrandrManagerPrivate
         gboolean client_filter_set;
 
         GtkStatusIcon *status_icon;
+        GtkWidget *popup_menu;
+        GnomeRRConfig *configuration;
+        GnomeRRLabeler *labeler;
         GConfClient *client;
         int notify_id;
 };
@@ -186,30 +190,423 @@ popup_menu_configure_display_cb (GtkMenuItem *item, gpointer data)
 }
 
 static void
+status_icon_popup_menu_selection_done_cb (GtkMenuShell *menu_shell, gpointer data)
+{
+        GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+
+        gtk_widget_destroy (priv->popup_menu);
+        priv->popup_menu = NULL;
+
+        gnome_rr_labeler_hide (priv->labeler);
+        g_object_unref (priv->labeler);
+        priv->labeler = NULL;
+
+        gnome_rr_config_free (priv->configuration);
+        priv->configuration = NULL;
+}
+
+#define OUTPUT_TITLE_ITEM_BORDER 2
+#define OUTPUT_TITLE_ITEM_PADDING 4
+
+/* This is an expose-event hander for the title label for each GnomeRROutput.
+ * We want each title to have a colored background, so we paint that background, then
+ * return FALSE to let GtkLabel expose itself (i.e. paint the label's text), and then
+ * we have a signal_connect_after handler as well.  See the comments below
+ * to see why that "after" handler is needed.
+ */
+static gboolean
+output_title_label_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+        GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        GnomeOutputInfo *output;
+        GdkColor color;
+        cairo_t *cr;
+
+        g_assert (GTK_IS_LABEL (widget));
+
+        output = g_object_get_data (G_OBJECT (widget), "output");
+        g_assert (output != NULL);
+
+        g_assert (priv->labeler != NULL);
+
+        /* Draw a black rectangular border, filled with the color that corresponds to this output */
+
+        gnome_rr_labeler_get_color_for_output (priv->labeler, output, &color);
+
+        cr = gdk_cairo_create (widget->window);
+
+        cairo_set_source_rgb (cr, 0, 0, 0);
+        cairo_set_line_width (cr, OUTPUT_TITLE_ITEM_BORDER);
+        cairo_rectangle (cr,
+                         widget->allocation.x + OUTPUT_TITLE_ITEM_BORDER / 2.0,
+                         widget->allocation.y + OUTPUT_TITLE_ITEM_BORDER / 2.0,
+                         widget->allocation.width - OUTPUT_TITLE_ITEM_BORDER,
+                         widget->allocation.height - OUTPUT_TITLE_ITEM_BORDER);
+        cairo_stroke (cr);
+
+        gdk_cairo_set_source_color (cr, &color);
+        cairo_rectangle (cr,
+                         widget->allocation.x + OUTPUT_TITLE_ITEM_BORDER,
+                         widget->allocation.y + OUTPUT_TITLE_ITEM_BORDER,
+                         widget->allocation.width - 2 * OUTPUT_TITLE_ITEM_BORDER,
+                         widget->allocation.height - 2 * OUTPUT_TITLE_ITEM_BORDER);
+
+        cairo_fill (cr);
+
+        /* We want the label to always show up as if it were sensitive
+         * ("style->fg[GTK_STATE_NORMAL]"), even though the label is insensitive
+         * due to being inside an insensitive menu item.  So, here we have a
+         * HACK in which we frob the label's state directly.  GtkLabel's expose
+         * handler will be run after this function, so it will think that the
+         * label is in GTK_STATE_NORMAL.  We reset the label's state back to
+         * insensitive in output_title_label_after_expose_event_cb().
+         *
+         * Yay for fucking with GTK+'s internals.
+         */
+
+        widget->state = GTK_STATE_NORMAL;
+
+        return FALSE;        
+}
+
+/* See the comment in output_title_event_box_expose_event_cb() about this funny label widget */
+static gboolean
+output_title_label_after_expose_event_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+        g_assert (GTK_IS_LABEL (widget));
+        widget->state = GTK_STATE_INSENSITIVE;
+
+        return FALSE;
+}
+
+static void
+title_item_size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer data)
+{
+        /* When GtkMenu does size_request on its items, it asks them for their "toggle size",
+         * which will be non-zero when there are check/radio items.  GtkMenu remembers
+         * the largest of those sizes.  During the size_allocate pass, GtkMenu calls
+         * gtk_menu_item_toggle_size_allocate() with that value, to tell the menu item
+         * that it should later paint its child a bit to the right of its edge.
+         *
+         * However, we want the "title" menu items for each RANDR output to span the *whole*
+         * allocation of the menu item, not just the "allocation minus toggle" area.
+         *
+         * So, we let the menu item size_allocate itself as usual, but this
+         * callback gets run afterward.  Here we hack a toggle size of 0 into
+         * the menu item, and size_allocate it by hand *again*.  We also need to
+         * avoid recursing into this function.
+         */
+
+        g_assert (GTK_IS_MENU_ITEM (widget));
+
+        gtk_menu_item_toggle_size_allocate (GTK_MENU_ITEM (widget), 0);
+
+        g_signal_handlers_block_by_func (widget, title_item_size_allocate_cb, NULL);
+
+        /* Sigh. There is no way to turn on GTK_ALLOC_NEEDED outside of GTK+
+         * itself; also, since calling size_allocate on a widget with the same
+         * allcation is a no-op, we need to allocate with a "different" size
+         * first.
+         */
+
+        allocation->width++;
+        gtk_widget_size_allocate (widget, allocation);
+
+        allocation->width--;
+        gtk_widget_size_allocate (widget, allocation);
+
+        g_signal_handlers_unblock_by_func (widget, title_item_size_allocate_cb, NULL);
+}
+
+static GtkWidget *
+make_menu_item_for_output_title (GsdXrandrManager *manager, GnomeOutputInfo *output)
+{
+        GtkWidget *item;
+        GtkWidget *label;
+        char *str;
+
+        item = gtk_menu_item_new ();
+
+        g_signal_connect (item, "size-allocate",
+                          G_CALLBACK (title_item_size_allocate_cb), NULL);
+
+        str = g_markup_printf_escaped ("<b>%s</b>", output->display_name);
+        label = gtk_label_new (NULL);
+        gtk_label_set_markup (GTK_LABEL (label), str);
+        g_free (str);
+
+        /* Add padding around the label to fit the box that we'll draw for color-coding */
+        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+        gtk_misc_set_padding (GTK_MISC (label),
+                              OUTPUT_TITLE_ITEM_BORDER + OUTPUT_TITLE_ITEM_PADDING,
+                              OUTPUT_TITLE_ITEM_BORDER + OUTPUT_TITLE_ITEM_PADDING);
+
+        gtk_container_add (GTK_CONTAINER (item), label);
+
+        /* We want to paint a colored box as the background of the label, so we connect
+         * to its expose-event signal.  See the comment in *** to see why need to connect
+         * to the label both 'before' and 'after'.
+         */
+        g_signal_connect (label, "expose-event",
+                          G_CALLBACK (output_title_label_expose_event_cb), manager);
+        g_signal_connect_after (label, "expose-event",
+                                G_CALLBACK (output_title_label_after_expose_event_cb), manager);
+
+        g_object_set_data (G_OBJECT (label), "output", output);
+
+        gtk_widget_set_sensitive (item, FALSE); /* the title is not selectable */
+        gtk_widget_show_all (item);
+
+        return item;
+}
+
+static void
+get_allowed_rotations_for_output (GsdXrandrManager *manager, GnomeOutputInfo *output, int *out_num_rotations, GnomeRRRotation *out_rotations)
+{
+        static const GnomeRRRotation possible_rotations[] = {
+                GNOME_RR_ROTATION_0,
+                GNOME_RR_ROTATION_90,
+                GNOME_RR_ROTATION_180,
+                GNOME_RR_ROTATION_270
+                /* We don't allow REFLECT_X or REFLECT_Y for now, as gnome-display-properties doesn't allow them, either */
+        };
+                
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        GnomeRRRotation current_rotation;
+        int i;
+
+        *out_num_rotations = 0;
+        *out_rotations = 0;
+
+        current_rotation = output->rotation;
+
+        /* Yay for brute force */
+
+        for (i = 0; i < G_N_ELEMENTS (possible_rotations); i++) {
+                GnomeRRRotation rotation_to_test;
+
+                rotation_to_test = possible_rotations[i];
+
+                output->rotation = rotation_to_test;
+
+                if (gnome_rr_config_applicable (priv->configuration, priv->rw_screen)) {
+                        (*out_num_rotations)++;
+                        (*out_rotations) |= rotation_to_test;
+                }
+        }
+
+        output->rotation = current_rotation;
+
+        if (*out_num_rotations == 0 || *out_rotations == 0) {
+                g_warning ("Huh, output %p says it doesn't support any rotations, and yet it has a current rotation?", output);
+                *out_num_rotations = 1;
+                *out_rotations = output->rotation;
+        }
+}
+
+static void
+add_unsupported_rotation_item (GsdXrandrManager *manager)
+{
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        GtkWidget *item;
+        GtkWidget *label;
+
+        item = gtk_menu_item_new ();
+
+        label = gtk_label_new (NULL);
+        gtk_label_set_markup (GTK_LABEL (label), _("<i>Rotation not supported</i>"));
+        gtk_container_add (GTK_CONTAINER (item), label);
+
+        gtk_widget_show_all (item);
+        gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+}
+
+static void
+error_dialog (const char *title, const char *msg)
+{
+        GtkWidget *dialog;
+
+        dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+                                         "%s", title);
+        gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", msg);
+
+        gtk_dialog_run (GTK_DIALOG (dialog));
+        gtk_widget_destroy (dialog);
+}
+
+static void
+output_rotation_item_activate_cb (GtkMenuItem *item, gpointer data)
+{
+        GsdXrandrManager *manager = GSD_XRANDR_MANAGER (data);
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        GnomeOutputInfo *output;
+        GnomeRRRotation rotation;
+        GError *error;
+
+        output = g_object_get_data (G_OBJECT (item), "output");
+        rotation = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "rotation"));
+
+        output->rotation = rotation;
+
+        error = NULL;
+        if (gnome_rr_config_save (priv->configuration, &error)) {
+                if (!gnome_rr_config_apply_stored (priv->rw_screen)) {
+                        error_dialog (_("The selected rotation could not be applied"),
+                                      _("An error occurred while configuring the screen"));
+                        /* FIXME: that message is really useless.  Make
+                         * gnome_rr_config_apply_stored() give us a meaningful
+                         * error message!
+                         */
+                }
+        } else {
+                error_dialog (_("The selected rotation could not be applied"),
+                              error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+add_items_for_rotations (GsdXrandrManager *manager, GnomeOutputInfo *output, GnomeRRRotation allowed_rotations)
+{
+        typedef struct {
+                GnomeRRRotation	rotation;
+                const char *	name;
+        } RotationInfo;
+        static const RotationInfo rotations[] = {
+                { GNOME_RR_ROTATION_0, N_("Normal") },
+                { GNOME_RR_ROTATION_90, N_("Left") },
+                { GNOME_RR_ROTATION_270, N_("Right") },
+                { GNOME_RR_ROTATION_180, N_("Upside Down") },
+                /* We don't allow REFLECT_X or REFLECT_Y for now, as gnome-display-properties doesn't allow them, either */
+        };
+
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        int i;
+        GSList *group;
+        GtkWidget *active_item;
+        gulong active_item_activate_id;
+
+        group = NULL;
+        active_item = NULL;
+        active_item_activate_id = 0;
+
+        for (i = 0; i < G_N_ELEMENTS (rotations); i++) {
+                GnomeRRRotation rot;
+                GtkWidget *item;
+                gulong activate_id;
+
+                rot = rotations[i].rotation;
+
+                if ((allowed_rotations & rot) == 0) {
+                        /* don't display items for rotations which are
+                         * unavailable.  Their availability is not under the
+                         * user's control, anyway.
+                         */
+                        continue;
+                }
+
+                item = gtk_radio_menu_item_new_with_label (group, _(rotations[i].name));
+                gtk_widget_show_all (item);
+                gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+                g_object_set_data (G_OBJECT (item), "output", output);
+                g_object_set_data (G_OBJECT (item), "rotation", GINT_TO_POINTER (rot));
+
+                activate_id = g_signal_connect (item, "activate",
+                                                G_CALLBACK (output_rotation_item_activate_cb), manager);
+
+                group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+
+                if (rot == output->rotation) {
+                        active_item = item;
+                        active_item_activate_id = activate_id;
+                }
+        }
+
+        if (active_item) {
+                /* Block the signal temporarily so our callback won't be called;
+                 * we are just setting up the UI.
+                 */
+                g_signal_handler_block (active_item, active_item_activate_id);
+
+                gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (active_item), TRUE);
+
+                g_signal_handler_unblock (active_item, active_item_activate_id);
+        }
+
+}
+
+static void
+add_rotation_items_for_output (GsdXrandrManager *manager, GnomeOutputInfo *output)
+{
+        int num_rotations;
+        GnomeRRRotation rotations;
+
+        get_allowed_rotations_for_output (manager, output, &num_rotations, &rotations);
+
+        if (num_rotations == 1)
+                add_unsupported_rotation_item (manager);
+        else
+                add_items_for_rotations (manager, output, rotations);
+}
+
+static void
+add_menu_items_for_output (GsdXrandrManager *manager, GnomeOutputInfo *output)
+{
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        GtkWidget *item;
+
+        item = make_menu_item_for_output_title (manager, output);
+        gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+        add_rotation_items_for_output (manager, output);
+}
+
+static void
+add_menu_items_for_outputs (GsdXrandrManager *manager)
+{
+        struct GsdXrandrManagerPrivate *priv = manager->priv;
+        int i;
+
+        for (i = 0; priv->configuration->outputs[i] != NULL; i++) {
+                if (priv->configuration->outputs[i]->connected)
+                        add_menu_items_for_output (manager, priv->configuration->outputs[i]);
+        }
+}
+
+static void
 status_icon_popup_menu (GsdXrandrManager *manager, guint button, guint32 timestamp)
 {
         struct GsdXrandrManagerPrivate *priv = manager->priv;
-        GtkWidget *menu;
         GtkWidget *item;
 
-        menu = gtk_menu_new ();
+        g_assert (priv->configuration == NULL);
+        priv->configuration = gnome_rr_config_new_current (priv->rw_screen);
+
+        g_assert (priv->labeler == NULL);
+        priv->labeler = gnome_rr_labeler_new (priv->configuration);
+
+        g_assert (priv->popup_menu == NULL);
+        priv->popup_menu = gtk_menu_new ();
+
+        add_menu_items_for_outputs (manager);
 
-        item = gtk_menu_item_new_with_label (_("Screen Rotation"));
-        gtk_widget_set_sensitive (item, FALSE);
+        item = gtk_separator_menu_item_new ();
         gtk_widget_show (item);
-        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+        gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
 
         item = gtk_menu_item_new_with_mnemonic (_("_Configure Display Settings ..."));
         g_signal_connect (item, "activate",
                           G_CALLBACK (popup_menu_configure_display_cb), manager);
         gtk_widget_show (item);
-        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
-        /* FIXME */
+        gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
 
-        g_signal_connect (menu, "selection-done",
-                          G_CALLBACK (gtk_widget_destroy), NULL);
+        g_signal_connect (priv->popup_menu, "selection-done",
+                          G_CALLBACK (status_icon_popup_menu_selection_done_cb), manager);
 
-        gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+        gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
                         gtk_status_icon_position_menu,
                         priv->status_icon, button, timestamp);
 }
 capplets/display/ChangeLog        |   13 +++++++++++++
 capplets/display/xrandr-capplet.c |   29 +++++++++++++++++++++++++----
 2 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/capplets/display/ChangeLog b/capplets/display/ChangeLog
index df3f4ec..bb21b22 100644
--- a/capplets/display/ChangeLog
+++ b/capplets/display/ChangeLog
@@ -1,3 +1,16 @@
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* xrandr-capplet.c (paint_output): Instead of using hard-coded
+	colors for the rectangles that represent the monitors, get the
+	colors from the GnomeRRLabeler.
+
+2008-08-13  Federico Mena Quintero  <federico novell com>
+
+	* xrandr-capplet.c (on_screen_changed): Create a GnomeRRLabeler
+	based on the current screen configuration, so that the physical
+	monitors will be labeled while the capplet is up.
+	(struct App): New field "labeler".
+
 2008-08-11  Jens Granseuer  <jensgr gmx net>
 
 	* xrandr-capplet.c: (run_application): fail gracefully if we cannot
diff --git a/capplets/display/xrandr-capplet.c b/capplets/display/xrandr-capplet.c
index 23d3c5d..faa75a7 100644
--- a/capplets/display/xrandr-capplet.c
+++ b/capplets/display/xrandr-capplet.c
@@ -28,6 +28,7 @@
 #define GNOME_DESKTOP_USE_UNSTABLE_API
 #include <libgnomeui/gnome-rr.h>
 #include <libgnomeui/gnome-rr-config.h>
+#include <libgnomeui/gnome-rr-labeler.h>
 #include <gdk/gdkx.h>
 #include <X11/Xlib.h>
 #include <glib/gi18n.h>
@@ -40,6 +41,7 @@ struct App
 {
     GnomeRRScreen       *screen;
     GnomeRRConfig  *current_configuration;
+    GnomeRRLabeler *labeler;
     GnomeOutputInfo         *current_output;
 
     GtkWidget	   *dialog;
@@ -139,6 +141,13 @@ on_screen_changed (GnomeRRScreen *scr,
     qsort (app->current_configuration->outputs, i, sizeof (GnomeOutputInfo *),
 	   compare_outputs);
 
+    if (app->labeler) {
+	gnome_rr_labeler_hide (app->labeler);
+	g_object_unref (app->labeler);
+    }
+
+    app->labeler = gnome_rr_labeler_new (app->current_configuration);
+
 #if 0
     for (i = 0; app->current_configuration->outputs[i] != NULL; ++i)
     {
@@ -1351,6 +1360,8 @@ paint_output (App *app, cairo_t *cr, int i)
     PangoRectangle extent;
     GdkRectangle viewport;
     double angle;
+    GdkColor output_color;
+    double r, g, b;
 
     cairo_save (cr);
 
@@ -1420,10 +1431,20 @@ paint_output (App *app, cairo_t *cr, int i)
     cairo_rectangle (cr, x, y, w * scale + 0.5, h * scale + 0.5);
     cairo_clip_preserve (cr);
 
-    if (output->on)
-	cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
-    else
-	cairo_set_source_rgba (cr, 0.2, 0.2, 0.2, 1.0);
+    gnome_rr_labeler_get_color_for_output (app->labeler, output, &output_color);
+    r = output_color.red / 65535.0;
+    g = output_color.green / 65535.0;
+    b = output_color.blue / 65535.0;
+
+    if (!output->on)
+    {
+	/* If the output is turned off, just darken the selected color */
+	r *= 0.2;
+	g *= 0.2;
+	b *= 0.2;
+    }
+
+    cairo_set_source_rgba (cr, r, g, b, 1.0);
 
     foo_scroll_area_add_input_from_fill (FOO_SCROLL_AREA (app->area),
 					 cr, on_output_event, output);


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