diff --git a/mp-s7-src/retro/Console.cc b/mp-s7-src/retro/Console.cc
new file mode 100644
index 0000000..bc10bf5
--- /dev/null
+++ b/mp-s7-src/retro/Console.cc
@@ -0,0 +1,403 @@
+/*
+ Copyright 2012 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include "Console.h"
+#include "MacUtils.h"
+#include "Fonts.h"
+#include "Processes.h"
+
+#include
+
+using namespace retro;
+
+Console *Console::currentInstance = NULL;
+
+namespace
+{
+ class FontSetup
+ {
+ short saveFont, saveSize;
+ public:
+ FontSetup()
+ {
+#if TARGET_API_MAC_CARBON
+ GrafPtr port;
+ GetPort(&port);
+ saveFont = GetPortTextFont(port);
+ saveSize = GetPortTextSize(port);
+#else
+ saveFont = qd.thePort->txFont;
+ saveSize = qd.thePort->txSize;
+#endif
+ TextFont(kFontIDMonaco);
+ TextSize(9);
+ }
+
+ ~FontSetup()
+ {
+ TextFont(saveFont);
+ TextSize(saveSize);
+ }
+ };
+}
+
+Console::Console()
+{
+}
+
+Console::Console(GrafPtr port, Rect r)
+{
+ Init(port, r);
+}
+
+Console::~Console()
+{
+ if(currentInstance == this)
+ currentInstance = NULL;
+}
+
+void Console::Init(GrafPtr port, Rect r)
+{
+ consolePort = port;
+ bounds = r;
+
+ PortSetter setport(consolePort);
+ FontSetup fontSetup;
+
+ InsetRect(&bounds, 2,2);
+
+ cellSizeY = 12;
+ cellSizeX = CharWidth('M');
+
+ rows = (bounds.bottom - bounds.top) / cellSizeY;
+ cols = (bounds.right - bounds.left) / cellSizeX;
+
+ chars = std::vector(rows*cols, ' ');
+
+ onscreen = chars;
+
+ cursorX = cursorY = 0;
+}
+
+Rect Console::CellRect(short x, short y)
+{
+ return { (short) (bounds.top + y * cellSizeY), (short) (bounds.left + x * cellSizeX),
+ (short) (bounds.top + (y+1) * cellSizeY), (short) (bounds.left + (x+1) * cellSizeX) };
+}
+void Console::DrawCell(short x, short y, bool erase)
+{
+ Rect r = CellRect(x,y);
+
+ if(cursorDrawn)
+ {
+ if(y == cursorY && x == cursorX)
+ {
+ erase = true;
+ cursorDrawn = false;
+ }
+ }
+
+ if(erase)
+ EraseRect(&r);
+ MoveTo(r.left, r.bottom - 2);
+ DrawChar(chars[y * cols + x]);
+}
+
+void Console::DrawCells(short x1, short x2, short y, bool erase)
+{
+ Rect r = { (short) (bounds.top + y * cellSizeY), (short) (bounds.left + x1 * cellSizeX),
+ (short) (bounds.top + (y+1) * cellSizeY), (short) (bounds.left + x2 * cellSizeX) };
+
+ if(cursorDrawn)
+ {
+ if(y == cursorY && x1 <= cursorX && x2 > cursorX)
+ {
+ erase = true;
+ cursorDrawn = false;
+ }
+ }
+
+ if(erase)
+ EraseRect(&r);
+ MoveTo(r.left, r.bottom - 2);
+ DrawText(&chars[y * cols + x1], 0, x2 - x1);
+}
+
+void Console::Draw(Rect r)
+{
+ if(!consolePort)
+ return;
+ PortSetter setport(consolePort);
+ FontSetup fontSetup;
+
+ SectRect(&r, &bounds, &r);
+
+ short minRow = std::max(0, (r.top - bounds.top) / cellSizeY);
+ short maxRow = std::min((int)rows, (r.bottom - bounds.top + cellSizeY - 1) / cellSizeY);
+
+ short minCol = std::max(0, (r.left - bounds.left) / cellSizeX);
+ short maxCol = std::min((int)cols, (r.right - bounds.left + cellSizeX - 1) / cellSizeX);
+
+ EraseRect(&r);
+ for(short row = minRow; row < maxRow; ++row)
+ {
+ DrawCells(minCol, maxCol, row, false);
+ }
+ if(cursorDrawn)
+ {
+ Rect cursor = CellRect(cursorX, cursorY);
+ InvertRect(&cursor);
+ }
+ onscreen = chars;
+}
+
+void Console::ScrollUp(short n)
+{
+ cursorY--;
+ std::copy(chars.begin() + cols, chars.end(), chars.begin());
+ std::fill(chars.end() - cols, chars.end(), ' ');
+ std::copy(onscreen.begin() + cols, onscreen.end(), onscreen.begin());
+ std::fill(onscreen.end() - cols, onscreen.end(), ' ');
+ RgnHandle rgn = NewRgn();
+ ScrollRect(&bounds, 0, -cellSizeY, rgn);
+ DisposeRgn(rgn);
+ dirtyRect.top = dirtyRect.top > 0 ? dirtyRect.top - 1 : 0;
+ dirtyRect.bottom = dirtyRect.bottom > 0 ? dirtyRect.bottom - 1 : 0;
+}
+
+
+void Console::PutCharNoUpdate(char c)
+{
+ InvalidateCursor();
+ switch(c)
+ {
+ case '\r':
+ cursorX = 0;
+ break;
+ case '\n':
+ cursorY++;
+ cursorX = 0;
+ if(cursorY >= rows)
+ ScrollUp();
+ break;
+ default:
+ chars[cursorY * cols + cursorX] = c;
+ if(dirtyRect.right == 0)
+ {
+ dirtyRect.right = (dirtyRect.left = cursorX) + 1;
+ dirtyRect.bottom = (dirtyRect.top = cursorY) + 1;
+ }
+ else
+ {
+ dirtyRect.left = std::min(dirtyRect.left, cursorX);
+ dirtyRect.top = std::min(dirtyRect.top, cursorY);
+ dirtyRect.right = std::max(dirtyRect.right, short(cursorX + 1));
+ dirtyRect.bottom = std::max(dirtyRect.bottom, short(cursorY + 1));
+ }
+
+ cursorX++;
+ if(cursorX >= cols)
+ PutCharNoUpdate('\n');
+ }
+}
+
+void Console::Update()
+{
+ PortSetter setport(consolePort);
+ FontSetup fontSetup;
+
+ for(short row = dirtyRect.top; row < dirtyRect.bottom; ++row)
+ {
+ short start = -1;
+ bool needclear = false;
+ for(short col = dirtyRect.left; col < dirtyRect.right; ++col)
+ {
+ char old = onscreen[row * cols + col];
+ if(chars[row * cols + col] != old)
+ {
+ if(start == -1)
+ start = col;
+ if(old != ' ')
+ needclear = true;
+ onscreen[row * cols + col] = chars[row * cols + col];
+ }
+ else
+ {
+ if(start != -1)
+ DrawCells(start, col, row, needclear);
+ start = -1;
+ needclear = false;
+ }
+ }
+ if(start != -1)
+ DrawCells(start, dirtyRect.right, row, needclear);
+ }
+ dirtyRect = Rect();
+
+ if(cursorVisible != cursorDrawn)
+ {
+ Rect r = CellRect(cursorX, cursorY);
+ if(cursorDrawn)
+ DrawCell(cursorX, cursorY, true);
+ else
+ InvertRect(&r);
+ cursorDrawn = !cursorDrawn;
+ }
+
+#if TARGET_API_MAC_CARBON
+ QDFlushPortBuffer(consolePort,NULL);
+#endif
+}
+
+void Console::putch(char c)
+{
+ if(!rows)
+ return;
+ PutCharNoUpdate(c);
+ Update();
+}
+
+void Console::write(const char *p, int n)
+{
+ if(!rows)
+ return;
+
+ for(int i = 0; i < n; i++)
+ Console::currentInstance->PutCharNoUpdate(*p++);
+ Update();
+}
+
+
+std::string Console::ReadLine()
+{
+ if(!consolePort)
+ return "";
+
+ std::string buffer;
+ char c;
+
+ do
+ {
+ c = WaitNextChar();
+ if(!c)
+ {
+ eof = true;
+ return "";
+ }
+
+ if(c == '\r')
+ c = '\n';
+
+ if(c == '\b')
+ {
+ if(buffer.size())
+ {
+ InvalidateCursor();
+ cursorX--;
+ PutCharNoUpdate(' ');
+ cursorX--;
+ Update();
+
+ buffer.resize(buffer.size()-1);
+ }
+
+ continue;
+ }
+
+ putch(c);
+ buffer.append(1,c);
+ } while(c != '\n');
+ return buffer;
+}
+
+void Console::InvalidateCursor()
+{
+ if(cursorDrawn)
+ {
+ PortSetter setport(consolePort);
+
+ DrawCell(cursorX, cursorY, true);
+ cursorDrawn = false;
+ }
+}
+
+void Console::Idle()
+{
+ long ticks = TickCount();
+ if(ticks - blinkTicks > 60)
+ {
+ cursorVisible = !cursorVisible;
+ blinkTicks = ticks;
+ Update();
+ }
+}
+
+void Console::Reshape(Rect newBounds)
+{
+ if(!consolePort)
+ return;
+
+ InsetRect(&newBounds, 2,2);
+
+ bounds = newBounds;
+ short newRows = (bounds.bottom - bounds.top) / cellSizeY;
+ short newCols = (bounds.right - bounds.left) / cellSizeX;
+
+ short upshift = 0;
+ if(cursorY >= newRows)
+ {
+ upshift = cursorY - (newRows - 1);
+
+ InvalidateCursor();
+ cursorY = newRows - 1;
+ }
+
+ std::vector newChars(newRows*newCols, ' ');
+ for(short row = 0; row < newRows && row + upshift < rows; row++)
+ {
+ char *src = &chars[(row+upshift) * cols];
+ char *dst = &newChars[row * newCols];
+ std::copy(src, src + std::min(cols, newCols), dst);
+ }
+ chars.swap(newChars);
+ /*newChars = std::vector(newRows*newCols, ' ');
+ for(short row = 0; row < newRows && row < rows; row++)
+ {
+ char *src = &chars[row * cols];
+ char *dst = &newChars[row * newCols];
+ std::copy(src, src + std::min(cols, newCols), dst);
+ }
+ onscreen.swap(newChars);*/
+ onscreen = newChars;
+
+ rows = newRows;
+ cols = newCols;
+
+ if(upshift)
+ {
+ //dirtyRect = Rect { 0, 0, rows, cols };
+ //Update();
+ Draw();
+ }
+}
+
+char Console::WaitNextChar()
+{
+ return 0;
+}
diff --git a/mp-s7-src/retro/Console.h b/mp-s7-src/retro/Console.h
new file mode 100644
index 0000000..d94417c
--- /dev/null
+++ b/mp-s7-src/retro/Console.h
@@ -0,0 +1,92 @@
+/*
+ Copyright 2012 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+#ifndef RETRO68_CONSOLE_H_
+#define RETRO68_CONSOLE_H_
+
+#include
+#include
+#include
+
+namespace retro
+{
+ class Console
+ {
+ public:
+ Console();
+ Console(GrafPtr port, Rect r);
+ ~Console();
+
+ void Reshape(Rect newBounds);
+
+ void Draw(Rect r);
+ void Draw() { Draw(bounds); }
+ void putch(char c);
+
+ void write(const char *s, int n);
+ std::string ReadLine();
+
+ static Console *currentInstance;
+
+ short GetRows() const { return rows; }
+ short GetCols() const { return cols; }
+
+ void Idle();
+
+ bool IsEOF() const { return eof; }
+ private:
+ GrafPtr consolePort = nullptr;
+ Rect bounds;
+
+ std::vector chars, onscreen;
+
+ short cellSizeX;
+ short cellSizeY;
+
+ short rows = 0, cols = 0;
+
+ short cursorX, cursorY;
+
+ Rect dirtyRect = {};
+
+ long blinkTicks = 0;
+ bool cursorDrawn = false;
+ bool cursorVisible = true;
+ bool eof = false;
+
+ void PutCharNoUpdate(char c);
+ void Update();
+
+ Rect CellRect(short x, short y);
+ void DrawCell(short x, short y, bool erase = true);
+ void DrawCells(short x1, short x2, short y, bool erase = true);
+ void ScrollUp(short n = 1);
+
+ void InvalidateCursor();
+
+ virtual char WaitNextChar();
+
+ protected:
+ void Init(GrafPtr port, Rect r);
+
+ };
+
+
+}
+
+#endif /* RETRO68_CONSOLE_H_ */
diff --git a/mp-s7-src/retro/ConsoleWindow.cc b/mp-s7-src/retro/ConsoleWindow.cc
new file mode 100644
index 0000000..1f4092d
--- /dev/null
+++ b/mp-s7-src/retro/ConsoleWindow.cc
@@ -0,0 +1,127 @@
+/*
+ Copyright 2012 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include "ConsoleWindow.h"
+#include "Events.h"
+#include
+
+using namespace retro;
+
+namespace
+{
+ std::unordered_map *windows = NULL;
+}
+
+ConsoleWindow::ConsoleWindow(Rect r, ConstStr255Param title)
+{
+ GrafPtr port;
+
+ win = NewWindow(NULL, &r, "\pRetro68 Console", true, 0, (WindowPtr)-1, false, 0);
+
+#if !TARGET_API_MAC_CARBON
+ port = win;
+ Rect portRect = port->portRect;
+#else
+ port = GetWindowPort(win);
+ Rect portRect;
+ GetPortBounds(port, &portRect);
+#endif
+
+ SetPort(port);
+ EraseRect(&portRect);
+
+ if(!windows)
+ windows = new std::unordered_map();
+ (*windows)[win] = this;
+
+ Init(port, portRect);
+}
+
+ConsoleWindow::~ConsoleWindow()
+{
+ windows->erase(win);
+ DisposeWindow(win);
+}
+
+char ConsoleWindow::WaitNextChar()
+{
+ EventRecord event;
+ WindowPtr eventWin;
+ ConsoleWindow *realConsole;
+#if TARGET_API_MAC_CARBON
+ Rect *boundsPtr = NULL;
+#else
+ Rect *boundsPtr = &qd.screenBits.bounds;
+#endif
+
+ do
+ {
+ #if TARGET_API_MAC_CARBON
+ #define SystemTask()
+ #endif
+ SystemTask();
+ Idle();
+ while(!GetNextEvent(everyEvent, &event))
+ {
+ SystemTask();
+ Idle();
+ }
+
+ switch(event.what)
+ {
+ case updateEvt:
+ eventWin = (WindowPtr)event.message;
+ realConsole = (*windows)[(WindowPtr)event.message];
+ if(realConsole)
+ {
+ Rect updateRect;
+ BeginUpdate(eventWin);
+#if TARGET_API_MAC_CARBON
+ RgnHandle rgn = NewRgn();
+ GetPortVisibleRegion(GetWindowPort(eventWin), rgn);
+ GetRegionBounds(rgn, &updateRect);
+ DisposeRgn(rgn);
+#else
+ updateRect = (*qd.thePort->visRgn)->rgnBBox; // Life was simple back then.
+#endif
+ realConsole->Draw(updateRect);
+ EndUpdate(eventWin);
+ }
+ break;
+ case mouseDown:
+
+ switch(FindWindow(event.where, &eventWin))
+ {
+ case inDrag:
+ DragWindow(eventWin, event.where, boundsPtr);
+ break;
+ case inGrow:
+ {
+ long growResult = GrowWindow(eventWin, event.where, boundsPtr);
+ SizeWindow(eventWin, growResult & 0xFFFF, growResult >> 16, false);
+ Reshape(Rect {0, 0, (short) (growResult >> 16), (short) (growResult & 0xFFFF) });
+ }
+ break;
+ }
+ break;
+ }
+ } while(event.what != keyDown && event.what != autoKey);
+
+ return event.message & charCodeMask;
+}
diff --git a/mp-s7-src/retro/ConsoleWindow.h b/mp-s7-src/retro/ConsoleWindow.h
new file mode 100644
index 0000000..6d491f7
--- /dev/null
+++ b/mp-s7-src/retro/ConsoleWindow.h
@@ -0,0 +1,38 @@
+/*
+ Copyright 2012 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include
+#include
+#include
+
+#include "Console.h"
+
+namespace retro
+{
+ class ConsoleWindow : public Console
+ {
+ public:
+ ConsoleWindow(Rect r, ConstStr255Param title);
+ ~ConsoleWindow();
+ private:
+ WindowPtr win;
+
+ virtual char WaitNextChar();
+ };
+}
diff --git a/mp-s7-src/retro/InitConsole.cc b/mp-s7-src/retro/InitConsole.cc
new file mode 100644
index 0000000..31c096d
--- /dev/null
+++ b/mp-s7-src/retro/InitConsole.cc
@@ -0,0 +1,111 @@
+/*
+ Copyright 2014 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "MacUtils.h"
+#include "Console.h"
+#include "ConsoleWindow.h"
+
+namespace retro
+{
+ void InitConsole();
+}
+
+using namespace retro;
+
+void retro::InitConsole()
+{
+ if(Console::currentInstance)
+ return;
+ Console::currentInstance = (Console*) -1;
+
+#if !TARGET_API_MAC_CARBON
+ InitGraf(&qd.thePort);
+ InitFonts();
+ InitWindows();
+ InitMenus();
+
+ Rect r = qd.screenBits.bounds;
+#else
+ Rect r = (*GetMainDevice())->gdRect;
+#endif
+ {
+ // give MultiFinder a chance to bring the App to front
+ // see Technote TB 35 - MultiFinder Miscellanea
+ // "If your application [...] has the canBackground bit set in the
+ // size resource, then it should call _EventAvail several times
+ // (or _WaitNextEvent or _GetNextEvent) before putting up the splash
+ // screen, or the splash screen will come up behind the frontmost
+ // layer. If the canBackground bit is set, MultiFinder will not move
+ // your layer to the front until you call _GetNextEvent,
+ // _WaitNextEvent, or _EventAvail."
+
+ EventRecord event;
+ for(int i = 0; i < 5; i++)
+ EventAvail(everyEvent, &event);
+ }
+
+ r.top += 40;
+ InsetRect(&r, 5,5);
+
+ Console::currentInstance = new ConsoleWindow(r, "\pRetro68 Console");
+ InitCursor();
+}
+
+extern "C" ssize_t _consolewrite(int fd, const void *buf, size_t count)
+{
+ if(!Console::currentInstance)
+ InitConsole();
+ if(Console::currentInstance == (Console*)-1)
+ return 0;
+
+ Console::currentInstance->write((const char*)buf, count);
+ return count;
+}
+
+extern "C" ssize_t _consoleread(int fd, void *buf, size_t count)
+{
+ if(!Console::currentInstance)
+ InitConsole();
+ if(Console::currentInstance == (Console*)-1)
+ return 0;
+
+ static std::string consoleBuf;
+ if(consoleBuf.size() == 0)
+ {
+ consoleBuf = Console::currentInstance->ReadLine();
+ if(!Console::currentInstance->IsEOF())
+ consoleBuf += "\n";
+ }
+ if(count > consoleBuf.size())
+ count = consoleBuf.size();
+ memcpy(buf, consoleBuf.data(), count);
+ consoleBuf = consoleBuf.substr(count);
+ return count;
+}
diff --git a/mp-s7-src/retro/MacUtils.h b/mp-s7-src/retro/MacUtils.h
new file mode 100644
index 0000000..4bff8f9
--- /dev/null
+++ b/mp-s7-src/retro/MacUtils.h
@@ -0,0 +1,40 @@
+/*
+ Copyright 2012 Wolfgang Thaller.
+
+ This file is part of Retro68.
+
+ Retro68 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Retro68 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Retro68. If not, see .
+*/
+
+#include
+#include
+
+namespace retro
+{
+ class PortSetter
+ {
+ GrafPtr save;
+ public:
+ PortSetter(GrafPtr port)
+ {
+ ::GetPort(&save);
+ ::SetPort(port);
+ }
+
+ ~PortSetter()
+ {
+ ::SetPort(save);
+ }
+ };
+}