1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
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
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "ScrollBarWidget.hxx"
19#include "FBSurface.hxx"
20#include "Font.hxx"
21#include "StellaKeys.hxx"
22#include "Version.hxx"
23#include "Debugger.hxx"
24#include "DebuggerDialog.hxx"
25#include "DebuggerParser.hxx"
26
27#include "PromptWidget.hxx"
28#include "CartDebug.hxx"
29
30#define PROMPT "> "
31
32// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33PromptWidget::PromptWidget(GuiObject* boss, const GUI::Font& font,
34 int x, int y, int w, int h)
35 : Widget(boss, font, x, y, w - kScrollBarWidth, h),
36 CommandSender(boss),
37 _historySize(0),
38 _historyIndex(0),
39 _historyLine(0),
40 _makeDirty(false),
41 _firstTime(true),
42 _exitedEarly(false)
43{
44 _flags = Widget::FLAG_ENABLED | Widget::FLAG_CLEARBG | Widget::FLAG_RETAIN_FOCUS |
45 Widget::FLAG_WANTS_TAB | Widget::FLAG_WANTS_RAWDATA;
46 _textcolor = kTextColor;
47 _bgcolor = kWidColor;
48 _bgcolorlo = kDlgColor;
49
50 _kConsoleCharWidth = font.getMaxCharWidth();
51 _kConsoleCharHeight = font.getFontHeight();
52 _kConsoleLineHeight = _kConsoleCharHeight + 2;
53
54 // Calculate depending values
55 _lineWidth = (_w - kScrollBarWidth - 2) / _kConsoleCharWidth;
56 _linesPerPage = (_h - 2) / _kConsoleLineHeight;
57 _linesInBuffer = kBufferSize / _lineWidth;
58
59 // Add scrollbar
60 _scrollBar = new ScrollBarWidget(boss, font, _x + _w, _y, kScrollBarWidth, _h);
61 _scrollBar->setTarget(this);
62
63 // Init colors
64 _inverse = false;
65
66 clearScreen();
67
68 addFocusWidget(this);
69}
70
71// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
72void PromptWidget::drawWidget(bool hilite)
73{
74//cerr << "PromptWidget::drawWidget\n";
75 ColorId fgcolor, bgcolor;
76
77 FBSurface& s = _boss->dialog().surface();
78 bool onTop = _boss->dialog().isOnTop();
79
80 // Draw text
81 int start = _scrollLine - _linesPerPage + 1;
82 int y = _y + 2;
83
84 for (int line = 0; line < _linesPerPage; ++line)
85 {
86 int x = _x + 1;
87 for (int column = 0; column < _lineWidth; ++column) {
88 int c = buffer((start + line) * _lineWidth + column);
89
90 if(c & (1 << 17)) // inverse video flag
91 {
92 fgcolor = _bgcolor;
93 bgcolor = ColorId((c & 0x1ffff) >> 8);
94 s.fillRect(x, y, _kConsoleCharWidth, _kConsoleCharHeight, bgcolor);
95 }
96 else
97 fgcolor = ColorId(c >> 8);
98
99 s.drawChar(_font, c & 0x7f, x, y, onTop ? fgcolor : kColor);
100 x += _kConsoleCharWidth;
101 }
102 y += _kConsoleLineHeight;
103 }
104
105 // Draw the caret
106 drawCaret();
107
108 // Draw the scrollbar
109 _scrollBar->draw();
110}
111
112// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
113void PromptWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
114{
115// cerr << "PromptWidget::handleMouseDown\n";
116}
117
118// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119void PromptWidget::handleMouseWheel(int x, int y, int direction)
120{
121 _scrollBar->handleMouseWheel(x, y, direction);
122}
123
124// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
125void PromptWidget::printPrompt()
126{
127 string watches = instance().debugger().showWatches();
128 if(watches.length() > 0)
129 print(watches);
130
131 print(PROMPT);
132 _promptStartPos = _promptEndPos = _currentPos;
133}
134
135// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136bool PromptWidget::handleText(char text)
137{
138 if(text >= 0)
139 {
140 // FIXME - convert this class to inherit from EditableWidget
141 for(int i = _promptEndPos - 1; i >= _currentPos; i--)
142 buffer(i + 1) = buffer(i);
143 _promptEndPos++;
144 putcharIntern(text);
145 scrollToCurrent();
146 }
147 return true;
148}
149
150// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151bool PromptWidget::handleKeyDown(StellaKey key, StellaMod mod)
152{
153 bool handled = true;
154 bool dirty = false;
155
156 switch(key)
157 {
158 case KBDK_RETURN:
159 case KBDK_KP_ENTER:
160 {
161 nextLine();
162
163 assert(_promptEndPos >= _promptStartPos);
164 int len = _promptEndPos - _promptStartPos;
165
166 if (len > 0)
167 {
168 // Copy the user input to command
169 string command;
170 for (int i = 0; i < len; i++)
171 command += buffer(_promptStartPos + i) & 0x7f;
172
173 // Add the input to the history
174 addToHistory(command.c_str());
175
176 // Pass the command to the debugger, and print the result
177 string result = instance().debugger().run(command);
178
179 // This is a bit of a hack
180 // Certain commands remove the debugger dialog from underneath us,
181 // so we shouldn't print any messages
182 // Those commands will return '_EXIT_DEBUGGER' as their result
183 if(result == "_EXIT_DEBUGGER")
184 {
185 _exitedEarly = true;
186 return true;
187 }
188 else if(result != "")
189 print(result + "\n");
190 }
191
192 printPrompt();
193 dirty = true;
194 break;
195 }
196
197 case KBDK_TAB:
198 {
199 // Tab completion: we complete either commands or labels, but not
200 // both at once.
201
202 if(_currentPos <= _promptStartPos)
203 break;
204
205 scrollToCurrent();
206 int len = _promptEndPos - _promptStartPos;
207 if(len > 255) len = 255;
208
209 int lastDelimPos = -1;
210 char delimiter = '\0';
211
212 char inputStr[256];
213 for (int i = 0; i < len; i++)
214 {
215 inputStr[i] = buffer(_promptStartPos + i) & 0x7f;
216 // whitespace characters
217 if(strchr("{*@<> =[]()+-/&|!^~%", inputStr[i]))
218 {
219 lastDelimPos = i;
220 delimiter = inputStr[i];
221 }
222 }
223 inputStr[len] = '\0';
224 size_t strLen = len - lastDelimPos - 1;
225
226 StringList list;
227 string completionList;
228 string prefix;
229
230 if(lastDelimPos < 0)
231 {
232 // no delimiters, do only command completion:
233 const DebuggerParser& parser = instance().debugger().parser();
234 parser.getCompletions(inputStr, list);
235
236 if(list.size() < 1)
237 break;
238
239 sort(list.begin(), list.end());
240 completionList = list[0];
241 for(uInt32 i = 1; i < list.size(); ++i)
242 completionList += " " + list[i];
243 prefix = getCompletionPrefix(list);
244 }
245 else
246 {
247 // Special case for 'help' command
248 if(BSPF::startsWithIgnoreCase(inputStr, "help"))
249 {
250 instance().debugger().parser().getCompletions(inputStr + lastDelimPos + 1, list);
251 }
252 else
253 {
254 // do not show ALL labels without any filter as it makes no sense
255 if(strLen > 0)
256 {
257 // we got a delimiter, so this must be a label or a function
258 const Debugger& dbg = instance().debugger();
259
260 dbg.cartDebug().getCompletions(inputStr + lastDelimPos + 1, list);
261 dbg.getCompletions(inputStr + lastDelimPos + 1, list);
262 }
263 }
264
265 if(list.size() < 1)
266 break;
267
268 sort(list.begin(), list.end());
269 completionList = list[0];
270 for(uInt32 i = 1; i < list.size(); ++i)
271 completionList += " " + list[i];
272 prefix = getCompletionPrefix(list);
273 }
274
275 // TODO: tab through list
276
277 if(list.size() == 1)
278 {
279 // add to buffer as though user typed it (plus a space)
280 _currentPos = _promptStartPos + lastDelimPos + 1;
281 const char* clptr = completionList.c_str();
282 while(*clptr != '\0')
283 putcharIntern(*clptr++);
284
285 putcharIntern(' ');
286 _promptEndPos = _currentPos;
287 }
288 else
289 {
290 nextLine();
291 // add to buffer as-is, then add PROMPT plus whatever we have so far
292 _currentPos = _promptStartPos + lastDelimPos + 1;
293
294 print("\n");
295 print(completionList);
296 print("\n");
297 print(PROMPT);
298
299 _promptStartPos = _currentPos;
300
301 if(prefix.length() < strLen)
302 {
303 for(int i = 0; i < len; i++)
304 putcharIntern(inputStr[i]);
305 }
306 else
307 {
308 for(int i = 0; i < lastDelimPos; i++)
309 putcharIntern(inputStr[i]);
310
311 if(lastDelimPos > 0)
312 putcharIntern(delimiter);
313
314 print(prefix);
315 }
316 _promptEndPos = _currentPos;
317 }
318 dirty = true;
319 break;
320 }
321
322 case KBDK_BACKSPACE:
323 if (_currentPos > _promptStartPos)
324 killChar(-1);
325
326 scrollToCurrent();
327 dirty = true;
328 break;
329
330 case KBDK_DELETE:
331 case KBDK_KP_PERIOD: // actually the num delete
332 killChar(+1);
333 dirty = true;
334 break;
335
336 case KBDK_PAGEUP:
337 if (StellaModTest::isShift(mod))
338 {
339 // Don't scroll up when at top of buffer
340 if(_scrollLine < _linesPerPage)
341 break;
342
343 _scrollLine -= _linesPerPage - 1;
344 if (_scrollLine < _firstLineInBuffer + _linesPerPage - 1)
345 _scrollLine = _firstLineInBuffer + _linesPerPage - 1;
346 updateScrollBuffer();
347
348 dirty = true;
349 }
350 break;
351
352 case KBDK_PAGEDOWN:
353 if (StellaModTest::isShift(mod))
354 {
355 // Don't scroll down when at bottom of buffer
356 if(_scrollLine >= _promptEndPos / _lineWidth)
357 break;
358
359 _scrollLine += _linesPerPage - 1;
360 if (_scrollLine > _promptEndPos / _lineWidth)
361 _scrollLine = _promptEndPos / _lineWidth;
362 updateScrollBuffer();
363
364 dirty = true;
365 }
366 break;
367
368 case KBDK_HOME:
369 if (StellaModTest::isShift(mod))
370 {
371 _scrollLine = _firstLineInBuffer + _linesPerPage - 1;
372 updateScrollBuffer();
373 }
374 else
375 _currentPos = _promptStartPos;
376
377 dirty = true;
378 break;
379
380 case KBDK_END:
381 if (StellaModTest::isShift(mod))
382 {
383 _scrollLine = _promptEndPos / _lineWidth;
384 if (_scrollLine < _linesPerPage - 1)
385 _scrollLine = _linesPerPage - 1;
386 updateScrollBuffer();
387 }
388 else
389 _currentPos = _promptEndPos;
390
391 dirty = true;
392 break;
393
394 case KBDK_UP:
395 if (StellaModTest::isShift(mod))
396 {
397 if(_scrollLine <= _firstLineInBuffer + _linesPerPage - 1)
398 break;
399
400 _scrollLine -= 1;
401 updateScrollBuffer();
402
403 dirty = true;
404 }
405 else
406 historyScroll(+1);
407 break;
408
409 case KBDK_DOWN:
410 if (StellaModTest::isShift(mod))
411 {
412 // Don't scroll down when at bottom of buffer
413 if(_scrollLine >= _promptEndPos / _lineWidth)
414 break;
415
416 _scrollLine += 1;
417 updateScrollBuffer();
418
419 dirty = true;
420 }
421 else
422 historyScroll(-1);
423 break;
424
425 case KBDK_RIGHT:
426 if (_currentPos < _promptEndPos)
427 _currentPos++;
428
429 dirty = true;
430 break;
431
432 case KBDK_LEFT:
433 if (_currentPos > _promptStartPos)
434 _currentPos--;
435
436 dirty = true;
437 break;
438
439 default:
440 if (StellaModTest::isControl(mod))
441 {
442 specialKeys(key);
443 }
444 else if (StellaModTest::isAlt(mod))
445 {
446 // Placeholder only - this will never be reached
447 }
448 else
449 handled = false;
450 break;
451 }
452
453 // Take care of changes made above
454 if(dirty)
455 setDirty();
456
457 // There are times when we want the prompt and scrollbar to be marked
458 // as dirty *after* they've been drawn above. One such occurrence is
459 // when we issue a command that indirectly redraws the entire parent
460 // dialog (such as 'scanline' or 'frame').
461 // In those cases, the return code of the command must be shown, but the
462 // entire dialog contents are redrawn at a later time. So the prompt and
463 // scrollbar won't be redrawn unless they're dirty again.
464 if(_makeDirty)
465 {
466 setDirty();
467 _scrollBar->setDirty();
468 _makeDirty = false;
469 }
470
471 return handled;
472}
473
474#if 0
475// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
476void PromptWidget::insertIntoPrompt(const char* str)
477{
478 Int32 l = (Int32)strlen(str);
479 for(Int32 i = _promptEndPos - 1; i >= _currentPos; i--)
480 buffer(i + l) = buffer(i);
481
482 for(Int32 j = 0; j < l; ++j)
483 {
484 _promptEndPos++;
485 putcharIntern(str[j]);
486 }
487}
488#endif
489
490// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
491void PromptWidget::handleCommand(CommandSender* sender, int cmd,
492 int data, int id)
493{
494 switch (cmd)
495 {
496 case GuiObject::kSetPositionCmd:
497 int newPos = int(data) + _linesPerPage - 1 + _firstLineInBuffer;
498 if (newPos != _scrollLine)
499 {
500 _scrollLine = newPos;
501 setDirty();
502 }
503 break;
504 }
505}
506
507// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
508void PromptWidget::loadConfig()
509{
510 // See logic at the end of handleKeyDown for an explanation of this
511 _makeDirty = true;
512
513 // Show the prompt the first time we draw this widget
514 if(_firstTime)
515 {
516 _firstTime = false;
517
518 // Display greetings & prompt
519 string version = string("Stella ") + STELLA_VERSION + "\n";
520 print(version);
521 print(PROMPT);
522
523 // Take care of one-time debugger stuff
524 // fill the history from the saved breaks, traps and watches commands
525 StringList history;
526 print(instance().debugger().autoExec(&history));
527 for(uInt32 i = 0; i < history.size(); ++i)
528 {
529 addToHistory(history[i].c_str());
530 }
531 history.clear();
532 print(instance().debugger().cartDebug().loadConfigFile() + "\n");
533 print(instance().debugger().cartDebug().loadListFile() + "\n");
534 print(instance().debugger().cartDebug().loadSymbolFile() + "\n");
535 print(PROMPT);
536
537 _promptStartPos = _promptEndPos = _currentPos;
538 _exitedEarly = false;
539 }
540 else if(_exitedEarly)
541 {
542 printPrompt();
543 _exitedEarly = false;
544 }
545}
546
547// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
548int PromptWidget::getWidth() const
549{
550 return _w + kScrollBarWidth;
551}
552
553// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
554void PromptWidget::specialKeys(StellaKey key)
555{
556 bool handled = true;
557
558 switch(key)
559 {
560 case KBDK_D:
561 killChar(+1);
562 break;
563 case KBDK_K:
564 killLine(+1);
565 break;
566 case KBDK_U:
567 killLine(-1);
568 break;
569 case KBDK_W:
570 killLastWord();
571 break;
572 case KBDK_A:
573 textSelectAll();
574 break;
575 case KBDK_X:
576 textCut();
577 break;
578 case KBDK_C:
579 textCopy();
580 break;
581 case KBDK_V:
582 textPaste();
583 break;
584 default:
585 handled = false;
586 break;
587 }
588
589 if(handled)
590 setDirty();
591}
592
593// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
594void PromptWidget::killChar(int direction)
595{
596 if(direction == -1) // Delete previous character (backspace)
597 {
598 if(_currentPos <= _promptStartPos)
599 return;
600
601 _currentPos--;
602 for (int i = _currentPos; i < _promptEndPos; i++)
603 buffer(i) = buffer(i + 1);
604
605 buffer(_promptEndPos) = ' ';
606 _promptEndPos--;
607 }
608 else if(direction == 1) // Delete next character (delete)
609 {
610 if(_currentPos >= _promptEndPos)
611 return;
612
613 // There are further characters to the right of cursor
614 if(_currentPos + 1 <= _promptEndPos)
615 {
616 for (int i = _currentPos; i < _promptEndPos; i++)
617 buffer(i) = buffer(i + 1);
618
619 buffer(_promptEndPos) = ' ';
620 _promptEndPos--;
621 }
622 }
623}
624
625// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
626void PromptWidget::killLine(int direction)
627{
628 if(direction == -1) // erase from current position to beginning of line
629 {
630 int count = _currentPos - _promptStartPos;
631 if(count > 0)
632 for (int i = 0; i < count; i++)
633 killChar(-1);
634 }
635 else if(direction == 1) // erase from current position to end of line
636 {
637 for (int i = _currentPos; i < _promptEndPos; i++)
638 buffer(i) = ' ';
639
640 _promptEndPos = _currentPos;
641 }
642}
643
644// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
645void PromptWidget::killLastWord()
646{
647 int cnt = 0;
648 bool space = true;
649 while (_currentPos > _promptStartPos)
650 {
651 if ((buffer(_currentPos - 1) & 0xff) == ' ')
652 {
653 if (!space)
654 break;
655 }
656 else
657 space = false;
658
659 _currentPos--;
660 cnt++;
661 }
662
663 for (int i = _currentPos; i < _promptEndPos; i++)
664 buffer(i) = buffer(i + cnt);
665
666 buffer(_promptEndPos) = ' ';
667 _promptEndPos -= cnt;
668}
669
670// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
671void PromptWidget::textSelectAll()
672{
673}
674
675// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
676void PromptWidget::textCut()
677{
678}
679
680// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
681void PromptWidget::textCopy()
682{
683}
684
685// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
686void PromptWidget::textPaste()
687{
688}
689
690// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
691void PromptWidget::addToHistory(const char* str)
692{
693#if defined(BSPF_WINDOWS)
694 strncpy_s(_history[_historyIndex], kLineBufferSize, str, kLineBufferSize - 1);
695#else
696 strncpy(_history[_historyIndex], str, kLineBufferSize - 1);
697#endif
698 _historyIndex = (_historyIndex + 1) % kHistorySize;
699 _historyLine = 0;
700
701 if (_historySize < kHistorySize)
702 _historySize++;
703}
704
705#if 0 // FIXME
706// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
707int PromptWidget::compareHistory(const char *histLine)
708{
709 return 1;
710}
711#endif
712
713// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
714void PromptWidget::historyScroll(int direction)
715{
716 if (_historySize == 0)
717 return;
718
719 if (_historyLine == 0 && direction > 0)
720 {
721 int i;
722 for (i = 0; i < _promptEndPos - _promptStartPos; i++)
723 _history[_historyIndex][i] = buffer(_promptStartPos + i); //FIXME: int to char??
724
725 _history[_historyIndex][i] = '\0';
726 }
727
728 // Advance to the next line in the history
729 int line = _historyLine + direction;
730 if(line < 0)
731 line += _historySize + 1;
732 line %= (_historySize + 1);
733
734 // If they press arrow-up with anything in the buffer, search backwards
735 // in the history.
736 /*
737 if(direction < 0 && _currentPos > _promptStartPos) {
738 for(;line > 0; line--) {
739 if(compareHistory(_history[line]) == 0)
740 break;
741 }
742 }
743 */
744
745 _historyLine = line;
746
747 // Remove the current user text
748 _currentPos = _promptStartPos;
749 killLine(1); // to end of line
750
751 // ... and ensure the prompt is visible
752 scrollToCurrent();
753
754 // Print the text from the history
755 int idx;
756 if (_historyLine > 0)
757 idx = (_historyIndex - _historyLine + _historySize) % _historySize;
758 else
759 idx = _historyIndex;
760
761 for (int i = 0; i < kLineBufferSize && _history[idx][i] != '\0'; i++)
762 putcharIntern(_history[idx][i]);
763
764 _promptEndPos = _currentPos;
765
766 // Ensure once more the caret is visible (in case of very long history entries)
767 scrollToCurrent();
768
769 setDirty();
770}
771
772// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
773void PromptWidget::nextLine()
774{
775 // Reset colors every line, so I don't have to remember to do it myself
776 _textcolor = kTextColor;
777 _inverse = false;
778
779 int line = _currentPos / _lineWidth;
780 if (line == _scrollLine)
781 _scrollLine++;
782
783 _currentPos = (line + 1) * _lineWidth;
784
785 updateScrollBuffer();
786}
787
788// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
789// Call this (at least) when the current line changes or when a new line is added
790void PromptWidget::updateScrollBuffer()
791{
792 int lastchar = std::max(_promptEndPos, _currentPos);
793 int line = lastchar / _lineWidth;
794 int numlines = (line < _linesInBuffer) ? line + 1 : _linesInBuffer;
795 int firstline = line - numlines + 1;
796
797 if (firstline > _firstLineInBuffer)
798 {
799 // clear old line from buffer
800 for (int i = lastchar; i < (line+1) * _lineWidth; ++i)
801 buffer(i) = ' ';
802
803 _firstLineInBuffer = firstline;
804 }
805
806 _scrollBar->_numEntries = numlines;
807 _scrollBar->_currentPos = _scrollBar->_numEntries - (line - _scrollLine + _linesPerPage);
808 _scrollBar->_entriesPerPage = _linesPerPage;
809 _scrollBar->recalc();
810}
811
812// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
813int PromptWidget::printf(const char* format, ...)
814{
815 va_list argptr;
816
817 va_start(argptr, format);
818 int count = this->vprintf(format, argptr);
819 va_end (argptr);
820 return count;
821}
822
823// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
824int PromptWidget::vprintf(const char* format, va_list argptr)
825{
826 char buf[2048]; // Note: generates warnings about 'nonliteral' format
827 int count = std::vsnprintf(buf, sizeof(buf), format, argptr);
828
829 print(buf);
830 return count;
831}
832
833// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
834void PromptWidget::putcharIntern(int c)
835{
836 if (c == '\n')
837 nextLine();
838 else if(c & 0x80) { // set foreground color to TIA color
839 // don't print or advance cursor
840 _textcolor = ColorId((c & 0x7f) << 1);
841 }
842 else if(c && c < 0x1e) { // first actual character is large dash
843 // More colors (the regular GUI ones)
844 _textcolor = ColorId(c + 0x100);
845 }
846 else if(c == 0x7f) { // toggle inverse video (DEL char)
847 _inverse = !_inverse;
848 }
849 else if(isprint(c))
850 {
851 buffer(_currentPos) = c | (_textcolor << 8) | (_inverse << 17);
852 _currentPos++;
853 if ((_scrollLine + 1) * _lineWidth == _currentPos)
854 {
855 _scrollLine++;
856 updateScrollBuffer();
857 }
858 }
859 setDirty();
860}
861
862// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
863void PromptWidget::print(const string& str)
864{
865 for(char c: str)
866 putcharIntern(c);
867}
868
869// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
870void PromptWidget::drawCaret()
871{
872//cerr << "PromptWidget::drawCaret()\n";
873 FBSurface& s = _boss->dialog().surface();
874 bool onTop = _boss->dialog().isOnTop();
875
876 int line = _currentPos / _lineWidth;
877
878 // Don't draw the cursor if it's not in the current view
879 if(_scrollLine < line)
880 return;
881
882 int displayLine = line - _scrollLine + _linesPerPage - 1;
883 int x = _x + 1 + (_currentPos % _lineWidth) * _kConsoleCharWidth;
884 int y = _y + displayLine * _kConsoleLineHeight;
885
886 char c = buffer(_currentPos); //FIXME: int to char??
887 s.fillRect(x, y, _kConsoleCharWidth, _kConsoleLineHeight, onTop ? kTextColor : kColor);
888 s.drawChar(_font, c, x, y + 2, kBGColor);
889}
890
891// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
892void PromptWidget::scrollToCurrent()
893{
894 int line = _promptEndPos / _lineWidth;
895
896 if (line + _linesPerPage <= _scrollLine)
897 {
898 // TODO - this should only occur for long edit lines, though
899 }
900 else if (line > _scrollLine)
901 {
902 _scrollLine = line;
903 updateScrollBuffer();
904 }
905}
906
907// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
908bool PromptWidget::saveBuffer(const FilesystemNode& file)
909{
910 ofstream out(file.getPath());
911 if(!out.is_open())
912 return false;
913
914 for(int start = 0; start < _promptStartPos; start += _lineWidth)
915 {
916 int end = start + _lineWidth - 1;
917
918 // Look for first non-space, printing char from end of line
919 while( char(_buffer[end] & 0xff) <= ' ' && end >= start)
920 end--;
921
922 // Spit out the line minus its trailing junk
923 // Strip off any color/inverse bits
924 for(int j = start; j <= end; ++j)
925 out << char(_buffer[j] & 0xff);
926
927 // add a \n
928 out << endl;
929 }
930
931 return true;
932}
933
934// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
935string PromptWidget::getCompletionPrefix(const StringList& completions)
936{
937 // Find the number of characters matching for each of the completions provided
938 for(uInt32 len = 1;; ++len)
939 {
940 for(uInt32 i = 0; i < completions.size(); ++i)
941 {
942 string s1 = completions[i];
943 if(s1.length() < len)
944 {
945 return s1.substr(0, len - 1);
946 }
947 string find = s1.substr(0, len);
948
949 for(uInt32 j = i + 1; j < completions.size(); ++j)
950 {
951 if(!BSPF::startsWithIgnoreCase(completions[j], find))
952 return s1.substr(0, len - 1);
953 }
954 }
955 }
956}
957
958// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
959void PromptWidget::clearScreen()
960{
961 // Initialize start position
962 _currentPos = 0;
963 _scrollLine = _linesPerPage - 1;
964 _firstLineInBuffer = 0;
965 _promptStartPos = _promptEndPos = -1;
966 memset(_buffer, 0, kBufferSize * sizeof(int));
967
968 if(!_firstTime)
969 updateScrollBuffer();
970}
971