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