[rhythmbox] header: fix display of mixed-direction text (bug #610753)
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] header: fix display of mixed-direction text (bug #610753)
- Date: Sun, 7 Mar 2010 03:18:41 +0000 (UTC)
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]