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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
33 | PromptWidget::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
72 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
113 | void PromptWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount) |
114 | { |
115 | // cerr << "PromptWidget::handleMouseDown\n"; |
116 | } |
117 | |
118 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
119 | void PromptWidget::handleMouseWheel(int x, int y, int direction) |
120 | { |
121 | _scrollBar->handleMouseWheel(x, y, direction); |
122 | } |
123 | |
124 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
125 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
136 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
151 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
476 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
491 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
508 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
548 | int PromptWidget::getWidth() const |
549 | { |
550 | return _w + kScrollBarWidth; |
551 | } |
552 | |
553 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
554 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
594 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
626 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
645 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
671 | void PromptWidget::textSelectAll() |
672 | { |
673 | } |
674 | |
675 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
676 | void PromptWidget::textCut() |
677 | { |
678 | } |
679 | |
680 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
681 | void PromptWidget::textCopy() |
682 | { |
683 | } |
684 | |
685 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
686 | void PromptWidget::textPaste() |
687 | { |
688 | } |
689 | |
690 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
691 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
707 | int PromptWidget::compareHistory(const char *histLine) |
708 | { |
709 | return 1; |
710 | } |
711 | #endif |
712 | |
713 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
714 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
773 | void 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 |
790 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
813 | int 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
824 | int 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
834 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
863 | void PromptWidget::print(const string& str) |
864 | { |
865 | for(char c: str) |
866 | putcharIntern(c); |
867 | } |
868 | |
869 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
870 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
892 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
908 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
935 | string 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
959 | void 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 | |