/*
* Copyright (c) 2001-2007 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author, nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Rendering/sizing of UCS-4 or UTF-8 text using FreeType (if available) or
* an internal bitmap font engine.
*
* TextSizeFT(), TextRenderFT_Mono(), TextRenderFT_Blended() are based on
* code from SDL_ttf (http://libsdl.org/projects/SDL_ttf/), placed under BSD
* license with kind permission from Sam Lantinga.
*/
#include "opengl.h"
#include
#include
#include
#include
#include
#ifdef HAVE_FREETYPE
#include "ttf.h"
#endif
#include "view.h"
#include "load_xcf.h"
#include "window.h"
#include "vbox.h"
#include "box.h"
#include "label.h"
#include "textbox.h"
#include "button.h"
#include "ucombo.h"
#include "numerical.h"
#include "keymap.h"
#include "checkbox.h"
#include
#include
#include
#include "icons.h"
#include "fonts.h"
#include "fonts_data.h"
/* Default fonts */
const char *agDefaultFaceFT = "_agFontVera";
const char *agDefaultFaceBitmap = "_agFontMinimal";
/* Statically compiled fonts */
AG_StaticFont *agBuiltinFonts[] = {
&agFontVera,
&agFontMinimal
};
const int agBuiltinFontCount = sizeof(agBuiltinFonts)/sizeof(agBuiltinFonts[0]);
int agTextFontHeight = 0; /* Default font height (px) */
int agTextFontAscent = 0; /* Default font ascent (px) */
int agTextFontDescent = 0; /* Default font descent (px) */
int agTextFontLineSkip = 0; /* Default font line skip (px) */
int agGlyphGC = 0; /* Enable glyph garbage collector */
int agFreetypeInited = 0; /* Initialized Freetype library */
static AG_TextState states[AG_TEXT_STATES_MAX];
static Uint curState = 0;
AG_TextState *agTextState;
#define GLYPH_NBUCKETS 1024 /* Buckets for glyph cache table */
#define GLYPH_GC_INTERVAL 1000 /* Garbage collection interval (ms) */
/* #define SYMBOLS */ /* Escape $(x) type symbols */
/* #define GLYPH_GC */
static const char *agTextMsgTitles[] = {
N_("Error"),
N_("Warning"),
N_("Information")
};
AG_Mutex agTextLock;
static SLIST_HEAD(ag_fontq, ag_font) fonts = SLIST_HEAD_INITIALIZER(&fonts);
AG_Font *agDefaultFont = NULL;
static struct {
SLIST_HEAD(, ag_glyph) glyphs;
} agGlyphCache[GLYPH_NBUCKETS+1];
static AG_Timeout textMsgTo = AG_TIMEOUT_INITIALIZER; /* For AG_TextTmsg() */
#ifdef GLYPH_GC
static AG_Timeout glyphGcTo = AG_TIMEOUT_INITIALIZER; /* For glyph GC */
#endif
/* Load an individual glyph from a bitmap font file. */
static void
LoadBitmapGlyph(AG_Surface *su, const char *lbl, void *p)
{
AG_Font *font = p;
if (font->nglyphs == 0) {
Strlcpy(font->bspec, lbl, sizeof(font->bspec));
}
font->bglyphs = Realloc(font->bglyphs,
(font->nglyphs+1)*sizeof(AG_Surface *));
font->bglyphs[font->nglyphs++] = su;
}
static void
FontDestroy(void *obj)
{
AG_Font *font = obj;
int i;
switch (font->type) {
#ifdef HAVE_FREETYPE
case AG_FONT_VECTOR:
AG_TTFCloseFont(font->ttf);
break;
#endif
case AG_FONT_BITMAP:
for (i = 0; i < font->nglyphs; i++) {
AG_SurfaceFree(font->bglyphs[i]);
}
Free(font->bglyphs);
break;
default:
break;
}
}
static int
GetFontTypeFromSignature(const char *path, enum ag_font_type *pType)
{
char buf[13];
FILE *f;
if ((f = fopen(path, "rb")) == NULL) {
AG_SetError(_("Unable to open %s"), path);
return (-1);
}
if (fread(buf, 13, 1, f) == 13) {
if (strncmp(buf, "gimp xcf file", 13) == 0) {
*pType = AG_FONT_BITMAP;
} else {
*pType = AG_FONT_VECTOR;
}
} else {
*pType = AG_FONT_VECTOR;
}
fclose(f);
return (0);
}
/*
* Search for a given font face/size/flags combination and load it from
* disk or cache.
*/
AG_Font *
AG_FetchFont(const char *pname, int psize, int pflags)
{
char path[AG_PATHNAME_MAX];
char name[AG_OBJECT_NAME_MAX];
int ptsize = (psize >= 0) ? psize : AG_CfgInt("font.size");
Uint flags = (pflags >= 0) ? pflags : AG_CfgUint("font.flags");
enum ag_font_type type;
AG_StaticFont *builtin;
AG_Font *font;
int i;
if (pname != NULL) {
Strlcpy(name, pname, sizeof(name));
} else {
AG_CopyCfgString("font.face", name, sizeof(name));
}
AG_MutexLock(&agTextLock);
SLIST_FOREACH(font, &fonts, fonts) {
if (font->size == ptsize &&
font->flags == flags &&
strcmp(OBJECT(font)->name, name) == 0)
break;
}
if (font != NULL)
goto out;
font = Malloc(sizeof(AG_Font));
AG_ObjectInit(font, &agFontClass);
AG_ObjectSetName(font, "%s", name);
font->size = ptsize;
font->flags = flags;
font->c0 = 0;
font->c1 = 0;
font->height = 0;
font->ascent = 0;
font->descent = 0;
font->lineskip = 0;
if (name[0] == '_') {
for (i = 0; i < agBuiltinFontCount; i++) {
if (strcmp(agBuiltinFonts[i]->name, &name[1]) == 0)
break;
}
if (i == agBuiltinFontCount) {
AG_SetError(_("No such builtin font: %s"), name);
goto fail;
}
builtin = agBuiltinFonts[i];
type = builtin->type;
} else {
if (AG_ConfigFile("font-path", name, NULL, path, sizeof(path))
== -1) {
goto fail;
}
builtin = NULL;
if (GetFontTypeFromSignature(path, &type) == -1)
goto fail;
}
#ifdef HAVE_FREETYPE
if (type == AG_FONT_VECTOR) {
int tflags = 0;
AG_TTFFont *ttf;
if (builtin != NULL) {
Verbose("Using builtin vector font: %s\n", name);
if ((font->ttf = ttf = AG_TTFOpenFontFromMemory(
builtin->data, builtin->size, ptsize)) == NULL) {
goto fail;
}
} else {
Verbose("Loading vector font: %s\n", name);
if ((font->ttf = ttf = AG_TTFOpenFont(path, ptsize))
== NULL)
goto fail;
}
if (flags & AG_FONT_BOLD) { tflags |= TTF_STYLE_BOLD; }
if (flags & AG_FONT_ITALIC) { tflags |= TTF_STYLE_ITALIC; }
if (flags & AG_FONT_UNDERLINE) { tflags |= TTF_STYLE_UNDERLINE;}
AG_TTFSetFontStyle(ttf, tflags);
font->type = AG_FONT_VECTOR;
font->height = ttf->height;
font->ascent = ttf->ascent;
font->descent = ttf->descent;
font->lineskip = ttf->lineskip;
} else
#endif
{
char *s;
char *msig, *c0, *c1;
AG_DataSource *ds;
if (builtin != NULL) {
Verbose("Using builtin bitmap font: %s\n", name);
ds = AG_OpenConstCore(builtin->data, builtin->size);
} else {
Verbose("Loading bitmap font: %s\n", name);
if ((ds = AG_OpenFile(path, "rb")) == NULL)
goto fail;
}
font->type = AG_FONT_BITMAP;
font->bglyphs = Malloc(sizeof(AG_Surface *));
font->nglyphs = 0;
if (AG_XCFLoad(ds, 0, LoadBitmapGlyph, font) == -1) {
goto fail;
}
AG_CloseDataSource(ds);
/* Get the range of characters from the "MAP:x-y" string. */
s = font->bspec;
msig = AG_Strsep(&s, ":");
c0 = AG_Strsep(&s, "-");
c1 = AG_Strsep(&s, "-");
if (font->nglyphs < 1 ||
msig == NULL || strcmp(msig, "MAP") != 0 ||
c0 == NULL || c1 == NULL ||
c0[0] == '\0' || c1[0] == '\0') {
AG_SetError(_("Missing bitmap fontspec"));
goto fail;
}
font->c0 = (Uint32)strtol(c0, NULL, 10);
font->c1 = (Uint32)strtol(c1, NULL, 10);
if (font->nglyphs < (font->c1 - font->c0)) {
AG_SetError(_("Inconsistent bitmap fontspec"));
goto fail;
}
font->height = font->bglyphs[0]->h;
font->ascent = font->height;
font->descent = 0;
font->lineskip = font->height+2;
}
SLIST_INSERT_HEAD(&fonts, font, fonts);
out:
AG_MutexUnlock(&agTextLock);
return (font);
fail:
AG_MutexUnlock(&agTextLock);
AG_ObjectDestroy(font);
return (NULL);
}
void
AG_DestroyFont(AG_Font *font)
{
if (font != agDefaultFont)
AG_ObjectDestroy(font);
}
void
AG_SetDefaultFont(AG_Font *font)
{
AG_MutexLock(&agTextLock);
agDefaultFont = font;
AG_MutexUnlock(&agTextLock);
}
static Uint32
TextTmsgExpire(void *obj, Uint32 ival, void *arg)
{
AG_Window *win = arg;
AG_ViewDetach(win);
return (0);
}
static void
InitTextState(void)
{
agTextState->font = agDefaultFont;
agTextState->color = AG_MapRGB(agSurfaceFmt, 255,255,255);
agTextState->colorBG = AG_MapRGBA(agSurfaceFmt, 0,0,0,0);
agTextState->justify = AG_TEXT_LEFT;
agTextState->valign = AG_TEXT_TOP;
}
/* Must be invoked in rendering context. */
static void
FreeGlyph(AG_Glyph *gl)
{
AG_SurfaceFree(gl->su);
#ifdef HAVE_OPENGL
if (agView->opengl)
glDeleteTextures(1, (GLuint *)&gl->texture);
#endif
Free(gl);
}
#ifdef GLYPH_GC
/*
* Perform garbage collection of unused glyphs. Must be invoked in rendering
* context.
*/
static Uint32
GlyphGC(void *obj, Uint32 ival, void *arg)
{
AG_Glyph *gl;
Uint32 t = SDL_GetTicks();
int i;
for (i = 0; i < GLYPH_NBUCKETS; i++) {
SLIST_FOREACH(gl, &agGlyphCache[i].glyphs, glyphs) {
if (gl->nrefs > 0 ||
(t - gl->lastRef) < GLYPH_GC_INTERVAL) {
continue;
}
SLIST_REMOVE(&agGlyphCache[i].glyphs, gl, ag_glyph,
glyphs);
FreeGlyph(gl);
}
}
return (GLYPH_GC_INTERVAL);
}
#endif /* GLYPH_GC */
/* Initialize the font engine and configure the default font. */
int
AG_TextInit(void)
{
int i;
AG_MutexInitRecursive(&agTextLock);
/* Set the default font search path. */
if (!AG_CfgDefined("font-path")) {
#if defined(__APPLE__)
# if defined(HAVE_GETPWUID) && defined(HAVE_GETUID)
char savePath[AG_PATHNAME_MAX];
char home[AG_PATHNAME_MAX];
AG_CopyCfgString("save-path", savePath, sizeof(savePath));
AG_CopyCfgString("home", home, sizeof(home));
AG_SetCfgString("font-path",
"%s/fonts:%s:%s/Library/Fonts:/Library/Fonts:/System/Library/Fonts",
savePath, TTFDIR, home);
# else
char savePath[AG_PATHNAME_MAX];
AG_CopyCfgString("save-path", savePath, sizeof(savePath));
AG_SetCfgString("font-path",
"%s/fonts:%s:/Library/Fonts:/System/Library/Fonts",
savePath, TTFDIR);
# endif
#elif defined(_WIN32)
AG_SetCfgString("font-path", "fonts:.");
#else
char savePath[AG_PATHNAME_MAX];
AG_CopyCfgString("save-path", savePath, sizeof(savePath));
AG_SetCfgString("font-path", "%s/fonts:%s", savePath, TTFDIR);
#endif
}
/* Initialize FreeType if available. */
#ifdef HAVE_FREETYPE
if (AG_TTFInit() == 0) {
agFreetypeInited = 1;
} else {
AG_Verbose("Failed to initialize FreeType (%s); falling back "
"to monospace font engine", AG_GetError());
}
#endif
/* Load the default font. */
if (!AG_CfgDefined("font.face")) {
AG_SetCfgString("font.face", agFreetypeInited ?
agDefaultFaceFT :
agDefaultFaceBitmap);
}
if (!AG_CfgDefined("font.size")) {
AG_SetCfgInt("font.size", 10);
}
if (!AG_CfgDefined("font.flags")) {
AG_SetCfgUint("font.flags", 0);
}
if ((agDefaultFont = AG_FetchFont(NULL, -1, -1)) == NULL) {
AG_SetError("Failed to load default font: %s", AG_GetError());
goto fail;
}
agTextFontHeight = agDefaultFont->height;
agTextFontAscent = agDefaultFont->ascent;
agTextFontDescent = agDefaultFont->descent;
agTextFontLineSkip = agDefaultFont->lineskip;
/* Initialize the state engine and cache. */
curState = 0;
agTextState = &states[0];
InitTextState();
for (i = 0; i < GLYPH_NBUCKETS; i++) {
SLIST_INIT(&agGlyphCache[i].glyphs);
}
#ifdef GLYPH_GC
AG_SetTimeout(&glyphGcTo, GlyphGC, NULL, 0);
agGlyphGC = 0;
#endif
return (0);
fail:
#ifdef HAVE_FREETYPE
if (agFreetypeInited) {
AG_TTFDestroy();
agFreetypeInited = 0;
}
#endif
return (-1);
}
/* Clear the glyph cache. Must be invoked in rendering context. */
void
AG_ClearGlyphCache(void)
{
int i;
AG_Glyph *gl, *ngl;
for (i = 0; i < GLYPH_NBUCKETS; i++) {
for (gl = SLIST_FIRST(&agGlyphCache[i].glyphs);
gl != SLIST_END(&agGlyphCache[i].glyphs);
gl = ngl) {
ngl = SLIST_NEXT(gl, glyphs);
FreeGlyph(gl);
}
SLIST_INIT(&agGlyphCache[i].glyphs);
}
}
void
AG_TextDestroy(void)
{
AG_Font *font, *nextfont;
#ifdef GLYPH_GC
AG_LockTimeouts(NULL);
if (AG_TimeoutIsScheduled(NULL, &glyphGcTo)) {
AG_DelTimeout(NULL, &glyphGcTo);
}
AG_UnlockTimeouts(NULL);
#endif
AG_ClearGlyphCache();
for (font = SLIST_FIRST(&fonts);
font != SLIST_END(&fonts);
font = nextfont) {
nextfont = SLIST_NEXT(font, fonts);
AG_ObjectDestroy(font);
if (font == agDefaultFont)
agDefaultFont = NULL;
}
#ifdef HAVE_FREETYPE
if (agFreetypeInited) {
AG_TTFDestroy();
agFreetypeInited = 0;
}
#endif
AG_MutexDestroy(&agTextLock);
}
static __inline__ Uint
HashGlyph(Uint32 ch)
{
return (ch % GLYPH_NBUCKETS);
}
/*
* Lookup/insert a glyph in the glyph cache.
* Must be called from GUI rendering context.
*/
AG_Glyph *
AG_TextRenderGlyph(Uint32 ch)
{
AG_Glyph *gl;
Uint h = HashGlyph(ch);
SLIST_FOREACH(gl, &agGlyphCache[h].glyphs, glyphs) {
if (agTextState->font->size == gl->fontsize &&
agTextState->color == gl->color &&
(strcmp(OBJECT(agTextState->font)->name, gl->fontname)
== 0) &&
ch == gl->ch)
break;
}
if (gl == NULL) {
Uint32 ucs[2];
gl = Malloc(sizeof(AG_Glyph));
Strlcpy(gl->fontname, OBJECT(agTextState->font)->name,
sizeof(gl->fontname));
gl->fontsize = agTextState->font->size;
gl->color = agTextState->color;
gl->ch = ch;
ucs[0] = ch;
ucs[1] = '\0';
gl->su = AG_TextRenderUCS4(ucs);
switch (agTextState->font->type) {
#ifdef HAVE_FREETYPE
case AG_FONT_VECTOR:
{
AG_TTFGlyph *gt;
if (AG_TTFFindGlyph(agTextState->font->ttf, ch,
TTF_CACHED_METRICS|
TTF_CACHED_BITMAP) == 0) {
gt = ((AG_TTFFont *)agTextState->font->ttf)->current;
gl->advance = gt->advance;
} else {
gl->advance = gl->su->w;
}
}
break;
#endif
case AG_FONT_BITMAP:
gl->advance = gl->su->w;
break;
default:
break;
}
#ifdef HAVE_OPENGL
if (agView->opengl)
gl->texture = AG_SurfaceTexture(gl->su, gl->texcoord);
#endif
gl->nrefs = 1;
SLIST_INSERT_HEAD(&agGlyphCache[h].glyphs, gl, glyphs);
} else {
gl->nrefs++;
}
gl->lastRef = SDL_GetTicks();
return (gl);
}
void
AG_TextUnusedGlyph(AG_Glyph *gl)
{
if (gl->nrefs > 0) {
gl->nrefs--;
}
#ifdef GLYPH_GC
if (agGlyphGC == 0) {
agGlyphGC = 1;
AG_ScheduleTimeout(NULL, &glyphGcTo, GLYPH_GC_INTERVAL);
}
#endif
}
/* Save the current text rendering state. */
void
AG_PushTextState(void)
{
AG_MutexLock(&agTextLock);
if ((curState+1) >= AG_TEXT_STATES_MAX) {
AG_FatalError("Text state stack overflow");
}
agTextState = &states[++curState];
InitTextState();
AG_MutexUnlock(&agTextLock);
}
/* Restore the previous rendering state. */
void
AG_PopTextState(void)
{
AG_MutexLock(&agTextLock);
if (curState == 0) {
AG_FatalError("No text state to pop");
}
agTextState = &states[--curState];
AG_MutexUnlock(&agTextLock);
}
/* Select the font face to use in rendering text. */
int
AG_TextFontLookup(const char *face, int size, Uint flags)
{
int rv;
AG_MutexLock(&agTextLock);
if ((agTextState->font = AG_FetchFont(face, size, flags)) == NULL) {
AG_FatalError("No such font: %s:%d", face, size);
}
rv = (agTextState->font != NULL) ? 0 : -1;
AG_MutexUnlock(&agTextLock);
return (rv);
}
/* Varargs variant of TextRender(). */
AG_Surface *
AG_TextRenderf(const char *fmt, ...)
{
char *text;
va_list args;
AG_Surface *su;
va_start(args, fmt);
Vasprintf(&text, fmt, args);
va_end(args);
su = AG_TextRender(text);
free(text);
return (su);
}
#ifdef SYMBOLS
static __inline__ AG_Surface *
GetSymbolSurface(Uint32 ch)
{
switch (ch) {
case 'L': return agIconLeftButton.s;
case 'M': return agIconMidButton.s;
case 'R': return agIconRightButton.s;
case 'C': return agIconCtrlKey.s;
default: return (NULL);
}
}
#endif /* SYMBOLS */
static __inline__ void
InitMetrics(AG_TextMetrics *tm)
{
tm->w = 0;
tm->h = 0;
tm->wLines = NULL;
tm->nLines = 0;
}
static __inline__ void
FreeMetrics(AG_TextMetrics *tm)
{
Free(tm->wLines);
}
#ifdef HAVE_FREETYPE
/*
* Compute the rendered size of UCS-4 text with a FreeType font. If the
* string is multiline and nLines is non-NULL, the width of individual lines
* is returned into wLines, and the number of lines into nLines.
*/
static void
TextSizeFT(const Uint32 *ucs, AG_TextMetrics *tm, int extended)
{
AG_Font *font = agTextState->font;
AG_TTFFont *ftFont = font->ttf;
AG_TTFGlyph *glyph;
const Uint32 *ch;
int xMin=0, xMax=0, yMin=0, yMax;
int xMinLine=0, xMaxLine=0;
int x, z;
/* Compute the sum of the bounding box of the characters. */
yMax = font->height;
x = 0;
for (ch = &ucs[0]; *ch != '\0'; ch++) {
if (*ch == '\n') {
if (extended) {
tm->wLines = Realloc(tm->wLines,
(tm->nLines+2)*sizeof(Uint));
tm->wLines[tm->nLines++] = (xMaxLine-xMinLine);
xMinLine = 0;
xMaxLine = 0;
}
yMax += font->lineskip;
x = 0;
continue;
}
if (AG_TTFFindGlyph(ftFont, *ch, TTF_CACHED_METRICS) != 0) {
continue;
}
glyph = ftFont->current;
z = x + glyph->minx;
if (xMin > z) { xMin = z; }
if (xMinLine > z) { xMinLine = z; }
if (ftFont->style & TTF_STYLE_BOLD) {
x += ftFont->glyph_overhang;
}
z = x + MAX(glyph->advance,glyph->maxx);
if (xMax < z) { xMax = z; }
if (xMaxLine < z) { xMaxLine = z; }
x += glyph->advance;
if (glyph->miny < yMin) { yMin = glyph->miny; }
if (glyph->maxy > yMax) { yMax = glyph->maxy; }
}
if (*ch != '\n' && extended) {
if (tm->nLines > 0) {
tm->wLines = Realloc(tm->wLines,
(tm->nLines+2)*sizeof(Uint));
tm->wLines[tm->nLines] = (xMaxLine-xMinLine);
}
tm->nLines++;
}
tm->w = (xMax-xMin);
tm->h = (yMax-yMin);
}
# ifdef SYMBOLS
static int
TextRenderSymbol(Uint ch, AG_Surface *su, int x, int y)
{
AG_Surface *sym;
int row;
if ((sym = GetSymbolSurface(ch)) == NULL) {
return (0);
}
for (row = 0; row < sym->h; row++) {
Uint8 *dst = (Uint8 *)su->pixels + (y+row)*su->pitch +
(x+2);
Uint8 *src = (Uint8 *)sym->pixels + row*sym->pitch;
int col;
for (col = 0; col < sym->w; col++) {
if (AG_GET_PIXEL(sym,src) != sym->format->colorkey) {
*dst = 1;
}
src += sym->format->BytesPerPixel;
dst++;
}
}
return (sym->w + 4);
}
static int
TextRenderSymbol_Blended(Uint ch, AG_Surface *su, int x, int y, Uint32 pixel)
{
AG_Surface *sym;
Uint32 alpha;
int row;
if ((sym = GetSymbolSurface(ch)) == NULL) {
return (0);
}
for (row = 0; row < sym->h; row++) {
Uint8 *dst = (Uint8 *)su->pixels + (y+row)*(su->pitch/4) +
(x+2);
Uint8 *src = (Uint8 *)sym->pixels + row*sym->pitch;
int col;
for (col = 0; col < sym->w; col++) {
alpha = *src;
if (AG_GET_PIXEL(sym,src) != sym->format->colorkey) {
dst[0] = 0xff;
dst[1] = 0xff;
dst[2] = 0xff;
}
src += sym->format->BytesPerPixel;
dst+=3;
}
}
return (sym->w + 4);
}
# endif /* SYMBOLS */
/* Render UCS-4 text to a monochrome surface using FreeType. */
static AG_Surface *
TextRenderFT_Mono(const Uint32 *ucs)
{
AG_TextMetrics tm;
AG_Font *font = agTextState->font;
AG_TTFFont *ftFont = font->ttf;
AG_TTFGlyph *glyph;
AG_Surface *su;
AG_Palette *pal;
const Uint32 *ch;
Uint8 *src, *dst, *dstEnd, a;
int row, col;
int line;
int xStart, yStart;
FT_Error error;
InitMetrics(&tm);
TextSizeFT(ucs, &tm, 1);
if (tm.w <= 0 || tm.h <= 0)
goto empty;
if ((su = AG_SurfaceIndexed(tm.w, tm.h, 8, AG_SWSURFACE)) == NULL) {
Verbose("TextRenderFT_Indexed: %s\n", AG_GetError());
goto empty;
}
pal = su->format->palette;
AG_GetRGBA(agTextState->colorBG, agSurfaceFmt,
&pal->colors[0].r,
&pal->colors[0].g,
&pal->colors[0].b,
&a);
if (a == 0) {
AG_SetColorKey(su, AG_SRCCOLORKEY, 0);
}
AG_GetRGB(agTextState->color, agSurfaceFmt,
&pal->colors[1].r,
&pal->colors[1].g,
&pal->colors[1].b);
/* For bounds checking */
dstEnd = (Uint8 *)su->pixels + su->w*su->h;
/* Load and render each character. */
line = 0;
yStart = 0;
xStart = (tm.nLines > 1) ? AG_TextJustifyOffset(tm.w, tm.wLines[0]) : 0;
for (ch = &ucs[0]; *ch != '\0'; ch++) {
if (*ch == '\n') {
yStart += font->lineskip;
xStart = AG_TextJustifyOffset(tm.w, tm.wLines[++line]);
continue;
}
#ifdef SYMBOLS
if (ch[0] == '$' && agTextSymbols &&
ch[1] == '(' && ch[2] != '\0' && ch[3] == ')') {
xStart += TextRenderSymbol(ch[2], su, xStart, yStart);
ch += 3;
continue;
}
#endif
error = AG_TTFFindGlyph(ftFont, *ch, TTF_CACHED_METRICS|
TTF_CACHED_BITMAP);
if (error) {
AG_SurfaceFree(su);
goto empty;
}
glyph = ftFont->current;
/* Prevent texture wrapping with first glyph. */
if ((ch == &ucs[0]) && (glyph->minx < 0))
xStart -= glyph->minx;
for (row = 0; row < glyph->bitmap.rows; row++) {
if (glyph->yoffset < 0) {
glyph->yoffset = 0;
}
if (row+glyph->yoffset >= su->h)
continue;
dst = (Uint8 *)su->pixels +
(yStart+row+glyph->yoffset)*su->pitch +
xStart + glyph->minx;
src = glyph->bitmap.buffer + row*glyph->bitmap.pitch;
for (col = glyph->bitmap.width;
col > 0 && dst < dstEnd;
--col)
*dst++ |= *src++;
}
xStart += glyph->advance;
if (ftFont->style & TTF_STYLE_BOLD)
xStart += ftFont->glyph_overhang;
}
if (ftFont->style & TTF_STYLE_UNDERLINE) {
row = ftFont->ascent - ftFont->underline_offset - 1;
if (row >= su->h) {
row = (su->h-1) - ftFont->underline_height;
}
dst = (Uint8 *)su->pixels + row*su->pitch;
for (row = ftFont->underline_height; row > 0; --row) {
memset(dst, 1, su->w);
dst += su->pitch;
}
}
FreeMetrics(&tm);
return (su);
empty:
FreeMetrics(&tm);
return AG_SurfaceEmpty();
}
static AG_Surface *
TextRenderFT_Blended(const Uint32 *ucs)
{
AG_TextMetrics tm;
AG_Font *font = agTextState->font;
AG_TTFFont *ftFont = font->ttf;
AG_TTFGlyph *glyph;
const Uint32 *ch;
int xStart, yStart;
int line;
AG_Surface *su;
Uint32 pixel;
Uint8 *src;
Uint32 *dst, *dstEnd;
int row, col;
FT_Error error;
FT_UInt prev_index = 0;
Uint8 r, g, b;
int w;
InitMetrics(&tm);
TextSizeFT(ucs, &tm, 1);
if (tm.w <= 0 || tm.h <= 0)
goto empty;
su = AG_SurfaceRGBA(tm.w, tm.h, 32, 0,
0x00ff0000,
0x0000ff00,
0x000000ff,
0xff000000);
if (su == NULL) {
Verbose("TextRenderFT_Blended: %s\n", AG_GetError());
goto empty;
}
/* For bounds checking */
dstEnd = (Uint32 *)su->pixels + su->pitch/4 * su->h;
/* Load and render each character */
line = 0;
xStart = (tm.nLines > 1) ? AG_TextJustifyOffset(tm.w, tm.wLines[0]) : 0;
yStart = 0;
AG_GetRGB(agTextState->color, agSurfaceFmt, &r,&g,&b);
pixel = (r<<16) | (g<<8) | b;
AG_FillRect(su, NULL, pixel); /* Initialize with fg and 0 alpha */
for (ch = &ucs[0]; *ch != '\0'; ch++) {
if (*ch == '\n') {
yStart += font->lineskip;
xStart = AG_TextJustifyOffset(tm.w, tm.wLines[++line]);
continue;
}
#ifdef SYMBOLS
if (ch[0] == '$' && agTextSymbols &&
ch[1] == '(' && ch[2] != '\0' && ch[3] == ')') {
xStart += TextRenderSymbol_Blended(ch[2], su, xStart,
yStart, pixel);
ch += 3;
continue;
}
#endif
error = AG_TTFFindGlyph(ftFont, *ch, TTF_CACHED_METRICS|
TTF_CACHED_PIXMAP);
if (error) {
AG_SurfaceFree(su);
goto empty;
}
glyph = ftFont->current;
/*
* Ensure the width of the pixmap is correct. On some cases,
* freetype may report a larger pixmap than possible.
*/
w = glyph->pixmap.width;
if (w > glyph->maxx - glyph->minx) {
w = glyph->maxx - glyph->minx;
}
if (FT_HAS_KERNING(ftFont->face) && prev_index &&
glyph->index) {
FT_Vector delta;
FT_Get_Kerning(ftFont->face, prev_index, glyph->index,
ft_kerning_default, &delta);
xStart += delta.x >> 6;
}
/* Prevent texture wrapping with first glyph. */
if ((ch == &ucs[0]) && (glyph->minx < 0))
xStart -= glyph->minx;
for (row = 0; row < glyph->pixmap.rows; row++) {
if (row+glyph->yoffset < 0 ||
row+glyph->yoffset >= su->h) {
continue;
}
dst = (Uint32 *)su->pixels +
(yStart+row+glyph->yoffset) * su->pitch/4 +
xStart + glyph->minx;
/* Adjust src for pixmaps to account for pitch. */
src = (Uint8 *) (glyph->pixmap.buffer +
glyph->pixmap.pitch*row);
for (col = w;
col > 0 && dst < dstEnd;
col--) {
Uint32 alpha = *src++;
*dst++ |= pixel | (alpha << 24);
}
}
xStart += glyph->advance;
if (ftFont->style & TTF_STYLE_BOLD) {
xStart += ftFont->glyph_overhang;
}
prev_index = glyph->index;
}
if (ftFont->style & TTF_STYLE_UNDERLINE) {
row = ftFont->ascent - ftFont->underline_offset - 1;
if ( row >= su->h) {
row = (su->h-1) - ftFont->underline_height;
}
dst = (Uint32 *)su->pixels + row * su->pitch/4;
pixel |= 0xFF000000; /* Amask */
for (row = ftFont->underline_height; row > 0; row--) {
for (col = 0; col < su->w; col++) {
dst[col] = pixel;
}
dst += su->pitch/4;
}
}
FreeMetrics(&tm);
return (su);
empty:
FreeMetrics(&tm);
return AG_SurfaceEmpty();
}
#endif /* HAVE_FREETYPE */
static __inline__ AG_Surface *
GetBitmapGlyph(AG_Font *font, Uint32 c)
{
if ((font->flags & AG_FONT_UPPERCASE) &&
(isalpha(c) && islower(c))) {
c = toupper(c);
}
if (c < font->c0 || c > font->c1) {
return (agTextState->font->bglyphs[0]);
}
return (font->bglyphs[c - font->c0 + 1]);
}
/* Compute the rendered size of UCS-4 text with a bitmap font. */
static __inline__ void
TextSizeBitmap(const Uint32 *ucs, AG_TextMetrics *tm, int extended)
{
AG_Font *font = agTextState->font;
const Uint32 *c;
AG_Surface *sGlyph;
int wLine = 0;
for (c = &ucs[0]; *c != '\0'; c++) {
sGlyph = GetBitmapGlyph(font, *c);
if (*c == '\n') {
if (extended) {
tm->wLines = Realloc(tm->wLines,
(tm->nLines+2)*sizeof(Uint));
tm->wLines[tm->nLines++] = wLine;
wLine = 0;
}
tm->h += agTextState->font->lineskip;
continue;
}
wLine += sGlyph->w;
tm->w += sGlyph->w;
tm->h = MAX(tm->h, sGlyph->h);
}
if (*c != '\n' && extended) {
if (tm->nLines > 0) {
tm->wLines = Realloc(tm->wLines,
(tm->nLines+2)*sizeof(Uint));
tm->wLines[tm->nLines] = wLine;
}
tm->nLines++;
}
}
/* Render UCS-4 text to a new surface using a bitmap font. */
/* TODO: blend colors */
static AG_Surface *
TextRenderBitmap(const Uint32 *ucs)
{
AG_TextMetrics tm;
AG_Font *font = agTextState->font;
AG_Rect rd;
int line;
const Uint32 *c;
AG_Surface *sGlyph, *su;
InitMetrics(&tm);
TextSizeBitmap(ucs, &tm, 1);
if ((su = AG_SurfaceStdRGB(tm.w, tm.h)) == NULL)
AG_FatalError(NULL);
line = 0;
rd.x = (tm.nLines > 1) ? AG_TextJustifyOffset(tm.w, tm.wLines[0]) : 0;
rd.y = 0;
for (c = &ucs[0]; *c != '\0'; c++) {
if (*c == '\n') {
rd.y += font->lineskip;
rd.x = AG_TextJustifyOffset(tm.w, tm.wLines[++line]);
continue;
}
sGlyph = GetBitmapGlyph(font, *c);
AG_SurfaceBlit(sGlyph, NULL, su, rd.x, rd.y);
rd.x += sGlyph->w;
}
AG_SetColorKey(su, AG_SRCCOLORKEY, 0);
AG_SetAlpha(su, font->bglyphs[0]->flags & AG_SRCALPHA,
font->bglyphs[0]->format->alpha);
FreeMetrics(&tm);
return (su);
}
/* Render an UCS-4 text string onto a new 8-bit surface. */
AG_Surface *
AG_TextRenderUCS4(const Uint32 *text)
{
switch (agTextState->font->type) {
#ifdef HAVE_FREETYPE
case AG_FONT_VECTOR:
if (agTextAntialiasing) {
return TextRenderFT_Blended(text);
} else {
return TextRenderFT_Mono(text);
}
#endif
case AG_FONT_BITMAP:
return TextRenderBitmap(text);
default:
return AG_SurfaceEmpty();
}
}
/* Return the rendered size in pixels of a UCS4-encoded string. */
void
AG_TextSizeUCS4(const Uint32 *ucs4, int *w, int *h)
{
AG_TextMetrics tm;
InitMetrics(&tm);
switch (agTextState->font->type) {
#ifdef HAVE_FREETYPE
case AG_FONT_VECTOR:
TextSizeFT(ucs4, &tm, 0);
break;
#endif
case AG_FONT_BITMAP:
TextSizeBitmap(ucs4, &tm, 0);
break;
default:
break;
}
if (w != NULL) { *w = tm.w; }
if (h != NULL) { *h = tm.h; }
FreeMetrics(&tm);
}
/*
* Return the rendered size in pixels of a UCS4-encoded string, along with
* a line count and the width of each line in an array.
*/
void
AG_TextSizeMultiUCS4(const Uint32 *ucs4, int *w, int *h, Uint **wLines,
Uint *nLines)
{
AG_TextMetrics tm;
InitMetrics(&tm);
switch (agTextState->font->type) {
#ifdef HAVE_FREETYPE
case AG_FONT_VECTOR:
TextSizeFT(ucs4, &tm, 1);
break;
#endif
case AG_FONT_BITMAP:
TextSizeBitmap(ucs4, &tm, 1);
break;
default:
break;
}
if (w != NULL) { *w = tm.w; }
if (h != NULL) { *h = tm.h; }
if (tm.nLines == 1) {
tm.wLines = Realloc(tm.wLines, sizeof(Uint));
tm.wLines[0] = tm.w;
}
if (wLines != NULL) { *wLines = tm.wLines; }
if (nLines != NULL) { *nLines = tm.nLines; }
}
/* Return the rendered size in pixels of a text string. */
void
AG_TextSize(const char *text, int *w, int *h)
{
Uint32 *ucs4;
if (text == NULL || text[0] == '\0') {
if (w != NULL) { *w = 0; }
if (h != NULL) { *h = 0; }
return;
}
#ifdef UTF8
ucs4 = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, text, 0);
#else
ucs4 = AG_ImportUnicode(AG_UNICODE_FROM_USASCII, text, 0);
#endif
AG_TextSizeUCS4(ucs4, w, h);
free(ucs4);
}
/*
* Return the rendered size in pixels of a text string, along with a line
* count and the width of each line in an array.
*/
void
AG_TextSizeMulti(const char *text, int *w, int *h, Uint **wLines, Uint *nLines)
{
Uint32 *ucs4;
#ifdef UTF8
ucs4 = AG_ImportUnicode(AG_UNICODE_FROM_UTF8, text, 0);
#else
ucs4 = AG_ImportUnicode(AG_UNICODE_FROM_USASCII, text, 0);
#endif
AG_TextSizeMultiUCS4(ucs4, w, h, wLines, nLines);
free(ucs4);
}
/*
* Parse a command-line font specification and set the default font.
* The format is ,,. Acceptable flags include 'b'
* (bold), 'i' (italic) and 'U' (uppercase).
*/
void
AG_TextParseFontSpec(const char *fontspec)
{
char buf[128];
char *fs, *s, *c;
Strlcpy(buf, fontspec, sizeof(buf));
fs = &buf[0];
if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
s[0] != '\0') {
AG_SetCfgString("font.face", s);
}
if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
s[0] != '\0') {
AG_SetCfgInt("font.size", atoi(s));
}
if ((s = AG_Strsep(&fs, ":,/")) != NULL &&
s[0] != '\0') {
Uint flags = 0;
for (c = &s[0]; *c != '\0'; c++) {
switch (*c) {
case 'b': flags |= AG_FONT_BOLD; break;
case 'i': flags |= AG_FONT_ITALIC; break;
case 'U': flags |= AG_FONT_UPPERCASE; break;
}
}
AG_SetCfgUint("font.flags", flags);
}
}
/*
* Canned dialogs.
*/
/* Display a message. */
void
AG_TextMsg(enum ag_text_msg_title title, const char *format, ...)
{
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
AG_Button *btnOK;
va_list args;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE|AG_WINDOW_NOBORDERS);
AG_WindowSetCaption(win, "%s", _(agTextMsgTitles[title]));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, 0);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
AG_WidgetFocus(btnOK);
AG_WindowShow(win);
}
/* Display a message for a given period of time. */
void
AG_TextTmsg(enum ag_text_msg_title title, Uint32 expire, const char *format,
...)
{
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
va_list args;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE|
AG_WINDOW_NOBORDERS);
AG_WindowSetCaption(win, "%s", _(agTextMsgTitles[title]));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, 0);
AG_LabelNewString(vb, 0, msg);
AG_WindowShow(win);
AG_LockTimeouts(NULL);
if (AG_TimeoutIsScheduled(NULL, &textMsgTo)) {
AG_ViewDetach((AG_Window *)textMsgTo.arg);
AG_DelTimeout(NULL, &textMsgTo);
}
AG_UnlockTimeouts(NULL);
AG_SetTimeout(&textMsgTo, TextTmsgExpire, win, 0);
AG_ScheduleTimeout(NULL, &textMsgTo, expire);
}
/*
* Display an informational message with a "Don't tell me again" option.
* The user preference is preserved in a persistent table. Unlike warnings,
* the dialog window is not modal.
*/
void
AG_TextInfo(const char *key, const char *format, ...)
{
char disableSw[64];
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
AG_Checkbox *cb;
AG_Button *btnOK;
va_list args;
AG_Variable *Vdisable;
Strlcpy(disableSw, "info.", sizeof(disableSw));
Strlcat(disableSw, key, sizeof(disableSw));
if (AG_Defined(agConfig,disableSw) &&
AG_GetInt(agConfig,disableSw) == 1)
return;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE|
AG_WINDOW_NOBORDERS);
AG_WindowSetCaption(win, "%s", _(agTextMsgTitles[AG_MSG_INFO]));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, 0);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
cb = AG_CheckboxNew(win, AG_CHECKBOX_HFILL, _("Don't tell me again"));
Vdisable = AG_SetInt(agConfig,disableSw,0);
AG_BindInt(cb, "state", &Vdisable->data.i);
AG_WidgetFocus(btnOK);
AG_WindowShow(win);
}
/*
* Display a warning message with a "Don't tell me again" option.
* The user preference is preserved in a persistent table.
*/
void
AG_TextWarning(const char *key, const char *format, ...)
{
char disableSw[64];
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
AG_Checkbox *cb;
AG_Button *btnOK;
va_list args;
AG_Variable *Vdisable;
Strlcpy(disableSw, "warn.", sizeof(disableSw));
Strlcat(disableSw, key, sizeof(disableSw));
if (AG_Defined(agConfig,disableSw) &&
AG_GetInt(agConfig,disableSw) == 1)
return;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE|
AG_WINDOW_NOBORDERS);
AG_WindowSetCaption(win, "%s", _(agTextMsgTitles[AG_MSG_WARNING]));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, 0);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
cb = AG_CheckboxNew(win, AG_CHECKBOX_HFILL, _("Don't tell me again"));
Vdisable = AG_SetInt(agConfig,disableSw,0);
AG_BindInt(cb, "state", &Vdisable->data.i);
AG_WidgetFocus(btnOK);
AG_WindowShow(win);
}
/* Display an error message. */
void
AG_TextError(const char *format, ...)
{
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
AG_Button *btnOK;
va_list args;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|AG_WINDOW_NOCLOSE|
AG_WINDOW_NOMINIMIZE|AG_WINDOW_NOMAXIMIZE|AG_WINDOW_NOBORDERS);
AG_WindowSetCaption(win, "%s", _(agTextMsgTitles[AG_MSG_ERROR]));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, 0);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
btnOK = AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
AG_WidgetFocus(btnOK);
AG_WindowShow(win);
}
/* Prompt the user with a choice of options. */
AG_Window *
AG_TextPromptOptions(AG_Button **bOpts, Uint nbOpts, const char *fmt, ...)
{
char text[AG_LABEL_MAX];
AG_Window *win;
AG_Box *bo;
va_list ap;
Uint i;
va_start(ap, fmt);
Vsnprintf(text, sizeof(text), fmt, ap);
va_end(ap);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NORESIZE|
AG_WINDOW_NOTITLE);
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 0);
AG_WindowSetSpacing(win, 8);
AG_LabelNewString(win, 0, text);
bo = AG_BoxNew(win, AG_BOX_HORIZ, AG_BOX_HOMOGENOUS|AG_BOX_HFILL);
for (i = 0; i < nbOpts; i++) {
bOpts[i] = AG_ButtonNew(bo, 0, "XXXXXXXXXXX");
}
AG_WindowShow(win);
return (win);
}
/* Prompt the user for a floating-point value. */
void
AG_TextEditFloat(double *fp, double min, double max, const char *unit,
const char *format, ...)
{
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
va_list args;
AG_Numerical *num;
va_start(args, format);
Vsnprintf(msg, sizeof(msg), format, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NOVRESIZE);
AG_WindowSetCaption(win, "%s", _("Enter real number"));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, AG_VBOX_HFILL);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HFILL);
{
num = AG_NumericalNewDblR(vb, 0, unit, _("Number: "),
fp, min, max);
AG_SetEvent(num, "numerical-return", AGWINDETACH(win));
}
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
/* TODO test type */
AG_WidgetFocus(num);
AG_WindowShow(win);
}
/* Create a dialog to edit a string value. */
void
AG_TextEditString(char *sp, size_t len, const char *msgfmt, ...)
{
char msg[AG_LABEL_MAX];
AG_Window *win;
AG_VBox *vb;
va_list args;
AG_Textbox *tb;
va_start(args, msgfmt);
Vsnprintf(msg, sizeof(msg), msgfmt, args);
va_end(args);
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NOVRESIZE);
AG_WindowSetCaption(win, "%s", _("Edit string"));
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 1);
vb = AG_VBoxNew(win, AG_VBOX_HFILL);
AG_LabelNewString(vb, 0, msg);
vb = AG_VBoxNew(win, AG_VBOX_HFILL);
tb = AG_TextboxNew(vb, 0, NULL);
AG_TextboxBindUTF8(tb, sp, len);
AG_SetEvent(tb, "textbox-return", AGWINDETACH(win));
vb = AG_VBoxNew(win, AG_VBOX_HOMOGENOUS|AG_VBOX_HFILL|AG_VBOX_VFILL);
AG_ButtonNewFn(vb, 0, _("Ok"), AGWINDETACH(win));
AG_WidgetFocus(tb);
AG_WindowShow(win);
}
/* Prompt the user for a string. */
void
AG_TextPromptString(const char *prompt, void (*ok_fn)(AG_Event *),
const char *fmt, ...)
{
AG_Window *win;
AG_Box *bo;
AG_Button *btnOK;
AG_Textbox *tb;
AG_Event *ev;
win = AG_WindowNew(AG_WINDOW_MODAL|AG_WINDOW_NOVRESIZE|
AG_WINDOW_NOTITLE);
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 0);
AG_WindowSetSpacing(win, 8);
bo = AG_BoxNew(win, AG_BOX_VERT, AG_BOX_HFILL);
AG_LabelNewString(bo, 0, prompt);
bo = AG_BoxNew(win, AG_BOX_VERT, AG_BOX_HFILL);
{
tb = AG_TextboxNew(bo, 0, NULL);
ev = AG_SetEvent(tb, "textbox-return", ok_fn, NULL);
AG_EVENT_GET_ARGS(ev, fmt)
AG_EVENT_INS_VAL(ev, AG_VARIABLE_STRING, "string", s,
&tb->ed->string[0]);
AG_AddEvent(tb, "textbox-return", AGWINDETACH(win));
}
bo = AG_BoxNew(win, AG_BOX_HORIZ, AG_BOX_HOMOGENOUS|AG_BOX_HFILL);
{
btnOK = AG_ButtonNew(bo, 0, _("Ok"));
ev = AG_SetEvent(btnOK, "button-pushed", ok_fn, NULL);
AG_EVENT_GET_ARGS(ev, fmt);
AG_EVENT_INS_VAL(ev, AG_VARIABLE_STRING, "string", s,
&tb->ed->string[0]);
AG_AddEvent(btnOK, "button-pushed", AGWINDETACH(win));
AG_ButtonNewFn(bo, 0, _("Cancel"), AGWINDETACH(win));
}
AG_WidgetFocus(tb);
AG_WindowShow(win);
}
/* Align a text surface inside a given space. */
void
AG_TextAlign(int *x, int *y, int wArea, int hArea, int wText, int hText,
int lPad, int rPad, int tPad, int bPad, enum ag_text_justify justify,
enum ag_text_valign valign)
{
switch (justify) {
case AG_TEXT_LEFT:
*x = lPad;
break;
case AG_TEXT_CENTER:
*x = (wArea + lPad + rPad)/2 - wText/2;
break;
case AG_TEXT_RIGHT:
*x = wArea - rPad - wText;
break;
}
switch (valign) {
case AG_TEXT_TOP:
*y = tPad;
break;
case AG_TEXT_MIDDLE:
*y = (hArea + tPad + bPad)/2 - hText/2;
break;
case AG_TEXT_BOTTOM:
*y = hArea - bPad - wText;
break;
}
}
AG_ObjectClass agFontClass = {
"AG_Font",
sizeof(AG_Font),
{ 0, 0 },
NULL, /* init */
NULL, /* free */
FontDestroy,
NULL, /* load */
NULL, /* save */
NULL, /* edit */
};