From a4811d429c913eebe9fa074ca2f39302a452c7b5 Mon Sep 17 00:00:00 2001 From: Jared Young Date: Mon, 4 Nov 2019 02:55:27 +0000 Subject: [PATCH] added in the retro console library from Retro68, to be edited a bit for single char inputs --- mp-s7-src/retro/Console.cc | 403 +++++++++++++++++++++++++++++++ mp-s7-src/retro/Console.h | 92 +++++++ mp-s7-src/retro/ConsoleWindow.cc | 127 ++++++++++ mp-s7-src/retro/ConsoleWindow.h | 38 +++ mp-s7-src/retro/InitConsole.cc | 111 +++++++++ mp-s7-src/retro/MacUtils.h | 40 +++ 6 files changed, 811 insertions(+) create mode 100644 mp-s7-src/retro/Console.cc create mode 100644 mp-s7-src/retro/Console.h create mode 100644 mp-s7-src/retro/ConsoleWindow.cc create mode 100644 mp-s7-src/retro/ConsoleWindow.h create mode 100644 mp-s7-src/retro/InitConsole.cc create mode 100644 mp-s7-src/retro/MacUtils.h 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); + } + }; +}