527 lines
13 KiB
C
527 lines
13 KiB
C
|
/* -*- Mode: C; tab-width: 4 -*- */
|
||
|
/* marquee --- types a text-file or a text ribbon */
|
||
|
|
||
|
#if !defined( lint ) && !defined( SABER )
|
||
|
static const char sccsid[] = "@(#)marquee.c 5.00 2000/11/01 xlockmore";
|
||
|
|
||
|
#endif
|
||
|
|
||
|
/*-
|
||
|
* Copyright (c) 1995 by Tobias Gloth and David Bagley
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software and its
|
||
|
* documentation for any purpose and without fee is hereby granted,
|
||
|
* provided that the above copyright notice appear in all copies and that
|
||
|
* both that copyright notice and this permission notice appear in
|
||
|
* supporting documentation.
|
||
|
*
|
||
|
* This file is provided AS IS with no warranties of any kind. The author
|
||
|
* shall have no liability with respect to the infringement of copyrights,
|
||
|
* trade secrets or any patents by this file or any part thereof. In no
|
||
|
* event will the author be liable for any lost revenue or profits or
|
||
|
* other special, indirect and consequential damages.
|
||
|
*
|
||
|
* Revision History:
|
||
|
* 01-Nov-2000: Allocation checks
|
||
|
* 10-May-1997: Compatible with xscreensaver
|
||
|
* 03-Nov-1995: Many changes (hopefully good ones) by David Bagley
|
||
|
* 01-Oct-1995: Written by Tobias Gloth
|
||
|
*/
|
||
|
|
||
|
#ifdef STANDALONE
|
||
|
#define MODE_marquee
|
||
|
#define PROGCLASS "Marquee"
|
||
|
#define HACK_INIT init_marquee
|
||
|
#define HACK_DRAW draw_marquee
|
||
|
#define marquee_opts xlockmore_opts
|
||
|
#define DEFAULTS "*delay: 100000 \n" \
|
||
|
"*ncolors: 64 \n" \
|
||
|
"*font: \n" \
|
||
|
"*text: \n" \
|
||
|
"*filename: \n" \
|
||
|
"*fortunefile: \n" \
|
||
|
"*program: \n"
|
||
|
#define SMOOTH_COLORS
|
||
|
#include "xlockmore.h" /* in xscreensaver distribution */
|
||
|
#else /* STANDALONE */
|
||
|
#include "xlock.h" /* in xlockmore distribution */
|
||
|
#endif /* STANDALONE */
|
||
|
#include "iostuff.h"
|
||
|
|
||
|
#ifdef MODE_marquee
|
||
|
|
||
|
ModeSpecOpt marquee_opts =
|
||
|
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};
|
||
|
|
||
|
#ifdef USE_MODULES
|
||
|
ModStruct marquee_description =
|
||
|
{"marquee", "init_marquee", "draw_marquee", "release_marquee",
|
||
|
"init_marquee", "init_marquee", (char *) NULL, &marquee_opts,
|
||
|
100000, 1, 1, 1, 64, 1.0, "",
|
||
|
"Shows messages", 0, NULL};
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#define font_height(f) ((f==None)?8:f->ascent + f->descent)
|
||
|
|
||
|
extern XFontStruct *getFont(Display * display);
|
||
|
extern char *getWords(int screen, int screens);
|
||
|
extern int isRibbon(void);
|
||
|
|
||
|
typedef struct {
|
||
|
int ascent;
|
||
|
int height;
|
||
|
int win_height;
|
||
|
int win_width;
|
||
|
int x;
|
||
|
int y;
|
||
|
int t;
|
||
|
int startx;
|
||
|
int nonblanks;
|
||
|
int color;
|
||
|
int time;
|
||
|
GC gc;
|
||
|
char *words;
|
||
|
char modwords[256];
|
||
|
} marqueestruct;
|
||
|
|
||
|
static marqueestruct *marquees = (marqueestruct *) NULL;
|
||
|
|
||
|
static XFontStruct *mode_font = None;
|
||
|
static int char_width[256];
|
||
|
|
||
|
static int
|
||
|
font_width(XFontStruct * font, char ch)
|
||
|
{
|
||
|
int dummy;
|
||
|
XCharStruct xcs;
|
||
|
|
||
|
(void) XTextExtents(font, &ch, 1, &dummy, &dummy, &dummy, &xcs);
|
||
|
return xcs.width;
|
||
|
}
|
||
|
|
||
|
/* returns 1 if c is a printable char, the test should work for most 8 * bit
|
||
|
charsets (e.g. latin1), if we would use isprint, we would * depend of
|
||
|
locale settings that are probably incorrect. */
|
||
|
|
||
|
static int
|
||
|
is_valid_char(char c)
|
||
|
{
|
||
|
return (unsigned char) c >= ' ';
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
is_char_back_char(char *s)
|
||
|
{
|
||
|
return is_valid_char(*s) && *(s + 1) == '\b' &&
|
||
|
*(s + 2) && is_valid_char(*(s + 2));
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
char_back_char_width(char *s)
|
||
|
{
|
||
|
int w1 = char_width[(int) (unsigned char) *s];
|
||
|
int w2 = char_width[(int) (unsigned char) *(s + 2)];
|
||
|
|
||
|
return w2 < w1 ? w1 : w2;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*-
|
||
|
* fix strings of the form abc^H^H^H123 to a^H1b^H2c^H3, since we only
|
||
|
* handle backspace for char, back, char correctly. We do this without
|
||
|
* duplicating the string, but I'm not sure if there are conditions
|
||
|
* when the string is actually const. (when it is def_message, no ^Hs
|
||
|
* are present, I'm not sure about resource strings)
|
||
|
*/
|
||
|
|
||
|
static char *
|
||
|
fixup_back(char *s)
|
||
|
{
|
||
|
char *p, *p1, *p2;
|
||
|
char tmp[1000];
|
||
|
char *t;
|
||
|
char *w;
|
||
|
|
||
|
/* first of all, check if we have to do anything */
|
||
|
|
||
|
if (!*s)
|
||
|
return s;
|
||
|
|
||
|
for (p = s + 1; *p; p++)
|
||
|
if (*p == '\b' && *(p + 1) == '\b')
|
||
|
break;
|
||
|
if (!*p)
|
||
|
return s;
|
||
|
|
||
|
/* now search for runs of the form char*n, back*n, char. */
|
||
|
|
||
|
for (p = s; *p; p++) {
|
||
|
for (p1 = p; *p1 && is_valid_char(*p1); p1++);
|
||
|
if (*p1 == '\b') {
|
||
|
for (p2 = p1; *p2 && *p2 == '\b'; p2++);
|
||
|
|
||
|
/* do we have `enough' chars for the backspaces? */
|
||
|
|
||
|
if (p2 - p1 > 1 && p1 - p >= p2 - p1) {
|
||
|
if (p1 - p > p2 - p1) {
|
||
|
p = p1 - (p2 - p1);
|
||
|
}
|
||
|
/* the situation is as follows:
|
||
|
p points to the first char,
|
||
|
p1 to the first backspace (end first char run),
|
||
|
p2 to the first char in the 2nd run
|
||
|
|
||
|
Question: how to do that without tmp storage?
|
||
|
*/
|
||
|
(void) strncpy(tmp, p, p1 - p);
|
||
|
t = tmp;
|
||
|
w = p;
|
||
|
while (t - tmp < p1 - p && *p2) {
|
||
|
*w++ = *t++;
|
||
|
*w++ = '\b';
|
||
|
*w++ = *p2++;
|
||
|
}
|
||
|
p = p2;
|
||
|
} else {
|
||
|
p = p2;
|
||
|
}
|
||
|
} else {
|
||
|
/* we hit some other control char, just continue at this
|
||
|
position */
|
||
|
p = p1;
|
||
|
}
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
text_font_width(char *string)
|
||
|
{
|
||
|
int n = 0, x = 0, t = 0;
|
||
|
|
||
|
/* The following does not handle a tab or other weird junk */
|
||
|
while (*string != '\0') {
|
||
|
if (x > n)
|
||
|
n = x;
|
||
|
switch (*string) {
|
||
|
case '\v':
|
||
|
case '\f':
|
||
|
case '\n':
|
||
|
x = 0;
|
||
|
t = 0;
|
||
|
break;
|
||
|
case '\b':
|
||
|
/* we handle only char, ^H, char smartly, if
|
||
|
* we have something different, we use the
|
||
|
* (probably wrong) assumption that we have
|
||
|
* a monospaced font. */
|
||
|
if (t) {
|
||
|
t--;
|
||
|
x -= char_width[(int) (' ')];
|
||
|
}
|
||
|
break;
|
||
|
case '\t':
|
||
|
x += char_width[(int) (' ')] * (8 - (t % 8));
|
||
|
t = ((t + 8) / 8) * 8;
|
||
|
break;
|
||
|
case '\r':
|
||
|
break;
|
||
|
default:
|
||
|
t++;
|
||
|
/* handle char, ^H, char */
|
||
|
if (is_char_back_char(string)) {
|
||
|
x += char_back_char_width(string);
|
||
|
string += 2;
|
||
|
} else {
|
||
|
x += char_width[(int) (unsigned char) *string];
|
||
|
}
|
||
|
}
|
||
|
string++;
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
text_height(char *string)
|
||
|
{
|
||
|
int n = 0;
|
||
|
|
||
|
while (*string != '\0') {
|
||
|
if ((*string == '\n') || (*string == '\f') || (*string == '\v'))
|
||
|
n++;
|
||
|
string++;
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
add_blanks(marqueestruct * mp)
|
||
|
{
|
||
|
if (mp->t < 251) {
|
||
|
mp->modwords[mp->t] = ' ';
|
||
|
mp->t++;
|
||
|
mp->modwords[mp->t] = ' ';
|
||
|
mp->t++;
|
||
|
mp->modwords[mp->t] = '\0';
|
||
|
(void) strcat(mp->modwords, " ");
|
||
|
}
|
||
|
mp->x -= 2 * char_width[(int) (' ')];
|
||
|
if (mp->x <= -char_width[(int) (unsigned char) mp->modwords[0]]) {
|
||
|
mp->x += char_width[(int) (unsigned char) mp->modwords[0]];
|
||
|
(void) memcpy(mp->modwords, &(mp->modwords[1]), mp->nonblanks);
|
||
|
mp->nonblanks--;
|
||
|
}
|
||
|
return (mp->nonblanks < 0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
add_letter(marqueestruct * mp, char letter)
|
||
|
{
|
||
|
if (mp->t < 252) {
|
||
|
mp->modwords[mp->t] = letter;
|
||
|
mp->t++;
|
||
|
mp->modwords[mp->t] = '\0';
|
||
|
(void) strcat(mp->modwords, " ");
|
||
|
}
|
||
|
mp->x -= char_width[(int) letter];
|
||
|
if (mp->x <= -char_width[(int) (unsigned char) mp->modwords[0]]) {
|
||
|
mp->x += char_width[(int) (unsigned char) mp->modwords[0]];
|
||
|
(void) memcpy(mp->modwords, &(mp->modwords[1]), mp->t);
|
||
|
mp->modwords[mp->t] = ' ';
|
||
|
mp->t--;
|
||
|
} else
|
||
|
mp->nonblanks = mp->t;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
init_marquee(ModeInfo * mi)
|
||
|
{
|
||
|
Display *display = MI_DISPLAY(mi);
|
||
|
marqueestruct *mp;
|
||
|
XGCValues gcv;
|
||
|
int i;
|
||
|
|
||
|
if (marquees == NULL) {
|
||
|
if ((marquees = (marqueestruct *) calloc(MI_NUM_SCREENS(mi),
|
||
|
sizeof (marqueestruct))) == NULL)
|
||
|
return;
|
||
|
}
|
||
|
mp = &marquees[MI_SCREEN(mi)];
|
||
|
|
||
|
mp->win_width = MI_WIDTH(mi);
|
||
|
mp->win_height = MI_HEIGHT(mi);
|
||
|
if (MI_NPIXELS(mi) > 2)
|
||
|
mp->color = NRAND(MI_NPIXELS(mi));
|
||
|
mp->time = 0;
|
||
|
mp->t = 0;
|
||
|
mp->nonblanks = 0;
|
||
|
mp->x = 0;
|
||
|
|
||
|
MI_CLEARWINDOW(mi);
|
||
|
|
||
|
if (mode_font == None)
|
||
|
mode_font = getFont(display);
|
||
|
if (mp->gc == NULL && mode_font != None) {
|
||
|
gcv.font = mode_font->fid;
|
||
|
XSetFont(display, MI_GC(mi), mode_font->fid);
|
||
|
gcv.graphics_exposures = False;
|
||
|
gcv.foreground = MI_WHITE_PIXEL(mi);
|
||
|
gcv.background = MI_BLACK_PIXEL(mi);
|
||
|
if ((mp->gc = XCreateGC(display, MI_WINDOW(mi),
|
||
|
GCForeground | GCBackground | GCGraphicsExposures | GCFont,
|
||
|
&gcv)) == None) {
|
||
|
return;
|
||
|
}
|
||
|
mp->ascent = mode_font->ascent;
|
||
|
mp->height = font_height(mode_font);
|
||
|
for (i = 0; i < 256; i++)
|
||
|
if ((i >= (int) mode_font->min_char_or_byte2) &&
|
||
|
(i <= (int) mode_font->max_char_or_byte2))
|
||
|
char_width[i] = font_width(mode_font, (char) i);
|
||
|
else
|
||
|
char_width[i] = font_width(mode_font, (char) mode_font->default_char);
|
||
|
} else if (mode_font == None) {
|
||
|
for (i = 0; i < 256; i++)
|
||
|
char_width[i] = 8;
|
||
|
}
|
||
|
mp->words = fixup_back(getWords(MI_SCREEN(mi), MI_NUM_SCREENS(mi)));
|
||
|
mp->y = 0;
|
||
|
|
||
|
if (isRibbon()) {
|
||
|
mp->x = mp->win_width;
|
||
|
if (mp->win_height > font_height(mode_font))
|
||
|
mp->y += NRAND(mp->win_height - font_height(mode_font));
|
||
|
else if (mp->win_height < font_height(mode_font))
|
||
|
mp->y -= NRAND(font_height(mode_font) - mp->win_height);
|
||
|
} else {
|
||
|
int text_ht = text_height(mp->words);
|
||
|
int text_font_wid = text_font_width(mp->words);
|
||
|
|
||
|
if (mp->win_height > text_ht * font_height(mode_font))
|
||
|
mp->y = NRAND(mp->win_height - text_ht * font_height(mode_font));
|
||
|
if (mp->y < 0)
|
||
|
mp->y = 0;
|
||
|
mp->x = 0;
|
||
|
if (mp->win_width > text_font_wid)
|
||
|
mp->x += NRAND(mp->win_width - text_font_wid);
|
||
|
/* else if (mp->win_width < text_font_wid)
|
||
|
mp->x -= NRAND(text_font_wid - mp->win_width); */
|
||
|
mp->startx = mp->x;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
draw_marquee(ModeInfo * mi)
|
||
|
{
|
||
|
Display *display = MI_DISPLAY(mi);
|
||
|
Window window = MI_WINDOW(mi);
|
||
|
char *space = (char *) " ";
|
||
|
char *ch;
|
||
|
marqueestruct *mp = &marquees[MI_SCREEN(mi)];
|
||
|
|
||
|
if (marquees == NULL)
|
||
|
return;
|
||
|
mp = &marquees[MI_SCREEN(mi)];
|
||
|
if (mp->gc == None && mode_font != None)
|
||
|
return;
|
||
|
|
||
|
MI_IS_DRAWN(mi) = True;
|
||
|
ch = mp->words;
|
||
|
if (isRibbon()) {
|
||
|
ch = mp->words;
|
||
|
switch (*ch) {
|
||
|
case '\0':
|
||
|
if (add_blanks(mp)) {
|
||
|
init_marquee(mi);
|
||
|
return;
|
||
|
}
|
||
|
break;
|
||
|
case '\b':
|
||
|
case '\r':
|
||
|
case '\n':
|
||
|
case '\t':
|
||
|
case '\v':
|
||
|
case '\f':
|
||
|
add_letter(mp, ' ');
|
||
|
mp->words++;
|
||
|
break;
|
||
|
default:
|
||
|
add_letter(mp, *ch);
|
||
|
mp->words++;
|
||
|
}
|
||
|
if (MI_NPIXELS(mi) > 2) {
|
||
|
XSetForeground(display, mp->gc, MI_PIXEL(mi, mp->color));
|
||
|
if (++mp->color == MI_NPIXELS(mi))
|
||
|
mp->color = 0;
|
||
|
} else
|
||
|
XSetForeground(display, mp->gc, MI_WHITE_PIXEL(mi));
|
||
|
(void) XDrawImageString(display, MI_WINDOW(mi), mp->gc,
|
||
|
mp->x, mp->y + mp->ascent, mp->modwords, mp->t + 2);
|
||
|
} else {
|
||
|
switch (*ch) {
|
||
|
case '\0':
|
||
|
if (++mp->time > 16)
|
||
|
init_marquee(mi);
|
||
|
return;
|
||
|
case '\b':
|
||
|
if (mp->t) {
|
||
|
/* see note in text_font_width */
|
||
|
mp->t--;
|
||
|
mp->x -= char_width[(int) (' ')];
|
||
|
}
|
||
|
break;
|
||
|
case '\v':
|
||
|
case '\f':
|
||
|
case '\n':
|
||
|
mp->x = mp->startx;
|
||
|
mp->t = 0;
|
||
|
mp->y += mp->height;
|
||
|
if (mp->y + mp->height > mp->win_height) {
|
||
|
XCopyArea(display, window, window, mp->gc,
|
||
|
0, mp->height, mp->win_width, mp->y - mp->height, 0, 0);
|
||
|
XSetForeground(display, mp->gc, MI_BLACK_PIXEL(mi));
|
||
|
mp->y -= mp->height;
|
||
|
XFillRectangle(display, window, mp->gc,
|
||
|
0, mp->y, mp->win_width, mp->height);
|
||
|
}
|
||
|
break;
|
||
|
case '\t':
|
||
|
(void) XDrawString(display, window, mp->gc, mp->x, mp->y + mp->ascent,
|
||
|
space, 8 - (mp->t % 8));
|
||
|
mp->x += char_width[(int) (' ')] * (8 - (mp->t % 8));
|
||
|
mp->t = ((mp->t + 8) / 8) * 8;
|
||
|
break;
|
||
|
case '\r':
|
||
|
break;
|
||
|
default:
|
||
|
if (MI_NPIXELS(mi) > 2) {
|
||
|
XSetForeground(display, mp->gc, MI_PIXEL(mi, mp->color));
|
||
|
if (++mp->color == MI_NPIXELS(mi))
|
||
|
mp->color = 0;
|
||
|
} else
|
||
|
XSetForeground(display, mp->gc, MI_WHITE_PIXEL(mi));
|
||
|
if (is_char_back_char(ch)) {
|
||
|
int xmid = mp->x + (char_back_char_width(ch) + 1) / 2;
|
||
|
|
||
|
(void) XDrawString(display, window, mp->gc,
|
||
|
xmid - char_width[(int) (unsigned char) *ch] / 2,
|
||
|
mp->y + mp->ascent, ch, 1);
|
||
|
(void) XDrawString(display, window, mp->gc,
|
||
|
xmid - char_width[(int) (unsigned char) *(ch + 2)] / 2,
|
||
|
mp->y + mp->ascent, ch + 2, 1);
|
||
|
mp->x += char_back_char_width(ch);
|
||
|
mp->words += 2;
|
||
|
} else {
|
||
|
#ifdef USE_MB
|
||
|
int mb = (*ch & 0x80) ? 2 : 1;
|
||
|
|
||
|
(void) XDrawString(display, window, mp->gc,
|
||
|
mp->x, mp->y + mp->ascent, ch, mb);
|
||
|
if (mb == 1)
|
||
|
mp->x += char_width[(int) (unsigned char) *ch];
|
||
|
else {
|
||
|
XRectangle rect;
|
||
|
|
||
|
XmbTextExtents(fontset, ch, 2, NULL, &rect);
|
||
|
mp->x += rect.width;
|
||
|
}
|
||
|
#else
|
||
|
(void) XDrawString(display, window, mp->gc,
|
||
|
mp->x, mp->y + mp->ascent, ch, 1);
|
||
|
mp->x += char_width[(int) (unsigned char) *ch];
|
||
|
#endif
|
||
|
}
|
||
|
mp->t++;
|
||
|
}
|
||
|
#ifdef USE_MB
|
||
|
mp->words += (*mp->words & 0x80) ? 2 : 1;
|
||
|
#else
|
||
|
mp->words++;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
release_marquee(ModeInfo * mi)
|
||
|
{
|
||
|
if (marquees != NULL) {
|
||
|
int screen;
|
||
|
|
||
|
for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
|
||
|
marqueestruct *mp = &marquees[screen];
|
||
|
|
||
|
if (mp->gc != None)
|
||
|
XFreeGC(MI_DISPLAY(mi), mp->gc);
|
||
|
}
|
||
|
free(marquees);
|
||
|
marquees = (marqueestruct *) NULL;
|
||
|
}
|
||
|
if (mode_font != None) {
|
||
|
XFreeFont(MI_DISPLAY(mi), mode_font);
|
||
|
mode_font = None;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif /* MODE_marquee */
|