1
//============================================================================
5
// SS tttttt eeee ll ll aaaa
6
// SSSS tt ee ee ll ll aa
7
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8
// SS SS tt ee ll ll aa aa
9
// SSSS ttt eeeee llll llll aaaaa
11
// Copyright (c) 1995-2005 by Bradford W. Mott and the Stella team
13
// See the file "license" for information on usage and redistribution of
14
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16
// $Id: PromptWidget.cxx,v 1.8 2006/03/25 00:34:17 stephena Exp $
18
// Based on code from ScummVM - Scumm Interpreter
19
// Copyright (C) 2002-2004 The ScummVM project
20
//============================================================================
25
#include "ScrollBarWidget.hxx"
26
#include "FrameBuffer.hxx"
27
#include "EventHandler.hxx"
28
#include "Version.hxx"
29
#include "Debugger.hxx"
30
#include "DebuggerDialog.hxx"
31
#include "DebuggerParser.hxx"
33
#include "PromptWidget.hxx"
34
#include "EquateList.hxx"
39
* - it is very inefficient to redraw the full thingy when just one char is added/removed.
40
* Instead, we could just copy the GFX of the blank console (i.e. after the transparent
41
* background is drawn, before any text is drawn). Then using that, it becomes trivial
42
* to erase a single character, do scrolling etc.
43
* - a *lot* of others things, this code is in no way complete and heavily under progress
46
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
47
PromptWidget::PromptWidget(GuiObject* boss, const GUI::Font& font,
48
int x, int y, int w, int h)
49
: Widget(boss, font, x, y, w - kScrollBarWidth, h),
54
_flags = WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS |
56
_type = kPromptWidget;
58
_kConsoleCharWidth = font.getMaxCharWidth();
59
_kConsoleCharHeight = font.getFontHeight();
60
_kConsoleLineHeight = _kConsoleCharHeight + 2;
62
// Calculate depending values
63
_lineWidth = (_w - kScrollBarWidth - 2) / _kConsoleCharWidth;
64
_linesPerPage = (_h - 2) / _kConsoleLineHeight;
66
memset(_buffer, 0, kBufferSize * sizeof(int));
67
_linesInBuffer = kBufferSize / _lineWidth;
70
_scrollLine = _linesPerPage - 1;
71
_firstLineInBuffer = 0;
74
_scrollBar = new ScrollBarWidget(boss, font, _x + _w, _y, kScrollBarWidth, _h);
75
_scrollBar->setTarget(this);
78
defaultTextColor = kTextColor;
79
defaultBGColor = kBGColor;
80
textColor = defaultTextColor;
81
bgColor = defaultBGColor;
88
for (int i = 0; i < kHistorySize; i++)
89
_history[i][0] = '\0';
91
_promptStartPos = _promptEndPos = -1;
96
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
97
PromptWidget::~PromptWidget()
101
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
102
void PromptWidget::drawWidget(bool hilite)
104
//cerr << "PromptWidget::drawWidget\n";
105
int fgcolor, bgcolor;
107
FrameBuffer& fb = _boss->instance()->frameBuffer();
110
int start = _scrollLine - _linesPerPage + 1;
113
for (int line = 0; line < _linesPerPage; line++)
116
for (int column = 0; column < _lineWidth; column++) {
117
int c = buffer((start + line) * _lineWidth + column);
119
if(c & (1 << 17)) { // inverse video flag
121
bgcolor = (c & 0x1ffff) >> 8;
122
fb.fillRect(x, y, _kConsoleCharWidth, _kConsoleCharHeight, bgcolor);
127
fb.drawChar(&instance()->consoleFont(), c & 0x7f, x, y, fgcolor);
128
x += _kConsoleCharWidth;
130
y += _kConsoleLineHeight;
136
// Draw the scrollbar
140
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
141
void PromptWidget::handleMouseDown(int x, int y, int button, int clickCount)
143
// cerr << "PromptWidget::handleMouseDown\n";
146
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147
void PromptWidget::handleMouseWheel(int x, int y, int direction)
149
_scrollBar->handleMouseWheel(x, y, direction);
152
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
153
void PromptWidget::printPrompt()
155
string watches = instance()->debugger().showWatches();
156
if(watches.length() > 0)
160
_promptStartPos = _promptEndPos = _currentPos;
163
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
164
bool PromptWidget::handleKeyDown(int ascii, int keycode, int modifiers)
172
case '\n': // enter/return
177
assert(_promptEndPos >= _promptStartPos);
178
int len = _promptEndPos - _promptStartPos;
182
// We have to allocate the string buffer with new, since VC++ sadly does not
183
// comply to the C++ standard, so we can't use a dynamic sized stack array.
184
char *str = new char[len + 1];
186
// Copy the user input to str
187
for (i = 0; i < len; i++)
188
str[i] = buffer(_promptStartPos + i) & 0x7f;
191
// Add the input to the history
194
// Pass the command to the debugger, and print the result
195
print( instance()->debugger().run(str) + "\n");
197
// Get rid of the string buffer
208
// Tab completion: we complete either commands or labels, but not
211
if(_currentPos <= _promptStartPos)
215
int len = _promptEndPos - _promptStartPos;
217
int lastDelimPos = -1;
218
char delimiter = '\0';
220
char *str = new char[len + 1];
221
for (i = 0; i < len; i++) {
222
str[i] = buffer(_promptStartPos + i) & 0x7f;
223
if(strchr("*@<> ", str[i]) != NULL ) {
230
const char *completionList;
234
if(lastDelimPos < 0) {
235
// no delimiters, do command completion:
236
DebuggerParser *parser = instance()->debugger().parser();
237
possibilities = parser->countCompletions(str);
239
if(possibilities < 1) {
244
completionList = parser->getCompletions();
245
prefix = parser->getCompletionPrefix();
247
// we got a delimiter, so this must be a label:
248
EquateList *equates = instance()->debugger().equates();
249
possibilities = equates->countCompletions(str + lastDelimPos + 1);
251
if(possibilities < 1) {
256
completionList = equates->getCompletions();
257
prefix = equates->getCompletionPrefix();
260
if(possibilities == 1) {
261
// add to buffer as though user typed it (plus a space)
262
_currentPos = _promptStartPos + lastDelimPos + 1;
263
while(*completionList != '\0') {
264
putcharIntern(*completionList++);
267
_promptEndPos = _currentPos;
270
// add to buffer as-is, then add PROMPT plus whatever we have so far
271
_currentPos = _promptStartPos + lastDelimPos + 1;
274
print(completionList);
278
_promptStartPos = _currentPos;
280
for(i=0; i<lastDelimPos; i++)
281
putcharIntern(str[i]);
284
putcharIntern(delimiter);
287
_promptEndPos = _currentPos;
295
if (_currentPos > _promptStartPos)
307
case 256 + 24: // pageup
308
if (instance()->eventHandler().kbdShift(modifiers))
310
// Don't scroll up when at top of buffer
311
if(_scrollLine < _linesPerPage)
314
_scrollLine -= _linesPerPage - 1;
315
if (_scrollLine < _firstLineInBuffer + _linesPerPage - 1)
316
_scrollLine = _firstLineInBuffer + _linesPerPage - 1;
317
updateScrollBuffer();
323
case 256 + 25: // pagedown
324
if (instance()->eventHandler().kbdShift(modifiers))
326
// Don't scroll down when at bottom of buffer
327
if(_scrollLine >= _promptEndPos / _lineWidth)
330
_scrollLine += _linesPerPage - 1;
331
if (_scrollLine > _promptEndPos / _lineWidth)
332
_scrollLine = _promptEndPos / _lineWidth;
333
updateScrollBuffer();
339
case 256 + 22: // home
340
if (instance()->eventHandler().kbdShift(modifiers))
342
_scrollLine = _firstLineInBuffer + _linesPerPage - 1;
343
updateScrollBuffer();
346
_currentPos = _promptStartPos;
351
case 256 + 23: // end
352
if (instance()->eventHandler().kbdShift(modifiers))
354
_scrollLine = _promptEndPos / _lineWidth;
355
if (_scrollLine < _linesPerPage - 1)
356
_scrollLine = _linesPerPage - 1;
357
updateScrollBuffer();
360
_currentPos = _promptEndPos;
365
case 273: // cursor up
366
if (instance()->eventHandler().kbdShift(modifiers))
368
if(_scrollLine <= _firstLineInBuffer + _linesPerPage - 1)
372
updateScrollBuffer();
380
case 274: // cursor down
381
if (instance()->eventHandler().kbdShift(modifiers))
383
// Don't scroll down when at bottom of buffer
384
if(_scrollLine >= _promptEndPos / _lineWidth)
388
updateScrollBuffer();
396
case 275: // cursor right
397
if (_currentPos < _promptEndPos)
403
case 276: // cursor left
404
if (_currentPos > _promptStartPos)
411
if (instance()->eventHandler().kbdControl(modifiers))
413
specialKeys(keycode);
415
else if (instance()->eventHandler().kbdAlt(modifiers))
418
else if (isprint(ascii))
420
for (i = _promptEndPos - 1; i >= _currentPos; i--)
421
buffer(i + 1) = buffer(i);
431
// Take care of changes made above
437
// There are times when we want the prompt and scrollbar to be marked
438
// as dirty *after* they've been drawn above. One such occurrence is
439
// when we issue a command that indirectly redraws the entire parent
440
// dialog (such as 'scanline' or 'frame').
441
// In those cases, the return code of the command must be shown, but the
442
// entire dialog contents are redrawn at a later time. So the prompt and
443
// scrollbar won't be redrawn unless they're dirty again.
447
_scrollBar->setDirty();
454
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
455
void PromptWidget::insertIntoPrompt(const char* str)
457
unsigned int l = strlen(str);
459
for (int i = _promptEndPos - 1; i >= _currentPos; i--)
460
buffer(i + l) = buffer(i);
462
for (unsigned int j = 0; j < l; ++j)
465
putcharIntern(str[j]);
469
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
470
void PromptWidget::handleCommand(CommandSender* sender, int cmd,
475
case kSetPositionCmd:
476
int newPos = (int)data + _linesPerPage - 1 + _firstLineInBuffer;
477
if (newPos != _scrollLine)
479
_scrollLine = newPos;
486
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
487
GUI::Rect PromptWidget::getRect() const
489
// Account for attached scrollbar when calculating width
490
int x = getAbsX() - 1, y = getAbsY() - 1,
491
w = getWidth() + kScrollBarWidth + 2, h = getHeight() + 2;
493
GUI::Rect r(x, y, x+w, y+h);
497
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
498
void PromptWidget::loadConfig()
500
// See logic at the end of handleKeyDown for an explanation of this
503
// Show the prompt the first time we draw this widget
506
// Display greetings & prompt
507
string version = string("Stella ") + STELLA_VERSION + "\n";
508
print(version.c_str());
509
print("Debugger is ready\n");
511
_promptStartPos = _promptEndPos = _currentPos;
515
// Take care of one-time debugger stuff
516
instance()->debugger().autoExec();
520
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
521
void PromptWidget::specialKeys(int keycode)
523
bool handled = false;
528
_currentPos = _promptStartPos;
538
_currentPos = _promptEndPos;
564
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
565
void PromptWidget::killChar(int direction)
567
if(direction == -1) // Delete previous character (backspace)
569
if(_currentPos <= _promptStartPos)
573
for (int i = _currentPos; i < _promptEndPos; i++)
574
buffer(i) = buffer(i + 1);
576
buffer(_promptEndPos) = ' ';
579
else if(direction == 1) // Delete next character (delete)
581
if(_currentPos >= _promptEndPos)
584
// There are further characters to the right of cursor
585
if(_currentPos + 1 <= _promptEndPos)
587
for (int i = _currentPos; i < _promptEndPos; i++)
588
buffer(i) = buffer(i + 1);
590
buffer(_promptEndPos) = ' ';
596
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
597
void PromptWidget::killLine(int direction)
599
if(direction == -1) // erase from current position to beginning of line
601
int count = _currentPos - _promptStartPos;
603
for (int i = 0; i < count; i++)
606
else if(direction == 1) // erase from current position to end of line
608
for (int i = _currentPos; i < _promptEndPos; i++)
611
_promptEndPos = _currentPos;
615
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
616
void PromptWidget::killLastWord()
620
while (_currentPos > _promptStartPos)
622
if ((buffer(_currentPos - 1) & 0xff) == ' ')
634
for (int i = _currentPos; i < _promptEndPos; i++)
635
buffer(i) = buffer(i + cnt);
637
buffer(_promptEndPos) = ' ';
638
_promptEndPos -= cnt;
641
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
642
void PromptWidget::addToHistory(const char *str)
644
strcpy(_history[_historyIndex], str);
645
_historyIndex = (_historyIndex + 1) % kHistorySize;
648
if (_historySize < kHistorySize)
652
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
653
int PromptWidget::compareHistory(const char *histLine) {
657
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
658
void PromptWidget::historyScroll(int direction)
660
if (_historySize == 0)
663
if (_historyLine == 0 && direction > 0)
666
for (i = 0; i < _promptEndPos - _promptStartPos; i++)
667
_history[_historyIndex][i] = buffer(_promptStartPos + i);
669
_history[_historyIndex][i] = '\0';
672
// Advance to the next line in the history
673
int line = _historyLine + direction;
674
if ((direction < 0 && line < 0) || (direction > 0 && line > _historySize))
677
// If they press arrow-up with anything in the buffer, search backwards
680
if(direction < 0 && _currentPos > _promptStartPos) {
681
for(;line > 0; line--) {
682
if(compareHistory(_history[line]) == 0)
690
// Remove the current user text
691
_currentPos = _promptStartPos;
692
killLine(1); // to end of line
694
// ... and ensure the prompt is visible
697
// Print the text from the history
699
if (_historyLine > 0)
700
idx = (_historyIndex - _historyLine + _historySize) % _historySize;
704
for (int i = 0; i < kLineBufferSize && _history[idx][i] != '\0'; i++)
705
putcharIntern(_history[idx][i]);
707
_promptEndPos = _currentPos;
709
// Ensure once more the caret is visible (in case of very long history entries)
715
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
716
void PromptWidget::nextLine()
718
// reset colors every line, so I don't have to remember to do it myself
719
textColor = defaultTextColor;
720
bgColor = defaultBGColor;
723
int line = _currentPos / _lineWidth;
724
if (line == _scrollLine)
727
_currentPos = (line + 1) * _lineWidth;
729
updateScrollBuffer();
733
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
734
// Call this (at least) when the current line changes or when a new line is added
735
void PromptWidget::updateScrollBuffer()
737
int lastchar = MAX(_promptEndPos, _currentPos);
738
int line = lastchar / _lineWidth;
739
int numlines = (line < _linesInBuffer) ? line + 1 : _linesInBuffer;
740
int firstline = line - numlines + 1;
742
if (firstline > _firstLineInBuffer)
744
// clear old line from buffer
745
for (int i = lastchar; i < (line+1) * _lineWidth; ++i)
748
_firstLineInBuffer = firstline;
751
_scrollBar->_numEntries = numlines;
752
_scrollBar->_currentPos = _scrollBar->_numEntries - (line - _scrollLine + _linesPerPage);
753
_scrollBar->_entriesPerPage = _linesPerPage;
754
_scrollBar->recalc();
757
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
758
int PromptWidget::printf(const char *format, ...)
762
va_start(argptr, format);
763
int count = this->vprintf(format, argptr);
768
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
769
int PromptWidget::vprintf(const char *format, va_list argptr)
772
int count = VSNPRINTF(buf, sizeof(buf), format, argptr);
778
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
779
void PromptWidget::putchar(int c)
783
setDirty(); draw(); // FIXME - not nice to redraw the full console just for one char!
786
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
787
void PromptWidget::putcharIntern(int c)
791
else if(c & 0x80) { // set foreground color to TIA color
792
// don't print or advance cursor
793
// there are only 128 TIA colors, but
794
// OverlayColor contains 256 of them
795
textColor = (c & 0x7f) << 1;
797
else if(c < ' ') { // More colors (the regular GUI ones)
798
textColor = c + 0x100;
800
else if(c == 0x7f) { // toggle inverse video (DEL char)
801
_inverse = !_inverse;
805
buffer(_currentPos) = c | (textColor << 8) | (_inverse << 17);
807
if ((_scrollLine + 1) * _lineWidth == _currentPos)
810
updateScrollBuffer();
815
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
816
void PromptWidget::print(const string& str)
818
const char* c = str.c_str();
823
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
824
void PromptWidget::drawCaret()
826
//cerr << "PromptWidget::drawCaret()\n";
827
FrameBuffer& fb = _boss->instance()->frameBuffer();
829
int line = _currentPos / _lineWidth;
831
// Don't draw the cursor if it's not in the current view
832
if(_scrollLine < line)
835
int displayLine = line - _scrollLine + _linesPerPage - 1;
836
int x = _x + 1 + (_currentPos % _lineWidth) * _kConsoleCharWidth;
837
int y = _y + displayLine * _kConsoleLineHeight;
839
char c = buffer(_currentPos);
840
fb.fillRect(x, y, _kConsoleCharWidth, _kConsoleLineHeight, kTextColor);
841
fb.drawChar(&_boss->instance()->consoleFont(), c, x, y + 2, kBGColor);
844
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
845
void PromptWidget::scrollToCurrent()
847
int line = _promptEndPos / _lineWidth;
849
if (line + _linesPerPage <= _scrollLine)
851
// TODO - this should only occur for loong edit lines, though
853
else if (line > _scrollLine)
856
updateScrollBuffer();
860
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
861
bool PromptWidget::saveBuffer(string& filename)
863
ofstream out(filename.c_str());
867
for(int start=0; start<_promptStartPos; start+=_lineWidth) {
868
int end = start+_lineWidth-1;
870
// look for first non-space, printing char from end of line
871
while( char(_buffer[end] & 0xff) <= ' ' && end >= start)
874
// spit out the line minus its trailing junk.
875
// Strip off any color/inverse bits
876
for(int j=start; j<=end; j++)
877
out << char(_buffer[j] & 0xff);