1 /* 2 * Copyright 2001-2009, Haiku, Inc. 3 * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net 4 * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai. 5 * All rights reserved. Distributed under the terms of the MIT license. 6 * 7 * Authors: 8 * Stefano Ceccherini <stefano.ceccherini@gmail.com> 9 * Kian Duffy, myob@users.sourceforge.net 10 * Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp 11 * Ingo Weinhold <ingo_weinhold@gmx.de> 12 * Clemens Zeidler <haiku@Clemens-Zeidler.de> 13 */ 14 15 16 17 #include "TermView.h" 18 19 #include <ctype.h> 20 #include <signal.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <termios.h> 24 25 #include <algorithm> 26 #include <new> 27 28 #include <Alert.h> 29 #include <Application.h> 30 #include <Beep.h> 31 #include <Clipboard.h> 32 #include <Debug.h> 33 #include <Directory.h> 34 #include <Dragger.h> 35 #include <Input.h> 36 #include <MenuItem.h> 37 #include <Message.h> 38 #include <MessageRunner.h> 39 #include <Node.h> 40 #include <Path.h> 41 #include <PopUpMenu.h> 42 #include <PropertyInfo.h> 43 #include <Region.h> 44 #include <Roster.h> 45 #include <ScrollBar.h> 46 #include <ScrollView.h> 47 #include <String.h> 48 #include <StringView.h> 49 #include <Window.h> 50 51 #include "Encoding.h" 52 #include "InlineInput.h" 53 #include "Shell.h" 54 #include "TermConst.h" 55 #include "TerminalBuffer.h" 56 #include "TerminalCharClassifier.h" 57 #include "VTkeymap.h" 58 59 60 // defined in VTKeyTbl.c 61 extern int function_keycode_table[]; 62 extern char *function_key_char_table[]; 63 64 const static rgb_color kTermColorTable[8] = { 65 { 40, 40, 40, 0}, // black 66 {204, 0, 0, 0}, // red 67 { 78, 154, 6, 0}, // green 68 {218, 168, 0, 0}, // yellow 69 { 51, 102, 152, 0}, // blue 70 {115, 68, 123, 0}, // magenta 71 { 6, 152, 154, 0}, // cyan 72 {245, 245, 245, 0}, // white 73 }; 74 75 #define ROWS_DEFAULT 25 76 #define COLUMNS_DEFAULT 80 77 78 // selection granularity 79 enum { 80 SELECT_CHARS, 81 SELECT_WORDS, 82 SELECT_LINES 83 }; 84 85 86 static property_info sPropList[] = { 87 { "encoding", 88 {B_GET_PROPERTY, 0}, 89 {B_DIRECT_SPECIFIER, 0}, 90 "get terminal encoding"}, 91 { "encoding", 92 {B_SET_PROPERTY, 0}, 93 {B_DIRECT_SPECIFIER, 0}, 94 "set terminal encoding"}, 95 { "tty", 96 {B_GET_PROPERTY, 0}, 97 {B_DIRECT_SPECIFIER, 0}, 98 "get tty name."}, 99 { 0 } 100 }; 101 102 103 static const uint32 kUpdateSigWinch = 'Rwin'; 104 static const uint32 kBlinkCursor = 'BlCr'; 105 static const uint32 kAutoScroll = 'AScr'; 106 107 static const bigtime_t kSyncUpdateGranularity = 100000; // 0.1 s 108 109 static const int32 kCursorBlinkIntervals = 3; 110 static const int32 kCursorVisibleIntervals = 2; 111 static const bigtime_t kCursorBlinkInterval = 500000; 112 113 static const rgb_color kBlackColor = { 0, 0, 0, 255 }; 114 static const rgb_color kWhiteColor = { 255, 255, 255, 255 }; 115 116 static const char* kDefaultSpecialWordChars = ":@-./_~"; 117 static const char* kEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!"; 118 119 // secondary mouse button drop 120 const int32 kSecondaryMouseDropAction = 'SMDA'; 121 122 enum { 123 kInsert, 124 kChangeDirectory, 125 kLinkFiles, 126 kMoveFiles, 127 kCopyFiles 128 }; 129 130 131 template<typename Type> 132 static inline Type 133 restrict_value(const Type& value, const Type& min, const Type& max) 134 { 135 return value < min ? min : (value > max ? max : value); 136 } 137 138 139 class TermView::CharClassifier : public TerminalCharClassifier { 140 public: 141 CharClassifier(const char* specialWordChars) 142 : 143 fSpecialWordChars(specialWordChars) 144 { 145 } 146 147 virtual int Classify(const char* character) 148 { 149 // TODO: Deal correctly with non-ASCII chars. 150 char c = *character; 151 if (UTF8Char::ByteCount(c) > 1) 152 return CHAR_TYPE_WORD_CHAR; 153 154 if (isspace(c)) 155 return CHAR_TYPE_SPACE; 156 if (isalnum(c) || strchr(fSpecialWordChars, c) != NULL) 157 return CHAR_TYPE_WORD_CHAR; 158 159 return CHAR_TYPE_WORD_DELIMITER; 160 } 161 162 private: 163 const char* fSpecialWordChars; 164 }; 165 166 167 // #pragma mark - 168 169 170 TermView::TermView(BRect frame, int32 argc, const char** argv, int32 historySize) 171 : BView(frame, "termview", B_FOLLOW_ALL, 172 B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), 173 fColumns(COLUMNS_DEFAULT), 174 fRows(ROWS_DEFAULT), 175 fEncoding(M_UTF8), 176 fActive(false), 177 fScrBufSize(historySize), 178 fReportX10MouseEvent(false), 179 fReportNormalMouseEvent(false), 180 fReportButtonMouseEvent(false), 181 fReportAnyMouseEvent(false) 182 { 183 status_t status = _InitObject(argc, argv); 184 if (status != B_OK) 185 throw status; 186 SetTermSize(frame); 187 } 188 189 190 TermView::TermView(int rows, int columns, int32 argc, const char** argv, 191 int32 historySize) 192 : BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL, 193 B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE), 194 fColumns(columns), 195 fRows(rows), 196 fEncoding(M_UTF8), 197 fActive(false), 198 fScrBufSize(historySize), 199 fReportX10MouseEvent(false), 200 fReportNormalMouseEvent(false), 201 fReportButtonMouseEvent(false), 202 fReportAnyMouseEvent(false) 203 { 204 status_t status = _InitObject(argc, argv); 205 if (status != B_OK) 206 throw status; 207 208 // TODO: Don't show the dragger, since replicant capabilities 209 // don't work very well ATM. 210 /* 211 BRect rect(0, 0, 16, 16); 212 rect.OffsetTo(Bounds().right - rect.Width(), 213 Bounds().bottom - rect.Height()); 214 215 SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL); 216 AddChild(new BDragger(rect, this, 217 B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));*/ 218 } 219 220 221 TermView::TermView(BMessage* archive) 222 : 223 BView(archive), 224 fColumns(COLUMNS_DEFAULT), 225 fRows(ROWS_DEFAULT), 226 fEncoding(M_UTF8), 227 fActive(false), 228 fScrBufSize(1000), 229 fReportX10MouseEvent(false), 230 fReportNormalMouseEvent(false), 231 fReportButtonMouseEvent(false), 232 fReportAnyMouseEvent(false) 233 { 234 BRect frame = Bounds(); 235 236 if (archive->FindInt32("encoding", (int32*)&fEncoding) < B_OK) 237 fEncoding = M_UTF8; 238 if (archive->FindInt32("columns", (int32*)&fColumns) < B_OK) 239 fColumns = COLUMNS_DEFAULT; 240 if (archive->FindInt32("rows", (int32*)&fRows) < B_OK) 241 fRows = ROWS_DEFAULT; 242 243 int32 argc = 0; 244 if (archive->HasInt32("argc")) 245 archive->FindInt32("argc", &argc); 246 247 const char **argv = new const char*[argc]; 248 for (int32 i = 0; i < argc; i++) { 249 archive->FindString("argv", i, (const char**)&argv[i]); 250 } 251 252 // TODO: Retrieve colors, history size, etc. from archive 253 status_t status = _InitObject(argc, argv); 254 if (status != B_OK) 255 throw status; 256 257 bool useRect = false; 258 if ((archive->FindBool("use_rect", &useRect) == B_OK) && useRect) 259 SetTermSize(frame); 260 261 delete[] argv; 262 } 263 264 265 /*! Initializes the object for further use. 266 The members fRows, fColumns, fEncoding, and fScrBufSize must 267 already be initialized; they are not touched by this method. 268 */ 269 status_t 270 TermView::_InitObject(int32 argc, const char** argv) 271 { 272 SetFlags(Flags() | B_WILL_DRAW | B_FRAME_EVENTS 273 | B_FULL_UPDATE_ON_RESIZE/* | B_INPUT_METHOD_AWARE*/); 274 275 fShell = NULL; 276 fWinchRunner = NULL; 277 fCursorBlinkRunner = NULL; 278 fAutoScrollRunner = NULL; 279 fResizeRunner = NULL; 280 fResizeView = NULL; 281 fCharClassifier = NULL; 282 fFontWidth = 0; 283 fFontHeight = 0; 284 fFontAscent = 0; 285 fFrameResized = false; 286 fResizeViewDisableCount = 0; 287 fLastActivityTime = 0; 288 fCursorState = 0; 289 fCursorHeight = 0; 290 fCursor = TermPos(0, 0); 291 fTextBuffer = NULL; 292 fVisibleTextBuffer = NULL; 293 fScrollBar = NULL; 294 fInline = NULL; 295 fTextForeColor = kBlackColor; 296 fTextBackColor = kWhiteColor; 297 fCursorForeColor = kWhiteColor; 298 fCursorBackColor = kBlackColor; 299 fSelectForeColor = kWhiteColor; 300 fSelectBackColor = kBlackColor; 301 fScrollOffset = 0; 302 fLastSyncTime = 0; 303 fScrolledSinceLastSync = 0; 304 fSyncRunner = NULL; 305 fConsiderClockedSync = false; 306 fSelStart = TermPos(-1, -1); 307 fSelEnd = TermPos(-1, -1); 308 fMouseTracking = false; 309 fCheckMouseTracking = false; 310 fPrevPos = TermPos(-1, - 1); 311 fReportX10MouseEvent = false; 312 fReportNormalMouseEvent = false; 313 fReportButtonMouseEvent = false; 314 fReportAnyMouseEvent = false; 315 fMouseClipboard = be_clipboard; 316 317 fTextBuffer = new(std::nothrow) TerminalBuffer; 318 if (fTextBuffer == NULL) 319 return B_NO_MEMORY; 320 321 fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer; 322 if (fVisibleTextBuffer == NULL) 323 return B_NO_MEMORY; 324 325 // TODO: Make the special word chars user-settable! 326 fCharClassifier = new(std::nothrow) CharClassifier( 327 kDefaultSpecialWordChars); 328 if (fCharClassifier == NULL) 329 return B_NO_MEMORY; 330 331 status_t error = fTextBuffer->Init(fColumns, fRows, fScrBufSize); 332 if (error != B_OK) 333 return error; 334 fTextBuffer->SetEncoding(fEncoding); 335 336 error = fVisibleTextBuffer->Init(fColumns, fRows + 2, 0); 337 if (error != B_OK) 338 return error; 339 340 fShell = new (std::nothrow) Shell(); 341 if (fShell == NULL) 342 return B_NO_MEMORY; 343 344 SetTermFont(be_fixed_font); 345 346 error = fShell->Open(fRows, fColumns, 347 EncodingAsShortString(fEncoding), argc, argv); 348 349 if (error < B_OK) 350 return error; 351 352 error = _AttachShell(fShell); 353 if (error < B_OK) 354 return error; 355 356 SetLowColor(fTextBackColor); 357 SetViewColor(B_TRANSPARENT_32_BIT); 358 359 return B_OK; 360 } 361 362 363 TermView::~TermView() 364 { 365 Shell* shell = fShell; 366 // _DetachShell sets fShell to NULL 367 368 _DetachShell(); 369 370 delete fSyncRunner; 371 delete fAutoScrollRunner; 372 delete fCharClassifier; 373 delete fVisibleTextBuffer; 374 delete fTextBuffer; 375 delete shell; 376 } 377 378 379 /* static */ 380 BArchivable * 381 TermView::Instantiate(BMessage* data) 382 { 383 if (validate_instantiation(data, "TermView")) { 384 TermView *view = new (std::nothrow) TermView(data); 385 return view; 386 } 387 388 return NULL; 389 } 390 391 392 status_t 393 TermView::Archive(BMessage* data, bool deep) const 394 { 395 status_t status = BView::Archive(data, deep); 396 if (status == B_OK) 397 status = data->AddString("add_on", TERM_SIGNATURE); 398 if (status == B_OK) 399 status = data->AddInt32("encoding", (int32)fEncoding); 400 if (status == B_OK) 401 status = data->AddInt32("columns", (int32)fColumns); 402 if (status == B_OK) 403 status = data->AddInt32("rows", (int32)fRows); 404 405 if (data->ReplaceString("class", "TermView") != B_OK) 406 data->AddString("class", "TermView"); 407 408 return status; 409 } 410 411 412 inline int32 413 TermView::_LineAt(float y) 414 { 415 int32 location = int32(y + fScrollOffset); 416 417 // Make sure negative offsets are rounded towards the lower neighbor, too. 418 if (location < 0) 419 location -= fFontHeight - 1; 420 421 return location / fFontHeight; 422 } 423 424 425 inline float 426 TermView::_LineOffset(int32 index) 427 { 428 return index * fFontHeight - fScrollOffset; 429 } 430 431 432 // convert view coordinates to terminal text buffer position 433 inline TermPos 434 TermView::_ConvertToTerminal(const BPoint &p) 435 { 436 return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y)); 437 } 438 439 440 // convert terminal text buffer position to view coordinates 441 inline BPoint 442 TermView::_ConvertFromTerminal(const TermPos &pos) 443 { 444 return BPoint(fFontWidth * pos.x, _LineOffset(pos.y)); 445 } 446 447 448 inline void 449 TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2) 450 { 451 BRect rect(x1 * fFontWidth, _LineOffset(y1), 452 (x2 + 1) * fFontWidth - 1, _LineOffset(y2 + 1) - 1); 453 //debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top, 454 //rect.right, rect.bottom); 455 Invalidate(rect); 456 } 457 458 459 void 460 TermView::GetPreferredSize(float *width, float *height) 461 { 462 if (width) 463 *width = fColumns * fFontWidth - 1; 464 if (height) 465 *height = fRows * fFontHeight - 1; 466 } 467 468 469 const char * 470 TermView::TerminalName() const 471 { 472 if (fShell == NULL) 473 return NULL; 474 475 return fShell->TTYName(); 476 } 477 478 479 //! Get width and height for terminal font 480 void 481 TermView::GetFontSize(int* _width, int* _height) 482 { 483 *_width = fFontWidth; 484 *_height = fFontHeight; 485 } 486 487 488 int 489 TermView::Rows() const 490 { 491 return fRows; 492 } 493 494 495 int 496 TermView::Columns() const 497 { 498 return fColumns; 499 } 500 501 502 //! Set number of rows and columns in terminal 503 BRect 504 TermView::SetTermSize(int rows, int columns) 505 { 506 //debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns); 507 if (rows > 0) 508 fRows = rows; 509 if (columns > 0) 510 fColumns = columns; 511 512 // To keep things simple, get rid of the selection first. 513 _Deselect(); 514 515 { 516 BAutolock _(fTextBuffer); 517 if (fTextBuffer->ResizeTo(columns, rows) != B_OK 518 || fVisibleTextBuffer->ResizeTo(columns, rows + 2, 0) 519 != B_OK) { 520 return Bounds(); 521 } 522 } 523 524 //debug_printf("Invalidate()\n"); 525 Invalidate(); 526 527 if (fScrollBar != NULL) { 528 _UpdateScrollBarRange(); 529 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); 530 } 531 532 BRect rect(0, 0, fColumns * fFontWidth, fRows * fFontHeight); 533 534 // synchronize the visible text buffer 535 { 536 BAutolock _(fTextBuffer); 537 538 _SynchronizeWithTextBuffer(0, -1); 539 int32 offset = _LineAt(0); 540 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, 541 offset + rows + 2); 542 } 543 544 return rect; 545 } 546 547 548 void 549 TermView::SetTermSize(BRect rect) 550 { 551 int rows; 552 int columns; 553 554 GetTermSizeFromRect(rect, &rows, &columns); 555 SetTermSize(rows, columns); 556 } 557 558 559 void 560 TermView::GetTermSizeFromRect(const BRect &rect, int *_rows, 561 int *_columns) 562 { 563 int columns = (rect.IntegerWidth() + 1) / fFontWidth; 564 int rows = (rect.IntegerHeight() + 1) / fFontHeight; 565 566 if (_rows) 567 *_rows = rows; 568 if (_columns) 569 *_columns = columns; 570 } 571 572 573 void 574 TermView::SetTextColor(rgb_color fore, rgb_color back) 575 { 576 fTextForeColor = fore; 577 fTextBackColor = back; 578 579 SetLowColor(fTextBackColor); 580 } 581 582 583 void 584 TermView::SetSelectColor(rgb_color fore, rgb_color back) 585 { 586 fSelectForeColor = fore; 587 fSelectBackColor = back; 588 } 589 590 591 void 592 TermView::SetCursorColor(rgb_color fore, rgb_color back) 593 { 594 fCursorForeColor = fore; 595 fCursorBackColor = back; 596 } 597 598 599 int 600 TermView::Encoding() const 601 { 602 return fEncoding; 603 } 604 605 606 void 607 TermView::SetEncoding(int encoding) 608 { 609 // TODO: Shell::_Spawn() sets the "TTYPE" environment variable using 610 // the string value of encoding. But when this function is called and 611 // the encoding changes, the new value is never passed to Shell. 612 fEncoding = encoding; 613 614 BAutolock _(fTextBuffer); 615 fTextBuffer->SetEncoding(fEncoding); 616 } 617 618 619 void 620 TermView::SetMouseClipboard(BClipboard *clipboard) 621 { 622 fMouseClipboard = clipboard; 623 } 624 625 626 void 627 TermView::GetTermFont(BFont *font) const 628 { 629 if (font != NULL) 630 *font = fHalfFont; 631 } 632 633 634 //! Sets font for terminal 635 void 636 TermView::SetTermFont(const BFont *font) 637 { 638 int halfWidth = 0; 639 640 fHalfFont = font; 641 642 fHalfFont.SetSpacing(B_FIXED_SPACING); 643 644 // calculate half font's max width 645 // Not Bounding, check only A-Z(For case of fHalfFont is KanjiFont. ) 646 for (int c = 0x20 ; c <= 0x7e; c++){ 647 char buf[4]; 648 sprintf(buf, "%c", c); 649 int tmpWidth = (int)fHalfFont.StringWidth(buf); 650 if (tmpWidth > halfWidth) 651 halfWidth = tmpWidth; 652 } 653 654 fFontWidth = halfWidth; 655 656 font_height hh; 657 fHalfFont.GetHeight(&hh); 658 659 int font_ascent = (int)hh.ascent; 660 int font_descent =(int)hh.descent; 661 int font_leading =(int)hh.leading; 662 663 if (font_leading == 0) 664 font_leading = 1; 665 666 fFontAscent = font_ascent; 667 fFontHeight = font_ascent + font_descent + font_leading + 1; 668 669 fCursorHeight = fFontHeight; 670 671 _ScrollTo(0, false); 672 if (fScrollBar != NULL) 673 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); 674 } 675 676 677 void 678 TermView::SetScrollBar(BScrollBar *scrollBar) 679 { 680 fScrollBar = scrollBar; 681 if (fScrollBar != NULL) 682 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); 683 } 684 685 686 void 687 TermView::SetTitle(const char *title) 688 { 689 // TODO: Do something different in case we're a replicant, 690 // or in case we are inside a BTabView ? 691 if (Window()) 692 Window()->SetTitle(title); 693 } 694 695 696 void 697 TermView::Copy(BClipboard *clipboard) 698 { 699 BAutolock _(fTextBuffer); 700 701 if (!_HasSelection()) 702 return; 703 704 BString copyStr; 705 fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd); 706 707 if (clipboard->Lock()) { 708 BMessage *clipMsg = NULL; 709 clipboard->Clear(); 710 711 if ((clipMsg = clipboard->Data()) != NULL) { 712 clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(), 713 copyStr.Length()); 714 clipboard->Commit(); 715 } 716 clipboard->Unlock(); 717 } 718 } 719 720 721 void 722 TermView::Paste(BClipboard *clipboard) 723 { 724 if (clipboard->Lock()) { 725 BMessage *clipMsg = clipboard->Data(); 726 const char* text; 727 ssize_t numBytes; 728 if (clipMsg->FindData("text/plain", B_MIME_TYPE, 729 (const void**)&text, &numBytes) == B_OK ) { 730 _WritePTY(text, numBytes); 731 } 732 733 clipboard->Unlock(); 734 735 _ScrollTo(0, true); 736 } 737 } 738 739 740 void 741 TermView::SelectAll() 742 { 743 BAutolock _(fTextBuffer); 744 745 _Select(TermPos(0, -fTextBuffer->HistorySize()), 746 TermPos(0, fTextBuffer->Height()), false, true); 747 } 748 749 750 void 751 TermView::Clear() 752 { 753 _Deselect(); 754 755 { 756 BAutolock _(fTextBuffer); 757 fTextBuffer->Clear(true); 758 } 759 fVisibleTextBuffer->Clear(true); 760 761 //debug_printf("Invalidate()\n"); 762 Invalidate(); 763 764 _ScrollTo(0, false); 765 if (fScrollBar) { 766 fScrollBar->SetRange(0, 0); 767 fScrollBar->SetProportion(1); 768 } 769 } 770 771 772 //! Draw region 773 void 774 TermView::_InvalidateTextRange(TermPos start, TermPos end) 775 { 776 if (end < start) 777 std::swap(start, end); 778 779 if (start.y == end.y) { 780 _InvalidateTextRect(start.x, start.y, end.x, end.y); 781 } else { 782 _InvalidateTextRect(start.x, start.y, fColumns, start.y); 783 784 if (end.y - start.y > 0) 785 _InvalidateTextRect(0, start.y + 1, fColumns, end.y - 1); 786 787 _InvalidateTextRect(0, end.y, end.x, end.y); 788 } 789 } 790 791 792 status_t 793 TermView::_AttachShell(Shell *shell) 794 { 795 if (shell == NULL) 796 return B_BAD_VALUE; 797 798 fShell = shell; 799 800 return fShell->AttachBuffer(TextBuffer()); 801 } 802 803 804 void 805 TermView::_DetachShell() 806 { 807 fShell->DetachBuffer(); 808 fShell = NULL; 809 } 810 811 812 void 813 TermView::_Activate() 814 { 815 fActive = true; 816 817 if (fCursorBlinkRunner == NULL) { 818 BMessage blinkMessage(kBlinkCursor); 819 fCursorBlinkRunner = new (std::nothrow) BMessageRunner( 820 BMessenger(this), &blinkMessage, kCursorBlinkInterval); 821 } 822 } 823 824 825 void 826 TermView::_Deactivate() 827 { 828 // make sure the cursor becomes visible 829 fCursorState = 0; 830 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 831 delete fCursorBlinkRunner; 832 fCursorBlinkRunner = NULL; 833 834 fActive = false; 835 } 836 837 838 //! Draw part of a line in the given view. 839 void 840 TermView::_DrawLinePart(int32 x1, int32 y1, uint16 attr, char *buf, 841 int32 width, bool mouse, bool cursor, BView *inView) 842 { 843 rgb_color rgb_fore = fTextForeColor, rgb_back = fTextBackColor; 844 845 inView->SetFont(&fHalfFont); 846 847 // Set pen point 848 int x2 = x1 + fFontWidth * width; 849 int y2 = y1 + fFontHeight; 850 851 // color attribute 852 int forecolor = IS_FORECOLOR(attr); 853 int backcolor = IS_BACKCOLOR(attr); 854 855 if (IS_FORESET(attr)) 856 rgb_fore = kTermColorTable[forecolor]; 857 858 if (IS_BACKSET(attr)) 859 rgb_back = kTermColorTable[backcolor]; 860 861 // Selection check. 862 if (cursor) { 863 rgb_fore = fCursorForeColor; 864 rgb_back = fCursorBackColor; 865 } else if (mouse) { 866 rgb_fore = fSelectForeColor; 867 rgb_back = fSelectBackColor; 868 } else { 869 // Reverse attribute(If selected area, don't reverse color). 870 if (IS_INVERSE(attr)) { 871 rgb_color rgb_tmp = rgb_fore; 872 rgb_fore = rgb_back; 873 rgb_back = rgb_tmp; 874 } 875 } 876 877 // Fill color at Background color and set low color. 878 inView->SetHighColor(rgb_back); 879 inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1)); 880 inView->SetLowColor(rgb_back); 881 882 inView->SetHighColor(rgb_fore); 883 884 // Draw character. 885 inView->MovePenTo(x1, y1 + fFontAscent); 886 inView->DrawString((char *) buf); 887 888 // bold attribute. 889 if (IS_BOLD(attr)) { 890 inView->MovePenTo(x1 + 1, y1 + fFontAscent); 891 892 inView->SetDrawingMode(B_OP_OVER); 893 inView->DrawString((char *)buf); 894 inView->SetDrawingMode(B_OP_COPY); 895 } 896 897 // underline attribute 898 if (IS_UNDER(attr)) { 899 inView->MovePenTo(x1, y1 + fFontAscent); 900 inView->StrokeLine(BPoint(x1 , y1 + fFontAscent), 901 BPoint(x2 , y1 + fFontAscent)); 902 } 903 } 904 905 906 /*! Caller must have locked fTextBuffer. 907 */ 908 void 909 TermView::_DrawCursor() 910 { 911 BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0); 912 rect.right = rect.left + fFontWidth - 1; 913 rect.bottom = rect.top + fCursorHeight - 1; 914 int32 firstVisible = _LineAt(0); 915 916 UTF8Char character; 917 uint16 attr; 918 919 bool cursorVisible = _IsCursorVisible(); 920 921 bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y)); 922 if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x, 923 character, attr) == A_CHAR) { 924 int32 width; 925 if (IS_WIDTH(attr)) 926 width = 2; 927 else 928 width = 1; 929 930 char buffer[5]; 931 int32 bytes = UTF8Char::ByteCount(character.bytes[0]); 932 memcpy(buffer, character.bytes, bytes); 933 buffer[bytes] = '\0'; 934 935 _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer, 936 width, selected, cursorVisible, this); 937 } else { 938 if (selected) 939 SetHighColor(fSelectBackColor); 940 else 941 SetHighColor(cursorVisible ? fCursorBackColor : fTextBackColor); 942 943 FillRect(rect); 944 } 945 } 946 947 948 bool 949 TermView::_IsCursorVisible() const 950 { 951 return fCursorState < kCursorVisibleIntervals; 952 } 953 954 955 void 956 TermView::_BlinkCursor() 957 { 958 bool wasVisible = _IsCursorVisible(); 959 960 if (!wasVisible && fInline && fInline->IsActive()) 961 return; 962 963 bigtime_t now = system_time(); 964 if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval) 965 fCursorState = (fCursorState + 1) % kCursorBlinkIntervals; 966 else 967 fCursorState = 0; 968 969 if (wasVisible != _IsCursorVisible()) 970 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 971 } 972 973 974 void 975 TermView::_ActivateCursor(bool invalidate) 976 { 977 fLastActivityTime = system_time(); 978 if (invalidate && fCursorState != 0) 979 _BlinkCursor(); 980 else 981 fCursorState = 0; 982 } 983 984 985 //! Update scroll bar range and knob size. 986 void 987 TermView::_UpdateScrollBarRange() 988 { 989 if (fScrollBar == NULL) 990 return; 991 992 int32 historySize; 993 { 994 BAutolock _(fTextBuffer); 995 historySize = fTextBuffer->HistorySize(); 996 } 997 998 float viewHeight = fRows * fFontHeight; 999 float historyHeight = (float)historySize * fFontHeight; 1000 1001 //debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n", 1002 //historySize, -historyHeight); 1003 1004 fScrollBar->SetRange(-historyHeight, 0); 1005 if (historySize > 0) 1006 fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight)); 1007 } 1008 1009 1010 //! Handler for SIGWINCH 1011 void 1012 TermView::_UpdateSIGWINCH() 1013 { 1014 if (fFrameResized) { 1015 fShell->UpdateWindowSize(fRows, fColumns); 1016 fFrameResized = false; 1017 } 1018 } 1019 1020 1021 void 1022 TermView::AttachedToWindow() 1023 { 1024 fMouseButtons = 0; 1025 1026 MakeFocus(true); 1027 if (fScrollBar) { 1028 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); 1029 _UpdateScrollBarRange(); 1030 } 1031 1032 BMessenger thisMessenger(this); 1033 1034 BMessage message(kUpdateSigWinch); 1035 fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger, 1036 &message, 500000); 1037 1038 { 1039 BAutolock _(fTextBuffer); 1040 fTextBuffer->SetListener(thisMessenger); 1041 _SynchronizeWithTextBuffer(0, -1); 1042 } 1043 1044 be_clipboard->StartWatching(thisMessenger); 1045 } 1046 1047 1048 void 1049 TermView::DetachedFromWindow() 1050 { 1051 be_clipboard->StopWatching(BMessenger(this)); 1052 1053 delete fWinchRunner; 1054 fWinchRunner = NULL; 1055 1056 delete fCursorBlinkRunner; 1057 fCursorBlinkRunner = NULL; 1058 1059 delete fResizeRunner; 1060 fResizeRunner = NULL; 1061 1062 { 1063 BAutolock _(fTextBuffer); 1064 fTextBuffer->UnsetListener(); 1065 } 1066 } 1067 1068 1069 void 1070 TermView::Draw(BRect updateRect) 1071 { 1072 // if (IsPrinting()) { 1073 // _DoPrint(updateRect); 1074 // return; 1075 // } 1076 1077 // debug_printf("TermView::Draw((%f, %f) - (%f, %f))\n", updateRect.left, 1078 // updateRect.top, updateRect.right, updateRect.bottom); 1079 // { 1080 // BRect bounds(Bounds()); 1081 // debug_printf("Bounds(): (%f, %f) - (%f - %f)\n", bounds.left, bounds.top, 1082 // bounds.right, bounds.bottom); 1083 // debug_printf("clipping region:\n"); 1084 // BRegion region; 1085 // GetClippingRegion(®ion); 1086 // for (int32 i = 0; i < region.CountRects(); i++) { 1087 // BRect rect(region.RectAt(i)); 1088 // debug_printf(" (%f, %f) - (%f, %f)\n", rect.left, rect.top, rect.right, 1089 // rect.bottom); 1090 // } 1091 // } 1092 1093 int32 x1 = (int32)(updateRect.left) / fFontWidth; 1094 int32 x2 = (int32)(updateRect.right) / fFontWidth; 1095 1096 int32 firstVisible = _LineAt(0); 1097 int32 y1 = _LineAt(updateRect.top); 1098 int32 y2 = _LineAt(updateRect.bottom); 1099 1100 //debug_printf("TermView::Draw(): (%ld, %ld) - (%ld, %ld), top: %f, fontHeight: %d, scrollOffset: %f\n", 1101 //x1, y1, x2, y2, updateRect.top, fFontHeight, fScrollOffset); 1102 1103 for (int32 j = y1; j <= y2; j++) { 1104 int32 k = x1; 1105 char buf[fColumns * 4 + 1]; 1106 1107 if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k)) 1108 k--; 1109 1110 if (k < 0) 1111 k = 0; 1112 1113 for (int32 i = k; i <= x2;) { 1114 int32 lastColumn = x2; 1115 bool insideSelection = _CheckSelectedRegion(j, i, lastColumn); 1116 uint16 attr; 1117 int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i, 1118 lastColumn, buf, attr); 1119 1120 //debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n", 1121 //j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection); 1122 1123 if (count == 0) { 1124 BRect rect(fFontWidth * i, _LineOffset(j), 1125 fFontWidth * (lastColumn + 1) - 1, 0); 1126 rect.bottom = rect.top + fFontHeight - 1; 1127 1128 SetHighColor(insideSelection ? fSelectBackColor 1129 : fTextBackColor); 1130 FillRect(rect); 1131 1132 i = lastColumn + 1; 1133 continue; 1134 } 1135 1136 if (IS_WIDTH(attr)) 1137 count = 2; 1138 1139 _DrawLinePart(fFontWidth * i, (int32)_LineOffset(j), 1140 attr, buf, count, insideSelection, false, this); 1141 i += count; 1142 } 1143 } 1144 1145 if (fInline && fInline->IsActive()) 1146 _DrawInlineMethodString(); 1147 1148 if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2)) 1149 _DrawCursor(); 1150 } 1151 1152 1153 void 1154 TermView::_DoPrint(BRect updateRect) 1155 { 1156 #if 0 1157 ushort attr; 1158 uchar buf[1024]; 1159 1160 const int numLines = (int)((updateRect.Height()) / fFontHeight); 1161 1162 int y1 = (int)(updateRect.top) / fFontHeight; 1163 y1 = y1 -(fScrBufSize - numLines * 2); 1164 if (y1 < 0) 1165 y1 = 0; 1166 1167 const int y2 = y1 + numLines -1; 1168 1169 const int x1 = (int)(updateRect.left) / fFontWidth; 1170 const int x2 = (int)(updateRect.right) / fFontWidth; 1171 1172 for (int j = y1; j <= y2; j++) { 1173 // If(x1, y1) Buffer is in string full width character, 1174 // alignment start position. 1175 1176 int k = x1; 1177 if (fTextBuffer->IsFullWidthChar(j, k)) 1178 k--; 1179 1180 if (k < 0) 1181 k = 0; 1182 1183 for (int i = k; i <= x2;) { 1184 int count = fTextBuffer->GetString(j, i, x2, buf, &attr); 1185 if (count < 0) { 1186 i += abs(count); 1187 continue; 1188 } 1189 1190 _DrawLinePart(fFontWidth * i, fFontHeight * j, 1191 attr, buf, count, false, false, this); 1192 i += count; 1193 } 1194 } 1195 #endif // 0 1196 } 1197 1198 1199 void 1200 TermView::WindowActivated(bool active) 1201 { 1202 BView::WindowActivated(active); 1203 if (active && IsFocus()) { 1204 if (!fActive) 1205 _Activate(); 1206 } else { 1207 if (fActive) 1208 _Deactivate(); 1209 } 1210 } 1211 1212 1213 void 1214 TermView::MakeFocus(bool focusState) 1215 { 1216 BView::MakeFocus(focusState); 1217 1218 if (focusState && Window() && Window()->IsActive()) { 1219 if (!fActive) 1220 _Activate(); 1221 } else { 1222 if (fActive) 1223 _Deactivate(); 1224 } 1225 } 1226 1227 1228 void 1229 TermView::KeyDown(const char *bytes, int32 numBytes) 1230 { 1231 int32 key, mod, rawChar; 1232 BMessage *currentMessage = Looper()->CurrentMessage(); 1233 if (currentMessage == NULL) 1234 return; 1235 1236 currentMessage->FindInt32("modifiers", &mod); 1237 currentMessage->FindInt32("key", &key); 1238 currentMessage->FindInt32("raw_char", &rawChar); 1239 1240 _ActivateCursor(true); 1241 1242 // handle multi-byte chars 1243 if (numBytes > 1) { 1244 if (fEncoding != M_UTF8) { 1245 char destBuffer[16]; 1246 int32 destLen; 1247 long state = 0; 1248 convert_from_utf8(fEncoding, bytes, &numBytes, destBuffer, 1249 &destLen, &state, '?'); 1250 _ScrollTo(0, true); 1251 fShell->Write(destBuffer, destLen); 1252 return; 1253 } 1254 1255 _ScrollTo(0, true); 1256 fShell->Write(bytes, numBytes); 1257 return; 1258 } 1259 1260 // Terminal filters RET, ENTER, F1...F12, and ARROW key code. 1261 const char *toWrite = NULL; 1262 1263 switch (*bytes) { 1264 case B_RETURN: 1265 if (rawChar == B_RETURN) 1266 toWrite = "\r"; 1267 break; 1268 1269 case B_DELETE: 1270 toWrite = DELETE_KEY_CODE; 1271 break; 1272 1273 case B_BACKSPACE: 1274 // Translate only the actual backspace key to the backspace 1275 // code. CTRL-H shall just be echoed. 1276 if (!((mod & B_CONTROL_KEY) && rawChar == 'h')) 1277 toWrite = BACKSPACE_KEY_CODE; 1278 break; 1279 1280 case B_LEFT_ARROW: 1281 if (rawChar == B_LEFT_ARROW) { 1282 if (mod & B_SHIFT_KEY) { 1283 BMessage message(MSG_PREVIOUS_TAB); 1284 message.AddPointer("termView", this); 1285 Window()->PostMessage(&message); 1286 return; 1287 } else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) { 1288 toWrite = CTRL_LEFT_ARROW_KEY_CODE; 1289 } else 1290 toWrite = LEFT_ARROW_KEY_CODE; 1291 } 1292 break; 1293 1294 case B_RIGHT_ARROW: 1295 if (rawChar == B_RIGHT_ARROW) { 1296 if (mod & B_SHIFT_KEY) { 1297 BMessage message(MSG_NEXT_TAB); 1298 message.AddPointer("termView", this); 1299 Window()->PostMessage(&message); 1300 return; 1301 } else if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) { 1302 toWrite = CTRL_RIGHT_ARROW_KEY_CODE; 1303 } else 1304 toWrite = RIGHT_ARROW_KEY_CODE; 1305 } 1306 break; 1307 1308 case B_UP_ARROW: 1309 if (mod & B_SHIFT_KEY) { 1310 _ScrollTo(fScrollOffset - fFontHeight, true); 1311 return; 1312 } 1313 if (rawChar == B_UP_ARROW) { 1314 if (mod & B_CONTROL_KEY) 1315 toWrite = CTRL_UP_ARROW_KEY_CODE; 1316 else 1317 toWrite = UP_ARROW_KEY_CODE; 1318 } 1319 break; 1320 1321 case B_DOWN_ARROW: 1322 if (mod & B_SHIFT_KEY) { 1323 _ScrollTo(fScrollOffset + fFontHeight, true); 1324 return; 1325 } 1326 1327 if (rawChar == B_DOWN_ARROW) { 1328 if (mod & B_CONTROL_KEY) 1329 toWrite = CTRL_DOWN_ARROW_KEY_CODE; 1330 else 1331 toWrite = DOWN_ARROW_KEY_CODE; 1332 } 1333 break; 1334 1335 case B_INSERT: 1336 if (rawChar == B_INSERT) 1337 toWrite = INSERT_KEY_CODE; 1338 break; 1339 1340 case B_HOME: 1341 if (rawChar == B_HOME) 1342 toWrite = HOME_KEY_CODE; 1343 break; 1344 1345 case B_END: 1346 if (rawChar == B_END) 1347 toWrite = END_KEY_CODE; 1348 break; 1349 1350 case B_PAGE_UP: 1351 if (mod & B_SHIFT_KEY) { 1352 _ScrollTo(fScrollOffset - fFontHeight * fRows, true); 1353 return; 1354 } 1355 if (rawChar == B_PAGE_UP) 1356 toWrite = PAGE_UP_KEY_CODE; 1357 break; 1358 1359 case B_PAGE_DOWN: 1360 if (mod & B_SHIFT_KEY) { 1361 _ScrollTo(fScrollOffset + fFontHeight * fRows, true); 1362 return; 1363 } 1364 if (rawChar == B_PAGE_DOWN) 1365 toWrite = PAGE_DOWN_KEY_CODE; 1366 break; 1367 1368 case B_FUNCTION_KEY: 1369 for (int32 i = 0; i < 12; i++) { 1370 if (key == function_keycode_table[i]) { 1371 toWrite = function_key_char_table[i]; 1372 break; 1373 } 1374 } 1375 break; 1376 } 1377 1378 // If the above code proposed an alternative string to write, we get it's 1379 // length. Otherwise we write exactly the bytes passed to this method. 1380 size_t toWriteLen; 1381 if (toWrite != NULL) { 1382 toWriteLen = strlen(toWrite); 1383 } else { 1384 toWrite = bytes; 1385 toWriteLen = numBytes; 1386 } 1387 1388 _ScrollTo(0, true); 1389 fShell->Write(toWrite, toWriteLen); 1390 } 1391 1392 1393 void 1394 TermView::FrameResized(float width, float height) 1395 { 1396 //debug_printf("TermView::FrameResized(%f, %f)\n", width, height); 1397 int32 columns = ((int32)width + 1) / fFontWidth; 1398 int32 rows = ((int32)height + 1) / fFontHeight; 1399 1400 if (columns == fColumns && rows == fRows) 1401 return; 1402 1403 bool hasResizeView = fResizeRunner != NULL; 1404 if (!hasResizeView) { 1405 // show the current size in a view 1406 fResizeView = new BStringView(BRect(100, 100, 300, 140), "size", ""); 1407 fResizeView->SetAlignment(B_ALIGN_CENTER); 1408 fResizeView->SetFont(be_bold_font); 1409 1410 BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED); 1411 fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this), 1412 &message, 25000LL); 1413 } 1414 1415 BString text; 1416 text << columns << " x " << rows; 1417 fResizeView->SetText(text.String()); 1418 fResizeView->GetPreferredSize(&width, &height); 1419 fResizeView->ResizeTo(width * 1.5, height * 1.5); 1420 fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2, 1421 (Bounds().Height()- fResizeView->Bounds().Height()) / 2); 1422 if (!hasResizeView && fResizeViewDisableCount < 1) 1423 AddChild(fResizeView); 1424 1425 if (fResizeViewDisableCount > 0) 1426 fResizeViewDisableCount--; 1427 1428 SetTermSize(rows, columns); 1429 1430 fFrameResized = true; 1431 } 1432 1433 1434 void 1435 TermView::MessageReceived(BMessage *msg) 1436 { 1437 entry_ref ref; 1438 const char *ctrl_l = "\x0c"; 1439 1440 // first check for any dropped message 1441 if (msg->WasDropped() && (msg->what == B_SIMPLE_DATA 1442 || msg->what == B_MIME_DATA)) { 1443 char *text; 1444 int32 numBytes; 1445 //rgb_color *color; 1446 1447 int32 i = 0; 1448 1449 if (msg->FindRef("refs", i++, &ref) == B_OK) { 1450 // first check if secondary mouse button is pressed 1451 int32 buttons = 0; 1452 msg->FindInt32("buttons", &buttons); 1453 1454 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1455 // start popup menu 1456 _SecondaryMouseButtonDropped(msg); 1457 return; 1458 } 1459 1460 _DoFileDrop(ref); 1461 1462 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1463 _WritePTY(" ", 1); 1464 _DoFileDrop(ref); 1465 } 1466 return; 1467 #if 0 1468 } else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE, 1469 (const void **)&color, &numBytes) == B_OK 1470 && numBytes == sizeof(color)) { 1471 // TODO: handle color drop 1472 // maybe only on replicants ? 1473 return; 1474 #endif 1475 } else if (msg->FindData("text/plain", B_MIME_TYPE, 1476 (const void **)&text, &numBytes) == B_OK) { 1477 _WritePTY(text, numBytes); 1478 return; 1479 } 1480 } 1481 1482 switch (msg->what){ 1483 case B_ABOUT_REQUESTED: 1484 // (replicant) about box requested 1485 AboutRequested(); 1486 break; 1487 1488 case B_SIMPLE_DATA: 1489 case B_REFS_RECEIVED: 1490 { 1491 // handle refs if they weren't dropped 1492 int32 i = 0; 1493 if (msg->FindRef("refs", i++, &ref) == B_OK) { 1494 _DoFileDrop(ref); 1495 1496 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1497 _WritePTY(" ", 1); 1498 _DoFileDrop(ref); 1499 } 1500 } else 1501 BView::MessageReceived(msg); 1502 break; 1503 } 1504 1505 case B_COPY: 1506 Copy(be_clipboard); 1507 break; 1508 1509 case B_PASTE: 1510 { 1511 int32 code; 1512 if (msg->FindInt32("index", &code) == B_OK) 1513 Paste(be_clipboard); 1514 break; 1515 } 1516 1517 case B_CLIPBOARD_CHANGED: 1518 // This message originates from the system clipboard. Overwrite 1519 // the contents of the mouse clipboard with the ones from the 1520 // system clipboard, in case it contains text data. 1521 if (be_clipboard->Lock()) { 1522 if (fMouseClipboard->Lock()) { 1523 BMessage* clipMsgA = be_clipboard->Data(); 1524 const char* text; 1525 ssize_t numBytes; 1526 if (clipMsgA->FindData("text/plain", B_MIME_TYPE, 1527 (const void**)&text, &numBytes) == B_OK ) { 1528 fMouseClipboard->Clear(); 1529 BMessage* clipMsgB = fMouseClipboard->Data(); 1530 clipMsgB->AddData("text/plain", B_MIME_TYPE, 1531 text, numBytes); 1532 fMouseClipboard->Commit(); 1533 } 1534 fMouseClipboard->Unlock(); 1535 } 1536 be_clipboard->Unlock(); 1537 } 1538 break; 1539 1540 case B_SELECT_ALL: 1541 SelectAll(); 1542 break; 1543 1544 case B_SET_PROPERTY: 1545 { 1546 int32 i; 1547 int32 encodingID; 1548 BMessage specifier; 1549 msg->GetCurrentSpecifier(&i, &specifier); 1550 if (!strcmp("encoding", specifier.FindString("property", i))){ 1551 msg->FindInt32 ("data", &encodingID); 1552 SetEncoding(encodingID); 1553 msg->SendReply(B_REPLY); 1554 } else { 1555 BView::MessageReceived(msg); 1556 } 1557 break; 1558 } 1559 1560 case B_GET_PROPERTY: 1561 { 1562 int32 i; 1563 BMessage specifier; 1564 msg->GetCurrentSpecifier(&i, &specifier); 1565 if (!strcmp("encoding", specifier.FindString("property", i))){ 1566 BMessage reply(B_REPLY); 1567 reply.AddInt32("result", Encoding()); 1568 msg->SendReply(&reply); 1569 } else if (!strcmp("tty", specifier.FindString("property", i))) { 1570 BMessage reply(B_REPLY); 1571 reply.AddString("result", TerminalName()); 1572 msg->SendReply(&reply); 1573 } else { 1574 BView::MessageReceived(msg); 1575 } 1576 break; 1577 } 1578 1579 case B_INPUT_METHOD_EVENT: 1580 { 1581 int32 opcode; 1582 if (msg->FindInt32("be:opcode", &opcode) == B_OK) { 1583 switch (opcode) { 1584 case B_INPUT_METHOD_STARTED: 1585 { 1586 BMessenger messenger; 1587 if (msg->FindMessenger("be:reply_to", 1588 &messenger) == B_OK) { 1589 fInline = new (std::nothrow) 1590 InlineInput(messenger); 1591 } 1592 break; 1593 } 1594 1595 case B_INPUT_METHOD_STOPPED: 1596 delete fInline; 1597 fInline = NULL; 1598 break; 1599 1600 case B_INPUT_METHOD_CHANGED: 1601 if (fInline != NULL) 1602 _HandleInputMethodChanged(msg); 1603 break; 1604 1605 case B_INPUT_METHOD_LOCATION_REQUEST: 1606 if (fInline != NULL) 1607 _HandleInputMethodLocationRequest(); 1608 break; 1609 1610 default: 1611 break; 1612 } 1613 } 1614 break; 1615 } 1616 1617 case MENU_CLEAR_ALL: 1618 Clear(); 1619 fShell->Write(ctrl_l, 1); 1620 break; 1621 case kBlinkCursor: 1622 _BlinkCursor(); 1623 break; 1624 case kUpdateSigWinch: 1625 _UpdateSIGWINCH(); 1626 break; 1627 case kAutoScroll: 1628 _AutoScrollUpdate(); 1629 break; 1630 case kSecondaryMouseDropAction: 1631 _DoSecondaryMouseDropAction(msg); 1632 break; 1633 case MSG_TERMINAL_BUFFER_CHANGED: 1634 { 1635 BAutolock _(fTextBuffer); 1636 _SynchronizeWithTextBuffer(0, -1); 1637 break; 1638 } 1639 case MSG_SET_TERMNAL_TITLE: 1640 { 1641 const char* title; 1642 if (msg->FindString("title", &title) == B_OK) 1643 SetTitle(title); 1644 break; 1645 } 1646 case MSG_REPORT_MOUSE_EVENT: 1647 { 1648 bool report; 1649 if (msg->FindBool("reportX10MouseEvent", &report) == B_OK) 1650 fReportX10MouseEvent = report; 1651 1652 if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK) 1653 fReportNormalMouseEvent = report; 1654 1655 if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK) 1656 fReportButtonMouseEvent = report; 1657 1658 if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK) 1659 fReportAnyMouseEvent = report; 1660 break; 1661 } 1662 case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED: 1663 { 1664 BPoint point; 1665 uint32 buttons; 1666 GetMouse(&point, &buttons, false); 1667 if (buttons != 0) 1668 break; 1669 1670 if (fResizeView != NULL) { 1671 fResizeView->RemoveSelf(); 1672 delete fResizeView; 1673 fResizeView = NULL; 1674 } 1675 delete fResizeRunner; 1676 fResizeRunner = NULL; 1677 break; 1678 } 1679 1680 case MSG_QUIT_TERMNAL: 1681 { 1682 int32 reason; 1683 if (msg->FindInt32("reason", &reason) != B_OK) 1684 reason = 0; 1685 NotifyQuit(reason); 1686 break; 1687 } 1688 default: 1689 BView::MessageReceived(msg); 1690 break; 1691 } 1692 } 1693 1694 1695 status_t 1696 TermView::GetSupportedSuites(BMessage *message) 1697 { 1698 BPropertyInfo propInfo(sPropList); 1699 message->AddString("suites", "suite/vnd.naan-termview"); 1700 message->AddFlat("messages", &propInfo); 1701 return BView::GetSupportedSuites(message); 1702 } 1703 1704 1705 void 1706 TermView::ScrollTo(BPoint where) 1707 { 1708 //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y); 1709 float diff = where.y - fScrollOffset; 1710 if (diff == 0) 1711 return; 1712 1713 float bottom = Bounds().bottom; 1714 int32 oldFirstLine = _LineAt(0); 1715 int32 oldLastLine = _LineAt(bottom); 1716 int32 newFirstLine = _LineAt(diff); 1717 int32 newLastLine = _LineAt(bottom + diff); 1718 1719 fScrollOffset = where.y; 1720 1721 // invalidate the current cursor position before scrolling 1722 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 1723 1724 // scroll contents 1725 BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop())); 1726 BRect sourceRect(destRect.OffsetByCopy(0, diff)); 1727 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", 1728 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, 1729 //destRect.left, destRect.top, destRect.right, destRect.bottom); 1730 CopyBits(sourceRect, destRect); 1731 1732 // sync visible text buffer with text buffer 1733 if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) { 1734 if (newFirstLine != oldFirstLine) 1735 { 1736 //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine); 1737 fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine); 1738 } 1739 BAutolock _(fTextBuffer); 1740 if (diff < 0) 1741 _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1); 1742 else 1743 _SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine); 1744 } 1745 } 1746 1747 1748 void 1749 TermView::TargetedByScrollView(BScrollView *scrollView) 1750 { 1751 BView::TargetedByScrollView(scrollView); 1752 1753 SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL); 1754 } 1755 1756 1757 BHandler* 1758 TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 1759 int32 what, const char* property) 1760 { 1761 BHandler* target = this; 1762 BPropertyInfo propInfo(sPropList); 1763 if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) { 1764 target = BView::ResolveSpecifier(message, index, specifier, what, 1765 property); 1766 } 1767 1768 return target; 1769 } 1770 1771 1772 void 1773 TermView::_SecondaryMouseButtonDropped(BMessage* msg) 1774 { 1775 // Launch menu to choose what is to do with the msg data 1776 BPoint point; 1777 if (msg->FindPoint("_drop_point_", &point) != B_OK) 1778 return; 1779 1780 BMessage* insertMessage = new BMessage(*msg); 1781 insertMessage->what = kSecondaryMouseDropAction; 1782 insertMessage->AddInt8("action", kInsert); 1783 1784 BMessage* cdMessage = new BMessage(*msg); 1785 cdMessage->what = kSecondaryMouseDropAction; 1786 cdMessage->AddInt8("action", kChangeDirectory); 1787 1788 BMessage* lnMessage = new BMessage(*msg); 1789 lnMessage->what = kSecondaryMouseDropAction; 1790 lnMessage->AddInt8("action", kLinkFiles); 1791 1792 BMessage* mvMessage = new BMessage(*msg); 1793 mvMessage->what = kSecondaryMouseDropAction; 1794 mvMessage->AddInt8("action", kMoveFiles); 1795 1796 BMessage* cpMessage = new BMessage(*msg); 1797 cpMessage->what = kSecondaryMouseDropAction; 1798 cpMessage->AddInt8("action", kCopyFiles); 1799 1800 BMenuItem* insertItem = new BMenuItem("Insert path", insertMessage); 1801 BMenuItem* cdItem = new BMenuItem("Change directory", cdMessage); 1802 BMenuItem* lnItem = new BMenuItem("Create link here", lnMessage); 1803 BMenuItem* mvItem = new BMenuItem("Move here", mvMessage); 1804 BMenuItem* cpItem = new BMenuItem("Copy here", cpMessage); 1805 BMenuItem* chItem = new BMenuItem("Cancel", NULL); 1806 1807 // if the refs point to different directorys disable the cd menu item 1808 bool differentDirs = false; 1809 BDirectory firstDir; 1810 entry_ref ref; 1811 int i = 0; 1812 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1813 BNode node(&ref); 1814 BEntry entry(&ref); 1815 BDirectory dir; 1816 if (node.IsDirectory()) 1817 dir.SetTo(&ref); 1818 else 1819 entry.GetParent(&dir); 1820 1821 if (i == 1) { 1822 node_ref nodeRef; 1823 dir.GetNodeRef(&nodeRef); 1824 firstDir.SetTo(&nodeRef); 1825 } else if (firstDir != dir) { 1826 differentDirs = true; 1827 break; 1828 } 1829 } 1830 if (differentDirs) 1831 cdItem->SetEnabled(false); 1832 1833 BPopUpMenu *menu = new BPopUpMenu("Secondary Mouse Button Drop Menu"); 1834 menu->SetAsyncAutoDestruct(true); 1835 menu->AddItem(insertItem); 1836 menu->AddSeparatorItem(); 1837 menu->AddItem(cdItem); 1838 menu->AddItem(lnItem); 1839 menu->AddItem(mvItem); 1840 menu->AddItem(cpItem); 1841 menu->AddSeparatorItem(); 1842 menu->AddItem(chItem); 1843 menu->SetTargetForItems(this); 1844 menu->Go(point, true, true, true); 1845 } 1846 1847 1848 void 1849 TermView::_DoSecondaryMouseDropAction(BMessage* msg) 1850 { 1851 int8 action = -1; 1852 msg->FindInt8("action", &action); 1853 1854 BString outString = ""; 1855 BString itemString = ""; 1856 1857 switch (action) { 1858 case kInsert: 1859 break; 1860 case kChangeDirectory: 1861 outString = "cd "; 1862 break; 1863 case kLinkFiles: 1864 outString = "ln -s "; 1865 break; 1866 case kMoveFiles: 1867 outString = "mv "; 1868 break; 1869 case kCopyFiles: 1870 outString = "cp "; 1871 break; 1872 1873 default: 1874 return; 1875 } 1876 1877 bool listContainsDirectory = false; 1878 entry_ref ref; 1879 int32 i = 0; 1880 while (msg->FindRef("refs", i++, &ref) == B_OK) { 1881 BEntry ent(&ref); 1882 BNode node(&ref); 1883 BPath path(&ent); 1884 BString string(path.Path()); 1885 1886 if (node.IsDirectory()) 1887 listContainsDirectory = true; 1888 1889 if (i > 1) 1890 itemString += " "; 1891 1892 if (action == kChangeDirectory) { 1893 if (!node.IsDirectory()) { 1894 int32 slash = string.FindLast("/"); 1895 string.Truncate(slash); 1896 } 1897 string.CharacterEscape(kEscapeCharacters, '\\'); 1898 itemString += string; 1899 break; 1900 } 1901 string.CharacterEscape(kEscapeCharacters, '\\'); 1902 itemString += string; 1903 } 1904 1905 if (listContainsDirectory && action == kCopyFiles) 1906 outString += "-R "; 1907 1908 outString += itemString; 1909 1910 if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles) 1911 outString += " ."; 1912 1913 if (action != kInsert) 1914 outString += "\n"; 1915 1916 _WritePTY(outString.String(), outString.Length()); 1917 } 1918 1919 1920 //! Gets dropped file full path and display it at cursor position. 1921 void 1922 TermView::_DoFileDrop(entry_ref& ref) 1923 { 1924 BEntry ent(&ref); 1925 BPath path(&ent); 1926 BString string(path.Path()); 1927 1928 string.CharacterEscape(kEscapeCharacters, '\\'); 1929 _WritePTY(string.String(), string.Length()); 1930 } 1931 1932 1933 /*! Text buffer must already be locked. 1934 */ 1935 void 1936 TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop, 1937 int32 visibleDirtyBottom) 1938 { 1939 TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo(); 1940 int32 linesScrolled = info.linesScrolled; 1941 1942 //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " 1943 //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom, 1944 //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom); 1945 1946 bigtime_t now = system_time(); 1947 bigtime_t timeElapsed = now - fLastSyncTime; 1948 if (timeElapsed > 2 * kSyncUpdateGranularity) { 1949 // last sync was ages ago 1950 fLastSyncTime = now; 1951 fScrolledSinceLastSync = linesScrolled; 1952 } 1953 1954 if (fSyncRunner == NULL) { 1955 // We consider clocked syncing when more than a full screen height has 1956 // been scrolled in less than a sync update period. Once we're 1957 // actively considering it, the same condition will convince us to 1958 // actually do it. 1959 if (fScrolledSinceLastSync + linesScrolled <= fRows) { 1960 // Condition doesn't hold yet. Reset if time is up, or otherwise 1961 // keep counting. 1962 if (timeElapsed > kSyncUpdateGranularity) { 1963 fConsiderClockedSync = false; 1964 fLastSyncTime = now; 1965 fScrolledSinceLastSync = linesScrolled; 1966 } else 1967 fScrolledSinceLastSync += linesScrolled; 1968 } else if (fConsiderClockedSync) { 1969 // We are convinced -- create the sync runner. 1970 fLastSyncTime = now; 1971 fScrolledSinceLastSync = 0; 1972 1973 BMessage message(MSG_TERMINAL_BUFFER_CHANGED); 1974 fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this), 1975 &message, kSyncUpdateGranularity); 1976 if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK) 1977 return; 1978 1979 delete fSyncRunner; 1980 fSyncRunner = NULL; 1981 } else { 1982 // Looks interesting so far. Reset the counts and consider clocked 1983 // syncing. 1984 fConsiderClockedSync = true; 1985 fLastSyncTime = now; 1986 fScrolledSinceLastSync = 0; 1987 } 1988 } else if (timeElapsed < kSyncUpdateGranularity) { 1989 // sync time not passed yet -- keep counting 1990 fScrolledSinceLastSync += linesScrolled; 1991 return; 1992 } else if (fScrolledSinceLastSync + linesScrolled <= fRows) { 1993 // time's up, but not enough happened 1994 delete fSyncRunner; 1995 fSyncRunner = NULL; 1996 fLastSyncTime = now; 1997 fScrolledSinceLastSync = linesScrolled; 1998 } else { 1999 // Things are still rolling, but the sync time's up. 2000 fLastSyncTime = now; 2001 fScrolledSinceLastSync = 0; 2002 } 2003 2004 // Simple case first -- complete invalidation. 2005 if (info.invalidateAll) { 2006 Invalidate(); 2007 _UpdateScrollBarRange(); 2008 _Deselect(); 2009 2010 fCursor = fTextBuffer->Cursor(); 2011 _ActivateCursor(false); 2012 2013 int32 offset = _LineAt(0); 2014 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, 2015 offset + fTextBuffer->Height() + 2); 2016 2017 info.Reset(); 2018 return; 2019 } 2020 2021 BRect bounds = Bounds(); 2022 int32 firstVisible = _LineAt(0); 2023 int32 lastVisible = _LineAt(bounds.bottom); 2024 int32 historySize = fTextBuffer->HistorySize(); 2025 2026 bool doScroll = false; 2027 if (linesScrolled > 0) { 2028 _UpdateScrollBarRange(); 2029 2030 visibleDirtyTop -= linesScrolled; 2031 visibleDirtyBottom -= linesScrolled; 2032 2033 if (firstVisible < 0) { 2034 firstVisible -= linesScrolled; 2035 lastVisible -= linesScrolled; 2036 2037 float scrollOffset; 2038 if (firstVisible < -historySize) { 2039 firstVisible = -historySize; 2040 doScroll = true; 2041 scrollOffset = -historySize * fFontHeight; 2042 // We need to invalidate the lower linesScrolled lines of the 2043 // visible text buffer, since those will be scrolled up and 2044 // need to be replaced. We just use visibleDirty{Top,Bottom} 2045 // for that purpose. Unless invoked from ScrollTo() (i.e. 2046 // user-initiated scrolling) those are unused. In the unlikely 2047 // case that the user is scrolling at the same time we may 2048 // invalidate too many lines, since we have to extend the given 2049 // region. 2050 // Note that in the firstVisible == 0 case the new lines are 2051 // already in the dirty region, so they will be updated anyway. 2052 if (visibleDirtyTop <= visibleDirtyBottom) { 2053 if (lastVisible < visibleDirtyTop) 2054 visibleDirtyTop = lastVisible; 2055 if (visibleDirtyBottom < lastVisible + linesScrolled) 2056 visibleDirtyBottom = lastVisible + linesScrolled; 2057 } else { 2058 visibleDirtyTop = lastVisible + 1; 2059 visibleDirtyBottom = lastVisible + linesScrolled; 2060 } 2061 } else 2062 scrollOffset = fScrollOffset - linesScrolled * fFontHeight; 2063 2064 _ScrollTo(scrollOffset, false); 2065 } else 2066 doScroll = true; 2067 2068 if (doScroll && lastVisible >= firstVisible 2069 && !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop 2070 && lastVisible <= info.dirtyBottom)) { 2071 // scroll manually 2072 float scrollBy = linesScrolled * fFontHeight; 2073 BRect destRect(Frame().OffsetToCopy(B_ORIGIN)); 2074 BRect sourceRect(destRect.OffsetByCopy(0, scrollBy)); 2075 2076 // invalidate the current cursor position before scrolling 2077 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2078 2079 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", 2080 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, 2081 //destRect.left, destRect.top, destRect.right, destRect.bottom); 2082 CopyBits(sourceRect, destRect); 2083 2084 fVisibleTextBuffer->ScrollBy(linesScrolled); 2085 } 2086 2087 // move selection 2088 if (fSelStart != fSelEnd) { 2089 fSelStart.y -= linesScrolled; 2090 fSelEnd.y -= linesScrolled; 2091 fInitialSelectionStart.y -= linesScrolled; 2092 fInitialSelectionEnd.y -= linesScrolled; 2093 2094 if (fSelStart.y < -historySize) 2095 _Deselect(); 2096 } 2097 } 2098 2099 // invalidate dirty region 2100 if (info.IsDirtyRegionValid()) { 2101 _InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1, 2102 info.dirtyBottom); 2103 2104 // clear the selection, if affected 2105 if (fSelStart != fSelEnd) { 2106 // TODO: We're clearing the selection more often than necessary -- 2107 // to avoid that, we'd also need to track the x coordinates of the 2108 // dirty range. 2109 int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1; 2110 if (fSelStart.y <= info.dirtyBottom 2111 && info.dirtyTop <= selectionBottom) { 2112 _Deselect(); 2113 } 2114 } 2115 } 2116 2117 if (visibleDirtyTop <= visibleDirtyBottom) 2118 info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom); 2119 2120 if (linesScrolled != 0 || info.IsDirtyRegionValid()) { 2121 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible, 2122 info.dirtyTop, info.dirtyBottom); 2123 } 2124 2125 // invalidate cursor, if it changed 2126 TermPos cursor = fTextBuffer->Cursor(); 2127 if (fCursor != cursor || linesScrolled != 0) { 2128 // Before we scrolled we did already invalidate the old cursor. 2129 if (!doScroll) 2130 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2131 fCursor = cursor; 2132 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2133 _ActivateCursor(false); 2134 } 2135 2136 info.Reset(); 2137 } 2138 2139 2140 /*! Write strings to PTY device. If encoding system isn't UTF8, change 2141 encoding to UTF8 before writing PTY. 2142 */ 2143 void 2144 TermView::_WritePTY(const char* text, int32 numBytes) 2145 { 2146 if (fEncoding != M_UTF8) { 2147 while (numBytes > 0) { 2148 char buffer[1024]; 2149 int32 bufferSize = sizeof(buffer); 2150 int32 sourceSize = numBytes; 2151 int32 state = 0; 2152 if (convert_to_utf8(fEncoding, text, &sourceSize, buffer, 2153 &bufferSize, &state) != B_OK || bufferSize == 0) { 2154 break; 2155 } 2156 2157 fShell->Write(buffer, bufferSize); 2158 text += sourceSize; 2159 numBytes -= sourceSize; 2160 } 2161 } else { 2162 fShell->Write(text, numBytes); 2163 } 2164 } 2165 2166 2167 //! Returns the square of the actual pixel distance between both points 2168 float 2169 TermView::_MouseDistanceSinceLastClick(BPoint where) 2170 { 2171 return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x) 2172 + (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y); 2173 } 2174 2175 2176 void 2177 TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y, 2178 bool motion) 2179 { 2180 char xtermButtons; 2181 if (buttons == B_PRIMARY_MOUSE_BUTTON) 2182 xtermButtons = 32 + 0; 2183 else if (buttons == B_SECONDARY_MOUSE_BUTTON) 2184 xtermButtons = 32 + 1; 2185 else if (buttons == B_TERTIARY_MOUSE_BUTTON) 2186 xtermButtons = 32 + 2; 2187 else 2188 xtermButtons = 32 + 3; 2189 2190 if (motion) 2191 xtermButtons += 32; 2192 2193 char xtermX = x + 1 + 32; 2194 char xtermY = y + 1 + 32; 2195 2196 char destBuffer[6]; 2197 destBuffer[0] = '\033'; 2198 destBuffer[1] = '['; 2199 destBuffer[2] = 'M'; 2200 destBuffer[3] = xtermButtons; 2201 destBuffer[4] = xtermX; 2202 destBuffer[5] = xtermY; 2203 fShell->Write(destBuffer, 6); 2204 } 2205 2206 2207 void 2208 TermView::MouseDown(BPoint where) 2209 { 2210 if (!IsFocus()) 2211 MakeFocus(); 2212 2213 int32 buttons; 2214 int32 modifier; 2215 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 2216 Window()->CurrentMessage()->FindInt32("modifiers", &modifier); 2217 2218 fMouseButtons = buttons; 2219 2220 if (fReportAnyMouseEvent || fReportButtonMouseEvent 2221 || fReportNormalMouseEvent || fReportX10MouseEvent) { 2222 TermPos clickPos = _ConvertToTerminal(where); 2223 _SendMouseEvent(buttons, modifier, clickPos.x, clickPos.y, false); 2224 return; 2225 } 2226 2227 // paste button 2228 if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) { 2229 Paste(fMouseClipboard); 2230 fLastClickPoint = where; 2231 return; 2232 } 2233 2234 // Select Region 2235 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 2236 int32 clicks; 2237 Window()->CurrentMessage()->FindInt32("clicks", &clicks); 2238 2239 if (_HasSelection()) { 2240 TermPos inPos = _ConvertToTerminal(where); 2241 if (_CheckSelectedRegion(inPos)) { 2242 if (modifier & B_CONTROL_KEY) { 2243 BPoint p; 2244 uint32 bt; 2245 do { 2246 GetMouse(&p, &bt); 2247 2248 if (bt == 0) { 2249 _Deselect(); 2250 return; 2251 } 2252 2253 snooze(40000); 2254 2255 } while (abs((int)(where.x - p.x)) < 4 2256 && abs((int)(where.y - p.y)) < 4); 2257 2258 InitiateDrag(); 2259 return; 2260 } 2261 } 2262 } 2263 2264 // If mouse has moved too much, disable double/triple click. 2265 if (_MouseDistanceSinceLastClick(where) > 8) 2266 clicks = 1; 2267 2268 SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, 2269 B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); 2270 2271 TermPos clickPos = _ConvertToTerminal(where); 2272 2273 if (modifier & B_SHIFT_KEY) { 2274 fInitialSelectionStart = clickPos; 2275 fInitialSelectionEnd = clickPos; 2276 _ExtendSelection(fInitialSelectionStart, true, false); 2277 } else { 2278 _Deselect(); 2279 fInitialSelectionStart = clickPos; 2280 fInitialSelectionEnd = clickPos; 2281 } 2282 2283 // If clicks larger than 3, reset mouse click counter. 2284 clicks = (clicks - 1) % 3 + 1; 2285 2286 switch (clicks) { 2287 case 1: 2288 fCheckMouseTracking = true; 2289 fSelectGranularity = SELECT_CHARS; 2290 break; 2291 2292 case 2: 2293 _SelectWord(where, (modifier & B_SHIFT_KEY) != 0, false); 2294 fMouseTracking = true; 2295 fSelectGranularity = SELECT_WORDS; 2296 break; 2297 2298 case 3: 2299 _SelectLine(where, (modifier & B_SHIFT_KEY) != 0, false); 2300 fMouseTracking = true; 2301 fSelectGranularity = SELECT_LINES; 2302 break; 2303 } 2304 } 2305 fLastClickPoint = where; 2306 } 2307 2308 2309 void 2310 TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message) 2311 { 2312 if (fReportAnyMouseEvent || fReportButtonMouseEvent) { 2313 int32 modifier; 2314 Window()->CurrentMessage()->FindInt32("modifiers", &modifier); 2315 2316 TermPos clickPos = _ConvertToTerminal(where); 2317 2318 if (fReportButtonMouseEvent) { 2319 if (fPrevPos.x != clickPos.x || fPrevPos.y != clickPos.y) { 2320 _SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y, 2321 true); 2322 } 2323 fPrevPos = clickPos; 2324 return; 2325 } 2326 _SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y, true); 2327 return; 2328 } 2329 2330 if (fCheckMouseTracking) { 2331 if (_MouseDistanceSinceLastClick(where) > 9) 2332 fMouseTracking = true; 2333 } 2334 if (!fMouseTracking) 2335 return; 2336 2337 bool doAutoScroll = false; 2338 2339 if (where.y < 0) { 2340 doAutoScroll = true; 2341 fAutoScrollSpeed = where.y; 2342 where.x = 0; 2343 where.y = 0; 2344 } 2345 2346 BRect bounds(Bounds()); 2347 if (where.y > bounds.bottom) { 2348 doAutoScroll = true; 2349 fAutoScrollSpeed = where.y - bounds.bottom; 2350 where.x = bounds.right; 2351 where.y = bounds.bottom; 2352 } 2353 2354 if (doAutoScroll) { 2355 if (fAutoScrollRunner == NULL) { 2356 BMessage message(kAutoScroll); 2357 fAutoScrollRunner = new (std::nothrow) BMessageRunner( 2358 BMessenger(this), &message, 10000); 2359 } 2360 } else { 2361 delete fAutoScrollRunner; 2362 fAutoScrollRunner = NULL; 2363 } 2364 2365 switch (fSelectGranularity) { 2366 case SELECT_CHARS: 2367 { 2368 // If we just start selecting, we first select the initially 2369 // hit char, so that we get a proper initial selection -- the char 2370 // in question, which will thus always be selected, regardless of 2371 // whether selecting forward or backward. 2372 if (fInitialSelectionStart == fInitialSelectionEnd) { 2373 _Select(fInitialSelectionStart, fInitialSelectionEnd, true, 2374 true); 2375 } 2376 2377 _ExtendSelection(_ConvertToTerminal(where), true, true); 2378 break; 2379 } 2380 case SELECT_WORDS: 2381 _SelectWord(where, true, true); 2382 break; 2383 case SELECT_LINES: 2384 _SelectLine(where, true, true); 2385 break; 2386 } 2387 } 2388 2389 2390 void 2391 TermView::MouseUp(BPoint where) 2392 { 2393 fCheckMouseTracking = false; 2394 fMouseTracking = false; 2395 2396 if (fAutoScrollRunner != NULL) { 2397 delete fAutoScrollRunner; 2398 fAutoScrollRunner = NULL; 2399 } 2400 2401 // When releasing the first mouse button, we copy the selected text to the 2402 // clipboard. 2403 int32 buttons; 2404 Window()->CurrentMessage()->FindInt32("buttons", &buttons); 2405 2406 if (fReportAnyMouseEvent || fReportButtonMouseEvent 2407 || fReportNormalMouseEvent) { 2408 TermPos clickPos = _ConvertToTerminal(where); 2409 _SendMouseEvent(0, 0, clickPos.x, clickPos.y, false); 2410 } else { 2411 if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0 2412 && (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) { 2413 Copy(fMouseClipboard); 2414 } 2415 2416 } 2417 fMouseButtons = buttons; 2418 } 2419 2420 2421 //! Select a range of text. 2422 void 2423 TermView::_Select(TermPos start, TermPos end, bool inclusive, 2424 bool setInitialSelection) 2425 { 2426 BAutolock _(fTextBuffer); 2427 2428 _SynchronizeWithTextBuffer(0, -1); 2429 2430 if (end < start) 2431 std::swap(start, end); 2432 2433 if (inclusive) 2434 end.x++; 2435 2436 //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x, 2437 //start.y, end.x, end.y); 2438 2439 if (start.x < 0) 2440 start.x = 0; 2441 if (end.x >= fColumns) 2442 end.x = fColumns; 2443 2444 TermPos minPos(0, -fTextBuffer->HistorySize()); 2445 TermPos maxPos(0, fTextBuffer->Height()); 2446 start = restrict_value(start, minPos, maxPos); 2447 end = restrict_value(end, minPos, maxPos); 2448 2449 // if the end is past the end of the line, select the line break, too 2450 if (fTextBuffer->LineLength(end.y) < end.x 2451 && end.y < fTextBuffer->Height()) { 2452 end.y++; 2453 end.x = 0; 2454 } 2455 2456 if (fTextBuffer->IsFullWidthChar(start.y, start.x)) { 2457 start.x--; 2458 if (start.x < 0) 2459 start.x = 0; 2460 } 2461 2462 if (fTextBuffer->IsFullWidthChar(end.y, end.x)) { 2463 end.x++; 2464 if (end.x >= fColumns) 2465 end.x = fColumns; 2466 } 2467 2468 if (fSelStart != fSelEnd) 2469 _InvalidateTextRange(fSelStart, fSelEnd); 2470 2471 fSelStart = start; 2472 fSelEnd = end; 2473 2474 if (setInitialSelection) { 2475 fInitialSelectionStart = fSelStart; 2476 fInitialSelectionEnd = fSelEnd; 2477 } 2478 2479 _InvalidateTextRange(fSelStart, fSelEnd); 2480 } 2481 2482 2483 //! Extend selection (shift + mouse click). 2484 void 2485 TermView::_ExtendSelection(TermPos pos, bool inclusive, 2486 bool useInitialSelection) 2487 { 2488 if (!useInitialSelection && !_HasSelection()) 2489 return; 2490 2491 TermPos start = fSelStart; 2492 TermPos end = fSelEnd; 2493 2494 if (useInitialSelection) { 2495 start = fInitialSelectionStart; 2496 end = fInitialSelectionEnd; 2497 } 2498 2499 if (inclusive) { 2500 if (pos >= start && pos >= end) 2501 pos.x++; 2502 } 2503 2504 if (pos < start) 2505 _Select(pos, end, false, !useInitialSelection); 2506 else if (pos > end) 2507 _Select(start, pos, false, !useInitialSelection); 2508 else if (useInitialSelection) 2509 _Select(start, end, false, false); 2510 } 2511 2512 2513 // clear the selection. 2514 void 2515 TermView::_Deselect() 2516 { 2517 //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection()); 2518 if (!_HasSelection()) 2519 return; 2520 2521 _InvalidateTextRange(fSelStart, fSelEnd); 2522 2523 fSelStart.SetTo(0, 0); 2524 fSelEnd.SetTo(0, 0); 2525 fInitialSelectionStart.SetTo(0, 0); 2526 fInitialSelectionEnd.SetTo(0, 0); 2527 } 2528 2529 2530 bool 2531 TermView::_HasSelection() const 2532 { 2533 return fSelStart != fSelEnd; 2534 } 2535 2536 2537 void 2538 TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection) 2539 { 2540 BAutolock _(fTextBuffer); 2541 2542 TermPos pos = _ConvertToTerminal(where); 2543 TermPos start, end; 2544 if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end)) 2545 return; 2546 2547 if (extend) { 2548 if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) 2549 _ExtendSelection(start, false, useInitialSelection); 2550 else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) 2551 _ExtendSelection(end, false, useInitialSelection); 2552 else if (useInitialSelection) 2553 _Select(start, end, false, false); 2554 } else 2555 _Select(start, end, false, !useInitialSelection); 2556 } 2557 2558 2559 void 2560 TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection) 2561 { 2562 TermPos start = TermPos(0, _ConvertToTerminal(where).y); 2563 TermPos end = TermPos(0, start.y + 1); 2564 2565 if (extend) { 2566 if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) 2567 _ExtendSelection(start, false, useInitialSelection); 2568 else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) 2569 _ExtendSelection(end, false, useInitialSelection); 2570 else if (useInitialSelection) 2571 _Select(start, end, false, false); 2572 } else 2573 _Select(start, end, false, !useInitialSelection); 2574 } 2575 2576 2577 void 2578 TermView::_AutoScrollUpdate() 2579 { 2580 if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) { 2581 float value = fScrollBar->Value(); 2582 _ScrollTo(value + fAutoScrollSpeed, true); 2583 if (fAutoScrollSpeed < 0) { 2584 _ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true); 2585 } else { 2586 _ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true, 2587 true); 2588 } 2589 } 2590 } 2591 2592 2593 bool 2594 TermView::_CheckSelectedRegion(const TermPos &pos) const 2595 { 2596 return pos >= fSelStart && pos < fSelEnd; 2597 } 2598 2599 2600 bool 2601 TermView::_CheckSelectedRegion(int32 row, int32 firstColumn, 2602 int32& lastColumn) const 2603 { 2604 if (fSelStart == fSelEnd) 2605 return false; 2606 2607 if (row == fSelStart.y && firstColumn < fSelStart.x 2608 && lastColumn >= fSelStart.x) { 2609 // region starts before the selection, but intersects with it 2610 lastColumn = fSelStart.x - 1; 2611 return false; 2612 } 2613 2614 if (row == fSelEnd.y && firstColumn < fSelEnd.x 2615 && lastColumn >= fSelEnd.x) { 2616 // region starts in the selection, but exceeds the end 2617 lastColumn = fSelEnd.x - 1; 2618 return true; 2619 } 2620 2621 TermPos pos(firstColumn, row); 2622 return pos >= fSelStart && pos < fSelEnd; 2623 } 2624 2625 2626 void 2627 TermView::GetFrameSize(float *width, float *height) 2628 { 2629 int32 historySize; 2630 { 2631 BAutolock _(fTextBuffer); 2632 historySize = fTextBuffer->HistorySize(); 2633 } 2634 2635 if (width != NULL) 2636 *width = fColumns * fFontWidth; 2637 2638 if (height != NULL) 2639 *height = (fRows + historySize) * fFontHeight; 2640 } 2641 2642 2643 // Find a string, and select it if found 2644 bool 2645 TermView::Find(const BString &str, bool forwardSearch, bool matchCase, 2646 bool matchWord) 2647 { 2648 BAutolock _(fTextBuffer); 2649 _SynchronizeWithTextBuffer(0, -1); 2650 2651 TermPos start; 2652 if (_HasSelection()) { 2653 if (forwardSearch) 2654 start = fSelEnd; 2655 else 2656 start = fSelStart; 2657 } else { 2658 // search from the very beginning/end 2659 if (forwardSearch) 2660 start = TermPos(0, -fTextBuffer->HistorySize()); 2661 else 2662 start = TermPos(0, fTextBuffer->Height()); 2663 } 2664 2665 TermPos matchStart, matchEnd; 2666 if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase, 2667 matchWord, matchStart, matchEnd)) { 2668 return false; 2669 } 2670 2671 _Select(matchStart, matchEnd, false, true); 2672 _ScrollToRange(fSelStart, fSelEnd); 2673 2674 return true; 2675 } 2676 2677 2678 //! Get the selected text and copy to str 2679 void 2680 TermView::GetSelection(BString &str) 2681 { 2682 str.SetTo(""); 2683 BAutolock _(fTextBuffer); 2684 fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd); 2685 } 2686 2687 2688 void 2689 TermView::NotifyQuit(int32 reason) 2690 { 2691 // implemented in subclasses 2692 } 2693 2694 2695 void 2696 TermView::CheckShellGone() 2697 { 2698 if (!fShell) 2699 return; 2700 2701 // check, if the shell does still live 2702 pid_t pid = fShell->ProcessID(); 2703 team_info info; 2704 if (get_team_info(pid, &info) == B_BAD_TEAM_ID) { 2705 // the shell is gone 2706 NotifyQuit(0); 2707 } 2708 } 2709 2710 2711 void 2712 TermView::InitiateDrag() 2713 { 2714 BAutolock _(fTextBuffer); 2715 2716 BString copyStr(""); 2717 fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd); 2718 2719 BMessage message(B_MIME_DATA); 2720 message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), 2721 copyStr.Length()); 2722 2723 BPoint start = _ConvertFromTerminal(fSelStart); 2724 BPoint end = _ConvertFromTerminal(fSelEnd); 2725 2726 BRect rect; 2727 if (fSelStart.y == fSelEnd.y) 2728 rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight); 2729 else 2730 rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight); 2731 2732 rect = rect & Bounds(); 2733 2734 DragMessage(&message, rect); 2735 } 2736 2737 2738 /* static */ 2739 void 2740 TermView::AboutRequested() 2741 { 2742 BAlert *alert = new (std::nothrow) BAlert("about", 2743 "Terminal\n\n" 2744 "written by Kazuho Okui and Takashi Murai\n" 2745 "updated by Kian Duffy and others\n\n" 2746 "Copyright " B_UTF8_COPYRIGHT "2003-2009, Haiku.\n", "OK"); 2747 if (alert != NULL) 2748 alert->Go(); 2749 } 2750 2751 2752 void 2753 TermView::_ScrollTo(float y, bool scrollGfx) 2754 { 2755 if (!scrollGfx) 2756 fScrollOffset = y; 2757 2758 if (fScrollBar != NULL) 2759 fScrollBar->SetValue(y); 2760 else 2761 ScrollTo(BPoint(0, y)); 2762 } 2763 2764 2765 void 2766 TermView::_ScrollToRange(TermPos start, TermPos end) 2767 { 2768 if (start > end) 2769 std::swap(start, end); 2770 2771 float startY = _LineOffset(start.y); 2772 float endY = _LineOffset(end.y) + fFontHeight - 1; 2773 float height = Bounds().Height(); 2774 2775 if (endY - startY > height) { 2776 // The range is greater than the height. Scroll to the closest border. 2777 2778 // already as good as it gets? 2779 if (startY <= 0 && endY >= height) 2780 return; 2781 2782 if (startY > 0) { 2783 // scroll down to align the start with the top of the view 2784 _ScrollTo(fScrollOffset + startY, true); 2785 } else { 2786 // scroll up to align the end with the bottom of the view 2787 _ScrollTo(fScrollOffset + endY - height, true); 2788 } 2789 } else { 2790 // The range is smaller than the height. 2791 2792 // already visible? 2793 if (startY >= 0 && endY <= height) 2794 return; 2795 2796 if (startY < 0) { 2797 // scroll up to make the start visible 2798 _ScrollTo(fScrollOffset + startY, true); 2799 } else { 2800 // scroll down to make the end visible 2801 _ScrollTo(fScrollOffset + endY - height, true); 2802 } 2803 } 2804 } 2805 2806 2807 void 2808 TermView::DisableResizeView(int32 disableCount) 2809 { 2810 fResizeViewDisableCount += disableCount; 2811 } 2812 2813 2814 void 2815 TermView::_DrawInlineMethodString() 2816 { 2817 if (!fInline || !fInline->String()) 2818 return; 2819 2820 const int32 numChars = BString(fInline->String()).CountChars(); 2821 2822 BPoint startPoint = _ConvertFromTerminal(fCursor); 2823 BPoint endPoint = startPoint; 2824 endPoint.x += fFontWidth * numChars; 2825 endPoint.y += fFontHeight + 1; 2826 2827 BRect eraseRect(startPoint, endPoint); 2828 2829 PushState(); 2830 SetHighColor(kTermColorTable[7]); 2831 FillRect(eraseRect); 2832 PopState(); 2833 2834 BPoint loc = _ConvertFromTerminal(fCursor); 2835 loc.y += fFontHeight; 2836 SetFont(&fHalfFont); 2837 SetHighColor(kTermColorTable[0]); 2838 SetLowColor(kTermColorTable[7]); 2839 DrawString(fInline->String(), loc); 2840 } 2841 2842 2843 void 2844 TermView::_HandleInputMethodChanged(BMessage *message) 2845 { 2846 const char *string = NULL; 2847 if (message->FindString("be:string", &string) < B_OK || string == NULL) 2848 return; 2849 2850 _ActivateCursor(false); 2851 2852 if (IsFocus()) 2853 be_app->ObscureCursor(); 2854 2855 // If we find the "be:confirmed" boolean (and the boolean is true), 2856 // it means it's over for now, so the current InlineInput object 2857 // should become inactive. We will probably receive a 2858 // B_INPUT_METHOD_STOPPED message after this one. 2859 bool confirmed; 2860 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 2861 confirmed = false; 2862 2863 fInline->SetString(""); 2864 2865 Invalidate(); 2866 // TODO: Debug only 2867 snooze(100000); 2868 2869 fInline->SetString(string); 2870 fInline->ResetClauses(); 2871 2872 if (!confirmed && !fInline->IsActive()) 2873 fInline->SetActive(true); 2874 2875 // Get the clauses, and pass them to the InlineInput object 2876 // TODO: Find out if what we did it's ok, currently we don't consider 2877 // clauses at all, while the bebook says we should; though the visual 2878 // effect we obtained seems correct. Weird. 2879 int32 clauseCount = 0; 2880 int32 clauseStart; 2881 int32 clauseEnd; 2882 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) 2883 == B_OK 2884 && message->FindInt32("be:clause_end", clauseCount, &clauseEnd) 2885 == B_OK) { 2886 if (!fInline->AddClause(clauseStart, clauseEnd)) 2887 break; 2888 clauseCount++; 2889 } 2890 2891 if (confirmed) { 2892 fInline->SetString(""); 2893 _ActivateCursor(true); 2894 2895 // now we need to feed ourselves the individual characters as if the 2896 // user would have pressed them now - this lets KeyDown() pick out all 2897 // the special characters like B_BACKSPACE, cursor keys and the like: 2898 const char* currPos = string; 2899 const char* prevPos = currPos; 2900 while (*currPos != '\0') { 2901 if ((*currPos & 0xC0) == 0xC0) { 2902 // found the start of an UTF-8 char, we collect while it lasts 2903 ++currPos; 2904 while ((*currPos & 0xC0) == 0x80) 2905 ++currPos; 2906 } else if ((*currPos & 0xC0) == 0x80) { 2907 // illegal: character starts with utf-8 intermediate byte, skip it 2908 prevPos = ++currPos; 2909 } else { 2910 // single byte character/code, just feed that 2911 ++currPos; 2912 } 2913 KeyDown(prevPos, currPos - prevPos); 2914 prevPos = currPos; 2915 } 2916 2917 Invalidate(); 2918 } else { 2919 // temporarily show transient state of inline input 2920 int32 selectionStart = 0; 2921 int32 selectionEnd = 0; 2922 message->FindInt32("be:selection", 0, &selectionStart); 2923 message->FindInt32("be:selection", 1, &selectionEnd); 2924 2925 fInline->SetSelectionOffset(selectionStart); 2926 fInline->SetSelectionLength(selectionEnd - selectionStart); 2927 Invalidate(); 2928 } 2929 } 2930 2931 2932 void 2933 TermView::_HandleInputMethodLocationRequest() 2934 { 2935 BMessage message(B_INPUT_METHOD_EVENT); 2936 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 2937 2938 BString string(fInline->String()); 2939 2940 const int32 &limit = string.CountChars(); 2941 BPoint where = _ConvertFromTerminal(fCursor); 2942 where.y += fFontHeight; 2943 2944 for (int32 i = 0; i < limit; i++) { 2945 // Add the location of the UTF8 characters 2946 2947 where.x += fFontWidth; 2948 ConvertToScreen(&where); 2949 2950 message.AddPoint("be:location_reply", where); 2951 message.AddFloat("be:height_reply", fFontHeight); 2952 } 2953 2954 fInline->Method()->SendMessage(&message); 2955 } 2956 2957 2958 2959 void 2960 TermView::_CancelInputMethod() 2961 { 2962 if (!fInline) 2963 return; 2964 2965 InlineInput *inlineInput = fInline; 2966 fInline = NULL; 2967 2968 if (inlineInput->IsActive() && Window()) { 2969 Invalidate(); 2970 2971 BMessage message(B_INPUT_METHOD_EVENT); 2972 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 2973 inlineInput->Method()->SendMessage(&message); 2974 } 2975 2976 delete inlineInput; 2977 } 2978 2979