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