#include "common.h" #include "config.h" #include "texteffects.h" #include "tools.h" #include #ifndef DISABLE_ANIMATED_TEXT //Redefine macros #undef TE_LOCK #define TE_LOCK UpdateLock() #undef TE_UNLOCK #define TE_UNLOCK UpdateUnlock() #endif #ifdef HAVE_FREETYPE // needed for case-insensitive sort of vector (for fonts) struct NoCase { bool operator()(const string& x, const string& y) { string lv(x); string rv(y); lcase(lv); lcase(rv); return lv < rv; } void lcase(string& s) { int n = s.size(); for(int i = 0; i < n; i++) s[i] = tolower(s[i]); } }; #endif #ifdef DISABLE_ANIMATED_TEXT cEnigmaTextEffects EnigmaTextEffects; #else cEnigmaTextEffects EnigmaTextEffects("EnigmaNG effects"); #endif #ifdef DISABLE_ANIMATED_TEXT cEnigmaTextEffects::cEnigmaTextEffects(void) : osd(NULL) #else cEnigmaTextEffects::cEnigmaTextEffects(const char *Description) : cThread(Description), osd(NULL), condSleep(), mutexSleep(), mutexRunning() #endif { // SetPriority(19); #ifdef HAVE_FREETYPE availTTFs = NULL; nMaxTTFs = 0; #endif } cEnigmaTextEffects::~cEnigmaTextEffects(void) { #ifndef DISABLE_ANIMATED_TEXT //TODO? Stop(); #endif #ifdef HAVE_FREETYPE if (availTTFs) { char **ptr = availTTFs; while (*ptr) { delete(*ptr); ptr++; } free(availTTFs); availTTFs = NULL; } #endif } #ifndef DISABLE_ANIMATED_TEXT void cEnigmaTextEffects::Action(void) { mutexRunning.Lock(); mutexSleep.Lock(); debug("cEnigmaTextEffects::Action() %p\n", pthread_self()); while (EnigmaConfig.useTextEffects && osd) { uint64_t nNow = cTimeMs::Now(); int nSleepMs = 0; TE_LOCK; //This causes an initial wait until thet first Flush() is called (which TE_UNKOCKs) for (tEffects::iterator effect = vecEffects.begin(); (effect != vecEffects.end()) && osd; effect++) { tEffect *e = (*effect); if (e == NULL) continue; if (e->nNextUpdate == 0) { e->nNextUpdate = nNow + (e->nAction == 0 ? EnigmaConfig.scrollPause : EnigmaConfig.blinkPause); } else if(nNow >= e->nNextUpdate) { DoEffect(e, nNow); } // printf("NOW=%llu NEXT=%llu DIFF=%d SLEEP=%d\n", nNow, e->nNextUpdate, (int)(e->nNextUpdate - nNow), nSleepMs); int nDiff = max(3, (int)(e->nNextUpdate - nNow)); if (nSleepMs == 0 || nDiff < nSleepMs) nSleepMs = nDiff; } if (osd) osd->Flush(); TE_UNLOCK; if (osd) { // printf("SLEEP1: %d, %p\n", nSleepMs, pthread_self()); if (nSleepMs) condSleep.TimedWait(mutexSleep, nSleepMs); else condSleep.TimedWait(mutexSleep, EnigmaConfig.scrollPause); //TODO // printf("SLEEP2: %d, %p\n", nSleepMs, pthread_self()); } } mutexSleep.Unlock(); mutexRunning.Unlock(); } void cEnigmaTextEffects::DoEffect(tEffect *e, uint64_t nNow) { bool fDrawItem = ((yMessageTop == 0) || (e->y + e->Height < yMessageTop)); switch (e->nAction) { case 0: // Scroll DoScroll(e, nNow, fDrawItem); break; case 1: // Blink DoBlink(e, nNow, fDrawItem); break; } } void cEnigmaTextEffects::DoScroll(tEffect *e, uint64_t nNow, bool fDrawItem) { // debug("cEnigmaTextEffects::DoScroll()\n"); if (e->Font->Width(e->strText.c_str()) <= e->Width) { if (fDrawItem) { if (e->Skin) e->Skin->DrawTitle(e->strText.c_str()); else osd->DrawText(e->x, e->y, e->strText.c_str(), e->ColorFg, e->ColorBg, e->Font, e->Width, e->Height, e->Alignment); } if (nNow) e->nNextUpdate = nNow + EnigmaConfig.scrollPause; return; } if (nNow) { int nDelay = EnigmaConfig.scrollDelay; if (fDrawItem) { switch (e->nDirection) { case 0: // Scroll from left to right if (e->Font->Width(e->strText.c_str() + e->nOffset) <= e->Width) { if (EnigmaConfig.scrollMode) e->nDirection = 2; else e->nDirection = 1; nDelay = EnigmaConfig.scrollPause; } else if (e->nOffset < e->strText.length()) e->nOffset++; break; case 1: // Scroll from right to left if (e->nOffset > 0) e->nOffset--; if (e->nOffset <= 0) { e->nDirection = false; nDelay = EnigmaConfig.scrollPause; } break; case 2: // Restart scrolling from the left nDelay = EnigmaConfig.scrollPause; e->nOffset = 0; e->nDirection = 0; break; } } e->nNextUpdate = nNow + nDelay; } if (fDrawItem) { // printf("SCROLL: %d %d %d/%d (%s) %d %lu %lu\n", e->nOffset, e->nDirection, e->Font->Width(e->strText.c_str() + e->nOffset), e->Width, e->strText.c_str() + e->nOffset, e->strText.length(), nNow, e->nNextUpdate); if (e->Skin) e->Skin->DrawTitle(e->strText.c_str() + e->nOffset); else osd->DrawText(e->x, e->y, e->strText.c_str() + e->nOffset, e->ColorFg, e->ColorBg, e->Font, e->Width, e->Height); } } void cEnigmaTextEffects::DoBlink(tEffect *e, uint64_t nNow, bool fDrawItem) { // debug("cEnigmaTextEffects::DoBlink()\n"); if (fDrawItem) { if (nNow) { e->nDirection = (e->nDirection == 0 ? 1 : 0); e->nNextUpdate = nNow + EnigmaConfig.blinkPause; } if (e->nDirection == 1) osd->DrawText(e->x, e->y, e->strText.c_str() + e->nOffset, e->ColorFg, e->ColorBg, e->Font, e->Width, e->Height, e->Alignment); else osd->DrawText(e->x, e->y, e->strText.c_str() + e->nOffset, e->ColorBg, e->ColorBg, e->Font, e->Width, e->Height, e->Alignment); } else { e->nNextUpdate = nNow + EnigmaConfig.blinkPause; } } bool cEnigmaTextEffects::Start(cOsd *o) { osd = o; if (!EnigmaConfig.useTextEffects) return false; debug("cEnigmaTextEffects::Start(%p) %p\n", osd, pthread_self()); if (osd == NULL) return false; if (Running()) { error("cEnigmaTextEffects::Start - already running\n"); return false; //TODO? maybe Cancel() } yMessageTop = 0; TE_LOCK; return cThread::Start(); } void cEnigmaTextEffects::Stop(void) { //Must be TE_LOCKed by caller (calls TE_UNLOCK) debug("cEnigmaTextEffects::Stop()\n"); osd = NULL; Clear(); TE_UNLOCK; Wakeup(); // break sleeping Action() thread mutexRunning.Lock(); // Wait for Action() to finish mutexRunning.Unlock(); } void cEnigmaTextEffects::Clear(void) { debug("cEnigmaTextEffects::Clear()\n"); //Must be TE_LOCKed by caller for (tEffects::iterator effect = vecEffects.begin(); effect != vecEffects.end(); effect++) { delete(*effect); } vecEffects.clear(); } void cEnigmaTextEffects::PauseEffects(int y) { debug("cEnigmaTextEffects::PauseEffects(%d)\n", y); //Must be TE_LOCKed by caller yMessageTop = y; } void cEnigmaTextEffects::ResetText(int i, tColor ColorFg, tColor ColorBg, bool fDraw) { debug("cEnigmaTextEffects::ResetText(%d)\n", i); //Must be TE_LOCKed by caller if (i < 0 || i >= (int)vecEffects.size()) return; tEffect *e = vecEffects[i]; if (e) { if (fDraw && osd) osd->DrawText(e->x, e->y, e->strText.c_str(), ColorFg ? ColorFg : e->ColorFg, ColorBg ? ColorBg : e->ColorBg, e->Font, e->Width, e->Height); delete(e); vecEffects[i] = NULL; } } void cEnigmaTextEffects::UpdateTextWidth(int i, int Width) { debug("cEnigmaTextEffects::UpdateTextWidth(%d)\n", i); //Must be TE_LOCKed by caller if (i < 0 || i >= (int)vecEffects.size()) return; tEffect *e = vecEffects[i]; if (e) { e->Width = Width; } } int cEnigmaTextEffects::DrawAnimatedTitle(int o_id, int action, const char *s, const cFont *Font, int Width, cSkinEnigmaOsd *skin) { //Must be TE_LOCKed by caller if (Font == NULL || osd == NULL || skin == NULL) return -1; debug("cEnigmaTextEffects::DrawAnimatedTitle(%d, %d, %s)\n", o_id, EnigmaConfig.useTextEffects, s); if (o_id >= 0) { // Update animated text tEffect *effect = vecEffects[o_id]; if (effect) { if (s == NULL) effect->strText = ""; else if (strcmp(effect->strText.c_str(), s) != 0) { effect->strText = s; effect->nOffset = 0; effect->nDirection = 0; } DoEffect(effect); return o_id; } else { return -1; } } else { skin->DrawTitle(s); if (EnigmaConfig.useTextEffects && ((Font->Width(s ? s : "") > Width) || (action > 0))) { // New scrolling text tEffect *effect = new tEffect; if (effect == NULL) { return -1; } effect->nAction = action; effect->strText = std::string(s ? s : ""); effect->Width = Width; effect->Font = Font; effect->Skin = skin; vecEffects.push_back(effect); int id = vecEffects.size() - 1; return id; } else { return -1; } } } int cEnigmaTextEffects::DrawAnimatedText(int o_id, int action, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment) { //Must be TE_LOCKed by caller if (Font == NULL || osd == NULL) return -1; debug("cEnigmaTextEffects::DrawAnimatedText(%d, %d, %s)\n", o_id, EnigmaConfig.useTextEffects, s); if (o_id >= 0) { // Update animated text tEffect *effect = vecEffects[o_id]; if (effect) { if (s == NULL) effect->strText = ""; else if (strcmp(effect->strText.c_str(), s) != 0) { effect->strText = s; effect->nOffset = 0; effect->nDirection = 0; } DoEffect(effect); return o_id; } else { return -1; } } else { if (Height == 0) Height = Font->Height(s); osd->DrawText(x, y, s ? s : "", ColorFg, ColorBg, Font, Width, Height, Alignment); // New animated text tEffect *effect = new tEffect; if (effect == NULL) { return -1; } effect->nAction = action; effect->strText = std::string(s ? s : ""); effect->x = x; effect->y = y; effect->Width = Width; effect->Height = Height; effect->ColorFg = ColorFg; effect->ColorBg = ColorBg; effect->Font = Font; effect->Alignment = Alignment; vecEffects.push_back(effect); int id = vecEffects.size() - 1; return id; } } #endif //DISABLE_ANIMATED_TEXT #ifdef HAVE_FREETYPE const char **cEnigmaTextEffects::GetAvailTTFs(void) { if (availTTFs == NULL) { std::vector vecFonts; cReadDir d(EnigmaConfig.GetFontsDir()); struct dirent *e; while ((e = d.Next()) != NULL) { if ((strcmp(e->d_name, ".") != 0) && (strcmp(e->d_name, "..") != 0)) { if (strcmp(e->d_name + strlen(e->d_name) - 4, ".ttf") == 0) { debug("Loading %s\n", e->d_name); vecFonts.push_back(std::string(e->d_name)); } else { error("Ignoring non-font file: %s\n", e->d_name); } } } if (vecFonts.size() > 0) { sort(vecFonts.begin(), vecFonts.end(), NoCase()); availTTFs = (char **)calloc(vecFonts.size() + 1, sizeof(char*)); if (availTTFs) { char **ptr = availTTFs; for (vector::iterator i = vecFonts.begin(); i != vecFonts.end(); i++) { if (!(*i).empty()) { *ptr = strdup((*i).c_str()); ptr++; nMaxTTFs++; } } } } vecFonts.clear(); } return (const char**)availTTFs; } #endif // vim:et:sw=2:ts=2: