/* * This file is subject to the terms of the GFX License. If a copy of * the license was not distributed with this file, you can obtain one at: * * http://ugfx.org/license.html */ /** * @file src/gwin/console.c * @brief GWIN sub-system console code. */ #include "gfx.h" #if GFX_USE_GWIN && GWIN_NEED_CONSOLE #include #include "gwin/class_gwin.h" #define GWIN_CONSOLE_USE_CLEAR_LINES TRUE // Clear each line before using it #define GWIN_CONSOLE_USE_FILLED_CHARS FALSE // Use filled characters instead of drawn characters #define GWIN_CONSOLE_BUFFER_SCROLLING TRUE // Use the history buffer to scroll when it is available // Our control flags #define GCONSOLE_FLG_NOSTORE (GWIN_FIRST_CONTROL_FLAG<<0) #define GCONSOLE_FLG_OVERRUN (GWIN_FIRST_CONTROL_FLAG<<1) /* * Stream interface implementation. The interface is write only */ #if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM #define Stream2GWindow(ip) ((GHandle)(((char *)(ip)) - (size_t)(&(((GConsoleObject *)0)->stream)))) static size_t GWinStreamWrite(void *ip, const uint8_t *bp, size_t n) { gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } static size_t GWinStreamRead(void *ip, uint8_t *bp, size_t n) { (void)ip; (void)bp; (void)n; return 0; } static msg_t GWinStreamPut(void *ip, uint8_t b) { gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } static msg_t GWinStreamGet(void *ip) {(void)ip; return RDY_OK; } static msg_t GWinStreamPutTimed(void *ip, uint8_t b, systime_t time) { (void)time; gwinPutChar(Stream2GWindow(ip), (char)b); return RDY_OK; } static msg_t GWinStreamGetTimed(void *ip, systime_t timeout) { (void)ip; (void)timeout; return RDY_OK; } static size_t GWinStreamWriteTimed(void *ip, const uint8_t *bp, size_t n, systime_t time) { (void)time; gwinPutCharArray(Stream2GWindow(ip), (const char *)bp, n); return RDY_OK; } static size_t GWinStreamReadTimed(void *ip, uint8_t *bp, size_t n, systime_t time) { (void)ip; (void)bp; (void)n; (void)time; return 0; } struct GConsoleWindowVMT_t { _base_asynchronous_channel_methods }; static const struct GConsoleWindowVMT_t GWindowConsoleVMT = { GWinStreamWrite, GWinStreamRead, GWinStreamPut, GWinStreamGet, GWinStreamPutTimed, GWinStreamGetTimed, GWinStreamWriteTimed, GWinStreamReadTimed }; #endif #if GWIN_CONSOLE_USE_HISTORY static void HistoryDestroy(GWindowObject *gh) { #define gcw ((GConsoleObject *)gh) // Deallocate the history buffer if required. if (gcw->buffer) { gfxFree(gcw->buffer); gcw->buffer = 0; } #undef gcw } static void HistoryRedraw(GWindowObject *gh) { #define gcw ((GConsoleObject *)gh) // No redrawing if there is no history if (!gcw->buffer) return; // We are printing the buffer - don't store it again gh->flags |= GCONSOLE_FLG_NOSTORE; #if !GWIN_CONSOLE_USE_CLEAR_LINES // Clear the screen gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor); #endif // Reset the cursor gcw->cx = 0; gcw->cy = 0; // Print the buffer gwinPutCharArray(gh, gcw->buffer, gcw->bufpos); #if !GWIN_CONSOLE_USE_CLEAR_LINES // Clear the remaining space if (gcw->cy + fy < gh->height) gdispGFillArea(gh->display, gh->x, gh->y+gcw->cy+fy, gh->width, gh->height-(gcw->cy+fy), gh->bgcolor); #endif // Turn back on storing of buffer contents gh->flags &= ~GCONSOLE_FLG_NOSTORE; #undef gcw } /** * Put a character into our history buffer */ static void putCharInBuffer(GConsoleObject *gcw, char c) { // Only store if we need to if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE)) return; // Do we have enough space in the buffer if (gcw->bufpos >= gcw->bufsize) { char * p; size_t dp; /** * This should never really happen except if the user has changed the window * size without turning off and then on the buffer. Even then it is unlikely * because of our conservative allocation strategy. * If it really is needed we scroll one line to make some space. We also mark * it is an overrun so that if asked to really scroll later we know we already have. * Note we only use one bit to indicate an overrun, so an overrun of more * than one line will lead to some interesting scrolling and refreshing * effects. */ // Remove one line from the start for(p = gcw->buffer; *p && *p != '\n'; p++); // Was there a newline? if (*p != '\n') p = gcw->buffer; // Oops - no newline, just delete one char else gcw->g.flags |= GCONSOLE_FLG_OVERRUN; // Mark the overrun // Delete the data dp = ++p - gcw->buffer; // Calculate the amount to to be removed gcw->bufpos -= dp; // Calculate the new size if (gcw->bufpos) memcpy(gcw->buffer, p, gcw->bufpos); // Move the rest of the data } // Save the character gcw->buffer[gcw->bufpos++] = c; } /** * Scroll the history buffer by one line */ static void scrollBuffer(GConsoleObject *gcw) { char * p; size_t dp; // Only scroll if we need to if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE)) return; // If a buffer overrun has been marked don't scroll as we have already if ((gcw->g.flags & GCONSOLE_FLG_OVERRUN)) { gcw->g.flags &= ~GCONSOLE_FLG_OVERRUN; return; } // Remove one line from the start for(p = gcw->buffer; *p && *p != '\n'; p++); // Was there a newline, if not delete everything. if (*p != '\n') { gcw->bufpos = 0; return; } // Delete the data dp = ++p - gcw->buffer; // Calculate the amount to to be removed gcw->bufpos -= dp; // Calculate the new size if (gcw->bufpos) memcpy(gcw->buffer, p, gcw->bufpos); // Move the rest of the data } /** * Clear the history buffer */ static void clearBuffer(GConsoleObject *gcw) { // Only clear if we need to if (!gcw->buffer || (gcw->g.flags & GCONSOLE_FLG_NOSTORE)) return; gcw->bufpos = 0; } #else #define putCharInBuffer(gcw, c) #define scrollBuffer(gcw) #define clearBuffer(gcw) #endif static void AfterClear(GWindowObject *gh) { #define gcw ((GConsoleObject *)gh) gcw->cx = 0; gcw->cy = 0; clearBuffer(gcw); #undef gcw } static const gwinVMT consoleVMT = { "Console", // The classname sizeof(GConsoleObject), // The object size #if GWIN_CONSOLE_USE_HISTORY HistoryDestroy, // The destroy routine (custom) HistoryRedraw, // The redraw routine (custom) #else 0, // The destroy routine 0, // The redraw routine (default) #endif AfterClear, // The after-clear routine }; GHandle gwinGConsoleCreate(GDisplay *g, GConsoleObject *gc, const GWindowInit *pInit) { if (!(gc = (GConsoleObject *)_gwindowCreate(g, &gc->g, pInit, &consoleVMT, 0))) return 0; #if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM gc->stream.vmt = &GWindowConsoleVMT; #endif #if GWIN_CONSOLE_USE_HISTORY gc->buffer = 0; #if GWIN_CONSOLE_HISTORY_ATCREATE gwinConsoleSetBuffer(&gc->g, TRUE); #endif #endif gc->cx = 0; gc->cy = 0; gwinSetVisible((GHandle)gc, pInit->show); return (GHandle)gc; } #if GFX_USE_OS_CHIBIOS && GWIN_CONSOLE_USE_BASESTREAM BaseSequentialStream *gwinConsoleGetStream(GHandle gh) { if (gh->vmt != &consoleVMT) return 0; return (BaseSequentialStream *)&(((GConsoleObject *)(gh))->stream); } #endif #if GWIN_CONSOLE_USE_HISTORY bool_t gwinConsoleSetBuffer(GHandle gh, bool_t onoff) { #define gcw ((GConsoleObject *)gh) if (gh->vmt != &consoleVMT) return FALSE; // Do we want the buffer turned off? if (!onoff) { if (gcw->buffer) { gfxFree(gcw->buffer); gcw->buffer = 0; } return FALSE; } // Is the buffer already on? if (gcw->buffer) return TRUE; // Get the number of characters that fit in the x direction #if GWIN_CONSOLE_HISTORY_AVERAGING gcw->bufsize = gh->width / ((2*gdispGetFontMetric(gh->font, fontMinWidth)+gdispGetFontMetric(gh->font, fontMaxWidth))/3); #else gcw->bufsize = gh->width / gdispGetFontMetric(gh->font, fontMinWidth); #endif gcw->bufsize++; // Allow space for a newline on each line. // Multiply by the number of lines gcw->bufsize *= gh->height / gdispGetFontMetric(gh->font, fontHeight); // Allocate the buffer if (!(gcw->buffer = gfxAlloc(gcw->bufsize))) return FALSE; // All good! gh->flags &= ~GCONSOLE_FLG_OVERRUN; gcw->bufpos = 0; return TRUE; #undef gcw } #endif void gwinPutChar(GHandle gh, char c) { #define gcw ((GConsoleObject *)gh) uint8_t width, fy; if (gh->vmt != &consoleVMT || !gh->font) return; // only render new character if the console is visible if (!gwinGetVisible(gh)) return; fy = gdispGetFontMetric(gh->font, fontHeight); #if GDISP_NEED_CLIP gdispGSetClip(gh->display, gh->x, gh->y, gh->width, gh->height); #endif /** * Special Characters: * * Carriage returns and line feeds (\r & \n) are handled in unix terminal cooked mode; that is, * line feeds perform both actions and carriage-returns are ignored. * * All other characters are treated as printable. */ switch (c) { case '\n': // clear to the end of the line #if GWIN_CONSOLE_USE_CLEAR_LINES if (gcw->cx == 0 && gcw->cy+fy < gh->height) gdispGFillArea(gh->display, gh->x, gh->y + gcw->cy, gh->width, fy, gh->bgcolor); #endif // update the cursor gcw->cx = 0; gcw->cy += fy; putCharInBuffer(gcw, '\n'); // We use lazy scrolling here and only scroll when the next char arrives return; case '\r': // gcw->cx = 0; return; } // Characters with no width are ignored if (!(width = gdispGetCharWidth(c, gh->font))) return; // Do we need to go to the next line to fit this character? if (gcw->cx + width >= gh->width) { gcw->cx = 0; gcw->cy += fy; putCharInBuffer(gcw, '\n'); } // Do we need to scroll to fit this character? if (gcw->cy + fy > gh->height) { #if GWIN_CONSOLE_USE_HISTORY && GWIN_CONSOLE_BUFFER_SCROLLING if (gcw->buffer) { // Scroll the buffer and then redraw using the buffer scrollBuffer(gcw); HistoryRedraw(gh); } else #endif #if GDISP_NEED_SCROLL { // Scroll the console using hardware scrollBuffer(gcw); gdispGVerticalScroll(gh->display, gh->x, gh->y, gh->width, gh->height, fy, gh->bgcolor); // Set the cursor to the start of the last line gcw->cx = 0; gcw->cy = (((coord_t)(gh->height/fy))-1)*fy; } #else { // Clear the console and reset the cursor clearBuffer(gcw); gdispGFillArea(gh->display, gh->x, gh->y, gh->width, gh->height, gh->bgcolor); gcw->cx = 0; gcw->cy = 0; } #endif } // If we are at the beginning of a new line clear the line #if GWIN_CONSOLE_USE_CLEAR_LINES if (gcw->cx == 0) gdispGFillArea(gh->display, gh->x, gh->y + gcw->cy, gh->width, fy, gh->bgcolor); #endif // Draw the character #if GWIN_CONSOLE_USE_FILLED_CHARS gdispGFillChar(gh->display, gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color, gh->bgcolor); #else gdispGDrawChar(gh->display, gh->x + gcw->cx, gh->y + gcw->cy, c, gh->font, gh->color); #endif putCharInBuffer(gcw, c); // Update the cursor gcw->cx += width + gdispGetFontMetric(gh->font, fontCharPadding); #undef gcw } void gwinPutString(GHandle gh, const char *str) { while(*str) gwinPutChar(gh, *str++); } void gwinPutCharArray(GHandle gh, const char *str, size_t n) { while(n--) gwinPutChar(gh, *str++); } #include #define MAX_FILLER 11 #define FLOAT_PRECISION 100000 static char *ltoa_wd(char *p, long num, unsigned radix, long divisor) { int i; char *q; if (!divisor) divisor = num; q = p + MAX_FILLER; do { i = (int)(num % radix); i += '0'; if (i > '9') i += 'A' - '0' - 10; *--q = i; num /= radix; } while ((divisor /= radix) != 0); i = (int)(p + MAX_FILLER - q); do { *p++ = *q++; } while (--i); return p; } #if GWIN_CONSOLE_USE_FLOAT static char *ftoa(char *p, double num) { long l; unsigned long precision = FLOAT_PRECISION; l = num; p = ltoa_wd(p, l, 10, 0); *p++ = '.'; l = (num - l) * precision; return ltoa_wd(p, l, 10, precision / 10); } #endif void gwinPrintf(GHandle gh, const char *fmt, ...) { va_list ap; char *p, *s, c, filler; int i, precision, width; bool_t is_long, left_align; long l; #if GWIN_CONSOLE_USE_FLOAT float f; char tmpbuf[2*MAX_FILLER + 1]; #else char tmpbuf[MAX_FILLER + 1]; #endif if (gh->vmt != &consoleVMT || !gh->font) return; va_start(ap, fmt); while (TRUE) { c = *fmt++; if (c == 0) { va_end(ap); return; } if (c != '%') { gwinPutChar(gh, c); continue; } p = tmpbuf; s = tmpbuf; left_align = FALSE; if (*fmt == '-') { fmt++; left_align = TRUE; } filler = ' '; if (*fmt == '.') { fmt++; filler = '0'; } width = 0; while (TRUE) { c = *fmt++; if (c >= '0' && c <= '9') c -= '0'; else if (c == '*') c = va_arg(ap, int); else break; width = width * 10 + c; } precision = 0; if (c == '.') { while (TRUE) { c = *fmt++; if (c >= '0' && c <= '9') c -= '0'; else if (c == '*') c = va_arg(ap, int); else break; precision = precision * 10 + c; } } /* Long modifier.*/ if (c == 'l' || c == 'L') { is_long = TRUE; if (*fmt) c = *fmt++; } else is_long = (c >= 'A') && (c <= 'Z'); /* Command decoding.*/ switch (c) { case 'c': filler = ' '; *p++ = va_arg(ap, int); break; case 's': filler = ' '; if ((s = va_arg(ap, char *)) == 0) s = "(null)"; if (precision == 0) precision = 32767; for (p = s; *p && (--precision >= 0); p++); break; case 'D': case 'd': if (is_long) l = va_arg(ap, long); else l = va_arg(ap, int); if (l < 0) { *p++ = '-'; l = -l; } p = ltoa_wd(p, l, 10, 0); break; #if GWIN_CONSOLE_USE_FLOAT case 'f': f = (float) va_arg(ap, double); if (f < 0) { *p++ = '-'; f = -f; } p = ftoa(p, f); break; #endif case 'X': case 'x': c = 16; goto unsigned_common; case 'U': case 'u': c = 10; goto unsigned_common; case 'O': case 'o': c = 8; unsigned_common: if (is_long) l = va_arg(ap, long); else l = va_arg(ap, int); p = ltoa_wd(p, l, c, 0); break; default: *p++ = c; break; } i = (int)(p - s); if ((width -= i) < 0) width = 0; if (left_align == FALSE) width = -width; if (width < 0) { if (*s == '-' && filler == '0') { gwinPutChar(gh, *s++); i--; } do { gwinPutChar(gh, filler); } while (++width != 0); } while (--i >= 0) gwinPutChar(gh, *s++); while (width) { gwinPutChar(gh, filler); width--; } } } #endif /* GFX_USE_GWIN && GWIN_NEED_CONSOLE */