[rhythmbox] header: fix display of mixed-direction text (bug #610753)



commit 05050be6f0af3fc2a79c8e4abef49128655c23d3
Author: Uri Sivan <tartif gmail com>
Date:   Sun Mar 7 13:11:47 2010 +1000

    header: fix display of mixed-direction text (bug #610753)
    
    When the track details displayed in the header widget contain
    mixed-direction text, use Unicode text direction marks to get the text
    to display correctly, and use a direction-neutral separator rather than
    verbal ones.

 doc/reference/rhythmbox-docs.sgml    |    1 +
 doc/reference/rhythmbox-sections.txt |    7 +
 lib/Makefile.am                      |    4 +-
 lib/rb-text-helpers.c                |  224 ++++++++++++++++++++++++++++++++++
 lib/rb-text-helpers.h                |   44 +++++++
 widgets/rb-header.c                  |  103 ++++++++++------
 6 files changed, 344 insertions(+), 39 deletions(-)
---
diff --git a/doc/reference/rhythmbox-docs.sgml b/doc/reference/rhythmbox-docs.sgml
index 58077c5..5160c66 100644
--- a/doc/reference/rhythmbox-docs.sgml
+++ b/doc/reference/rhythmbox-docs.sgml
@@ -25,6 +25,7 @@
 		<xi:include href="xml/rb-string-value-map.xml"/>
 		<xi:include href="xml/rb-tree-dnd.xml"/>
 		<xi:include href="xml/rb-util.xml"/>
+		<xi:include href="xml/rb-text-helpers.xml"/>
 	</chapter>
 
 	<chapter>  
diff --git a/doc/reference/rhythmbox-sections.txt b/doc/reference/rhythmbox-sections.txt
index 4419be2..3f205fc 100644
--- a/doc/reference/rhythmbox-sections.txt
+++ b/doc/reference/rhythmbox-sections.txt
@@ -1562,3 +1562,10 @@ RB_BROWSER_SOURCE_GET_CLASS
 RBAsyncQueueWatchFunc
 rb_async_queue_watch_new
 </SECTION>
+
+<SECTION>
+<FILE>rb-text-helpers</FILE>
+rb_text_direction_conflict
+rb_text_common_direction
+rb_text_cat
+</SECTION>
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 3c75f72..9d48295 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -38,7 +38,9 @@ librb_la_SOURCES =					\
 	rb-string-value-map.c				\
 	rb-string-value-map.h				\
 	rb-async-queue-watch.c				\
-	rb-async-queue-watch.h
+	rb-async-queue-watch.h				\
+	rb-text-helpers.c				\
+	rb-text-helpers.h
 
 INCLUDES =						\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
diff --git a/lib/rb-text-helpers.c b/lib/rb-text-helpers.c
new file mode 100644
index 0000000..b397bda
--- /dev/null
+++ b/lib/rb-text-helpers.c
@@ -0,0 +1,224 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010  Uri Sivan
+ *
+ *  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, or (at your option)
+ *  any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include <rb-text-helpers.h>
+
+/**
+ * SECTION:rb-text-helpers
+ * @short_description: text direction (LTR/RTL) functions
+ */
+
+/* unicode direction markup characters
+ * see http://unicode.org/reports/tr9/, in particular sections 2.1-2.4
+ *
+ * LRM = Left-to-Right Mark = invisible character with LTR direction
+ * RLM = Right-to-Left Mark = invisible character with RTL direction
+ * LRE = Left-to-Right Embedding = start of LTR "island" in RTL text
+ * RLE = Right-to-Left Embedding = start of RTL "island" in LTR text
+ * PDF = Pop Directional Format = close last LRE or RLE section
+ *
+ * the following constants are in UTF-8 encoding
+ */
+static const char const *UNICODE_LRM = "\xE2\x80\x8E";
+static const char const *UNICODE_RLM = "\xE2\x80\x8F";
+static const char const *UNICODE_LRE = "\xE2\x80\xAA";
+static const char const *UNICODE_RLE = "\xE2\x80\xAB";
+static const char const *UNICODE_PDF = "\xE2\x80\xAC";
+
+static void
+append_and_free (GString *str, char *text)
+{
+	g_string_append (str, text);
+	g_free (text);
+}
+
+/**
+ * rb_text_direction_conflict:
+ * @dir1: direction A
+ * @dir2: direction B
+ *
+ * Direction conflict here means the two directions are defined (non-neutral)
+ * and they are different.
+ *
+ * Return value: %TRUE if the two directions conflict.
+ */
+gboolean
+rb_text_direction_conflict (PangoDirection dir1, PangoDirection dir2)
+{
+	return (dir1 != dir2) &&
+	       (dir1 != PANGO_DIRECTION_NEUTRAL) &&
+	       (dir2 != PANGO_DIRECTION_NEUTRAL);
+}
+
+/**
+ * rb_text_common_direction:
+ * @first: first string
+ * @...: rest of strings, terminated with %NULL
+ *
+ * This functions checks the direction of all given strings and:
+ *
+ * 1. If all strings are direction neutral, returns %PANGO_DIRECTION_NEUTRAL;
+ *
+ * 2. If all strings are either LTR or neutral, returns %PANGO_DIRECTION_LTR;
+ *
+ * 3. If all strings are either RTL or neutral, returns %PANGO_DIRECTION_RTL;
+ *
+ * 4. If at least one is RTL and one LTR, returns %PANGO_DIRECTION_NEUTRAL.
+ *
+ * Note: neutral (1) and mixed (4) are two very different situations,
+ * they share a return code here only because they're the same for our
+ * specific use.
+ *
+ * Return value: common direction of all strings, as defined above.
+ */
+PangoDirection
+rb_text_common_direction (const char *first, ...)
+{
+	PangoDirection common_dir = PANGO_DIRECTION_NEUTRAL;
+	PangoDirection text_dir;
+	const char *text;
+	va_list args;
+
+	va_start (args, first);
+
+	for (text = first; text; text = va_arg(args, const char *)) {
+		if (!text[0])
+			continue;
+
+		text_dir = pango_find_base_dir (text, -1);
+
+		if (rb_text_direction_conflict (text_dir, common_dir)) {
+			/* mixed direction */
+			common_dir = PANGO_DIRECTION_NEUTRAL;
+			break;
+		}
+
+		common_dir = text_dir;
+	}
+
+	va_end (args);
+
+	return common_dir;
+}
+
+/**
+ * rb_text_cat:
+ * @base_dir: direction of the result string.
+ * @...: pairs of strings (content, format) terminated with %NULL.
+ *
+ * This function concatenates strings to a single string, preserving
+ * each part's original direction (LTR or RTL) using unicode markup,
+ * as detailed here: http://unicode.org/reports/tr9/.
+ *
+ * It is called like this:
+ *
+ * s = rb_text_cat(base_dir, str1, format1, ..., strN, formatN, %NULL)
+ *
+ * Format is a printf format with exactly one \%s. "\%s" or "" will
+ * insert the string as is.
+ *
+ * Any string that is empty ("") will be skipped, its format must still be
+ * passed.
+ *
+ * A space is inserted between strings.
+ *
+ * The algorithm:
+ *
+ * 1. Caller supplies the base direction of the result in base_dir.
+ *
+ * 2. Insert either %LRM or %RLM at the beginning of the string to set
+ *    its base direction, according to base_dir.
+ *
+ * 3. Find the direction of each string using pango.
+ *
+ * 4. For strings that have the same direction as the base direction,
+ *    just insert them in.
+ *
+ * 5. For strings that have the opposite direction than the base one,
+ *    insert them surrounded with embedding codes %RLE/%LRE .. %PDF.
+ *
+ * Return value: a new string containing the result.
+ */
+char *
+rb_text_cat (PangoDirection base_dir, ...)
+{
+	PangoDirection text_dir;
+	va_list args;
+	const char *embed_start;
+	const char *embed_stop = UNICODE_PDF;
+	GString *result;
+
+	va_start (args, base_dir);
+
+	result = g_string_sized_new (100);
+
+	if (base_dir == PANGO_DIRECTION_LTR) {
+		/* base direction LTR, embedded parts are RTL */
+		g_string_append (result, UNICODE_LRM);
+		embed_start = UNICODE_RLE;
+	} else {
+		/* base direction RTL, embedded parts are LTR */
+		g_string_append (result, UNICODE_RLM);
+		embed_start = UNICODE_LRE;
+	}
+
+	while (1) {
+		const char *text = va_arg (args, const char *);
+		const char *format;
+
+		if (!text)
+			break;
+
+		format = va_arg (args, const char *);
+		if (!text[0])
+			continue;
+		if (!format[0])
+			format = "%s";
+
+		if (result->len > 0) {
+			g_string_append (result, " ");
+		}
+
+		text_dir = pango_find_base_dir (text, -1);
+
+		if (rb_text_direction_conflict (text_dir, base_dir)) {
+			/* surround text with embed codes */
+			g_string_append (result, embed_start);
+			append_and_free (result, g_markup_printf_escaped (format, text));
+			g_string_append (result, embed_stop);
+		} else {
+			append_and_free (result, g_markup_printf_escaped (format, text));
+		}
+	}
+
+	va_end (args);
+
+	return g_string_free (result, FALSE);
+}
diff --git a/lib/rb-text-helpers.h b/lib/rb-text-helpers.h
new file mode 100644
index 0000000..90e9b1f
--- /dev/null
+++ b/lib/rb-text-helpers.h
@@ -0,0 +1,44 @@
+/*
+ *  Copyright (C) 2010  Uri Sivan
+ *
+ *  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, or (at your option)
+ *  any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_TEXT_HELPERS_H
+#define __RB_TEXT_HELPERS_H
+
+#include <glib.h>
+#include <pango/pango-bidi-type.h>
+
+G_BEGIN_DECLS
+
+gboolean rb_text_direction_conflict (PangoDirection dir1, PangoDirection dir2);
+
+PangoDirection rb_text_common_direction (const char *first, ...);
+
+char *rb_text_cat (PangoDirection base_dir, ...);
+
+G_END_DECLS
+
+#endif /* __RB_TEXT_HELPERS_H */
diff --git a/widgets/rb-header.c b/widgets/rb-header.c
index 9525169..6a252f9 100644
--- a/widgets/rb-header.c
+++ b/widgets/rb-header.c
@@ -44,6 +44,7 @@
 #include "rb-util.h"
 #include "rhythmdb.h"
 #include "rb-player.h"
+#include "rb-text-helpers.h"
 
 /**
  * SECTION:rb-header
@@ -119,10 +120,10 @@ enum
 	PROP_SLIDER_DRAGGING
 };
 
-#define TITLE_MARKUP(xTITLE) g_markup_printf_escaped ("<big><b>%s</b></big>", xTITLE)
-#define ALBUM_MARKUP(xALBUM) g_markup_printf_escaped (" %s <i>%s</i>", _("from"), xALBUM)
-#define ARTIST_MARKUP(xARTIST) g_markup_printf_escaped (" %s <i>%s</i>", _("by"), xARTIST)
-#define STREAM_MARKUP(xSTREAM) g_markup_printf_escaped (" (%s)", xSTREAM)
+#define TITLE_FORMAT  "<big><b>%s</b></big>"
+#define ALBUM_FORMAT  "<i>%s</i>"
+#define ARTIST_FORMAT "<i>%s</i>"
+#define STREAM_FORMAT "(%s)"
 
 #define SCROLL_UP_SEEK_OFFSET	5
 #define SCROLL_DOWN_SEEK_OFFSET -5
@@ -396,14 +397,6 @@ rb_header_new (RBShellPlayer *shell_player, RhythmDB *db)
 }
 
 static void
-append_and_free (GString *str,
-		 char *text)
-{
-	g_string_append (str, text);
-	g_free (text);
-}
-
-static void
 get_extra_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *field, char **value)
 {
 	GValue *v;
@@ -418,6 +411,61 @@ get_extra_metadata (RhythmDB *db, RhythmDBEntry *entry, const char *field, char
 	}
 }
 
+/* unicode graphic characters, encoded in UTF-8 */
+static const char const *UNICODE_MIDDLE_DOT = "\xC2\xB7";
+
+static char *
+write_header (PangoDirection native_dir,
+	      const char *title,
+	      const char *artist,
+	      const char *album,
+	      const char *stream)
+{
+	const char *by;
+	const char *from;
+	PangoDirection tags_dir;
+	PangoDirection header_dir;
+
+	if (!title)
+		title  = "";
+	if (!artist)
+		artist = "";
+	if (!album )
+		album  = "";
+	if (!stream)
+		stream = "";
+
+	tags_dir = rb_text_common_direction (title, artist, album, stream, NULL);
+
+	/* if the tags have a defined direction that conflicts with the native
+	 * direction, show them in their natural direction with a neutral
+	 * separator
+	 */
+	if (!rb_text_direction_conflict (tags_dir, native_dir)) {
+		header_dir = native_dir;
+		by = _("by");
+		from = _("from");
+	} else {
+		header_dir = tags_dir;
+		by = UNICODE_MIDDLE_DOT;
+		from = UNICODE_MIDDLE_DOT;
+	}
+
+	if (!artist[0])
+		by = "";
+	if (!album[0])
+		from = "";
+
+	return rb_text_cat (header_dir,
+			 title,  TITLE_FORMAT,
+			 by,     "%s",
+			 artist, ARTIST_FORMAT,
+			 from,   "%s",
+			 album,  ALBUM_FORMAT,
+			 stream, STREAM_FORMAT,
+			 NULL);
+}
+
 /**
  * rb_header_sync:
  * @header: the #RBHeader
@@ -444,7 +492,7 @@ rb_header_sync (RBHeader *header)
 		char *streaming_title;
 		char *streaming_artist;
 		char *streaming_album;
-		GString *label_str;
+		PangoDirection widget_dir;
 
 		gboolean have_duration = (header->priv->duration > 0);
 
@@ -480,32 +528,11 @@ rb_header_sync (RBHeader *header)
 			album = streaming_album;
 		}
 
-		label_str = g_string_sized_new (100);
-
-		/* force the string to have the same direction as the widget.
-		 * this fixes the display when the head of the text (usually
-		 * the track name) has a different direction than the widget.
-		 */
-		if (gtk_widget_get_direction (GTK_WIDGET (header->priv->song)) == GTK_TEXT_DIR_RTL) {
-			/* insert a unicode RLM */
-			g_string_append (label_str, "\xE2\x80\x8F");
-		} else {
-			/* insert a unicode LRM */
-			g_string_append (label_str, "\xE2\x80\x8E");
-		}
-
-		append_and_free (label_str, TITLE_MARKUP (title));
-
-		if (artist != NULL && artist[0] != '\0')
-			append_and_free (label_str, ARTIST_MARKUP (artist));
-
-		if (album != NULL && album[0] != '\0')
-			append_and_free (label_str, ALBUM_MARKUP (album));
+		widget_dir = (gtk_widget_get_direction (GTK_WIDGET (header->priv->song)) == GTK_TEXT_DIR_LTR) ?
+			     PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;
 
-		if (stream_name && stream_name[0] != '\0')
-			append_and_free (label_str, STREAM_MARKUP (stream_name));
+		label_text = write_header (widget_dir, title, artist, album, stream_name);
 
-		label_text = g_string_free (label_str, FALSE);
 		gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
 		g_free (label_text);
 
@@ -518,7 +545,7 @@ rb_header_sync (RBHeader *header)
 		g_free (streaming_title);
 	} else {
 		rb_debug ("not playing");
-		label_text = TITLE_MARKUP (_("Not Playing"));
+		label_text = g_markup_printf_escaped (TITLE_FORMAT, _("Not Playing"));
 		gtk_label_set_markup (GTK_LABEL (header->priv->song), label_text);
 		g_free (label_text);
 



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