Index: apps/plugins/lib/viewer.c
===================================================================
--- apps/plugins/lib/viewer.c	(revision 0)
+++ apps/plugins/lib/viewer.c	(revision 0)
@@ -0,0 +1,371 @@
+/***************************************************************************
+*             __________               __   ___.
+*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+*                     \/            \/     \/    \/            \/
+* $Id: viewer.c 12008 2007-01-14 13:48:09Z dave $
+*
+* Copyright (C) 2007 Timo Horstschäfer
+*
+*
+* All files in this archive are subject to the GNU General Public License.
+* See the file COPYING in the source tree root for full license agreement.
+*
+* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+* KIND, either express or implied.
+*
+****************************************************************************/
+
+#include "plugin.h"
+#include "pluginlib_actions.h"
+#include "viewer.h"
+
+#define VIEWER_LINE_BUF 255
+#define VIEWER_LAST_PAGE (v.last_line - v.rows)
+
+#define VIEWER_SCROLLBAR_WIDTH 4
+
+#define VIEWER_SEEK_BACK 100
+
+static struct plugin_api *rb;
+
+/* Viewer configuration */
+struct _viewer_conf {
+    /* reinitialized on every viewer_set_text() call */
+    const char          *text;
+    int                 last_line, size;
+    const char          *pos;
+    int                 line;
+
+    /* text independant, only initialized by viewer_init() */
+    int                 cols, rows;
+#ifdef HAVE_REMOTE_LCD
+    int                 remote_cols, remote_rows;
+#endif
+
+    /* user-controllable configuration */
+    void                (*callback)(int button);
+    int                 scroll;
+
+    bool                quit;
+    int                 retval;
+};
+
+struct _viewer_conf v;
+
+/** \brief Returns a pointer to the last character of a line.
+ *
+ * @param s Pointer to the actual line.
+ */
+static char *viewer_endl(const char *s, int cols)
+{
+    const char *space = NULL;
+    int l;
+
+    while (true)
+    {
+        if ((l = rb->ansi_escape(s, NULL)))
+        {
+            s += l;
+            continue;
+        }
+        if (*s == '\n' || *s == '\0')
+            return (char *)s;
+        if (*s == ' ')
+            space = s;
+        if (!cols--)
+            break;
+        s += rb->utf8seek(s, 1);
+    }
+
+    return (char *) ( (space==NULL) ? s : space );
+}
+
+//! Returns a pointer to the beginning of the given line
+static char *viewer_line(const char *s, int line)
+{
+    while (line-- > 0)
+    {
+        s = viewer_endl(s, v.cols);
+        if (!*s)
+            return NULL;
+        s += rb->utf8seek(s, 1);
+    }
+
+    return (char *) s;
+}
+
+/** \brief Draws text on the display.
+ *
+ * Handles newline characters and line wrapping.
+ */
+static void viewer_draw(const char *s, int style_begin)
+{
+    const char *p, *next;
+    int line, len;
+    char buf[VIEWER_LINE_BUF];
+    int style;
+
+    rb->lcd_clear_display();
+
+#ifdef HAVE_LCD_BITMAP
+    if (v.last_line > v.rows)
+    { 
+        rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], 0, 0,
+                                VIEWER_SCROLLBAR_WIDTH-1, LCD_HEIGHT,
+                                v.last_line+v.rows-1, v.line, v.line+v.rows,
+                                VERTICAL);
+        rb->lcd_setmargins(VIEWER_SCROLLBAR_WIDTH, 0);
+    }
+#endif
+
+    /* draw main display */
+    style = style_begin; 
+    for (p=s, line=0; line<v.rows; line++)
+    {
+        next = viewer_endl(p, v.cols);
+
+        len = next-p;
+        rb->strncpy(buf, p, len);
+        buf[len] = '\0';
+
+        rb->lcd_puts_style(0, line, buf, style|STYLE_INTERPRET);
+        style = rb->lcd_get_last_style();
+
+        if (!*next)
+            break;
+        p = next + rb->utf8seek(p, 1);
+    }
+
+#ifdef HAVE_LCD_BITMAP
+    rb->lcd_update();
+#endif
+
+#ifdef HAVE_REMOTE_LCD
+    rb->lcd_remote_clear_display();
+
+    /* draw remote */
+    style = style_begin; 
+    for (p=s, line=0; line<v.remote_rows; line++)
+    {
+        next = viewer_endl(p, v.remote_cols);
+    rb->lcd_update();
+
+        len = next-p;
+        rb->strncpy(buf, p, len);
+        buf[len] = '\0';
+
+        rb->lcd_remote_puts_style(0, line, buf, style|STYLE_INTERPRET);
+        style = rb->lcd_remote_get_last_style();
+
+        if (!*next)
+            break;
+        p = next + rb->utf8seek(p, 1);
+    }
+
+    rb->lcd_remote_update();
+#endif
+}
+
+inline int viewer(struct plugin_api *api, const char *text)
+{
+    viewer_init(api);
+    viewer_set_text(text);
+
+    return viewer_run();
+}
+
+void viewer_init(struct plugin_api *api)
+{
+    if (!api)
+        return;
+
+    rb = api;
+
+    v.callback = NULL;
+
+#ifdef HAVE_LCD_BITMAP
+    rb->lcd_getstringsize("o", &v.cols, &v.rows);
+
+#ifdef HAVE_REMOTE_LCD
+    v.remote_cols = LCD_REMOTE_WIDTH / v.cols;
+    v.remote_rows = LCD_REMOTE_HEIGHT / v.rows;
+#endif
+
+    v.cols = (LCD_WIDTH-VIEWER_SCROLLBAR_WIDTH) / v.cols;
+    v.rows = LCD_HEIGHT / v.rows;
+#else
+    v.cols = 11;
+    v.rows = 2;
+#endif
+}
+
+int viewer_run(void)
+{
+    int button;
+    const struct button_mapping *viewer_contexts[] = {
+        generic_directions,
+        generic_actions
+    };
+
+    v.quit = false;
+    viewer_redraw();
+
+    while (!v.quit)
+    {
+        rb->yield();
+
+        button = pluginlib_getaction(rb, HZ, viewer_contexts, 2);
+        switch (button)
+        {
+            case PLA_UP: case PLA_UP_REPEAT:
+                viewer_up();
+                break;
+            case PLA_DOWN: case PLA_DOWN_REPEAT:
+                viewer_down();
+                break;
+            case PLA_QUIT:
+                viewer_exit(VIEWER_EXIT);
+                break;
+            default:
+                if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
+                    viewer_exit(VIEWER_ATTACHED_USB);
+
+                if (v.callback)
+                    v.callback(button);
+
+                break;
+        }
+   }
+
+    return v.retval;
+}
+
+void viewer_set_text(const char *text)
+{
+    const char *p;
+
+    v.text = v.pos = p = text;
+    v.last_line = v.line = 0;
+
+    /* get the last line */
+    while (true)
+    {
+        v.last_line++;
+        p = viewer_endl(p, v.cols);
+        if (*p)
+            p += rb->utf8seek(p, 1);
+        else
+            break;
+    }
+    v.size = p - v.text;
+}
+
+void viewer_set_scroll(int lines)
+{
+    v.scroll = (lines <= 0) ? v.rows : lines;
+}
+
+void viewer_set_callback(void (*callback)(int button))
+{
+    v.callback = callback;
+}
+
+void viewer_set_line(int line)
+{
+    v.line = (line < 0 || line > v.last_line) ? VIEWER_LAST_PAGE : line;
+    v.pos = viewer_line(v.text, v.line);
+
+    viewer_redraw();
+}
+
+void viewer_set_pos(int pos)
+{
+    const char *p;
+
+    if (pos < 0 || pos >= v.size)
+    {
+        viewer_set_line(VIEWER_BOTTOM);
+        return;
+    }
+
+    v.pos = p = v.text;
+    v.line = 0;
+    while (true)
+    {
+        p = viewer_endl(p, v.cols);
+        p += rb->utf8seek(p, 1);
+        if (p-v.text < pos)
+        {
+            v.pos = p;
+            v.line++;
+        } else
+            break;
+    }
+
+    viewer_redraw();
+}
+
+int viewer_get_line(void)
+{
+    return v.line;
+}
+
+int viewer_get_pos(void)
+{
+    return v.pos - v.text;
+}
+
+void viewer_up(void)
+{
+    v.line = (v.line < v.scroll) ? 0 : v.line - v.scroll;
+    v.pos = viewer_line(v.text, v.line);
+
+    viewer_redraw();
+}
+
+void viewer_down(void)
+{
+    int n;
+
+#ifdef HAVE_REMOTE_LCD
+    if (v.last_line < v.remote_rows)
+#else
+    if (v.last_line < v.rows)
+#endif
+        return;
+
+    n = v.scroll;
+
+    if (v.line + n >= v.last_line)
+        n = v.last_line - v.line - 1;
+
+    v.pos = viewer_line(v.pos, n);
+    v.line += n;
+    
+    viewer_redraw();
+}
+
+void viewer_redraw(void)
+{
+    int style = STYLE_DEFAULT, len;
+    const char *p;
+
+    /* search for escape sequences, that are before the drawn text */
+    p = (v.pos-v.text > VIEWER_SEEK_BACK) ? v.pos-VIEWER_SEEK_BACK : v.text;
+    while (p < v.pos)
+    {
+        len = rb->ansi_escape(p, &style);
+        p += (len) ? len : 1;
+    }
+    
+    viewer_draw(v.pos, style);
+}
+
+void viewer_exit(int retval)
+{
+    v.retval = retval;
+    v.quit = true;
+}
Index: apps/plugins/lib/SOURCES
===================================================================
--- apps/plugins/lib/SOURCES	(revision 12963)
+++ apps/plugins/lib/SOURCES	(working copy)
@@ -28,3 +28,4 @@
 #endif
 #endif
 pluginlib_actions.c
+viewer.c
Index: apps/plugins/lib/viewer.h
===================================================================
--- apps/plugins/lib/viewer.h	(revision 0)
+++ apps/plugins/lib/viewer.h	(revision 0)
@@ -0,0 +1,66 @@
+/***************************************************************************
+*             __________               __   ___.
+*   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+*   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+*   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+*   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+*                     \/            \/     \/    \/            \/
+* $Id: viewer.h 12008 2007-01-14 13:48:09Z dave $
+*
+* Copyright (C) 2007 Timo Horstschäfer
+*
+*
+* All files in this archive are subject to the GNU General Public License.
+* See the file COPYING in the source tree root for full license agreement.
+*
+* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+* KIND, either express or implied.
+*
+****************************************************************************/
+
+#include "plugin.h"
+
+/** \brief Shows a text viewer.
+ *
+ * How to view some text:
+ *  viewer(api, textptr);       // Text must be zero-terminated.
+ *
+ * For full control, call each function seperately:
+ *  viewer_init(rb);            // Must be done once in a plugin.
+ *  viewer_set_text(textptr);
+ *  viewer_run();               // Display the text.
+ *
+ * viewer() is just a short form for all three calls.
+ *
+ * @param text Zero-terminated string.
+ */
+int viewer(struct plugin_api *api, const char *text);
+#define VIEWER_ATTACHED_USB -2
+#define VIEWER_EXIT         0
+#define VIEWER_LAST         VIEWER_EXIT
+/**< Use values greater than VIEWER_LAST for custom return values. */
+
+void viewer_init(struct plugin_api *api);
+int viewer_run(void);
+
+void viewer_set_text(const char *text);
+
+void viewer_set_scroll(int lines);
+#define VIEWER_SCROLL_PAGE 0
+
+/** Set a callback.
+ * Have a look at viewer_run() in viewer.c to see how it is used. */
+void viewer_set_callback(void (*callback)(int button));
+
+void viewer_set_line(int line);
+void viewer_set_pos(int pos);
+#define VIEWER_BOTTOM -1
+
+int viewer_get_line(void);
+int viewer_get_pos(void);
+
+/** Additional functions to control the viewer in the callback function. */
+void viewer_up(void);
+void viewer_down(void);
+void viewer_redraw(void);
+void viewer_exit(int retval);
