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