/* * Copyright (c) 2002-2008 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. */ #include #include #include "label.h" #include "primitive.h" #include "text_cache.h" #include #include #include static AG_LabelFormatSpec *fmts = NULL; /* Format specifiers */ static int nFmts = 0; #ifdef AG_THREADS static AG_Mutex fmtsLock; #endif /* Create a new polled label. */ AG_Label * AG_LabelNewPolled(void *parent, Uint flags, const char *fmt, ...) { AG_Label *lbl; va_list ap; const char *p; lbl = Malloc(sizeof(AG_Label)); AG_ObjectInit(lbl, &agLabelClass); lbl->type = AG_LABEL_POLLED; lbl->text = Strdup(fmt); lbl->flags |= flags; if (!(flags & AG_LABEL_NO_HFILL)) { AG_ExpandHoriz(lbl); } if (flags & AG_LABEL_VFILL) { AG_ExpandVert(lbl); } #ifdef AG_THREADS lbl->poll.lock = NULL; #endif lbl->tCache = AG_TextCacheNew(lbl, 64, 16); va_start(ap, fmt); for (p = fmt; *p != '\0'; p++) { if (*p != '%' || *(p+1) == '\0') { continue; } switch (*(p+1)) { case ' ': case '(': case ')': case '%': break; default: if (lbl->poll.nptrs+1 < AG_LABEL_MAX_POLLPTRS) { lbl->poll.ptrs[lbl->poll.nptrs++] = va_arg(ap, void *); } break; } } va_end(ap); AG_ObjectAttach(parent, lbl); return (lbl); } /* Create a new polled label associated with a mutex. */ AG_Label * AG_LabelNewPolledMT(void *parent, Uint flags, AG_Mutex *mutex, const char *fmt, ...) { AG_Label *lbl; va_list ap; const char *p; lbl = Malloc(sizeof(AG_Label)); AG_ObjectInit(lbl, &agLabelClass); lbl->type = AG_LABEL_POLLED_MT; lbl->text = Strdup(fmt); lbl->flags |= flags; if (!(flags & AG_LABEL_NO_HFILL)) { AG_ExpandHoriz(lbl); } if (flags & AG_LABEL_VFILL) { AG_ExpandVert(lbl); } #ifdef AG_THREADS lbl->poll.lock = mutex; #endif lbl->tCache = AG_TextCacheNew(lbl, 64, 16); va_start(ap, fmt); for (p = fmt; *p != '\0'; p++) { if (*p != '%' || *(p+1) == '\0') { continue; } switch (*(p+1)) { case ' ': case '(': case ')': case '%': break; default: if (lbl->poll.nptrs+1 < AG_LABEL_MAX_POLLPTRS) { lbl->poll.ptrs[lbl->poll.nptrs++] = va_arg(ap, void *); } break; } } va_end(ap); AG_ObjectAttach(parent, lbl); return (lbl); } /* Create a new static label using the given formatted text. */ AG_Label * AG_LabelNewStatic(void *parent, Uint flags, const char *fmt, ...) { AG_Label *lbl; va_list ap; lbl = Malloc(sizeof(AG_Label)); AG_ObjectInit(lbl, &agLabelClass); lbl->type = AG_LABEL_STATIC; lbl->flags |= flags; if (flags & AG_LABEL_HFILL) { AG_ExpandHoriz(lbl); } if (flags & AG_LABEL_VFILL) { AG_ExpandVert(lbl); } if (fmt != NULL) { va_start(ap, fmt); Vasprintf(&lbl->text, fmt, ap); va_end(ap); } else { lbl->text = NULL; } AG_ObjectAttach(parent, lbl); return (lbl); } /* LabelNewStatic() variant without format string. */ AG_Label * AG_LabelNewStaticString(void *parent, Uint flags, const char *text) { AG_Label *lbl; lbl = Malloc(sizeof(AG_Label)); AG_ObjectInit(lbl, &agLabelClass); lbl->type = AG_LABEL_STATIC; lbl->flags |= flags; if (flags & AG_LABEL_HFILL) { AG_ExpandHoriz(lbl); } if (flags & AG_LABEL_VFILL) { AG_ExpandVert(lbl); } lbl->text = (text != NULL) ? Strdup(text) : NULL; AG_ObjectAttach(parent, lbl); return (lbl); } static void SizeRequest(void *obj, AG_SizeReq *r) { AG_Label *lbl = obj; if (lbl->flags & AG_LABEL_NOMINSIZE) { r->w = lbl->lPad + lbl->rPad; r->h = agTextFontHeight + lbl->tPad + lbl->bPad; return; } switch (lbl->type) { case AG_LABEL_STATIC: if (lbl->surface != -1) { r->w = WSURFACE(lbl,lbl->surface)->w; r->h = WSURFACE(lbl,lbl->surface)->h; } else { AG_TextSize(lbl->text, &r->w, &r->h); } r->w += lbl->lPad + lbl->rPad; r->h += lbl->tPad + lbl->bPad; break; case AG_LABEL_POLLED: case AG_LABEL_POLLED_MT: r->w = lbl->wPre + lbl->lPad + lbl->rPad; r->h = lbl->hPre + lbl->tPad + lbl->bPad; break; } } static int SizeAllocate(void *obj, const AG_SizeAlloc *a) { AG_Label *lbl = obj; int wLbl, hLbl; if (a->w < 1 || a->h < 1) { return (-1); } lbl->rClip.x = lbl->lPad; lbl->rClip.y = lbl->tPad; lbl->rClip.w = a->w - lbl->rPad; lbl->rClip.h = a->h - lbl->bPad; if (lbl->text == NULL) return (0); /* * If the widget area is too small to display the complete * string, draw a "..." at the end. */ AG_TextSize(lbl->text, &wLbl, &hLbl); if ((wLbl + lbl->lPad + lbl->rPad) > a->w) { lbl->flags |= AG_LABEL_PARTIAL; if (lbl->surfaceCont == -1) { /* TODO share this between all widgets */ AG_PushTextState(); AG_TextColor(TEXT_COLOR); lbl->surfaceCont = AG_WidgetMapSurface(lbl, AG_TextRender(" ... ")); AG_PopTextState(); } } else { lbl->flags &= ~AG_LABEL_PARTIAL; } return (0); } static void Init(void *obj) { AG_Label *lbl = obj; lbl->type = AG_LABEL_STATIC; lbl->flags = 0; lbl->text = NULL; lbl->surface = -1; lbl->surfaceCont = -1; lbl->lPad = 2; lbl->rPad = 2; lbl->tPad = 0; lbl->bPad = 1; lbl->wPre = -1; lbl->hPre = agTextFontHeight; lbl->justify = AG_TEXT_LEFT; lbl->valign = AG_TEXT_TOP; lbl->tCache = NULL; SLIST_INIT(&lbl->lflags); memset(lbl->poll.ptrs, 0, sizeof(void *)*AG_LABEL_MAX_POLLPTRS); lbl->poll.nptrs = 0; } /* Size the widget to accomodate the given text. */ void AG_LabelSizeHint(AG_Label *lbl, Uint nlines, const char *text) { int hLbl; AG_ObjectLock(lbl); if (nlines > 0) { AG_TextSize(text, &lbl->wPre, &hLbl); lbl->hPre = nlines*hLbl + (nlines-1)*agTextFontLineSkip; } else { AG_TextSize(text, &lbl->wPre, &lbl->hPre); } AG_ObjectUnlock(lbl); } /* Set the padding around the label in pixels. */ void AG_LabelSetPadding(AG_Label *lbl, int lPad, int rPad, int tPad, int bPad) { AG_ObjectLock(lbl); if (lPad != -1) { lbl->lPad = lPad; } if (rPad != -1) { lbl->rPad = rPad; } if (tPad != -1) { lbl->tPad = tPad; } if (bPad != -1) { lbl->bPad = bPad; } AG_ObjectUnlock(lbl); } /* Justify the text in the specified way. */ void AG_LabelJustify(AG_Label *lbl, enum ag_text_justify justify) { AG_ObjectLock(lbl); lbl->justify = justify; AG_ObjectUnlock(lbl); } /* Vertically align the text in the specified way. */ void AG_LabelValign(AG_Label *lbl, enum ag_text_valign valign) { AG_ObjectLock(lbl); lbl->valign = valign; AG_ObjectUnlock(lbl); } /* Change the text displayed by the label (format string). */ void AG_LabelText(AG_Label *lbl, const char *fmt, ...) { va_list ap; AG_ObjectLock(lbl); Free(lbl->text); va_start(ap, fmt); Vasprintf(&lbl->text, fmt, ap); va_end(ap); lbl->flags |= AG_LABEL_REGEN; AG_ObjectUnlock(lbl); } /* Change the text displayed by the label. */ void AG_LabelString(AG_Label *lbl, const char *s) { AG_ObjectLock(lbl); Free(lbl->text); lbl->text = Strdup(s); lbl->flags |= AG_LABEL_REGEN; AG_ObjectUnlock(lbl); } /* * Built-in extended format specifiers. */ static void PrintU8(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%u", (unsigned int)AG_LABEL_ARG(lbl,Uint8)); } static void PrintS8(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%d", (int)AG_LABEL_ARG(lbl,Sint8)); } static void PrintU16(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%u", (unsigned int)AG_LABEL_ARG(lbl,Uint16)); } static void PrintS16(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%d", (int)AG_LABEL_ARG(lbl,Sint16)); } static void PrintU32(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%u", (unsigned int)AG_LABEL_ARG(lbl,Uint32)); } static void PrintS32(AG_Label *lbl, char *s, size_t len, int fPos) { Snprintf(s, len, "%d", (int)AG_LABEL_ARG(lbl,Sint32)); } static void PrintOBJNAME(AG_Label *lbl, char *s, size_t len, int fPos) { AG_Object *ob = AG_LABEL_ARG(lbl,AG_Object *); Snprintf(s, len, "%s", ob != NULL ? ob->name : "(null)"); } static void PrintOBJTYPE(AG_Label *lbl, char *s, size_t len, int fPos) { AG_Object *ob = AG_LABEL_ARG(lbl,AG_Object *); Snprintf(s, len, "%s", ob->cls->name); } static void PrintIBOOL(AG_Label *lbl, char *s, size_t len, int fPos) { int *flag = &AG_LABEL_ARG(lbl,int); Snprintf(s, len, "%s", *flag ? _("yes") : _("no")); } static void PrintFLAGS(AG_Label *lbl, char *s, size_t len, int fPos) { Uint *flags = &AG_LABEL_ARG(lbl,Uint); struct ag_label_flag *lfl; s[0] = '\0'; SLIST_FOREACH(lfl, &lbl->lflags, lflags) { if (lfl->idx == fPos && *flags & (Uint)lfl->v) { if (s[0] != '\0') { Strlcat(s, ", ", len); } Strlcat(s, lfl->text, len); } } } static void PrintFLAGS8(AG_Label *lbl, char *s, size_t len, int fPos) { Uint8 *flags = &AG_LABEL_ARG(lbl,Uint8); struct ag_label_flag *lfl; s[0] = '\0'; SLIST_FOREACH(lfl, &lbl->lflags, lflags) { if (lfl->idx == fPos && *flags & (Uint8)lfl->v) { if (s[0] != '\0') { Strlcat(s, ", ", len); } Strlcat(s, lfl->text, len); } } } static void PrintFLAGS16(AG_Label *lbl, char *s, size_t len, int fPos) { Uint16 *flags = &AG_LABEL_ARG(lbl,Uint16); struct ag_label_flag *lfl; s[0] = '\0'; SLIST_FOREACH(lfl, &lbl->lflags, lflags) { if (lfl->idx == fPos && *flags & (Uint16)lfl->v) { if (s[0] != '\0') { Strlcat(s, ", ", len); } Strlcat(s, lfl->text, len); } } } static void PrintFLAGS32(AG_Label *lbl, char *s, size_t len, int fPos) { Uint32 *flags = &AG_LABEL_ARG(lbl,Uint32); struct ag_label_flag *lfl; s[0] = '\0'; SLIST_FOREACH(lfl, &lbl->lflags, lflags) { if (lfl->idx == fPos && *flags & (Uint32)lfl->v) { if (s[0] != '\0') { Strlcat(s, ", ", len); } Strlcat(s, lfl->text, len); } } } /* Register built-in format specifiers. */ void AG_RegisterBuiltinLabelFormats(void) { AG_MutexInit(&fmtsLock); AG_RegisterLabelFormat("u8", PrintU8); AG_RegisterLabelFormat("s8", PrintS8); AG_RegisterLabelFormat("u16", PrintU16); AG_RegisterLabelFormat("s16", PrintS16); AG_RegisterLabelFormat("u32", PrintU32); AG_RegisterLabelFormat("s32", PrintS32); AG_RegisterLabelFormat("objname", PrintOBJNAME); AG_RegisterLabelFormat("objtype", PrintOBJTYPE); AG_RegisterLabelFormat("ibool", PrintIBOOL); AG_RegisterLabelFormat("flags", PrintFLAGS); AG_RegisterLabelFormat("flags8", PrintFLAGS8); AG_RegisterLabelFormat("flags16", PrintFLAGS16); AG_RegisterLabelFormat("flags32", PrintFLAGS32); } /* Register a new format specifier. */ void AG_RegisterLabelFormat(const char *fmt, AG_LabelFormatFn fn) { AG_LabelFormatSpec *fs; if (!agGUI) { return; } AG_MutexLock(&fmtsLock); fmts = Realloc(fmts, (nFmts+1)*sizeof(AG_LabelFormatSpec)); fs = &fmts[nFmts++]; fs->fmt = Strdup(fmt); fs->fmtLen = strlen(fmt); fs->fn = fn; AG_MutexUnlock(&fmtsLock); } #define PF(fmt,arg) \ Snprintf(s2, AG_LABEL_MAX, (fmt), (arg)); \ Strlcat(s, s2, AG_LABEL_MAX); \ fPos++ #define PSTRING(ps) \ Strlcat(s, ps, AG_LABEL_MAX); \ fPos++ #ifdef HAVE_64BIT /* Print a polled 64-bit value (%ll*). */ static int PrintPolled64(AG_Label *lbl, const char *f, char *s, char *s2) { int fPos = 0; switch (*f) { # ifdef HAVE_LONG_DOUBLE case 'f': PF("%.2Lf", AG_LABEL_ARG(lbl,long double)); break; case 'g': PF("%Lg", AG_LABEL_ARG(lbl,long double)); break; # endif case 'd': case 'i': PF("%lld", (long long)AG_LABEL_ARG(lbl,Sint64)); break; case 'o': PF("%llo", (unsigned long long)AG_LABEL_ARG(lbl,Uint64)); break; case 'u': PF("%llu", (unsigned long long)AG_LABEL_ARG(lbl,Uint64)); break; case 'x': PF("%llx", (unsigned long long)AG_LABEL_ARG(lbl,Uint64)); break; case 'X': PF("%llX", (unsigned long long)AG_LABEL_ARG(lbl,Uint64)); break; } return (fPos); } #endif /* HAVE_64BIT */ static __inline__ void GetPosition(AG_Label *lbl, AG_Surface *su, int *x, int *y) { *x = lbl->lPad + AG_TextJustifyOffset(WIDTH(lbl) - (lbl->lPad+lbl->rPad), su->w); *y = lbl->tPad + AG_TextValignOffset(HEIGHT(lbl) - (lbl->tPad+lbl->bPad), su->h); } /* Display a polled label. */ static void DrawPolled(AG_Label *lbl) { char s[AG_LABEL_MAX]; char s2[AG_LABEL_MAX]; char *f; int i, fPos = 0; int x, y; int su; if (lbl->text == NULL || lbl->text[0] == '\0') { return; } s[0] = '\0'; s2[0] = '\0'; for (f = lbl->text; *f != '\0'; f++) { if (f[0] == '%' && f[1] != '\0') { switch (f[1]) { case 'l': switch (f[2]) { case 'f': PF("%.2f", AG_LABEL_ARG(lbl,double)); f++; break; case 'g': PF("%g", AG_LABEL_ARG(lbl,double)); f++; break; #ifdef HAVE_64BIT case 'l': fPos += PrintPolled64(lbl, &f[3], s, s2); f++; break; #endif /* HAVE_64BIT */ } break; case 'd': case 'i': PF("%d", AG_LABEL_ARG(lbl,int)); break; case 'o': PF("%o", AG_LABEL_ARG(lbl,unsigned int)); break; case 'u': PF("%u", AG_LABEL_ARG(lbl,unsigned int)); break; case 'x': PF("%x", AG_LABEL_ARG(lbl,unsigned int)); break; case 'X': PF("%X", AG_LABEL_ARG(lbl,unsigned int)); break; case 'c': s2[0] = AG_LABEL_ARG(lbl,char); s2[1] = '\0'; PSTRING(s2); break; case 's': PSTRING(&AG_LABEL_ARG(lbl,char)); break; case 'p': PF("%p", AG_LABEL_ARG(lbl,void *)); break; case 'f': PF("%.2f", AG_LABEL_ARG(lbl,float)); break; case 'g': PF("%g", AG_LABEL_ARG(lbl,float)); break; case '[': for (i = 0; i < nFmts; i++) { if (strncmp(fmts[i].fmt, &f[2], fmts[i].fmtLen) != 0) { continue; } fmts[i].fn(lbl, s2, sizeof(s2), fPos); f += fmts[i].fmtLen+1; PSTRING(s2); break; } break; case '%': s2[0] = '%'; s2[1] = '\0'; Strlcat(s, s2, sizeof(s)); break; } f++; } else { s2[0] = *f; s2[1] = '\0'; Strlcat(s, s2, sizeof(s)); } } su = AG_TextCacheGet(lbl->tCache,s); GetPosition(lbl, WSURFACE(lbl,su), &x, &y); AG_WidgetBlitSurface(lbl, su, x, y); } static void Draw(void *obj) { AG_Label *lbl = obj; int x, y, cw = 0; /* make compiler happy */ if (lbl->flags & AG_LABEL_FRAME) AG_DrawFrame(lbl, AG_RECT(0, 0, WIDTH(lbl), HEIGHT(lbl)), -1, AG_COLOR(FRAME_COLOR)); if (lbl->flags & AG_LABEL_PARTIAL) { cw = WSURFACE(lbl,lbl->surfaceCont)->w; if (WIDTH(lbl) <= cw) { AG_PushClipRect(lbl, AG_RECT(0, 0, WIDTH(lbl), HEIGHT(lbl))); AG_WidgetBlitSurface(lbl, lbl->surfaceCont, 0, lbl->tPad); AG_PopClipRect(); return; } AG_PushClipRect(lbl, AG_RECT(0, 0, WIDTH(lbl)-cw, HEIGHT(lbl))); } else { AG_PushClipRect(lbl, lbl->rClip); } AG_PushTextState(); AG_TextJustify(lbl->justify); AG_TextValign(lbl->valign); AG_TextColor(TEXT_COLOR); switch (lbl->type) { case AG_LABEL_STATIC: if (lbl->surface == -1) { lbl->surface = (lbl->text == NULL) ? -1 : AG_WidgetMapSurface(lbl, AG_TextRender(lbl->text)); } else if (lbl->flags & AG_LABEL_REGEN) { if (lbl->text != NULL) { AG_WidgetReplaceSurface(lbl, 0, AG_TextRender(lbl->text)); } else { lbl->surface = -1; } } lbl->flags &= ~(AG_LABEL_REGEN); if (lbl->surface != -1) { GetPosition(lbl, WSURFACE(lbl,lbl->surface), &x, &y); AG_WidgetBlitSurface(lbl, lbl->surface, x, y); } break; case AG_LABEL_POLLED: DrawPolled(lbl); break; case AG_LABEL_POLLED_MT: AG_MutexLock(lbl->poll.lock); DrawPolled(lbl); AG_MutexUnlock(lbl->poll.lock); break; } AG_PopClipRect(); if (lbl->flags & AG_LABEL_PARTIAL) { GetPosition(lbl, WSURFACE(lbl,lbl->surfaceCont), &x, &y); AG_WidgetBlitSurface(lbl, lbl->surfaceCont, WIDTH(lbl) - cw, y); } AG_PopTextState(); } static void Destroy(void *p) { AG_Label *lbl = p; struct ag_label_flag *lfl, *lflNext; for (lfl = SLIST_FIRST(&lbl->lflags); lfl != SLIST_END(&lbl->lflags); lfl = lflNext) { lflNext = SLIST_NEXT(lfl, lflags); Free(lfl); } Free(lbl->text); if (lbl->tCache != NULL) AG_TextCacheDestroy(lbl->tCache); } /* Register a flag description text. */ void AG_LabelFlagNew(AG_Label *lbl, Uint idx, const char *text, AG_VariableType type, Uint32 v) { struct ag_label_flag *lfl; AG_ObjectLock(lbl); lfl = Malloc(sizeof(struct ag_label_flag)); lfl->idx = idx; lfl->text = text; lfl->type = type; lfl->v = v; SLIST_INSERT_HEAD(&lbl->lflags, lfl, lflags); AG_ObjectUnlock(lbl); } AG_WidgetClass agLabelClass = { { "Agar(Widget:Label)", sizeof(AG_Label), { 0,0 }, Init, NULL, /* free */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };