Browse Source

add console handler for better console applications

Kolja Strohm 2 months ago
parent
commit
dfe62092f7

+ 1304 - 0
Console.cpp

@@ -0,0 +1,1304 @@
+#include "Console.h"
+
+#include <iostream>
+
+#ifndef WIN32
+#    include <termios.h>
+#    include <unistd.h>
+#endif
+
+using namespace Framework;
+
+ConsoleCommand::ConsoleCommand(Text name)
+    : ReferenceCounter(),
+      name(name)
+{}
+
+ConsoleCommand::~ConsoleCommand() {}
+
+Text& Framework::ConsoleCommand::getName()
+{
+    return name;
+}
+
+Framework::StickyConsoleContent::StickyConsoleContent()
+    : ReferenceCounter(),
+      length(0),
+      content(0),
+      color(0),
+      backgroundColor(0),
+      zConsoleHandler(0)
+{}
+
+Framework::StickyConsoleContent::~StickyConsoleContent()
+{
+    delete[] content;
+    delete[] color;
+    delete[] backgroundColor;
+}
+
+int Framework::StickyConsoleContent::getLength() const
+{
+    return length;
+}
+
+void Framework::StickyConsoleContent::setContent(
+    int length, const char* content)
+{
+    delete[] this->content;
+    this->content = new char[length];
+    memcpy(this->content, content, length);
+    this->length = length;
+    delete[] color;
+    delete[] backgroundColor;
+    color = 0;
+    backgroundColor = 0;
+}
+
+void Framework::StickyConsoleContent::setContent(
+    int length, const char* content, Color color)
+{
+    setContent(length, content);
+    this->color = new Color[length];
+    for (int i = 0; i < length; i++)
+    {
+        this->color[i] = color;
+    }
+}
+
+void Framework::StickyConsoleContent::setContent(
+    int length, const char* content, Color color, Color backgroundColor)
+{
+    setContent(length, content, color);
+    this->backgroundColor = new Color[length];
+    for (int i = 0; i < length; i++)
+    {
+        this->backgroundColor[i] = backgroundColor;
+    }
+}
+
+void Framework::StickyConsoleContent::setContent(
+    int length, const char* content, Color* color)
+{
+    setContent(length, content);
+    this->color = new Color[length];
+    for (int i = 0; i < length; i++)
+    {
+        this->color[i] = color[i];
+    }
+}
+
+void Framework::StickyConsoleContent::setContent(
+    int length, const char* content, Color* color, Color* backgroundColor)
+{
+    setContent(length, content, color);
+    this->backgroundColor = new Color[length];
+    for (int i = 0; i < length; i++)
+    {
+        this->backgroundColor[i] = backgroundColor[i];
+    }
+}
+
+void Framework::StickyConsoleContent::repaceContent(
+    int start, int length, int newLength, const char* newContent)
+{
+    int resultLength = this->length + newLength - length;
+    char* resultContent = new char[resultLength];
+    memcpy(resultContent, content, start);
+    memcpy(resultContent + start, newContent, newLength);
+    memcpy(resultContent + start + newLength,
+        content + start + length,
+        this->length - start - length);
+    delete[] content;
+    content = resultContent;
+    if (color)
+    {
+        Color* resultColor = new Color[resultLength];
+        memcpy(resultColor, color, start);
+        for (int i = 0; i < newLength; i++)
+        {
+            resultColor[start + i] = color[start];
+        }
+        memcpy(resultColor + start + newLength,
+            color + start + length,
+            sizeof(Color) * (this->length - start - length));
+        delete[] color;
+        color = resultColor;
+    }
+    if (backgroundColor)
+    {
+        Color* resultBgColor = new Color[resultLength];
+        memcpy(resultBgColor, backgroundColor, start);
+        for (int i = 0; i < newLength; i++)
+        {
+            resultBgColor[start + i] = backgroundColor[start];
+        }
+        memcpy(resultBgColor + start + newLength,
+            backgroundColor + start + length,
+            sizeof(Color) * (this->length - start - length));
+        delete[] backgroundColor;
+        backgroundColor = resultBgColor;
+    }
+    this->length = resultLength;
+}
+
+void Framework::StickyConsoleContent::repaceContent(
+    int start, int length, int newLength, const char* newContent, Color color)
+{
+    repaceContent(start, length, newLength, newContent);
+    if (!this->color)
+    {
+        this->color = new Color[this->length];
+        for (int i = 0; i < this->length; i++)
+        {
+            this->color[i] = (Color)-1;
+        }
+    }
+    for (int i = start; i < start + newLength; i++)
+    {
+        this->color[i] = color;
+    }
+}
+
+void Framework::StickyConsoleContent::repaceContent(int start,
+    int length,
+    int newLength,
+    const char* newContent,
+    Color color,
+    Color backgroundColor)
+{
+    repaceContent(start, length, newLength, newContent, color);
+    if (!this->backgroundColor)
+    {
+        this->backgroundColor = new Color[this->length];
+        for (int i = 0; i < this->length; i++)
+        {
+            this->backgroundColor[i] = (Color)-1;
+        }
+    }
+    for (int i = start; i < start + newLength; i++)
+    {
+        this->backgroundColor[i] = backgroundColor;
+    }
+}
+
+void Framework::StickyConsoleContent::repaceContent(
+    int start, int length, int newLength, const char* newContent, Color* color)
+{
+    repaceContent(start, length, newLength, newContent);
+    if (!this->color)
+    {
+        this->color = new Color[this->length];
+        for (int i = 0; i < this->length; i++)
+        {
+            this->color[i] = (Color)-1;
+        }
+    }
+    memcpy(this->color + start, color, sizeof(Color) * newLength);
+}
+
+void Framework::StickyConsoleContent::repaceContent(int start,
+    int length,
+    int newLength,
+    const char* newContent,
+    Color* color,
+    Color* backgroundColor)
+{
+    repaceContent(start, length, newLength, newContent, color);
+    if (!this->backgroundColor)
+    {
+        this->backgroundColor = new Color[this->length];
+        for (int i = 0; i < this->length; i++)
+        {
+            this->backgroundColor[i] = (Color)-1;
+        }
+    }
+    memcpy(this->backgroundColor + start,
+        backgroundColor,
+        sizeof(Color) * newLength);
+}
+
+void Framework::StickyConsoleContent::triggerUpdate()
+{
+    if (zConsoleHandler != 0)
+    {
+        zConsoleHandler->print();
+    }
+}
+
+bool Framework::StickyConsoleContent::isInput()
+{
+    return false;
+}
+
+void Framework::StickyConsoleContent::setConsoleHandlerZ(
+    ConsoleHandler* zConsoleHandler)
+{
+    this->zConsoleHandler = zConsoleHandler;
+}
+
+void Framework::StickyConsoleContent::setCursorToBeginning() {}
+
+int Framework::StickyConsoleContent::print() const
+{
+    int lineCount = 0;
+    int maxWidth = zConsoleHandler->getWidth();
+    std::cout << "\r\33[0K"; // erase current line
+    int lineLength = 0;
+    int lastColor = -1;
+    int lastBgColor = -1;
+    for (int i = 0; i < length; i++)
+    {
+        if (color && (int)color[i] != lastColor)
+        {
+            if ((int)color[i] == -1)
+            {
+                std::cout << "\033[0m";
+                if (backgroundColor && (int)backgroundColor[i] != -1)
+                {
+                    if (backgroundColor[i] < Color::LIGHT_Black)
+                    {
+                        std::cout << Text("\033[1;")
+                                         + Text(40 + (int)backgroundColor[i])
+                                         + "m";
+                    }
+                    else
+                    {
+                        std::cout << Text("\033[1;")
+                                         + Text(100 + (int)backgroundColor[i])
+                                         + "m";
+                    }
+                }
+            }
+            else if (color[i] < Color::LIGHT_Black)
+            {
+                std::cout << Text("\033[1;") + Text(30 + (int)color[i]) + "m";
+            }
+            else
+            {
+                std::cout << Text("\033[1;") + Text(90 + (int)color[i]) + "m";
+            }
+        }
+        if (backgroundColor && (int)backgroundColor[i] != lastBgColor)
+        {
+            if ((int)backgroundColor[i] == -1)
+            {
+                std::cout << "\033[0m";
+                if (color && (int)color[i] != -1)
+                {
+                    if (color[i] < Color::LIGHT_Black)
+                    {
+                        std::cout
+                            << Text("\033[1;") + Text(30 + (int)color[i]) + "m";
+                    }
+                    else
+                    {
+                        std::cout
+                            << Text("\033[1;") + Text(90 + (int)color[i]) + "m";
+                    }
+                }
+            }
+            else if (backgroundColor[i] < Color::LIGHT_Black)
+            {
+                std::cout << Text("\033[1;")
+                                 + Text(40 + (int)backgroundColor[i]) + "m";
+            }
+            else
+            {
+                std::cout << Text("\033[1;")
+                                 + Text(100 + (int)backgroundColor[i]) + "m";
+            }
+        }
+        std::cout << content[i];
+        lastColor = color ? (int)color[i] : -1;
+        lastBgColor = backgroundColor ? (int)backgroundColor[i] : -1;
+        if ((content[i] == '\n' || lineLength == maxWidth) && i < length - 1)
+        {
+            if (lineLength == maxWidth && content[i] != '\n')
+            {
+                std::cout << "\n";
+            }
+            lineLength = 0;
+            lineCount++;
+        }
+        else
+        {
+            lineLength++;
+        }
+    }
+    if (lastColor != -1 || lastBgColor != -1) std::cout << "\033[0m";
+    if (lineCount == 0 && lineLength > 0)
+    {
+        lineCount++;
+    }
+    return lineCount;
+}
+
+void Framework::StickyConsoleContent::restoreCursorPos() {}
+
+ConsoleHandler* Framework::StickyConsoleContent::zConsoleHandlerRef() const
+{
+    return zConsoleHandler;
+}
+
+Framework::ConsoleProgressBar::ConsoleProgressBar()
+    : StickyConsoleContent(),
+      progress(0),
+      maxProgress(1),
+      maxWidth(-1)
+{}
+
+void Framework::ConsoleProgressBar::setMaxWidth(int maxWidth)
+{
+    this->maxWidth = maxWidth;
+}
+
+void Framework::ConsoleProgressBar::setProgress(int progress)
+{
+    if (progress < 0) progress = 0;
+    this->progress = progress;
+}
+
+void Framework::ConsoleProgressBar::setMaxProgress(int maxProgress)
+{
+    if (maxProgress < 0) maxProgress = 0;
+    this->maxProgress = maxProgress;
+}
+
+int Framework::ConsoleProgressBar::print() const
+{
+    int maxWidth = zConsoleHandlerRef()->getWidth();
+    int width = this->maxWidth == -1 ? maxWidth : this->maxWidth;
+    if (width > maxWidth)
+    {
+        width = maxWidth;
+    }
+    if (width < 7)
+    {
+        return 0;
+    }
+    std::cout << "\r\33[0K"; // erase current line
+    int progress = this->progress > maxProgress ? maxProgress : this->progress;
+    int progressChars = width - 7;
+    std::cout << "[\033[1;47m";
+    int progressWidth = (int)(((double)progress / maxProgress) * progressChars);
+    if (progressWidth > progressChars)
+    {
+        progressWidth = progressChars;
+    }
+    for (int i = 0; i < progressWidth; i++)
+    {
+        std::cout << " ";
+    }
+    std::cout << "\033[0m";
+    for (int i = 0; i < progressChars - progressWidth; i++)
+    {
+        std::cout << " ";
+    }
+    std::cout << "] ";
+    Text str((int)(((double)progress / maxProgress) * 100));
+    for (int i = 0; i < 3 - str.getLength(); i++)
+    {
+        std::cout << " ";
+    }
+    std::cout << str.getText() << "%";
+    return 1;
+}
+
+Framework::InputLine::InputLine()
+    : StickyConsoleContent(),
+      Thread(),
+      input(""),
+      cursorPos(0),
+      suggestions(0)
+{}
+
+Framework::InputLine::~InputLine()
+{
+    suggestions->release();
+}
+
+void Framework::InputLine::addPossibleCommand(ConsoleCommand* command)
+{
+    commands.add(command);
+}
+
+bool Framework::InputLine::isInput()
+{
+    return true;
+}
+
+void Framework::InputLine::setCursorToBeginning()
+{
+    cs.lock();
+    std::cout << "\r";
+}
+
+int Framework::InputLine::print() const
+{
+    std::cout << "\33[0K" // clear current line
+              << input.getText();
+    if (suggestions)
+    {
+        std::cout << "\n";
+        return 1 + dynamic_cast<StickyConsoleContent*>(suggestions)->print();
+    }
+    return 1;
+}
+
+void Framework::InputLine::restoreCursorPos()
+{
+    if (cursorPos > 0)
+    {
+        std::cout << Text("\33[") + Text(cursorPos) + "C";
+    }
+    cs.unlock();
+}
+
+void Framework::InputLine::thread()
+{
+#ifdef WIN32
+    INPUT_RECORD inputRecord;
+    DWORD eventsRead;
+    HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
+    while (true)
+    {
+        if (ReadConsoleInput(handle, &inputRecord, 1, &eventsRead))
+        {
+            cs.lock();
+            if (eventsRead > 0 && inputRecord.EventType == KEY_EVENT
+                && inputRecord.Event.KeyEvent.bKeyDown)
+            {
+                if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
+                {
+                    cursorPos--;
+                    if (cursorPos < 0)
+                    {
+                        cursorPos = 0;
+                    }
+                    else
+                    {
+                        std::cout << "\33[1D" << std::flush;
+                    }
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_HOME)
+                { // Pos1 key moves cursor to beginning of line
+                    std::cout << "\r" << std::flush;
+                    cursorPos = 0;
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
+                {
+                    cursorPos++;
+                    if (cursorPos > input.getLength())
+                    {
+                        cursorPos = input.getLength();
+                    }
+                    else
+                    {
+                        std::cout << "\33[1C" << std::flush;
+                    }
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_END)
+                { // Pos1 key moves cursor to beginning of line
+                    cursorPos = input.getLength();
+                    std::cout << Text("\r\33[") + Text(cursorPos) + "C"
+                              << std::flush;
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_TAB)
+                {
+                    applyAutocompletion();
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode
+                         == VK_RETURN)
+                {
+                    std::cout << "\r\33[0K" << std::flush; // eraze line
+                    Text cmd = input;
+                    cs.unlock();
+                    if (executeCommand(cmd))
+                    {
+                        cs.lock();
+                        input = "";
+                        cursorPos = 0;
+                        cs.unlock();
+                    }
+                    continue; // skip the unlock
+                }
+                else if (inputRecord.Event.KeyEvent.wVirtualKeyCode == VK_BACK)
+                {
+                    if (cursorPos > 0)
+                    {
+                        input.remove(cursorPos - 1, cursorPos);
+                        cursorPos--;
+                        std::cout << "\r\33[0K"      // eraze line
+                                  << input.getText() // output current input
+                                  // move cursor to current position
+                                  << Text("\r\33[") + Text(cursorPos) + "C"
+                                  << std::flush;
+                    }
+                }
+                else if (inputRecord.Event.KeyEvent.uChar.AsciiChar)
+                {
+                    input.insert(
+                        cursorPos, inputRecord.Event.KeyEvent.uChar.AsciiChar);
+                    cursorPos++;
+                    std::cout << "\r\33[0K"      // eraze line
+                              << input.getText() // output current input
+                              // move cursor to current position
+                              << Text("\r\33[") + Text(cursorPos) + "C"
+                              << std::flush;
+                }
+            }
+            cs.unlock();
+        }
+    }
+#else
+    char c;
+    while (read(STDIN_FILENO, &c, 1) == 1)
+    {
+        if (c == 27)
+        { // Check for the escape key (27 is the ASCII code for escape)
+            char seq[2];
+            if (read(STDIN_FILENO, &seq, 2) == 2)
+            {
+                cs.lock();
+                if (seq[0] == '[')
+                {
+                    if (seq[1] == 'D')
+                    { // left arrow key
+                        cursorPos--;
+                        if (cursorPos < 0)
+                        {
+                            cursorPos = 0;
+                        }
+                        else
+                        {
+                            std::cout << "\33[1D" << std::flush;
+                        }
+                    }
+                    else if (seq[1] == 'H')
+                    { // Pos1 key moves cursor to beginning of line
+                        std::cout << "\r" << std::flush;
+                        cursorPos = 0;
+                    }
+                    else if (seq[1] == 'C')
+                    { // right arrow key
+                        cursorPos++;
+                        if (cursorPos > input.getLength())
+                        {
+                            cursorPos = input.getLength();
+                        }
+                        else
+                        {
+                            std::cout << "\33[1C" << std::flush;
+                        }
+                    }
+                    else if (seq[1] == 'F')
+                    { // End key moves cursor to end of line
+                        cursorPos = input.getLength();
+                        std::cout << Text("\r\33[") + Text(cursorPos) + "C"
+                                  << std::flush;
+                    }
+                }
+                cs.unlock();
+            }
+        }
+        else if (c == 8)
+        { // backspace
+            if (cursorPos > 0)
+            {
+                cs.lock();
+                input.remove(cursorPos - 1, cursorPos);
+                cursorPos--;
+                std::cout << "\r\33[0K"      // eraze line
+                          << input.getText() // output current input
+                          // move cursor to current position
+                          << Text("\r\33[") + Text(cursorPos) + "C"
+                          << std::flush;
+                cs.unlock();
+            }
+        }
+        else if (c == 9)
+        { // tab
+            cs.lock();
+            applyAutocompletion();
+            cs.unlock();
+        }
+        else if (c == 13)
+        { // enter
+            cs.lock();
+            std::cout << "\r\33[0K" << std::flush; // eraze line
+            Text cmd = input;
+            cs.unlock();
+            if (executeCommand(cmd))
+            {
+                cs.lock();
+                input = "";
+                cursorPos = 0;
+                cs.unlock();
+            }
+        }
+        else
+        {
+            cs.lock();
+            input.insert(cursorPos, c);
+            cursorPos++;
+            std::cout << "\r\33[0K"      // eraze line
+                      << input.getText() // output current input
+                      // move cursor to current position
+                      << Text("\r\33[") + Text(cursorPos) + "C" << std::flush;
+            cs.unlock();
+        }
+    }
+#endif
+}
+
+void Framework::InputLine::applyAutocompletion()
+{
+    RCArray<Text> params;
+    bool lastArgFinished = 0;
+    Text* cmd = input.getTeilText(0, cursorPos);
+    parseCommand(*cmd, params, lastArgFinished);
+    cmd->release();
+    bool paramsStarted = params.getEintragAnzahl() > 1 || lastArgFinished;
+    Text* name;
+    if (params.getEintragAnzahl() > 0)
+    {
+        name = params.get(0);
+        params.remove(0);
+    }
+    else
+    {
+        name = new Text("");
+    }
+    RCArray<Text> suggestions;
+    for (ConsoleCommand* command : commands)
+    {
+        if ((!paramsStarted && command->getName().hatAt(0, *name))
+            || (paramsStarted && command->getName().istGleich(*name)))
+        {
+            if (paramsStarted)
+            {
+                command->addAutocompletePossibilities(
+                    params, !lastArgFinished, suggestions);
+                break;
+            }
+            else
+            {
+                suggestions.add(new Text(command->getName()));
+            }
+        }
+    }
+    if (this->suggestions)
+    {
+        this->suggestions->clear();
+    }
+    else
+    {
+        this->suggestions = new ConsoleListView();
+        this->suggestions->setConsoleHandlerZ(zConsoleHandlerRef());
+    }
+    for (Text* suggestion : suggestions)
+    {
+        this->suggestions->addItem(*suggestion);
+    }
+    if (this->suggestions->getItems().getEintragAnzahl() > 0)
+    {
+        bool commonChar = true;
+        for (int i
+             = lastArgFinished
+                 ? 0
+                 : (params.getEintragAnzahl() > 0
+                         ? params.z(params.getEintragAnzahl() - 1)->getLength()
+                         : name->getLength());
+             true;
+             i++)
+        {
+            if (i >= this->suggestions->getItems().z(0)->getLength())
+            {
+                commonChar = false;
+                break;
+            }
+            for (int j = 1;
+                 j < this->suggestions->getItems().getEintragAnzahl();
+                 j++)
+            {
+                if (i >= this->suggestions->getItems().z(j)->getLength())
+                {
+                    commonChar = false;
+                    break;
+                }
+                if (this->suggestions->getItems().z(j)->getText()[i]
+                    != this->suggestions->getItems().z(0)->getText()[i])
+                {
+                    commonChar = false;
+                    break;
+                }
+            }
+            if (!commonChar)
+            {
+                break;
+            }
+            input.insert(
+                cursorPos, this->suggestions->getItems().z(0)->getText()[i]);
+            cursorPos++;
+        }
+    }
+    name->release();
+    if (this->suggestions->getItems().getEintragAnzahl() <= 1)
+    {
+        this->suggestions->release();
+        this->suggestions = 0;
+    }
+    triggerUpdate();
+}
+
+bool Framework::InputLine::executeCommand(Text command)
+{
+    RCArray<Text> params;
+    bool lastArgFinished = 0;
+    Text* cmd = input.getTeilText(0, cursorPos);
+    if (!parseCommand(*cmd, params, lastArgFinished))
+    {
+        cmd->release();
+        return false;
+    }
+    cmd->release();
+    Text* name;
+    if (params.getEintragAnzahl() > 0)
+    {
+        name = params.get(0);
+        params.remove(0);
+    }
+    else
+    {
+        name = new Text("");
+    }
+    RCArray<Text> suggestions;
+    for (ConsoleCommand* command : commands)
+    {
+        if (command->getName().istGleich(*name))
+        {
+            name->release();
+            return command->execute(params);
+        }
+    }
+    name->release();
+    return false;
+}
+
+bool Framework::InputLine::parseCommand(
+    Text command, RCArray<Text>& split, bool& lastFinished)
+{
+    const char* cmd = command.getText();
+    Text str;
+    bool insideString = false;
+    bool insideString2 = false;
+    bool lastEscape = false;
+    lastFinished = false;
+    while (*cmd)
+    {
+        if (*cmd == ' ' && !insideString && !insideString2 && !lastEscape)
+        {
+            if (str.getLength() > 0)
+            {
+                split.add(new Text(str));
+                str = "";
+                lastFinished = true;
+            }
+        }
+        else if (*cmd == '"' && !insideString2 && !lastEscape)
+        {
+            insideString = !insideString;
+            lastFinished = !insideString;
+        }
+        else if (*cmd == '\'' && !insideString && !lastEscape)
+        {
+            insideString2 = !insideString2;
+            lastFinished = !insideString2;
+        }
+        else if (*cmd == '\\' && !lastEscape)
+        {
+            lastEscape = true;
+            lastFinished = false;
+        }
+        else
+        {
+            lastEscape = false;
+            str.append(*cmd);
+            lastFinished = false;
+        }
+        cmd++;
+    }
+    if (str.getLength() > 0)
+    {
+        split.add(new Text(str));
+    }
+    return !insideString && !insideString2 && !lastEscape;
+}
+
+Framework::ConsoleListView::ConsoleListView()
+    : StickyConsoleContent(),
+      maxColumns(-1),
+      maxVisibleLines(5),
+      lineOffset(0)
+{}
+
+int Framework::ConsoleListView::getUsedColumns() const
+{
+    if (!zConsoleHandlerRef()) return 0;
+    int maxWidth = zConsoleHandlerRef()->getWidth();
+    int columnSize = 0;
+    for (Text* item : items)
+    {
+        if (item->getLength() > columnSize)
+        {
+            columnSize = item->getLength();
+        }
+    }
+    columnSize += 4;
+    if (maxWidth < columnSize)
+    {
+        return 0;
+    }
+    int columns = maxWidth / columnSize;
+    if (maxColumns > 0 && columns > maxColumns)
+    {
+        columns = maxColumns;
+    }
+    if (columns > items.getEintragAnzahl())
+    {
+        columns = items.getEintragAnzahl();
+    }
+    return columns;
+}
+
+int Framework::ConsoleListView::getNeededLines() const
+{
+    int columns = getUsedColumns();
+    if (!columns)
+    {
+        return 0;
+    }
+    if (maxColumns > 0 && columns > maxColumns)
+    {
+        columns = maxColumns;
+    }
+    int lines = items.getEintragAnzahl() / columns;
+    if (items.getEintragAnzahl() % columns != 0)
+    {
+        lines++;
+    }
+    return lines;
+}
+
+void Framework::ConsoleListView::setMaxVisibleLines(int maxVisibleLines)
+{
+    this->maxVisibleLines = maxVisibleLines;
+}
+
+void Framework::ConsoleListView::setLineOffset(int lineOffset)
+{
+    this->lineOffset = lineOffset;
+    int maxLines = getNeededLines();
+    if (this->lineOffset > maxLines - maxVisibleLines)
+    {
+        this->lineOffset = maxLines - maxVisibleLines;
+    }
+    if (this->lineOffset < 0)
+    {
+        this->lineOffset = 0;
+    }
+}
+
+void Framework::ConsoleListView::setMaxColumns(int maxColumns)
+{
+    this->maxColumns = maxColumns;
+}
+
+void Framework::ConsoleListView::addItem(Text item)
+{
+    item.ersetzen("\n", " ");
+    int index = 0;
+    for (Text* curreent : items)
+    {
+        int i = 0;
+        while (i < curreent->getLength() && i < item.getLength())
+        {
+            if (curreent->getText()[i] != item.getText()[i])
+            {
+                break;
+            }
+            i++;
+        }
+        if (i == curreent->getLength() && i == item.getLength())
+        {
+            return; // item already exists
+        }
+        if (i < curreent->getLength() && i == item.getLength())
+        {
+            items.add(new Text(item), index);
+            return;
+        }
+        if (i < curreent->getLength() && i < item.getLength()
+            && curreent->getText()[i] > item.getText()[i])
+        {
+            items.add(new Text(item), index);
+            return;
+        }
+        index++;
+    }
+    items.add(new Text(item));
+}
+
+void Framework::ConsoleListView::clear()
+{
+    items.leeren();
+}
+
+const RCArray<Text>& Framework::ConsoleListView::getItems() const
+{
+    return items;
+}
+
+int Framework::ConsoleListView::print() const
+{
+    int lines = lineOffset;
+    int maxLines = getNeededLines();
+    if (lines > maxLines - maxVisibleLines)
+    {
+        lines = maxLines - maxVisibleLines;
+    }
+    if (lines < 0)
+    {
+        lines = 0;
+    }
+    int columns = getUsedColumns();
+    if (!columns)
+    {
+        return 0;
+    }
+    int columnSize = zConsoleHandlerRef()->getWidth() / columns;
+    int printetLines = 0;
+    int currentColumn = 0;
+    std::cout << "\33[0K"; // clear current line
+    for (int i = columns * lines; i < items.getEintragAnzahl(); i++)
+    {
+        if (i >= columns * (lines + maxVisibleLines))
+        {
+            break;
+        }
+        if (currentColumn >= columns)
+        {
+            std::cout << "\n\r\33[0K";
+            printetLines++;
+            currentColumn++;
+        }
+        if (!printetLines)
+        {
+            printetLines++;
+        }
+        std::cout << items.z(i)->getText();
+        for (int j = items.z(i)->getLength(); j < columnSize; j++)
+        {
+            std::cout << " ";
+        }
+        currentColumn++;
+    }
+    return printetLines;
+}
+
+Framework::ConsoleHandler::ConsoleHandler()
+    : ReferenceCounter(),
+      lines(0),
+      lineCounts(0),
+      contentCount(0)
+{
+#ifdef WIN32
+    hConsole = GetStdHandle(STD_INPUT_HANDLE);
+    SetConsoleMode(hConsole, ENABLE_PROCESSED_INPUT); // set raw mode
+    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+#else
+    struct termios t;
+    tcgetattr(STDIN_FILENO, &t);
+    t.c_lflag &= ~(ICANON | ECHO); // set raw mode
+    tcsetattr(STDIN_FILENO, TCSANOW, &t);
+#endif
+}
+
+Framework::ConsoleHandler::~ConsoleHandler()
+{
+    for (int i = 0; i < contentCount; i++)
+    {
+        lines[i]->release();
+    }
+    delete[] lines;
+    delete[] lineCounts;
+}
+
+void Framework::ConsoleHandler::addContent(
+    StickyConsoleContent* content, ConsoleContentPosition pos)
+{
+    cs.lock();
+    if (content->isInput())
+    {
+        for (int i = 0; i < contentCount; i++)
+        {
+            if (lines[i]->isInput())
+            {
+                content->release();
+                cs.unlock();
+                throw "There can only be one input line";
+            }
+        }
+    }
+    if (content->zConsoleHandler)
+    {
+        cs.unlock();
+        throw "A console content can only be added to one console handler "
+              "at "
+              "the same time.";
+    }
+    content->zConsoleHandler = this;
+    contentCount++;
+    int* lineCounts = new int[contentCount];
+    StickyConsoleContent** lines = new StickyConsoleContent*[contentCount];
+    if (pos == ConsoleContentPosition::Top)
+    {
+        lines[0] = content;
+        lineCounts[0] = 0;
+        for (int i = 1; i < contentCount; i++)
+        {
+            lines[i] = this->lines[i - 1];
+            lineCounts[i] = this->lineCounts[i - 1];
+        }
+    }
+    else
+    {
+        lines[contentCount - 1] = content;
+        lineCounts[contentCount - 1] = content->print();
+        for (int i = 0; i < contentCount - 1; i++)
+        {
+            lines[i] = this->lines[i];
+            lineCounts[i] = this->lineCounts[i];
+        }
+    }
+    delete[] this->lines;
+    delete[] this->lineCounts;
+    this->lines = lines;
+    this->lineCounts = lineCounts;
+    InputLine* input = dynamic_cast<InputLine*>(content);
+    if (input)
+    {
+        input->start();
+    }
+    cs.unlock();
+}
+
+void Framework::ConsoleHandler::removeContent(StickyConsoleContent* zContent)
+{
+    cs.lock();
+    int index = -1;
+    for (int i = 0; i < contentCount; i++)
+    {
+        if (lines[i] == zContent)
+        {
+            index = i;
+            break;
+        }
+    }
+    if (index == -1)
+    {
+        cs.unlock();
+        return;
+    }
+    zContent->zConsoleHandler = 0;
+    contentCount--;
+    int* lineCounts = new int[contentCount];
+    StickyConsoleContent** lines = new StickyConsoleContent*[contentCount];
+    for (int i = 0; i < index; i++)
+    {
+        lines[i] = this->lines[i];
+        lineCounts[i] = this->lineCounts[i];
+    }
+    for (int i = index; i < contentCount; i++)
+    {
+        lines[i] = this->lines[i + 1];
+        lineCounts[i] = this->lineCounts[i + 1];
+    }
+    delete[] this->lines;
+    delete[] this->lineCounts;
+    this->lines = lines;
+    this->lineCounts = lineCounts;
+    if (zContent->isInput())
+    {
+        InputLine* input = dynamic_cast<InputLine*>(zContent);
+        if (input)
+        {
+            input->start();
+        }
+    }
+    zContent->release();
+    cs.unlock();
+}
+
+int Framework::ConsoleHandler::getWidth() const
+{
+#ifdef WIN32
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    GetConsoleScreenBufferInfo(hConsole, &csbi);
+    return csbi.dwSize.X;
+#else
+    struct winsize ws;
+    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
+    {
+        return 0;
+    }
+    return ws.ws_col;
+#endif
+}
+
+int Framework::ConsoleHandler::getHeight() const
+{
+#ifdef WIN32
+    CONSOLE_SCREEN_BUFFER_INFO csbi;
+    GetConsoleScreenBufferInfo(hConsole, &csbi);
+    return csbi.dwSize.Y;
+#else
+    struct winsize ws;
+    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
+    {
+        return 0;
+    }
+    return ws.ws_row;
+#endif
+}
+
+void Framework::ConsoleHandler::clear()
+{
+    cs.lock();
+    for (int i = 0; i < contentCount; i++)
+    {
+        lines[i]->release();
+    }
+    delete[] lines;
+    delete[] lineCounts;
+    lines = 0;
+    lineCounts = 0;
+    contentCount = 0;
+    cs.unlock();
+}
+
+void Framework::ConsoleHandler::print()
+{
+    cs.lock();
+    int totalLines = 0;
+    bool adittionalLine = 0;
+    for (int i = 0; i < contentCount; i++)
+    {
+        if (lines[i]->isInput())
+        {
+            lines[i]->setCursorToBeginning();
+            break;
+        }
+        totalLines += lineCounts[i];
+    }
+    if (totalLines > 0)
+    {
+        std::cout << "\33[" << totalLines << "A";
+    }
+    totalLines = 0;
+    for (int i = 0; i < contentCount; i++)
+    {
+        lineCounts[i] = lines[i]->print();
+        if (lineCounts[i] > 0)
+        {
+            adittionalLine = 0;
+            if (i < contentCount - 1)
+            {
+                std::cout << "\n";
+                adittionalLine = 1;
+            }
+        }
+        totalLines += lineCounts[i];
+    }
+    if (adittionalLine)
+    {
+        totalLines++;
+    }
+    std::cout << "\33[" << (totalLines - 1) << "A\r";
+    for (int i = 0; i < contentCount; i++)
+    {
+        if (lines[i]->isInput())
+        {
+            lines[i]->restoreCursorPos();
+            break;
+        }
+        if (i < contentCount - 1 && lineCounts[i])
+        {
+            std::cout << "\33[" << lineCounts[i] << "B";
+        }
+    }
+    std::cout << std::flush;
+    cs.unlock();
+}
+
+void Framework::ConsoleHandler::print(Text str)
+{
+    std::cout << "\n";
+    cs.lock();
+    int totalLines = 0;
+    bool adittionalLine = 0;
+    for (int i = 0; i < contentCount; i++)
+    {
+        if (lines[i]->isInput())
+        {
+            lines[i]->setCursorToBeginning();
+            break;
+        }
+        totalLines += lineCounts[i];
+    }
+    if (totalLines > 0)
+    {
+        std::cout << "\33[" << totalLines + 1 << "A";
+    }
+    std::cout << "\33[0K" // clear current line
+              << str.getText();
+    if (str.getText()[str.getLength() - 1] != '\n')
+    {
+        std::cout << "\n";
+    }
+    totalLines = 0;
+    for (int i = 0; i < contentCount; i++)
+    {
+        lineCounts[i] = lines[i]->print();
+        if (lineCounts[i] > 0)
+        {
+            adittionalLine = 0;
+            if (i < contentCount - 1)
+            {
+                std::cout << "\n";
+                adittionalLine = 1;
+            }
+        }
+        totalLines += lineCounts[i];
+    }
+    if (adittionalLine)
+    {
+        totalLines++;
+    }
+    std::cout << "\33[" << (totalLines - 1) << "A\r";
+    for (int i = 0; i < contentCount; i++)
+    {
+        if (lines[i]->isInput())
+        {
+            lines[i]->restoreCursorPos();
+            break;
+        }
+        if (i < contentCount - 1 && lineCounts[i])
+        {
+            std::cout << "\33[" << lineCounts[i] << "B";
+        }
+    }
+    std::cout << std::flush;
+    cs.unlock();
+}

+ 562 - 0
Console.h

@@ -0,0 +1,562 @@
+#pragma once
+
+#include "Array.h"
+#include "Critical.h"
+#include "ReferenceCounter.h"
+#include "Text.h"
+#include "Thread.h"
+
+namespace Framework
+{
+    class ConsoleHandler;
+
+    enum class Color
+    {
+        Black,
+        RED,
+        GREEN,
+        YELLOW,
+        Blue,
+        Magenta,
+        CYAN,
+        WHITE,
+        LIGHT_Black,
+        LIGHT_RED,
+        LIGHT_GREEN,
+        LIGHT_YELLOW,
+        LIGHT_Blue,
+        LIGHT_Magenta,
+        LIGHT_CYAN,
+        LIGHT_WHITE,
+    };
+
+    /**
+     * simple console command interface used for autocompletion and execution of
+     * commands
+     */
+    class ConsoleCommand : public ReferenceCounter
+    {
+    private:
+        Text name;
+
+    public:
+        /**
+         * creates a new command
+         *
+         * \param name the name of the command. if the user types this name into
+         * the console at the beginning, the command will be executed
+         */
+        DLLEXPORT ConsoleCommand(Text name);
+        DLLEXPORT virtual ~ConsoleCommand();
+        /**
+         * the name of the command
+         *
+         * \return the name
+         */
+        DLLEXPORT Text& getName();
+        /**
+         * calculates autocomplete posibilities when the user presses the tab
+         * key and wants to use this command
+         *
+         * \param args the current arguments that would be passed to the command
+         * if it was executed
+         * \param appendToLast true if the last argument is not finished
+         * \param possibilities the array to which the
+         * possibilities should be added
+         */
+        virtual void addAutocompletePossibilities(RCArray<Text>& args,
+            bool appendToLast,
+            RCArray<Text>& possibilities)
+            = 0;
+        /**
+         * executes the command
+         *
+         * \param args the arguments that the user has passed to the command
+         * \return true if the command was executed successfully
+         */
+        virtual bool execute(RCArray<Text>& args) = 0;
+    };
+
+    /**
+     * repesents console content that is sticky, meaning it stays in the console
+     * even if other messages are printed or commands are executed
+     */
+    class StickyConsoleContent : public virtual ReferenceCounter
+    {
+    private:
+        int length;
+        char* content;
+        Color* color;
+        Color* backgroundColor;
+        ConsoleHandler* zConsoleHandler;
+
+    public:
+        DLLEXPORT StickyConsoleContent();
+        DLLEXPORT ~StickyConsoleContent();
+        /**
+         * the current length of the command.
+         *
+         * \return the length in characters including possible newlines
+         */
+        DLLEXPORT int getLength() const;
+        /**
+         * sets the current content of the command. all previous colors will be
+         * removed. does not free any memory of the given arguments.
+         *
+         * \param length the new length in characters
+         * \param content the new content
+         */
+        DLLEXPORT void setContent(int length, const char* content);
+        /**
+         * sets the current content of the command. all previous colors will be
+         * removed. does not free any memory of the given arguments.
+         *
+         * \param length the new length in characters
+         * \param content the new content
+         * \param color the font color of the content
+         */
+        DLLEXPORT void setContent(int length, const char* content, Color color);
+        /**
+         * sets the current content of the command. all previous colors will be
+         * removed. does not free any memory of the given arguments.
+         *
+         * \param length the new length in characters
+         * \param content the new content
+         * \param color the font color of the content
+         * \param backgroundColor the background color of the content
+         */
+        DLLEXPORT void setContent(int length,
+            const char* content,
+            Color color,
+            Color backgroundColor);
+        /**
+         * sets the current content of the command. all previous colors will be
+         * removed. does not free any memory of the given arguments.
+         *
+         * \param length the new length in characters
+         * \param content the new content
+         * \param color array of font colors for each character
+         */
+        DLLEXPORT void setContent(
+            int length, const char* content, Color* color);
+        /**
+         * sets the current content of the command. all previous colors will be
+         * removed. does not free any memory of the given arguments.
+         *
+         * \param length the new length in characters
+         * \param content the new content
+         * \param color array of font colors for each character
+         * \param backgroundColor array of background colors for each character
+         */
+        DLLEXPORT void setContent(int length,
+            const char* content,
+            Color* color,
+            Color* backgroundColor);
+        /**
+         * replaces a part of the current content. the colors of the current
+         * start position will be used for the new content. does not free any
+         * memory of the given arguments.
+         *
+         * \param start the start position of the replacement
+         * \param length the length of the content that should be replaced
+         * \param newLength the length of the new content that should be
+         * inserted instead of the old content
+         * \param newContent the new content
+         */
+        DLLEXPORT void repaceContent(
+            int start, int length, int newLength, const char* newContent);
+        /**
+         * replaces a part of the current content. the backgound color of the
+         * current start position will be used for the new content. does not
+         * free any memory of the given arguments.
+         *
+         * \param start the start position of the replacement
+         * \param length the length of the content that should be replaced
+         * \param newLength the length of the new content that should be
+         * inserted instead of the old content
+         * \param newContent the new content
+         * \param color the font color of the new content
+         */
+        DLLEXPORT void repaceContent(int start,
+            int length,
+            int newLength,
+            const char* newContent,
+            Color color);
+        /**
+         * replaces a part of the current content. does not
+         * free any memory of the given arguments.
+         *
+         * \param start the start position of the replacement
+         * \param length the length of the content that should be replaced
+         * \param newLength the length of the new content that should be
+         * inserted instead of the old content
+         * \param newContent the new content
+         * \param color the font color of the new content
+         * \param backgroundColor the background color of the new content
+         */
+        DLLEXPORT void repaceContent(int start,
+            int length,
+            int newLength,
+            const char* newContent,
+            Color color,
+            Color backgroundColor);
+        /**
+         * replaces a part of the current content. the backgound color of the
+         * current start position will be used for the new content. does not
+         * free any memory of the given arguments.
+         *
+         * \param start the start position of the replacement
+         * \param length the length of the content that should be replaced
+         * \param newLength the length of the new content that should be
+         * inserted instead of the old content
+         * \param newContent the new content
+         * \param color array the font colorc of the new content for each
+         * character
+         */
+        DLLEXPORT void repaceContent(int start,
+            int length,
+            int newLength,
+            const char* newContent,
+            Color* color);
+        /**
+         * replaces a part of the current content. does not
+         * free any memory of the given arguments.
+         *
+         * \param start the start position of the replacement
+         * \param length the length of the content that should be replaced
+         * \param newLength the length of the new content that should be
+         * inserted instead of the old content
+         * \param newContent the new content
+         * \param color array the font colorc of the new content for each
+         * character
+         * \param backgroundColor the background color of the new content for
+         * each character
+         */
+        DLLEXPORT void repaceContent(int start,
+            int length,
+            int newLength,
+            const char* newContent,
+            Color* color,
+            Color* backgroundColor);
+        /**
+         * triggers reprinting of all sticky console content.
+         */
+        DLLEXPORT void triggerUpdate();
+        /**
+         * true if the content is a input line where the user can type in
+         *
+         * \param true if the user can edit this content
+         */
+        DLLEXPORT virtual bool isInput();
+
+    public:
+        /**
+         * prints the content to the console
+         *
+         * \return number of lines printed
+         */
+        DLLEXPORT virtual int print() const;
+        /**
+         * sets the console handler instance where this content will be printed
+         * to. This will automatically be done if the content is added to a
+         * consolehandler.
+         *
+         * \param zConsoleHandler the handler instance without increasing the
+         * reference counter
+         */
+        DLLEXPORT void setConsoleHandlerZ(ConsoleHandler* zConsoleHandler);
+
+    protected:
+        /**
+         * called before all sticky console content is reprinted when isInput
+         * is true. restoreCursorPos() must be called after this
+         */
+        DLLEXPORT virtual void setCursorToBeginning();
+        /**
+         * reprints all input from the user so that the user can edit the
+         * content again. can only be called after setCursorToBeginning was
+         * called
+         */
+        DLLEXPORT virtual void restoreCursorPos();
+
+        ConsoleHandler* zConsoleHandlerRef() const;
+
+    public:
+        friend ConsoleHandler;
+    };
+
+    /**
+     * displays a progress bar in the console.
+     */
+    class ConsoleProgressBar : public StickyConsoleContent
+    {
+    private:
+        int progress;
+        int maxProgress;
+        int maxWidth;
+
+    public:
+        DLLEXPORT ConsoleProgressBar();
+        /**
+         * sets the max used with of the progress bar in charachters including
+         * the percentage number.
+         *
+         * \param maxWidth the width in characters
+         */
+        DLLEXPORT void setMaxWidth(int maxWidth);
+        /**
+         * sets the current progress.
+         *
+         * \param progress the progress
+         */
+        DLLEXPORT void setProgress(int progress);
+        /**
+         * sets the max progress steps needed to reach 100% progress.
+         *
+         * \param maxProgress max progress
+         */
+        DLLEXPORT void setMaxProgress(int maxProgress);
+
+    protected:
+        /**
+         * prints the progress bar to the console.
+         *
+         * \return number of lines printed
+         */
+        DLLEXPORT virtual int print() const override;
+    };
+
+    class ConsoleListView;
+
+    /**
+     * a command input line where the user can type in commands. the user can
+     * type the tab key for autocompletion.
+     * ConsoleListView is used for displaying the autocompletion possibilities.
+     */
+    class InputLine : public StickyConsoleContent,
+                      private Thread
+    {
+    private:
+        int cursorPos;
+        RCArray<ConsoleCommand> commands;
+        Critical cs;
+
+    protected:
+        Framework::Text input;
+        ConsoleListView* suggestions;
+
+    public:
+        DLLEXPORT InputLine();
+        DLLEXPORT ~InputLine();
+        /**
+         * ads a command to the list of possible commands used for
+         * autocompletion and execution.
+         *
+         * \param command the command to add
+         */
+        DLLEXPORT void addPossibleCommand(ConsoleCommand* command);
+        /**
+         * returns true because the user can edit the content of this line.
+         *
+         * \return true
+         */
+        DLLEXPORT virtual bool isInput() override;
+
+    protected:
+        /**
+         * called before all sticky console content is reprinted when isInput
+         * is true. restoreCursorPos() must be called after this
+         */
+        DLLEXPORT virtual void setCursorToBeginning();
+        /**
+         * prints the input line to the console. restoreCursorPos must be called
+         * later
+         *
+         * \return number of lines printed
+         */
+        DLLEXPORT virtual int print() const override;
+        /**
+         * reprints all input from the user so that the user can edit the
+         * content again. can only be called after setCursorToBeginning was
+         * called
+         */
+        DLLEXPORT virtual void restoreCursorPos() override;
+
+        /**
+         * writes the next autocomplete step to input or displays a list of
+         * possibilities in suggestions.
+         *
+         */
+        DLLEXPORT void applyAutocompletion();
+        /**
+         * executes a given command.
+         *
+         * \param command the command
+         * \return true if the command was executed successfully
+         */
+        DLLEXPORT bool executeCommand(Text command);
+        /**
+         * splits a given command into its name and arguments.
+         *
+         * \param command the command to split
+         * \param split the array with the name and arguments
+         * \param lastFinished true if the last argument is finished (folowed by
+         * a witespace or ending character)
+         * \return true if the command was split successfully
+         */
+        DLLEXPORT bool parseCommand(
+            Text command, RCArray<Text>& split, bool& lastFinished);
+
+    private:
+        DLLEXPORT void thread() override;
+
+        friend ConsoleHandler;
+    };
+
+    /**
+     * prints a list of words to the console. the list will be printed ordered
+     * by ascii value. it can be organized in multiple columns.
+     */
+    class ConsoleListView : public StickyConsoleContent
+    {
+    private:
+        int maxColumns;
+        RCArray<Text> items;
+        int maxVisibleLines;
+        int lineOffset;
+
+    public:
+        DLLEXPORT ConsoleListView();
+        /**
+         * the number of columns used to display the complete list of words.
+         *
+         * \return the number of columns
+         */
+        DLLEXPORT int getUsedColumns() const;
+        /**
+         * the number of lines needed to display the complete list of words.
+         *
+         * \return the number of lines
+         */
+        DLLEXPORT int getNeededLines() const;
+        /**
+         * specifies the maximum amount of lines visible.
+         *
+         * \param maxVisibleLines the amount of lines
+         */
+        DLLEXPORT void setMaxVisibleLines(int maxVisibleLines);
+        /**
+         * specifies the offset of the first line that should be printed.
+         *
+         * \param lineOffset the index of the first line that is printed
+         */
+        DLLEXPORT void setLineOffset(int lineOffset);
+        /**
+         * specifies the maximum amount of columns used to display the list of
+         * words.
+         *
+         * \param maxColumns the amount of columns
+         */
+        DLLEXPORT void setMaxColumns(int maxColumns);
+        /**
+         * adds a word to the list of words.
+         *
+         * \param item the word to add
+         */
+        DLLEXPORT void addItem(Text item);
+        /**
+         * removes all words from the list.
+         */
+        DLLEXPORT void clear();
+        /**
+         * the items in the list view.
+         *
+         * \return items
+         */
+        DLLEXPORT const RCArray<Text>& getItems() const;
+
+    protected:
+        /**
+         * prints the list of words to the console.
+         *
+         * \return number of lines printed
+         */
+        DLLEXPORT virtual int print() const override;
+    };
+
+    enum class ConsoleContentPosition
+    {
+        /**
+         * before all other sticky console content.
+         */
+        Top,
+        /**
+         * after all pther sticky console content.
+         */
+        Bottom,
+    };
+
+    class ConsoleHandler : public ReferenceCounter
+    {
+    private:
+        StickyConsoleContent** lines;
+        int* lineCounts;
+        int contentCount;
+        Critical cs;
+#ifdef WIN32
+        HANDLE hConsole;
+#endif
+
+    public:
+        DLLEXPORT ConsoleHandler();
+        DLLEXPORT ~ConsoleHandler();
+        /**
+         * adds a sticky console content to the console at a specified position.
+         *
+         * \param content the new content
+         * \param pos the position of the new content
+         */
+        DLLEXPORT void addContent(
+            StickyConsoleContent* content, ConsoleContentPosition pos);
+        /**
+         * removes a sticky console content from the list of displayed sticky
+         * console content.
+         *
+         * \param zContent the content to remove without increasing the
+         * reference counter
+         */
+        DLLEXPORT void removeContent(StickyConsoleContent* zContent);
+        /**
+         * the current with of the console window in characters.
+         *
+         * \return the with in characters
+         */
+        DLLEXPORT int getWidth() const;
+        /**
+         * the current height of the console window in characters.
+         *
+         * \return the height in characters
+         */
+        DLLEXPORT int getHeight() const;
+        /**
+         * removes all sticky console content from the list of displayed sticky
+         * console content.
+         */
+        DLLEXPORT void clear();
+        /**
+         * reprints all sticky console content that is currently displayed.
+         */
+        DLLEXPORT void print();
+        /**
+         * prints something to the console at the position of the first sticky
+         * console content and reprints all sticky console content below the
+         * newly printed content.
+         *
+         * \param str the string to print
+         */
+        DLLEXPORT void print(Text str);
+
+    public:
+        friend InputLine;
+    };
+} // namespace Framework

+ 4 - 0
Framework Linux.vcxproj

@@ -117,6 +117,7 @@
     <ClCompile Include="Base64.cpp" />
     <ClCompile Include="Bild.cpp" />
     <ClCompile Include="Bildschirm.cpp" />
+    <ClCompile Include="Console.cpp" />
     <ClCompile Include="Critical.cpp" />
     <ClCompile Include="Cube.cpp" />
     <ClCompile Include="Datei.cpp" />
@@ -151,9 +152,11 @@
     <ClCompile Include="Punkt.cpp" />
     <ClCompile Include="Rahmen.cpp" />
     <ClCompile Include="Random.cpp" />
+    <ClInclude Include="Console.h" />
     <ClInclude Include="Iterator.h" />
     <ClInclude Include="Logging.h" />
     <ClInclude Include="RCPointer.h" />
+    <ClCompile Include="Reader.cpp" />
     <ClCompile Include="ReferenceCounter.cpp" />
     <ClCompile Include="Regex.cpp" />
     <ClCompile Include="Schrift.cpp" />
@@ -172,6 +175,7 @@
     <ClCompile Include="UIMLView.cpp" />
     <ClCompile Include="Welt2D.cpp" />
     <ClCompile Include="Welt3D.cpp" />
+    <ClCompile Include="Writer.cpp" />
     <ClCompile Include="XML.cpp" />
     <ClCompile Include="Zeichnung.cpp" />
     <ClCompile Include="Zeichnung3D.cpp" />

+ 12 - 0
Framework Linux.vcxproj.filters

@@ -319,6 +319,9 @@
     <ClInclude Include="Supplier.h">
       <Filter>Framework\Data</Filter>
     </ClInclude>
+    <ClInclude Include="Console.h">
+      <Filter>Framework\IO</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Bild.cpp">
@@ -513,5 +516,14 @@
     <ClCompile Include="Logging.cpp">
       <Filter>Framework\Log</Filter>
     </ClCompile>
+    <ClCompile Include="Console.cpp">
+      <Filter>Framework\IO</Filter>
+    </ClCompile>
+    <ClCompile Include="Reader.cpp">
+      <Filter>Framework\IO</Filter>
+    </ClCompile>
+    <ClCompile Include="Writer.cpp">
+      <Filter>Framework\IO</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 11 - 11
Framework Tests/Array.cpp

@@ -1,8 +1,7 @@
 #include "pch.h"
 
 #define NO_MAIN
-#include <Array.h>
-
+#include "Array.h"
 #include "CppUnitTest.h"
 
 using namespace Microsoft::VisualStudio::CppUnitTestFramework;
@@ -335,30 +334,31 @@ namespace FrameworkTests
             Assert::IsTrue(deleteCounter == 7, L"Memory leaks detected");
         }
 
-        TEST_METHOD(IteratorTest)
+        TEST_METHOD (IteratorTest)
         {
             Framework::RCArray<Test<RCArrayTests>> array;
             array.add(new Test<RCArrayTests>(100, this));
             array.begin().remove();
             Assert::IsTrue(array.getEintragAnzahl() == 0,
-                				L"after adding 1 element and removing it "
-            				L"getEintragAnzahl() should be 0");
+                L"after adding 1 element and removing it "
+                L"getEintragAnzahl() should be 0");
             array.add(new Test<RCArrayTests>(100, this));
             array.add(new Test<RCArrayTests>(20, this), 1);
             array.add(new Test<RCArrayTests>(10, this), 0);
             array.add(new Test<RCArrayTests>(0, this));
             auto i = array.begin();
             Assert::IsTrue(i->getVal() == 10,
-                				L"invalid value at index 0 of array after adding elements");
+                L"invalid value at index 0 of array after adding elements");
             Assert::IsTrue(i.hasNext(), L"No next element after first element");
             Assert::IsTrue((++i)->getVal() == 100,
-                								L"invalid value at index 1 of array after adding elements");
-            Assert::IsTrue(i.hasNext(), L"No next element after second element");
+                L"invalid value at index 1 of array after adding elements");
+            Assert::IsTrue(
+                i.hasNext(), L"No next element after second element");
             Assert::IsTrue((++i)->getVal() == 20,
-                												L"invalid value at index 2 of array after adding elements");
+                L"invalid value at index 2 of array after adding elements");
             Assert::IsTrue(i.hasNext(), L"No next element after third element");
             Assert::IsTrue((++i)->getVal() == 0,
-                																		L"invalid value at index 3 of array after adding elements");
+                L"invalid value at index 3 of array after adding elements");
             Assert::IsFalse(i.hasNext(), L"Next element after last element");
             i = array.begin();
             int count = 4;
@@ -372,7 +372,7 @@ namespace FrameworkTests
             Assert::IsTrue(deleteCounter == 5, L"Memory leaks detected");
         }
 
-        TEST_METHOD(MoveTest)
+        TEST_METHOD (MoveTest)
         {
             Framework::RCArray<Test<RCArrayTests>> array;
             array.add(new Test<RCArrayTests>(100, this));

+ 5 - 1
Framework Tests/Framework Tests.vcxproj

@@ -54,7 +54,7 @@
     <UseDebugLibraries>false</UseDebugLibraries>
     <PlatformToolset>v143</PlatformToolset>
     <WholeProgramOptimization>true</WholeProgramOptimization>
-    <CharacterSet>Unicode</CharacterSet>
+    <CharacterSet>MultiByte</CharacterSet>
     <UseOfMfc>false</UseOfMfc>
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@@ -88,6 +88,8 @@
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
+    <SourcePath>C:\Users\kolja\Desktop\Kolja-Strohm-Games\Allgemein\Framework\;$(SourcePath)</SourcePath>
+    <IncludePath>C:\Users\kolja\Desktop\Kolja-Strohm-Games\Allgemein\Framework;$(IncludePath)</IncludePath>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
@@ -98,6 +100,7 @@
       <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <UseFullPaths>true</UseFullPaths>
       <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp20</LanguageStandard>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -156,6 +159,7 @@
       <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <UseFullPaths>true</UseFullPaths>
       <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
+      <LanguageStandard>stdcpp20</LanguageStandard>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>

+ 2 - 0
Framework.vcxproj

@@ -204,6 +204,7 @@ copy "x64\Release\Framework.dll" "..\..\Spiele Platform\SMP\Fertig\x64\framework
     <ClInclude Include="Array.h" />
     <ClInclude Include="AsynchronCall.h" />
     <ClInclude Include="AuswahlBox.h" />
+    <ClInclude Include="Console.h" />
     <ClInclude Include="Iterator.h" />
     <ClInclude Include="Logging.h" />
     <ClInclude Include="Regex.h" />
@@ -307,6 +308,7 @@ copy "x64\Release\Framework.dll" "..\..\Spiele Platform\SMP\Fertig\x64\framework
     <ClCompile Include="Animation3D.cpp" />
     <ClCompile Include="AsynchronCall.cpp" />
     <ClCompile Include="AuswahlBox.cpp" />
+    <ClCompile Include="Console.cpp" />
     <ClCompile Include="Logging.cpp" />
     <ClCompile Include="Regex.cpp" />
     <ClCompile Include="Base64.cpp" />

+ 6 - 0
Framework.vcxproj.filters

@@ -361,6 +361,9 @@
     <ClInclude Include="Logging.h">
       <Filter>Framework\Log</Filter>
     </ClInclude>
+    <ClInclude Include="Console.h">
+      <Filter>Framework\IO</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Model3DCollection.h">
@@ -612,6 +615,9 @@
     <ClCompile Include="Logging.cpp">
       <Filter>Framework\Log</Filter>
     </ClCompile>
+    <ClCompile Include="Console.cpp">
+      <Filter>Framework\IO</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <FxCompile Include="DX12VertexShader.hlsl">

+ 20 - 3
Logging.cpp

@@ -565,16 +565,33 @@ void Framework::Logging::LoggingChannel::writeMessage(
     }
 }
 
-Framework::Logging::ConsoleLoggingChannel::ConsoleLoggingChannel()
+Framework::Logging::CoutLoggingChannel::CoutLoggingChannel()
     : LoggingChannel()
 {}
 
-void Framework::Logging::ConsoleLoggingChannel::writeMessage(
-    const Text& msg) const
+void Framework::Logging::CoutLoggingChannel::writeMessage(const Text& msg) const
 {
     std::cout << msg.getText() << std::endl;
 }
 
+Framework::Logging::ConsoleHandlerLoggingChannel::ConsoleHandlerLoggingChannel(
+    ConsoleHandler* consolehandler)
+    : LoggingChannel(),
+      consolehandler(consolehandler)
+{}
+
+Framework::Logging::ConsoleHandlerLoggingChannel::
+    ~ConsoleHandlerLoggingChannel()
+{
+    consolehandler->release();
+}
+
+void Framework::Logging::ConsoleHandlerLoggingChannel::writeMessage(
+    const Text& msg) const
+{
+    consolehandler->print(msg);
+}
+
 Framework::Logging::FileLoggingChannel::FileLoggingChannel(Text filePath)
     : LoggingChannel(),
       file(new Datei(filePath))

+ 32 - 23
Logging.h

@@ -4,6 +4,7 @@
 #include <source_location>
 
 #include "Array.h"
+#include "Console.h"
 #include "Text.h"
 
 namespace Framework
@@ -98,26 +99,6 @@ namespace Framework
                 = 0;
         };
 
-        enum class Color
-        {
-            Black,
-            RED,
-            GREEN,
-            YELLOW,
-            Blue,
-            Magenta,
-            CYAN,
-            WHITE,
-            LIGHT_Black,
-            LIGHT_RED,
-            LIGHT_GREEN,
-            LIGHT_YELLOW,
-            LIGHT_Blue,
-            LIGHT_Magenta,
-            LIGHT_CYAN,
-            LIGHT_WHITE,
-        };
-
         /**
          * Builds a custom LoggingFormat.
          */
@@ -290,14 +271,14 @@ namespace Framework
         /**
          * logs messages to the std::cout.
          */
-        class ConsoleLoggingChannel : public LoggingChannel
+        class CoutLoggingChannel : public LoggingChannel
         {
         public:
             /**
-             * creates a new console logging channel.
+             * creates a new cout logging channel.
              *
              */
-            DLLEXPORT ConsoleLoggingChannel();
+            DLLEXPORT CoutLoggingChannel();
 
         protected:
             /**
@@ -310,6 +291,34 @@ namespace Framework
             DLLEXPORT virtual void writeMessage(const Text& msg) const override;
         };
 
+        /**
+         * logs messages to the std::cout.
+         */
+        class ConsoleHandlerLoggingChannel : public LoggingChannel
+        {
+        private:
+            ConsoleHandler* consolehandler;
+
+        public:
+            /**
+             * creates a new console logging channel.
+             *
+             */
+            DLLEXPORT ConsoleHandlerLoggingChannel(
+                ConsoleHandler* consolehandler);
+            DLLEXPORT ~ConsoleHandlerLoggingChannel();
+
+        protected:
+            /**
+             * writes a message to the given ConsoleHandler.
+             *
+             * \param msg the message
+             * \param level the log level
+             * \param location the location of the call
+             */
+            DLLEXPORT virtual void writeMessage(const Text& msg) const override;
+        };
+
         /**
          * logs messages to a file.
          */