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 rgb_color rgb_under = fTextForeColor; 1054 1055 // color attribute 1056 if (attr.IsForeSet()) 1057 rgb_fore = attr.ForegroundColor(fTextBuffer->Palette()); 1058 if (attr.IsBackSet()) 1059 rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); 1060 if (attr.IsUnderSet()) 1061 rgb_under = attr.UnderlineColor(fTextBuffer->Palette()); 1062 1063 // Selection check. 1064 if (cursor) { 1065 rgb_fore = fCursorForeColor; 1066 rgb_back = fCursorBackColor; 1067 } else if (highlight != NULL) { 1068 rgb_fore = highlight->Highlighter()->ForegroundColor(); 1069 rgb_back = highlight->Highlighter()->BackgroundColor(); 1070 } else { 1071 // Reverse attribute(If selected area, don't reverse color). 1072 if (attr.IsInverse()) { 1073 rgb_color rgb_tmp = rgb_fore; 1074 rgb_fore = rgb_back; 1075 rgb_back = rgb_tmp; 1076 } 1077 } 1078 1079 // Fill color at Background color and set low color. 1080 inView->SetHighColor(rgb_back); 1081 inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1)); 1082 inView->SetLowColor(rgb_back); 1083 1084 // underline attribute 1085 if (attr.IsUnder()) { 1086 inView->SetHighColor(rgb_under); 1087 switch (attr.UnderlineStyle()) { 1088 default: 1089 case SINGLE_UNDERLINE: 1090 inView->MovePenTo(x1, y1 + fFontAscent + 1); 1091 inView->StrokeLine(BPoint(x1 , y1 + fFontAscent + 1), 1092 BPoint(x2 , y1 + fFontAscent + 1)); 1093 break; 1094 case DOUBLE_UNDERLINE: 1095 inView->MovePenTo(x1, y1 + fFontAscent); 1096 inView->StrokeLine(BPoint(x1 , y1 + fFontAscent), 1097 BPoint(x2 , y1 + fFontAscent)); 1098 inView->MovePenTo(x1, y1 + fFontAscent + 2); 1099 inView->StrokeLine(BPoint(x1 , y1 + fFontAscent + 2), 1100 BPoint(x2 , y1 + fFontAscent + 2)); 1101 break; 1102 case CURLY_UNDERLINE: 1103 { 1104 inView->MovePenTo(x1, y1 + fFontAscent + 1); 1105 bool up = true; 1106 for (float x = x1; x < x2; x += 3) { 1107 inView->StrokeLine(BPoint(x, y1 + fFontAscent + (up ? 0 : 2)), 1108 BPoint(std::min(x + 2, x2), y1 + fFontAscent + (up ? 2 : 0))); 1109 up = !up; 1110 } 1111 break; 1112 } 1113 case DOTTED_UNDERLINE: 1114 { 1115 inView->MovePenTo(x1, y1 + fFontAscent + 1); 1116 for (float x = x1; x < x2; x += 4) { 1117 inView->StrokeLine(BPoint(x, y1 + fFontAscent + 1), 1118 BPoint(std::min(x, x2), y1 + fFontAscent + 1)); 1119 } 1120 break; 1121 } 1122 case DASHED_UNDERLINE: 1123 { 1124 inView->MovePenTo(x1, y1 + fFontAscent + 1); 1125 for (float x = x1; x < x2; x += 5) { 1126 inView->StrokeLine(BPoint(x, y1 + fFontAscent + 1), 1127 BPoint(std::min(x + 2, x2), y1 + fFontAscent + 1)); 1128 } 1129 break; 1130 } 1131 } 1132 } 1133 1134 1135 inView->SetHighColor(rgb_fore); 1136 1137 // Draw character. 1138 if (attr.IsBold()) { 1139 if (fEmulateBold) { 1140 inView->MovePenTo(x1 - 1, y1 + fFontAscent - 1); 1141 inView->DrawString((char *)buf); 1142 inView->SetDrawingMode(B_OP_BLEND); 1143 } else { 1144 rgb_color bright = rgb_fore; 1145 1146 bright.red = saturated_add<uint8>(bright.red, 64); 1147 bright.green = saturated_add<uint8>(bright.green, 64); 1148 bright.blue = saturated_add<uint8>(bright.blue, 64); 1149 1150 inView->SetHighColor(bright); 1151 } 1152 } 1153 1154 inView->MovePenTo(x1, y1 + fFontAscent); 1155 inView->DrawString((char *)buf); 1156 inView->SetDrawingMode(B_OP_COPY); 1157 1158 } 1159 1160 1161 /*! Caller must have locked fTextBuffer. 1162 */ 1163 void 1164 TermView::_DrawCursor() 1165 { 1166 BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0); 1167 rect.right = rect.left + fFontWidth - 1; 1168 rect.bottom = rect.top + fFontHeight - 1; 1169 int32 firstVisible = _LineAt(0); 1170 1171 UTF8Char character; 1172 Attributes attr; 1173 1174 bool cursorVisible = _IsCursorVisible(); 1175 1176 if (cursorVisible) { 1177 switch (fCursorStyle) { 1178 case UNDERLINE_CURSOR: 1179 rect.top = rect.bottom - 2; 1180 break; 1181 case IBEAM_CURSOR: 1182 rect.right = rect.left + 1; 1183 break; 1184 case BLOCK_CURSOR: 1185 default: 1186 break; 1187 } 1188 } 1189 1190 Highlight* highlight = _CheckHighlightRegion(TermPos(fCursor.x, fCursor.y)); 1191 if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x, 1192 character, attr) == A_CHAR 1193 && (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) { 1194 1195 int32 width = attr.IsWidth() ? FULL_WIDTH : HALF_WIDTH; 1196 char buffer[5]; 1197 int32 bytes = UTF8Char::ByteCount(character.bytes[0]); 1198 memcpy(buffer, character.bytes, bytes); 1199 buffer[bytes] = '\0'; 1200 1201 _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer, 1202 width, highlight, cursorVisible, this); 1203 } else { 1204 if (highlight != NULL) 1205 SetHighColor(highlight->Highlighter()->BackgroundColor()); 1206 else if (cursorVisible) 1207 SetHighColor(fCursorBackColor ); 1208 else { 1209 uint32 count = 0; 1210 rgb_color rgb_back = fTextBackColor; 1211 if (fTextBuffer->IsAlternateScreenActive()) 1212 // alternate screen uses cell attributes beyond the line ends 1213 fTextBuffer->GetCellAttributes( 1214 fCursor.y, fCursor.x, attr, count); 1215 else 1216 fVisibleTextBuffer->GetLineColor(fCursor.y - firstVisible, attr); 1217 1218 if (attr.IsBackSet()) 1219 rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); 1220 1221 SetHighColor(rgb_back); 1222 } 1223 1224 if (attr.IsWidth() && fCursorStyle != IBEAM_CURSOR) 1225 rect.right += fFontWidth; 1226 1227 FillRect(rect); 1228 } 1229 } 1230 1231 1232 bool 1233 TermView::_IsCursorVisible() const 1234 { 1235 return !fCursorHidden && fCursorState < kCursorVisibleIntervals; 1236 } 1237 1238 1239 void 1240 TermView::_BlinkCursor() 1241 { 1242 bool wasVisible = _IsCursorVisible(); 1243 1244 if (!wasVisible && fInline && fInline->IsActive()) 1245 return; 1246 1247 bigtime_t now = system_time(); 1248 if (Window()->IsActive() && now - fLastActivityTime >= kCursorBlinkInterval) 1249 fCursorState = (fCursorState + 1) % kCursorBlinkIntervals; 1250 else 1251 fCursorState = 0; 1252 1253 if (wasVisible != _IsCursorVisible()) 1254 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 1255 } 1256 1257 1258 void 1259 TermView::_ActivateCursor(bool invalidate) 1260 { 1261 fLastActivityTime = system_time(); 1262 if (invalidate && fCursorState != 0) 1263 _BlinkCursor(); 1264 else 1265 fCursorState = 0; 1266 } 1267 1268 1269 //! Update scroll bar range and knob size. 1270 void 1271 TermView::_UpdateScrollBarRange() 1272 { 1273 if (fScrollBar == NULL) 1274 return; 1275 1276 int32 historySize; 1277 { 1278 BAutolock _(fTextBuffer); 1279 historySize = fTextBuffer->HistorySize(); 1280 } 1281 1282 float viewHeight = fRows * fFontHeight; 1283 float historyHeight = (float)historySize * fFontHeight; 1284 1285 //debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n", 1286 //historySize, -historyHeight); 1287 1288 fScrollBar->SetRange(-historyHeight, 0); 1289 if (historySize > 0) 1290 fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight)); 1291 } 1292 1293 1294 //! Handler for SIGWINCH 1295 void 1296 TermView::_UpdateSIGWINCH() 1297 { 1298 if (fFrameResized) { 1299 fShell->UpdateWindowSize(fRows, fColumns); 1300 fFrameResized = false; 1301 } 1302 } 1303 1304 1305 void 1306 TermView::AttachedToWindow() 1307 { 1308 fMouseButtons = 0; 1309 1310 _UpdateModifiers(); 1311 1312 // update the terminal size because it may have changed while the TermView 1313 // was detached from the window. On such conditions FrameResized was not 1314 // called when the resize occured 1315 SetTermSize(Bounds(), true); 1316 MakeFocus(true); 1317 if (fScrollBar) { 1318 fScrollBar->SetSteps(fFontHeight, fFontHeight * fRows); 1319 _UpdateScrollBarRange(); 1320 } 1321 1322 BMessenger thisMessenger(this); 1323 1324 BMessage message(kUpdateSigWinch); 1325 fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger, 1326 &message, 500000); 1327 1328 { 1329 TextBufferSyncLocker _(this); 1330 fTextBuffer->SetListener(thisMessenger); 1331 _SynchronizeWithTextBuffer(0, -1); 1332 } 1333 1334 be_clipboard->StartWatching(thisMessenger); 1335 } 1336 1337 1338 void 1339 TermView::DetachedFromWindow() 1340 { 1341 be_clipboard->StopWatching(BMessenger(this)); 1342 1343 _NextState(fDefaultState); 1344 1345 delete fWinchRunner; 1346 fWinchRunner = NULL; 1347 1348 delete fCursorBlinkRunner; 1349 fCursorBlinkRunner = NULL; 1350 1351 delete fResizeRunner; 1352 fResizeRunner = NULL; 1353 1354 { 1355 BAutolock _(fTextBuffer); 1356 fTextBuffer->UnsetListener(); 1357 } 1358 } 1359 1360 1361 void 1362 TermView::Draw(BRect updateRect) 1363 { 1364 int32 x1 = (int32)(updateRect.left / fFontWidth); 1365 int32 x2 = std::min((int)(updateRect.right / fFontWidth), fColumns - 1); 1366 1367 int32 firstVisible = _LineAt(0); 1368 int32 y1 = _LineAt(updateRect.top); 1369 int32 y2 = std::min(_LineAt(updateRect.bottom), (int32)fRows - 1); 1370 1371 // clear the area to the right of the line ends 1372 if (y1 <= y2) { 1373 float clearLeft = fColumns * fFontWidth; 1374 if (clearLeft <= updateRect.right) { 1375 BRect rect(clearLeft, updateRect.top, updateRect.right, 1376 updateRect.bottom); 1377 SetHighColor(fTextBackColor); 1378 FillRect(rect); 1379 } 1380 } 1381 1382 // clear the area below the last line 1383 if (y2 == fRows - 1) { 1384 float clearTop = _LineOffset(fRows); 1385 if (clearTop <= updateRect.bottom) { 1386 BRect rect(updateRect.left, clearTop, updateRect.right, 1387 updateRect.bottom); 1388 SetHighColor(fTextBackColor); 1389 FillRect(rect); 1390 } 1391 } 1392 1393 // draw the affected line parts 1394 if (x1 <= x2) { 1395 Attributes attr; 1396 1397 for (int32 j = y1; j <= y2; j++) { 1398 int32 k = x1; 1399 char buf[fColumns * 4 + 1]; 1400 1401 if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k)) 1402 k--; 1403 1404 if (k < 0) 1405 k = 0; 1406 1407 for (int32 i = k; i <= x2;) { 1408 int32 lastColumn = x2; 1409 Highlight* highlight = _CheckHighlightRegion(j, i, lastColumn); 1410 // This will clip lastColumn to the selection start or end 1411 // to ensure the selection is not drawn at the same time as 1412 // something else 1413 int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i, 1414 lastColumn, buf, attr); 1415 1416 // debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n", 1417 // j - firstVisible, i, lastColumn, count, (int)count, buf, highlight); 1418 1419 if (count == 0) { 1420 // No chars to draw : we just fill the rectangle with the 1421 // back color of the last char at the left 1422 int nextColumn = lastColumn + 1; 1423 BRect rect(fFontWidth * i, _LineOffset(j), 1424 fFontWidth * nextColumn - 1, 0); 1425 rect.bottom = rect.top + fFontHeight - 1; 1426 1427 rgb_color rgb_back = highlight != NULL 1428 ? highlight->Highlighter()->BackgroundColor() 1429 : fTextBackColor; 1430 1431 if (fTextBuffer->IsAlternateScreenActive()) { 1432 // alternate screen uses cell attributes 1433 // beyond the line ends 1434 uint32 count = 0; 1435 fTextBuffer->GetCellAttributes(j, i, attr, count); 1436 rect.right = rect.left + fFontWidth * count - 1; 1437 nextColumn = i + count; 1438 } else 1439 fVisibleTextBuffer->GetLineColor(j - firstVisible, attr); 1440 1441 if (attr.IsBackSet()) 1442 rgb_back = attr.BackgroundColor(fTextBuffer->Palette()); 1443 1444 SetHighColor(rgb_back); 1445 rgb_back = HighColor(); 1446 FillRect(rect); 1447 1448 // Go on to the next block 1449 i = nextColumn; 1450 continue; 1451 } 1452 1453 // Note: full-width characters GetString()-ed always 1454 // with count 1, so this hardcoding is safe. From the other 1455 // side - drawing the whole string with one call render the 1456 // characters not aligned to cells grid - that looks much more 1457 // inaccurate for full-width strings than for half-width ones. 1458 if (attr.IsWidth()) 1459 count = FULL_WIDTH; 1460 1461 _DrawLinePart(fFontWidth * i, (int32)_LineOffset(j), 1462 attr, buf, count, highlight, false, this); 1463 i += count; 1464 } 1465 } 1466 } 1467 1468 if (fInline && fInline->IsActive()) 1469 _DrawInlineMethodString(); 1470 1471 if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2)) 1472 _DrawCursor(); 1473 } 1474 1475 1476 void 1477 TermView::_DoPrint(BRect updateRect) 1478 { 1479 #if 0 1480 uint32 attr; 1481 uchar buf[1024]; 1482 1483 const int numLines = (int)((updateRect.Height()) / fFontHeight); 1484 1485 int y1 = (int)(updateRect.top) / fFontHeight; 1486 y1 = y1 -(fScrBufSize - numLines * 2); 1487 if (y1 < 0) 1488 y1 = 0; 1489 1490 const int y2 = y1 + numLines -1; 1491 1492 const int x1 = (int)(updateRect.left) / fFontWidth; 1493 const int x2 = (int)(updateRect.right) / fFontWidth; 1494 1495 for (int j = y1; j <= y2; j++) { 1496 // If(x1, y1) Buffer is in string full width character, 1497 // alignment start position. 1498 1499 int k = x1; 1500 if (fTextBuffer->IsFullWidthChar(j, k)) 1501 k--; 1502 1503 if (k < 0) 1504 k = 0; 1505 1506 for (int i = k; i <= x2;) { 1507 int count = fTextBuffer->GetString(j, i, x2, buf, &attr); 1508 if (count < 0) { 1509 i += abs(count); 1510 continue; 1511 } 1512 1513 _DrawLinePart(fFontWidth * i, fFontHeight * j, 1514 attr, buf, count, false, false, this); 1515 i += count; 1516 } 1517 } 1518 #endif // 0 1519 } 1520 1521 1522 void 1523 TermView::WindowActivated(bool active) 1524 { 1525 BView::WindowActivated(active); 1526 if (active && IsFocus()) { 1527 if (!fActive) 1528 _Activate(); 1529 } else { 1530 if (fActive) 1531 _Deactivate(); 1532 } 1533 1534 _UpdateModifiers(); 1535 1536 fActiveState->WindowActivated(active); 1537 } 1538 1539 1540 void 1541 TermView::MakeFocus(bool focusState) 1542 { 1543 BView::MakeFocus(focusState); 1544 1545 if (focusState && Window() && Window()->IsActive()) { 1546 if (!fActive) 1547 _Activate(); 1548 } else { 1549 if (fActive) 1550 _Deactivate(); 1551 } 1552 } 1553 1554 1555 void 1556 TermView::KeyDown(const char *bytes, int32 numBytes) 1557 { 1558 _UpdateModifiers(); 1559 1560 fActiveState->KeyDown(bytes, numBytes); 1561 } 1562 1563 1564 void 1565 TermView::FrameResized(float width, float height) 1566 { 1567 //debug_printf("TermView::FrameResized(%f, %f)\n", width, height); 1568 int32 columns = (int32)((width + 1) / fFontWidth); 1569 int32 rows = (int32)((height + 1) / fFontHeight); 1570 1571 if (columns == fColumns && rows == fRows) 1572 return; 1573 1574 bool hasResizeView = fResizeRunner != NULL; 1575 if (!hasResizeView) { 1576 // show the current size in a view 1577 fResizeView = new BStringView(BRect(100, 100, 300, 140), "size", ""); 1578 fResizeView->SetAlignment(B_ALIGN_CENTER); 1579 fResizeView->SetFont(be_bold_font); 1580 fResizeView->SetViewColor(fTextBackColor); 1581 fResizeView->SetLowColor(fTextBackColor); 1582 fResizeView->SetHighColor(fTextForeColor); 1583 1584 BMessage message(MSG_REMOVE_RESIZE_VIEW_IF_NEEDED); 1585 fResizeRunner = new(std::nothrow) BMessageRunner(BMessenger(this), 1586 &message, 25000LL); 1587 } 1588 1589 BString text; 1590 text << columns << " x " << rows; 1591 fResizeView->SetText(text.String()); 1592 fResizeView->GetPreferredSize(&width, &height); 1593 fResizeView->ResizeTo(width * 1.5, height * 1.5); 1594 fResizeView->MoveTo((Bounds().Width() - fResizeView->Bounds().Width()) / 2, 1595 (Bounds().Height()- fResizeView->Bounds().Height()) / 2); 1596 if (!hasResizeView && fResizeViewDisableCount < 1) 1597 AddChild(fResizeView); 1598 1599 if (fResizeViewDisableCount > 0) 1600 fResizeViewDisableCount--; 1601 1602 SetTermSize(rows, columns, true); 1603 } 1604 1605 1606 void 1607 TermView::MessageReceived(BMessage *message) 1608 { 1609 if (fActiveState->MessageReceived(message)) 1610 return; 1611 1612 entry_ref ref; 1613 const char *ctrl_l = "\x0c"; 1614 1615 // first check for any dropped message 1616 if (message->WasDropped() && (message->what == B_SIMPLE_DATA 1617 || message->what == B_MIME_DATA)) { 1618 char *text; 1619 ssize_t numBytes; 1620 //rgb_color *color; 1621 1622 int32 i = 0; 1623 1624 if (message->FindRef("refs", i++, &ref) == B_OK) { 1625 // first check if secondary mouse button is pressed 1626 int32 buttons = 0; 1627 message->FindInt32("buttons", &buttons); 1628 1629 if (buttons == B_SECONDARY_MOUSE_BUTTON) { 1630 // start popup menu 1631 _SecondaryMouseButtonDropped(message); 1632 return; 1633 } 1634 1635 _DoFileDrop(ref); 1636 1637 while (message->FindRef("refs", i++, &ref) == B_OK) { 1638 _WritePTY(" ", 1); 1639 _DoFileDrop(ref); 1640 } 1641 return; 1642 #if 0 1643 } else if (message->FindData("RGBColor", B_RGB_COLOR_TYPE, 1644 (const void **)&color, &numBytes) == B_OK 1645 && numBytes == sizeof(color)) { 1646 // TODO: handle color drop 1647 // maybe only on replicants ? 1648 return; 1649 #endif 1650 } else if (message->FindData("text/plain", B_MIME_TYPE, 1651 (const void **)&text, &numBytes) == B_OK) { 1652 _WritePTY(text, numBytes); 1653 return; 1654 } 1655 } 1656 1657 switch (message->what) { 1658 case B_SIMPLE_DATA: 1659 case B_REFS_RECEIVED: 1660 { 1661 // handle refs if they weren't dropped 1662 int32 i = 0; 1663 if (message->FindRef("refs", i++, &ref) == B_OK) { 1664 _DoFileDrop(ref); 1665 1666 while (message->FindRef("refs", i++, &ref) == B_OK) { 1667 _WritePTY(" ", 1); 1668 _DoFileDrop(ref); 1669 } 1670 } else 1671 BView::MessageReceived(message); 1672 break; 1673 } 1674 1675 case B_COPY: 1676 Copy(be_clipboard); 1677 break; 1678 1679 case B_PASTE: 1680 { 1681 int32 code; 1682 if (message->FindInt32("index", &code) == B_OK) 1683 Paste(be_clipboard); 1684 break; 1685 } 1686 1687 case B_CLIPBOARD_CHANGED: 1688 // This message originates from the system clipboard. Overwrite 1689 // the contents of the mouse clipboard with the ones from the 1690 // system clipboard, in case it contains text data. 1691 if (be_clipboard->Lock()) { 1692 if (fMouseClipboard->Lock()) { 1693 BMessage* clipMsgA = be_clipboard->Data(); 1694 const char* text; 1695 ssize_t numBytes; 1696 if (clipMsgA->FindData("text/plain", B_MIME_TYPE, 1697 (const void**)&text, &numBytes) == B_OK ) { 1698 fMouseClipboard->Clear(); 1699 BMessage* clipMsgB = fMouseClipboard->Data(); 1700 clipMsgB->AddData("text/plain", B_MIME_TYPE, 1701 text, numBytes); 1702 fMouseClipboard->Commit(); 1703 } 1704 fMouseClipboard->Unlock(); 1705 } 1706 be_clipboard->Unlock(); 1707 } 1708 break; 1709 1710 case B_SELECT_ALL: 1711 SelectAll(); 1712 break; 1713 1714 case B_SET_PROPERTY: 1715 { 1716 int32 i; 1717 int32 encodingID; 1718 BMessage specifier; 1719 if (message->GetCurrentSpecifier(&i, &specifier) == B_OK 1720 && strcmp("encoding", 1721 specifier.FindString("property", i)) == 0) { 1722 message->FindInt32 ("data", &encodingID); 1723 SetEncoding(encodingID); 1724 message->SendReply(B_REPLY); 1725 } else { 1726 BView::MessageReceived(message); 1727 } 1728 break; 1729 } 1730 1731 case B_GET_PROPERTY: 1732 { 1733 int32 i; 1734 BMessage specifier; 1735 if (message->GetCurrentSpecifier(&i, &specifier) == B_OK) { 1736 if (strcmp("encoding", 1737 specifier.FindString("property", i)) == 0) { 1738 BMessage reply(B_REPLY); 1739 reply.AddInt32("result", Encoding()); 1740 message->SendReply(&reply); 1741 } else if (strcmp("tty", 1742 specifier.FindString("property", i)) == 0) { 1743 BMessage reply(B_REPLY); 1744 reply.AddString("result", TerminalName()); 1745 message->SendReply(&reply); 1746 } else 1747 BView::MessageReceived(message); 1748 } else 1749 BView::MessageReceived(message); 1750 break; 1751 } 1752 1753 case B_MODIFIERS_CHANGED: 1754 { 1755 _UpdateModifiers(); 1756 break; 1757 } 1758 1759 case B_INPUT_METHOD_EVENT: 1760 { 1761 int32 opcode; 1762 if (message->FindInt32("be:opcode", &opcode) == B_OK) { 1763 switch (opcode) { 1764 case B_INPUT_METHOD_STARTED: 1765 { 1766 BMessenger messenger; 1767 if (message->FindMessenger("be:reply_to", 1768 &messenger) == B_OK) { 1769 fInline = new (std::nothrow) 1770 InlineInput(messenger); 1771 } 1772 break; 1773 } 1774 1775 case B_INPUT_METHOD_STOPPED: 1776 delete fInline; 1777 fInline = NULL; 1778 break; 1779 1780 case B_INPUT_METHOD_CHANGED: 1781 if (fInline != NULL) 1782 _HandleInputMethodChanged(message); 1783 break; 1784 1785 case B_INPUT_METHOD_LOCATION_REQUEST: 1786 if (fInline != NULL) 1787 _HandleInputMethodLocationRequest(); 1788 break; 1789 1790 default: 1791 break; 1792 } 1793 } 1794 break; 1795 } 1796 1797 case B_MOUSE_WHEEL_CHANGED: 1798 { 1799 // overridden to allow scrolling emulation in alternative screen 1800 // mode 1801 BAutolock locker(fTextBuffer); 1802 float deltaY = 0; 1803 if (fTextBuffer->IsAlternateScreenActive() 1804 && message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK 1805 && deltaY != 0) { 1806 // We are in alternative screen mode and have a vertical delta 1807 // we can work with -- emulate scrolling via terminal escape 1808 // sequences. 1809 locker.Unlock(); 1810 1811 // scroll pagewise, if one of Option, Command, or Control is 1812 // pressed 1813 int32 steps; 1814 const char* stepString; 1815 if ((modifiers() & B_SHIFT_KEY) != 0) { 1816 // pagewise 1817 stepString = deltaY > 0 1818 ? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE; 1819 steps = abs((int)deltaY); 1820 } else { 1821 // three lines per step 1822 stepString = deltaY > 0 1823 ? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE; 1824 steps = 3 * abs((int)deltaY); 1825 } 1826 1827 // We want to do only a single write(), so compose a string 1828 // repeating the sequence as often as required by the delta. 1829 BString toWrite; 1830 for (int32 i = 0; i <steps; i++) 1831 toWrite << stepString; 1832 1833 _WritePTY(toWrite.String(), toWrite.Length()); 1834 } else { 1835 // let the BView's implementation handle the standard scrolling 1836 locker.Unlock(); 1837 BView::MessageReceived(message); 1838 } 1839 1840 break; 1841 } 1842 1843 case MENU_CLEAR_ALL: 1844 Clear(); 1845 fShell->Write(ctrl_l, 1); 1846 break; 1847 case kBlinkCursor: 1848 _BlinkCursor(); 1849 break; 1850 case kUpdateSigWinch: 1851 _UpdateSIGWINCH(); 1852 break; 1853 case kSecondaryMouseDropAction: 1854 _DoSecondaryMouseDropAction(message); 1855 break; 1856 case MSG_TERMINAL_BUFFER_CHANGED: 1857 { 1858 TextBufferSyncLocker _(this); 1859 _SynchronizeWithTextBuffer(0, -1); 1860 break; 1861 } 1862 case MSG_SET_TERMINAL_TITLE: 1863 { 1864 const char* title; 1865 if (message->FindString("title", &title) == B_OK) { 1866 if (fListener != NULL) 1867 fListener->SetTermViewTitle(this, title); 1868 } 1869 break; 1870 } 1871 case MSG_SET_TERMINAL_COLORS: 1872 { 1873 int32 count = 0; 1874 if (message->FindInt32("count", &count) != B_OK) 1875 break; 1876 bool dynamic = false; 1877 if (message->FindBool("dynamic", &dynamic) != B_OK) 1878 break; 1879 for (int i = 0; i < count; i++) { 1880 uint8 index = 0; 1881 if (message->FindUInt8("index", i, &index) != B_OK) 1882 break; 1883 1884 ssize_t bytes = 0; 1885 rgb_color* color = 0; 1886 if (message->FindData("color", B_RGB_COLOR_TYPE, 1887 i, (const void**)&color, &bytes) != B_OK) 1888 break; 1889 SetTermColor(index, *color, dynamic); 1890 } 1891 break; 1892 } 1893 case MSG_RESET_TERMINAL_COLORS: 1894 { 1895 int32 count = 0; 1896 if (message->FindInt32("count", &count) != B_OK) 1897 break; 1898 bool dynamic = false; 1899 if (message->FindBool("dynamic", &dynamic) != B_OK) 1900 break; 1901 for (int i = 0; i < count; i++) { 1902 uint8 index = 0; 1903 if (message->FindUInt8("index", i, &index) != B_OK) 1904 break; 1905 1906 SetTermColor(index, 1907 TermApp::DefaultPalette()[index], dynamic); 1908 } 1909 break; 1910 } 1911 case MSG_GET_TERMINAL_COLOR: 1912 { 1913 uint8 index = 0; 1914 if (message->FindUInt8("index", &index) != B_OK) 1915 break; 1916 rgb_color color; 1917 status_t status = GetTermColor(index, &color); 1918 if (status == B_OK) { 1919 BString reply; 1920 reply.SetToFormat("\033]%u;rgb:%02x/%02x/%02x\033\\", 1921 index, color.red, color.green, color.blue); 1922 fShell->Write(reply.String(), reply.Length()); 1923 } 1924 break; 1925 } 1926 case MSG_SET_CURSOR_STYLE: 1927 { 1928 int32 style = BLOCK_CURSOR; 1929 if (message->FindInt32("style", &style) == B_OK) 1930 fCursorStyle = style; 1931 1932 bool blinking = fCursorBlinking; 1933 if (message->FindBool("blinking", &blinking) == B_OK) 1934 SwitchCursorBlinking(blinking); 1935 1936 bool hidden = fCursorHidden; 1937 if (message->FindBool("hidden", &hidden) == B_OK) 1938 fCursorHidden = hidden; 1939 break; 1940 } 1941 case MSG_ENABLE_META_KEY: 1942 { 1943 bool enable; 1944 if (message->FindBool("enableInterpretMetaKey", &enable) == B_OK) 1945 fInterpretMetaKey = enable; 1946 1947 if (message->FindBool("enableMetaKeySendsEscape", &enable) == B_OK) 1948 fMetaKeySendsEscape = enable; 1949 break; 1950 } 1951 case MSG_ENABLE_BRACKETED_PASTE: 1952 { 1953 bool enable; 1954 if (message->FindBool("enableBracketedPaste", &enable) == B_OK) 1955 fUseBracketedPaste = enable; 1956 break; 1957 } 1958 case MSG_REPORT_MOUSE_EVENT: 1959 { 1960 bool value; 1961 if (message->FindBool("reportX10MouseEvent", &value) == B_OK) 1962 fReportX10MouseEvent = value; 1963 1964 // setting one of the three disables the other two 1965 if (message->FindBool("reportNormalMouseEvent", &value) == B_OK) { 1966 fReportNormalMouseEvent = value; 1967 fReportButtonMouseEvent = false; 1968 fReportAnyMouseEvent = false; 1969 } 1970 if (message->FindBool("reportButtonMouseEvent", &value) == B_OK) { 1971 fReportButtonMouseEvent = value; 1972 fReportNormalMouseEvent = false; 1973 fReportAnyMouseEvent = false; 1974 } 1975 if (message->FindBool("reportAnyMouseEvent", &value) == B_OK) { 1976 fReportAnyMouseEvent = value; 1977 fReportNormalMouseEvent = false; 1978 fReportButtonMouseEvent = false; 1979 } 1980 1981 if (message->FindBool( 1982 "enableExtendedMouseCoordinates", &value) == B_OK) 1983 fEnableExtendedMouseCoordinates = value; 1984 break; 1985 } 1986 case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED: 1987 { 1988 BPoint point; 1989 uint32 buttons; 1990 GetMouse(&point, &buttons, false); 1991 if (buttons != 0) 1992 break; 1993 1994 if (fResizeView != NULL) { 1995 fResizeView->RemoveSelf(); 1996 delete fResizeView; 1997 fResizeView = NULL; 1998 } 1999 delete fResizeRunner; 2000 fResizeRunner = NULL; 2001 break; 2002 } 2003 2004 case MSG_QUIT_TERMNAL: 2005 { 2006 int32 reason; 2007 if (message->FindInt32("reason", &reason) != B_OK) 2008 reason = 0; 2009 if (fListener != NULL) 2010 fListener->NotifyTermViewQuit(this, reason); 2011 break; 2012 } 2013 default: 2014 BView::MessageReceived(message); 2015 break; 2016 } 2017 } 2018 2019 2020 status_t 2021 TermView::GetSupportedSuites(BMessage* message) 2022 { 2023 BPropertyInfo propInfo(sPropList); 2024 message->AddString("suites", "suite/vnd.naan-termview"); 2025 message->AddFlat("messages", &propInfo); 2026 return BView::GetSupportedSuites(message); 2027 } 2028 2029 2030 void 2031 TermView::ScrollTo(BPoint where) 2032 { 2033 //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y); 2034 float diff = where.y - fScrollOffset; 2035 if (diff == 0) 2036 return; 2037 2038 float bottom = Bounds().bottom; 2039 int32 oldFirstLine = _LineAt(0); 2040 int32 oldLastLine = _LineAt(bottom); 2041 int32 newFirstLine = _LineAt(diff); 2042 int32 newLastLine = _LineAt(bottom + diff); 2043 2044 fScrollOffset = where.y; 2045 2046 // invalidate the current cursor position before scrolling 2047 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2048 2049 // scroll contents 2050 BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop())); 2051 BRect sourceRect(destRect.OffsetByCopy(0, diff)); 2052 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", 2053 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, 2054 //destRect.left, destRect.top, destRect.right, destRect.bottom); 2055 CopyBits(sourceRect, destRect); 2056 2057 // sync visible text buffer with text buffer 2058 if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) { 2059 if (newFirstLine != oldFirstLine) 2060 { 2061 //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine); 2062 fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine); 2063 } 2064 TextBufferSyncLocker _(this); 2065 if (diff < 0) 2066 _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1); 2067 else 2068 _SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine); 2069 } 2070 } 2071 2072 2073 void 2074 TermView::TargetedByScrollView(BScrollView *scrollView) 2075 { 2076 BView::TargetedByScrollView(scrollView); 2077 2078 SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL); 2079 } 2080 2081 2082 BHandler* 2083 TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 2084 int32 what, const char* property) 2085 { 2086 BHandler* target = this; 2087 BPropertyInfo propInfo(sPropList); 2088 if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) { 2089 target = BView::ResolveSpecifier(message, index, specifier, what, 2090 property); 2091 } 2092 2093 return target; 2094 } 2095 2096 2097 void 2098 TermView::_SecondaryMouseButtonDropped(BMessage* message) 2099 { 2100 // Launch menu to choose what is to do with the message data 2101 BPoint point; 2102 if (message->FindPoint("_drop_point_", &point) != B_OK) 2103 return; 2104 2105 BMessage* insertMessage = new BMessage(*message); 2106 insertMessage->what = kSecondaryMouseDropAction; 2107 insertMessage->AddInt8("action", kInsert); 2108 2109 BMessage* cdMessage = new BMessage(*message); 2110 cdMessage->what = kSecondaryMouseDropAction; 2111 cdMessage->AddInt8("action", kChangeDirectory); 2112 2113 BMessage* lnMessage = new BMessage(*message); 2114 lnMessage->what = kSecondaryMouseDropAction; 2115 lnMessage->AddInt8("action", kLinkFiles); 2116 2117 BMessage* mvMessage = new BMessage(*message); 2118 mvMessage->what = kSecondaryMouseDropAction; 2119 mvMessage->AddInt8("action", kMoveFiles); 2120 2121 BMessage* cpMessage = new BMessage(*message); 2122 cpMessage->what = kSecondaryMouseDropAction; 2123 cpMessage->AddInt8("action", kCopyFiles); 2124 2125 BMenuItem* insertItem = new BMenuItem( 2126 B_TRANSLATE("Insert path"), insertMessage); 2127 BMenuItem* cdItem = new BMenuItem( 2128 B_TRANSLATE("Change directory"), cdMessage); 2129 BMenuItem* lnItem = new BMenuItem( 2130 B_TRANSLATE("Create link here"), lnMessage); 2131 BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage); 2132 BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage); 2133 BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL); 2134 2135 // if the refs point to different directorys disable the cd menu item 2136 bool differentDirs = false; 2137 BDirectory firstDir; 2138 entry_ref ref; 2139 int i = 0; 2140 while (message->FindRef("refs", i++, &ref) == B_OK) { 2141 BNode node(&ref); 2142 BEntry entry(&ref); 2143 BDirectory dir; 2144 if (node.IsDirectory()) 2145 dir.SetTo(&ref); 2146 else 2147 entry.GetParent(&dir); 2148 2149 if (i == 1) { 2150 node_ref nodeRef; 2151 dir.GetNodeRef(&nodeRef); 2152 firstDir.SetTo(&nodeRef); 2153 } else if (firstDir != dir) { 2154 differentDirs = true; 2155 break; 2156 } 2157 } 2158 if (differentDirs) 2159 cdItem->SetEnabled(false); 2160 2161 BPopUpMenu *menu = new BPopUpMenu( 2162 "Secondary mouse button drop menu"); 2163 menu->SetAsyncAutoDestruct(true); 2164 menu->AddItem(insertItem); 2165 menu->AddSeparatorItem(); 2166 menu->AddItem(cdItem); 2167 menu->AddItem(lnItem); 2168 menu->AddItem(mvItem); 2169 menu->AddItem(cpItem); 2170 menu->AddSeparatorItem(); 2171 menu->AddItem(chItem); 2172 menu->SetTargetForItems(this); 2173 menu->Go(point, true, true, true); 2174 } 2175 2176 2177 void 2178 TermView::_DoSecondaryMouseDropAction(BMessage* message) 2179 { 2180 int8 action = -1; 2181 message->FindInt8("action", &action); 2182 2183 BString outString = ""; 2184 BString itemString = ""; 2185 2186 switch (action) { 2187 case kInsert: 2188 break; 2189 case kChangeDirectory: 2190 outString = "cd "; 2191 break; 2192 case kLinkFiles: 2193 outString = "ln -s "; 2194 break; 2195 case kMoveFiles: 2196 outString = "mv "; 2197 break; 2198 case kCopyFiles: 2199 outString = "cp "; 2200 break; 2201 2202 default: 2203 return; 2204 } 2205 2206 bool listContainsDirectory = false; 2207 entry_ref ref; 2208 int32 i = 0; 2209 while (message->FindRef("refs", i++, &ref) == B_OK) { 2210 BEntry ent(&ref); 2211 BNode node(&ref); 2212 BPath path(&ent); 2213 BString string(path.Path()); 2214 2215 if (node.IsDirectory()) 2216 listContainsDirectory = true; 2217 2218 if (i > 1) 2219 itemString += " "; 2220 2221 if (action == kChangeDirectory) { 2222 if (!node.IsDirectory()) { 2223 int32 slash = string.FindLast("/"); 2224 string.Truncate(slash); 2225 } 2226 string.CharacterEscape(kShellEscapeCharacters, '\\'); 2227 itemString += string; 2228 break; 2229 } 2230 string.CharacterEscape(kShellEscapeCharacters, '\\'); 2231 itemString += string; 2232 } 2233 2234 if (listContainsDirectory && action == kCopyFiles) 2235 outString += "-R "; 2236 2237 outString += itemString; 2238 2239 if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles) 2240 outString += " ."; 2241 2242 if (action != kInsert) 2243 outString += "\n"; 2244 2245 _WritePTY(outString.String(), outString.Length()); 2246 } 2247 2248 2249 //! Gets dropped file full path and display it at cursor position. 2250 void 2251 TermView::_DoFileDrop(entry_ref& ref) 2252 { 2253 BEntry ent(&ref); 2254 BPath path(&ent); 2255 BString string(path.Path()); 2256 2257 string.CharacterEscape(kShellEscapeCharacters, '\\'); 2258 _WritePTY(string.String(), string.Length()); 2259 } 2260 2261 2262 /*! Text buffer must already be locked. 2263 */ 2264 void 2265 TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop, 2266 int32 visibleDirtyBottom) 2267 { 2268 TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo(); 2269 int32 linesScrolled = info.linesScrolled; 2270 2271 //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " 2272 //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom, 2273 //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom); 2274 2275 bigtime_t now = system_time(); 2276 bigtime_t timeElapsed = now - fLastSyncTime; 2277 if (timeElapsed > 2 * kSyncUpdateGranularity) { 2278 // last sync was ages ago 2279 fLastSyncTime = now; 2280 fScrolledSinceLastSync = linesScrolled; 2281 } 2282 2283 if (fSyncRunner == NULL) { 2284 // We consider clocked syncing when more than a full screen height has 2285 // been scrolled in less than a sync update period. Once we're 2286 // actively considering it, the same condition will convince us to 2287 // actually do it. 2288 if (fScrolledSinceLastSync + linesScrolled <= fRows) { 2289 // Condition doesn't hold yet. Reset if time is up, or otherwise 2290 // keep counting. 2291 if (timeElapsed > kSyncUpdateGranularity) { 2292 fConsiderClockedSync = false; 2293 fLastSyncTime = now; 2294 fScrolledSinceLastSync = linesScrolled; 2295 } else 2296 fScrolledSinceLastSync += linesScrolled; 2297 } else if (fConsiderClockedSync) { 2298 // We are convinced -- create the sync runner. 2299 fLastSyncTime = now; 2300 fScrolledSinceLastSync = 0; 2301 2302 BMessage message(MSG_TERMINAL_BUFFER_CHANGED); 2303 fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this), 2304 &message, kSyncUpdateGranularity); 2305 if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK) 2306 return; 2307 2308 delete fSyncRunner; 2309 fSyncRunner = NULL; 2310 } else { 2311 // Looks interesting so far. Reset the counts and consider clocked 2312 // syncing. 2313 fConsiderClockedSync = true; 2314 fLastSyncTime = now; 2315 fScrolledSinceLastSync = 0; 2316 } 2317 } else if (timeElapsed < kSyncUpdateGranularity) { 2318 // sync time not passed yet -- keep counting 2319 fScrolledSinceLastSync += linesScrolled; 2320 return; 2321 } 2322 2323 if (fScrolledSinceLastSync + linesScrolled <= fRows) { 2324 // time's up, but not enough happened 2325 delete fSyncRunner; 2326 fSyncRunner = NULL; 2327 fLastSyncTime = now; 2328 fScrolledSinceLastSync = linesScrolled; 2329 } else { 2330 // Things are still rolling, but the sync time's up. 2331 fLastSyncTime = now; 2332 fScrolledSinceLastSync = 0; 2333 } 2334 2335 fVisibleTextBufferChanged = true; 2336 2337 // Simple case first -- complete invalidation. 2338 if (info.invalidateAll) { 2339 Invalidate(); 2340 _UpdateScrollBarRange(); 2341 _Deselect(); 2342 2343 fCursor = fTextBuffer->Cursor(); 2344 _ActivateCursor(false); 2345 2346 int32 offset = _LineAt(0); 2347 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, 2348 offset + fTextBuffer->Height() + 2); 2349 2350 info.Reset(); 2351 return; 2352 } 2353 2354 BRect bounds = Bounds(); 2355 int32 firstVisible = _LineAt(0); 2356 int32 lastVisible = _LineAt(bounds.bottom); 2357 int32 historySize = fTextBuffer->HistorySize(); 2358 2359 bool doScroll = false; 2360 if (linesScrolled > 0) { 2361 _UpdateScrollBarRange(); 2362 2363 visibleDirtyTop -= linesScrolled; 2364 visibleDirtyBottom -= linesScrolled; 2365 2366 if (firstVisible < 0) { 2367 firstVisible -= linesScrolled; 2368 lastVisible -= linesScrolled; 2369 2370 float scrollOffset; 2371 if (firstVisible < -historySize) { 2372 firstVisible = -historySize; 2373 doScroll = true; 2374 scrollOffset = -historySize * fFontHeight; 2375 // We need to invalidate the lower linesScrolled lines of the 2376 // visible text buffer, since those will be scrolled up and 2377 // need to be replaced. We just use visibleDirty{Top,Bottom} 2378 // for that purpose. Unless invoked from ScrollTo() (i.e. 2379 // user-initiated scrolling) those are unused. In the unlikely 2380 // case that the user is scrolling at the same time we may 2381 // invalidate too many lines, since we have to extend the given 2382 // region. 2383 // Note that in the firstVisible == 0 case the new lines are 2384 // already in the dirty region, so they will be updated anyway. 2385 if (visibleDirtyTop <= visibleDirtyBottom) { 2386 if (lastVisible < visibleDirtyTop) 2387 visibleDirtyTop = lastVisible; 2388 if (visibleDirtyBottom < lastVisible + linesScrolled) 2389 visibleDirtyBottom = lastVisible + linesScrolled; 2390 } else { 2391 visibleDirtyTop = lastVisible + 1; 2392 visibleDirtyBottom = lastVisible + linesScrolled; 2393 } 2394 } else 2395 scrollOffset = fScrollOffset - linesScrolled * fFontHeight; 2396 2397 _ScrollTo(scrollOffset, false); 2398 } else 2399 doScroll = true; 2400 2401 if (doScroll && lastVisible >= firstVisible 2402 && !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop 2403 && lastVisible <= info.dirtyBottom)) { 2404 // scroll manually 2405 float scrollBy = linesScrolled * fFontHeight; 2406 BRect destRect(Frame().OffsetToCopy(B_ORIGIN)); 2407 BRect sourceRect(destRect.OffsetByCopy(0, scrollBy)); 2408 2409 // invalidate the current cursor position before scrolling 2410 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2411 2412 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", 2413 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, 2414 //destRect.left, destRect.top, destRect.right, destRect.bottom); 2415 CopyBits(sourceRect, destRect); 2416 2417 fVisibleTextBuffer->ScrollBy(linesScrolled); 2418 } 2419 2420 // move highlights 2421 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { 2422 if (highlight->IsEmpty()) 2423 continue; 2424 2425 highlight->ScrollRange(linesScrolled); 2426 if (highlight == &fSelection) { 2427 fInitialSelectionStart.y -= linesScrolled; 2428 fInitialSelectionEnd.y -= linesScrolled; 2429 } 2430 2431 if (highlight->Start().y < -historySize) { 2432 if (highlight == &fSelection) 2433 _Deselect(); 2434 else 2435 _ClearHighlight(highlight); 2436 } 2437 } 2438 } 2439 2440 // invalidate dirty region 2441 if (info.IsDirtyRegionValid()) { 2442 _InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1, 2443 info.dirtyBottom); 2444 2445 // clear the selection, if affected 2446 if (!fSelection.IsEmpty()) { 2447 // TODO: We're clearing the selection more often than necessary -- 2448 // to avoid that, we'd also need to track the x coordinates of the 2449 // dirty range. 2450 int32 selectionBottom = fSelection.End().x > 0 2451 ? fSelection.End().y : fSelection.End().y - 1; 2452 if (fSelection.Start().y <= info.dirtyBottom 2453 && info.dirtyTop <= selectionBottom) { 2454 _Deselect(); 2455 } 2456 } 2457 } 2458 2459 if (visibleDirtyTop <= visibleDirtyBottom) 2460 info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom); 2461 2462 if (linesScrolled != 0 || info.IsDirtyRegionValid()) { 2463 fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible, 2464 info.dirtyTop, info.dirtyBottom); 2465 } 2466 2467 // invalidate cursor, if it changed 2468 TermPos cursor = fTextBuffer->Cursor(); 2469 if (fCursor != cursor || linesScrolled != 0) { 2470 // Before we scrolled we did already invalidate the old cursor. 2471 if (!doScroll) 2472 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2473 fCursor = cursor; 2474 _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); 2475 _ActivateCursor(false); 2476 } 2477 2478 info.Reset(); 2479 } 2480 2481 2482 void 2483 TermView::_VisibleTextBufferChanged() 2484 { 2485 if (!fVisibleTextBufferChanged) 2486 return; 2487 2488 fVisibleTextBufferChanged = false; 2489 fActiveState->VisibleTextBufferChanged(); 2490 } 2491 2492 2493 /*! Write strings to PTY device. If encoding system isn't UTF8, change 2494 encoding to UTF8 before writing PTY. 2495 */ 2496 void 2497 TermView::_WritePTY(const char* text, int32 numBytes) 2498 { 2499 if (fEncoding != M_UTF8) { 2500 while (numBytes > 0) { 2501 char buffer[1024]; 2502 int32 bufferSize = sizeof(buffer); 2503 int32 sourceSize = numBytes; 2504 int32 state = 0; 2505 if (convert_to_utf8(fEncoding, text, &sourceSize, buffer, 2506 &bufferSize, &state) != B_OK || bufferSize == 0) { 2507 break; 2508 } 2509 2510 fShell->Write(buffer, bufferSize); 2511 text += sourceSize; 2512 numBytes -= sourceSize; 2513 } 2514 } else { 2515 fShell->Write(text, numBytes); 2516 } 2517 } 2518 2519 2520 //! Returns the square of the actual pixel distance between both points 2521 float 2522 TermView::_MouseDistanceSinceLastClick(BPoint where) 2523 { 2524 return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x) 2525 + (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y); 2526 } 2527 2528 2529 void 2530 TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y, 2531 bool motion, bool upEvent) 2532 { 2533 if (!fEnableExtendedMouseCoordinates) { 2534 char xtermButtons; 2535 if (buttons == B_PRIMARY_MOUSE_BUTTON) 2536 xtermButtons = 32 + 0; 2537 else if (buttons == B_SECONDARY_MOUSE_BUTTON) 2538 xtermButtons = 32 + 1; 2539 else if (buttons == B_TERTIARY_MOUSE_BUTTON) 2540 xtermButtons = 32 + 2; 2541 else 2542 xtermButtons = 32 + 3; 2543 2544 // dragging motion 2545 if (buttons != 0 && motion && fReportButtonMouseEvent) 2546 xtermButtons += 32; 2547 2548 char xtermX = x + 1 + 32; 2549 char xtermY = y + 1 + 32; 2550 2551 char destBuffer[6]; 2552 destBuffer[0] = '\033'; 2553 destBuffer[1] = '['; 2554 destBuffer[2] = 'M'; 2555 destBuffer[3] = xtermButtons; 2556 destBuffer[4] = xtermX; 2557 destBuffer[5] = xtermY; 2558 fShell->Write(destBuffer, 6); 2559 } else { 2560 char xtermButtons; 2561 if ((buttons & B_PRIMARY_MOUSE_BUTTON) 2562 != (motion ? 0 : (fMouseButtons & B_PRIMARY_MOUSE_BUTTON))) { 2563 xtermButtons = 0; 2564 } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) 2565 != (motion ? 0 : (fMouseButtons & B_SECONDARY_MOUSE_BUTTON))) { 2566 xtermButtons = 2; 2567 } else if ((buttons & B_TERTIARY_MOUSE_BUTTON) 2568 != (motion ? 0 : (fMouseButtons & B_TERTIARY_MOUSE_BUTTON))) { 2569 xtermButtons = 1; 2570 } else 2571 xtermButtons = 3; 2572 2573 // nur button events requested 2574 if (buttons == 0 && motion && fReportButtonMouseEvent) 2575 return; 2576 2577 // dragging motion 2578 if (buttons != 0 && motion && fReportButtonMouseEvent) 2579 xtermButtons += 32; 2580 2581 int16 xtermX = x + 1; 2582 int16 xtermY = y + 1; 2583 2584 char destBuffer[21]; 2585 int size = snprintf(destBuffer, sizeof(destBuffer), "\033[<%u;%u;%u%c", 2586 xtermButtons, xtermX, xtermY, upEvent ? 'm' : 'M'); 2587 fShell->Write(destBuffer, size); 2588 } 2589 } 2590 2591 2592 void 2593 TermView::MouseDown(BPoint where) 2594 { 2595 if (!IsFocus()) 2596 MakeFocus(); 2597 2598 _UpdateModifiers(); 2599 2600 BMessage* currentMessage = Window()->CurrentMessage(); 2601 int32 buttons = currentMessage->GetInt32("buttons", 0); 2602 2603 fActiveState->MouseDown(where, buttons, fModifiers); 2604 2605 fMouseButtons = buttons; 2606 fLastClickPoint = where; 2607 } 2608 2609 2610 void 2611 TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message) 2612 { 2613 _UpdateModifiers(); 2614 2615 fActiveState->MouseMoved(where, transit, message, fModifiers); 2616 } 2617 2618 2619 void 2620 TermView::MouseUp(BPoint where) 2621 { 2622 _UpdateModifiers(); 2623 2624 int32 buttons = Window()->CurrentMessage()->GetInt32("buttons", 0); 2625 2626 fActiveState->MouseUp(where, buttons); 2627 2628 fMouseButtons = buttons; 2629 } 2630 2631 2632 //! Select a range of text. 2633 void 2634 TermView::_Select(TermPos start, TermPos end, bool inclusive, 2635 bool setInitialSelection) 2636 { 2637 TextBufferSyncLocker _(this); 2638 2639 _SynchronizeWithTextBuffer(0, -1); 2640 2641 if (end < start) 2642 std::swap(start, end); 2643 2644 if (inclusive) 2645 end.x++; 2646 2647 //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x, 2648 //start.y, end.x, end.y); 2649 2650 if (start.x < 0) 2651 start.x = 0; 2652 if (end.x >= fColumns) 2653 end.x = fColumns; 2654 2655 TermPos minPos(0, -fTextBuffer->HistorySize()); 2656 TermPos maxPos(0, fTextBuffer->Height()); 2657 start = restrict_value(start, minPos, maxPos); 2658 end = restrict_value(end, minPos, maxPos); 2659 2660 // if the end is past the end of the line, select the line break, too 2661 if (fTextBuffer->LineLength(end.y) < end.x 2662 && end.y < fTextBuffer->Height()) { 2663 end.y++; 2664 end.x = 0; 2665 } 2666 2667 if (fTextBuffer->IsFullWidthChar(start.y, start.x)) { 2668 start.x--; 2669 if (start.x < 0) 2670 start.x = 0; 2671 } 2672 2673 if (fTextBuffer->IsFullWidthChar(end.y, end.x)) { 2674 end.x++; 2675 if (end.x >= fColumns) 2676 end.x = fColumns; 2677 } 2678 2679 if (!fSelection.IsEmpty()) 2680 _InvalidateTextRange(fSelection.Start(), fSelection.End()); 2681 2682 fSelection.SetRange(start, end); 2683 2684 if (setInitialSelection) { 2685 fInitialSelectionStart = fSelection.Start(); 2686 fInitialSelectionEnd = fSelection.End(); 2687 } 2688 2689 _InvalidateTextRange(fSelection.Start(), fSelection.End()); 2690 } 2691 2692 2693 //! Extend selection (shift + mouse click). 2694 void 2695 TermView::_ExtendSelection(TermPos pos, bool inclusive, 2696 bool useInitialSelection) 2697 { 2698 if (!useInitialSelection && !_HasSelection()) 2699 return; 2700 2701 TermPos start = fSelection.Start(); 2702 TermPos end = fSelection.End(); 2703 2704 if (useInitialSelection) { 2705 start = fInitialSelectionStart; 2706 end = fInitialSelectionEnd; 2707 } 2708 2709 if (inclusive) { 2710 if (pos >= start && pos >= end) 2711 pos.x++; 2712 } 2713 2714 if (pos < start) 2715 _Select(pos, end, false, !useInitialSelection); 2716 else if (pos > end) 2717 _Select(start, pos, false, !useInitialSelection); 2718 else if (useInitialSelection) 2719 _Select(start, end, false, false); 2720 } 2721 2722 2723 // clear the selection. 2724 void 2725 TermView::_Deselect() 2726 { 2727 //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection()); 2728 if (_ClearHighlight(&fSelection)) { 2729 fInitialSelectionStart.SetTo(0, 0); 2730 fInitialSelectionEnd.SetTo(0, 0); 2731 } 2732 } 2733 2734 2735 bool 2736 TermView::_HasSelection() const 2737 { 2738 return !fSelection.IsEmpty(); 2739 } 2740 2741 2742 void 2743 TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection) 2744 { 2745 BAutolock _(fTextBuffer); 2746 2747 TermPos pos = _ConvertToTerminal(where); 2748 TermPos start, end; 2749 if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end)) 2750 return; 2751 2752 if (extend) { 2753 if (start 2754 < (useInitialSelection 2755 ? fInitialSelectionStart : fSelection.Start())) { 2756 _ExtendSelection(start, false, useInitialSelection); 2757 } else if (end 2758 > (useInitialSelection 2759 ? fInitialSelectionEnd : fSelection.End())) { 2760 _ExtendSelection(end, false, useInitialSelection); 2761 } else if (useInitialSelection) 2762 _Select(start, end, false, false); 2763 } else 2764 _Select(start, end, false, !useInitialSelection); 2765 } 2766 2767 2768 void 2769 TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection) 2770 { 2771 TermPos start = TermPos(0, _ConvertToTerminal(where).y); 2772 TermPos end = TermPos(0, start.y + 1); 2773 2774 if (extend) { 2775 if (start 2776 < (useInitialSelection 2777 ? fInitialSelectionStart : fSelection.Start())) { 2778 _ExtendSelection(start, false, useInitialSelection); 2779 } else if (end 2780 > (useInitialSelection 2781 ? fInitialSelectionEnd : fSelection.End())) { 2782 _ExtendSelection(end, false, useInitialSelection); 2783 } else if (useInitialSelection) 2784 _Select(start, end, false, false); 2785 } else 2786 _Select(start, end, false, !useInitialSelection); 2787 } 2788 2789 2790 void 2791 TermView::_AddHighlight(Highlight* highlight) 2792 { 2793 fHighlights.AddItem(highlight); 2794 2795 if (!highlight->IsEmpty()) 2796 _InvalidateTextRange(highlight->Start(), highlight->End()); 2797 } 2798 2799 2800 void 2801 TermView::_RemoveHighlight(Highlight* highlight) 2802 { 2803 if (!highlight->IsEmpty()) 2804 _InvalidateTextRange(highlight->Start(), highlight->End()); 2805 2806 fHighlights.RemoveItem(highlight); 2807 } 2808 2809 2810 bool 2811 TermView::_ClearHighlight(Highlight* highlight) 2812 { 2813 if (highlight->IsEmpty()) 2814 return false; 2815 2816 _InvalidateTextRange(highlight->Start(), highlight->End()); 2817 2818 highlight->SetRange(TermPos(0, 0), TermPos(0, 0)); 2819 return true; 2820 } 2821 2822 2823 TermView::Highlight* 2824 TermView::_CheckHighlightRegion(const TermPos &pos) const 2825 { 2826 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { 2827 if (highlight->RangeContains(pos)) 2828 return highlight; 2829 } 2830 2831 return NULL; 2832 } 2833 2834 2835 TermView::Highlight* 2836 TermView::_CheckHighlightRegion(int32 row, int32 firstColumn, 2837 int32& lastColumn) const 2838 { 2839 Highlight* nextHighlight = NULL; 2840 2841 for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) { 2842 if (highlight->IsEmpty()) 2843 continue; 2844 2845 if (row == highlight->Start().y && firstColumn < highlight->Start().x 2846 && lastColumn >= highlight->Start().x) { 2847 // region starts before the highlight, but intersects with it 2848 if (nextHighlight == NULL 2849 || highlight->Start().x < nextHighlight->Start().x) { 2850 nextHighlight = highlight; 2851 } 2852 continue; 2853 } 2854 2855 if (row == highlight->End().y && firstColumn < highlight->End().x 2856 && lastColumn >= highlight->End().x) { 2857 // region starts in the highlight, but exceeds the end 2858 lastColumn = highlight->End().x - 1; 2859 return highlight; 2860 } 2861 2862 TermPos pos(firstColumn, row); 2863 if (highlight->RangeContains(pos)) 2864 return highlight; 2865 } 2866 2867 if (nextHighlight != NULL) 2868 lastColumn = nextHighlight->Start().x - 1; 2869 return NULL; 2870 } 2871 2872 2873 void 2874 TermView::GetFrameSize(float *width, float *height) 2875 { 2876 int32 historySize; 2877 { 2878 BAutolock _(fTextBuffer); 2879 historySize = fTextBuffer->HistorySize(); 2880 } 2881 2882 if (width != NULL) 2883 *width = fColumns * fFontWidth; 2884 2885 if (height != NULL) 2886 *height = (fRows + historySize) * fFontHeight; 2887 } 2888 2889 2890 // Find a string, and select it if found 2891 bool 2892 TermView::Find(const BString &str, bool forwardSearch, bool matchCase, 2893 bool matchWord) 2894 { 2895 TextBufferSyncLocker _(this); 2896 _SynchronizeWithTextBuffer(0, -1); 2897 2898 TermPos start; 2899 if (_HasSelection()) { 2900 if (forwardSearch) 2901 start = fSelection.End(); 2902 else 2903 start = fSelection.Start(); 2904 } else { 2905 // search from the very beginning/end 2906 if (forwardSearch) 2907 start = TermPos(0, -fTextBuffer->HistorySize()); 2908 else 2909 start = TermPos(0, fTextBuffer->Height()); 2910 } 2911 2912 TermPos matchStart, matchEnd; 2913 if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase, 2914 matchWord, matchStart, matchEnd)) { 2915 return false; 2916 } 2917 2918 _Select(matchStart, matchEnd, false, true); 2919 _ScrollToRange(fSelection.Start(), fSelection.End()); 2920 2921 return true; 2922 } 2923 2924 2925 //! Get the selected text and copy to str 2926 void 2927 TermView::GetSelection(BString &str) 2928 { 2929 str.SetTo(""); 2930 BAutolock _(fTextBuffer); 2931 fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End()); 2932 } 2933 2934 2935 bool 2936 TermView::CheckShellGone() const 2937 { 2938 if (!fShell) 2939 return false; 2940 2941 // check, if the shell does still live 2942 pid_t pid = fShell->ProcessID(); 2943 team_info info; 2944 return get_team_info(pid, &info) == B_BAD_TEAM_ID; 2945 } 2946 2947 2948 void 2949 TermView::InitiateDrag() 2950 { 2951 BAutolock _(fTextBuffer); 2952 2953 BString copyStr(""); 2954 fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(), 2955 fSelection.End()); 2956 2957 BMessage message(B_MIME_DATA); 2958 message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), 2959 copyStr.Length()); 2960 2961 BPoint start = _ConvertFromTerminal(fSelection.Start()); 2962 BPoint end = _ConvertFromTerminal(fSelection.End()); 2963 2964 BRect rect; 2965 if (fSelection.Start().y == fSelection.End().y) 2966 rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight); 2967 else 2968 rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight); 2969 2970 rect = rect & Bounds(); 2971 2972 DragMessage(&message, rect); 2973 } 2974 2975 2976 void 2977 TermView::_ScrollTo(float y, bool scrollGfx) 2978 { 2979 if (!scrollGfx) 2980 fScrollOffset = y; 2981 2982 if (fScrollBar != NULL) 2983 fScrollBar->SetValue(y); 2984 else 2985 ScrollTo(BPoint(0, y)); 2986 } 2987 2988 2989 void 2990 TermView::_ScrollToRange(TermPos start, TermPos end) 2991 { 2992 if (start > end) 2993 std::swap(start, end); 2994 2995 float startY = _LineOffset(start.y); 2996 float endY = _LineOffset(end.y) + fFontHeight - 1; 2997 float height = Bounds().Height(); 2998 2999 if (endY - startY > height) { 3000 // The range is greater than the height. Scroll to the closest border. 3001 3002 // already as good as it gets? 3003 if (startY <= 0 && endY >= height) 3004 return; 3005 3006 if (startY > 0) { 3007 // scroll down to align the start with the top of the view 3008 _ScrollTo(fScrollOffset + startY, true); 3009 } else { 3010 // scroll up to align the end with the bottom of the view 3011 _ScrollTo(fScrollOffset + endY - height, true); 3012 } 3013 } else { 3014 // The range is smaller than the height. 3015 3016 // already visible? 3017 if (startY >= 0 && endY <= height) 3018 return; 3019 3020 if (startY < 0) { 3021 // scroll up to make the start visible 3022 _ScrollTo(fScrollOffset + startY, true); 3023 } else { 3024 // scroll down to make the end visible 3025 _ScrollTo(fScrollOffset + endY - height, true); 3026 } 3027 } 3028 } 3029 3030 3031 void 3032 TermView::DisableResizeView(int32 disableCount) 3033 { 3034 fResizeViewDisableCount += disableCount; 3035 } 3036 3037 3038 void 3039 TermView::_DrawInlineMethodString() 3040 { 3041 if (!fInline || !fInline->String()) 3042 return; 3043 3044 const int32 numChars = BString(fInline->String()).CountChars(); 3045 3046 BPoint startPoint = _ConvertFromTerminal(fCursor); 3047 BPoint endPoint = startPoint; 3048 endPoint.x += fFontWidth * numChars; 3049 endPoint.y += fFontHeight + 1; 3050 3051 BRect eraseRect(startPoint, endPoint); 3052 3053 PushState(); 3054 SetHighColor(fTextForeColor); 3055 FillRect(eraseRect); 3056 PopState(); 3057 3058 BPoint loc = _ConvertFromTerminal(fCursor); 3059 loc.y += fFontHeight; 3060 SetFont(&fHalfFont); 3061 SetHighColor(fTextBackColor); 3062 SetLowColor(fTextForeColor); 3063 DrawString(fInline->String(), loc); 3064 } 3065 3066 3067 void 3068 TermView::_HandleInputMethodChanged(BMessage *message) 3069 { 3070 const char *string = NULL; 3071 if (message->FindString("be:string", &string) < B_OK || string == NULL) 3072 return; 3073 3074 _ActivateCursor(false); 3075 3076 if (IsFocus()) 3077 be_app->ObscureCursor(); 3078 3079 // If we find the "be:confirmed" boolean (and the boolean is true), 3080 // it means it's over for now, so the current InlineInput object 3081 // should become inactive. We will probably receive a 3082 // B_INPUT_METHOD_STOPPED message after this one. 3083 bool confirmed; 3084 if (message->FindBool("be:confirmed", &confirmed) != B_OK) 3085 confirmed = false; 3086 3087 fInline->SetString(""); 3088 3089 Invalidate(); 3090 // TODO: Debug only 3091 snooze(100000); 3092 3093 fInline->SetString(string); 3094 fInline->ResetClauses(); 3095 3096 if (!confirmed && !fInline->IsActive()) 3097 fInline->SetActive(true); 3098 3099 // Get the clauses, and pass them to the InlineInput object 3100 // TODO: Find out if what we did it's ok, currently we don't consider 3101 // clauses at all, while the bebook says we should; though the visual 3102 // effect we obtained seems correct. Weird. 3103 int32 clauseCount = 0; 3104 int32 clauseStart; 3105 int32 clauseEnd; 3106 while (message->FindInt32("be:clause_start", clauseCount, &clauseStart) 3107 == B_OK 3108 && message->FindInt32("be:clause_end", clauseCount, &clauseEnd) 3109 == B_OK) { 3110 if (!fInline->AddClause(clauseStart, clauseEnd)) 3111 break; 3112 clauseCount++; 3113 } 3114 3115 if (confirmed) { 3116 fInline->SetString(""); 3117 _ActivateCursor(true); 3118 3119 // now we need to feed ourselves the individual characters as if the 3120 // user would have pressed them now - this lets KeyDown() pick out all 3121 // the special characters like B_BACKSPACE, cursor keys and the like: 3122 const char* currPos = string; 3123 const char* prevPos = currPos; 3124 while (*currPos != '\0') { 3125 if ((*currPos & 0xC0) == 0xC0) { 3126 // found the start of an UTF-8 char, we collect while it lasts 3127 ++currPos; 3128 while ((*currPos & 0xC0) == 0x80) 3129 ++currPos; 3130 } else if ((*currPos & 0xC0) == 0x80) { 3131 // illegal: character starts with utf-8 intermediate byte, skip it 3132 prevPos = ++currPos; 3133 } else { 3134 // single byte character/code, just feed that 3135 ++currPos; 3136 } 3137 KeyDown(prevPos, currPos - prevPos); 3138 prevPos = currPos; 3139 } 3140 } else { 3141 // temporarily show transient state of inline input 3142 int32 selectionStart = 0; 3143 int32 selectionEnd = 0; 3144 message->FindInt32("be:selection", 0, &selectionStart); 3145 message->FindInt32("be:selection", 1, &selectionEnd); 3146 3147 fInline->SetSelectionOffset(selectionStart); 3148 fInline->SetSelectionLength(selectionEnd - selectionStart); 3149 } 3150 Invalidate(); 3151 } 3152 3153 3154 void 3155 TermView::_HandleInputMethodLocationRequest() 3156 { 3157 BMessage message(B_INPUT_METHOD_EVENT); 3158 message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST); 3159 3160 BString string(fInline->String()); 3161 3162 const int32 &limit = string.CountChars(); 3163 BPoint where = _ConvertFromTerminal(fCursor); 3164 where.y += fFontHeight; 3165 3166 for (int32 i = 0; i < limit; i++) { 3167 // Add the location of the UTF8 characters 3168 3169 where.x += fFontWidth; 3170 ConvertToScreen(&where); 3171 3172 message.AddPoint("be:location_reply", where); 3173 message.AddFloat("be:height_reply", fFontHeight); 3174 } 3175 3176 fInline->Method()->SendMessage(&message); 3177 } 3178 3179 3180 void 3181 TermView::_CancelInputMethod() 3182 { 3183 if (!fInline) 3184 return; 3185 3186 InlineInput *inlineInput = fInline; 3187 fInline = NULL; 3188 3189 if (inlineInput->IsActive() && Window()) { 3190 Invalidate(); 3191 3192 BMessage message(B_INPUT_METHOD_EVENT); 3193 message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED); 3194 inlineInput->Method()->SendMessage(&message); 3195 } 3196 3197 delete inlineInput; 3198 } 3199 3200 3201 void 3202 TermView::_UpdateModifiers() 3203 { 3204 // TODO: This method is a general work-around for missing or out-of-order 3205 // B_MODIFIERS_CHANGED messages. This should really be fixed where it is 3206 // broken (app server?). 3207 int32 oldModifiers = fModifiers; 3208 fModifiers = modifiers(); 3209 if (fModifiers != oldModifiers && fActiveState != NULL) 3210 fActiveState->ModifiersChanged(oldModifiers, fModifiers); 3211 } 3212 3213 3214 void 3215 TermView::_NextState(State* state) 3216 { 3217 if (state != fActiveState) { 3218 if (fActiveState != NULL) 3219 fActiveState->Exited(); 3220 fActiveState = state; 3221 fActiveState->Entered(); 3222 } 3223 } 3224 3225 3226 // #pragma mark - Listener 3227 3228 3229 TermView::Listener::~Listener() 3230 { 3231 } 3232 3233 3234 void 3235 TermView::Listener::NotifyTermViewQuit(TermView* view, int32 reason) 3236 { 3237 } 3238 3239 3240 void 3241 TermView::Listener::SetTermViewTitle(TermView* view, const char* title) 3242 { 3243 } 3244 3245 3246 void 3247 TermView::Listener::PreviousTermView(TermView* view) 3248 { 3249 } 3250 3251 3252 void 3253 TermView::Listener::NextTermView(TermView* view) 3254 { 3255 } 3256 3257 3258 // #pragma mark - 3259 3260 3261 #ifdef USE_DEBUG_SNAPSHOTS 3262 3263 void 3264 TermView::MakeDebugSnapshots() 3265 { 3266 BAutolock _(fTextBuffer); 3267 time_t timeStamp = time(NULL); 3268 fTextBuffer->MakeLinesSnapshots(timeStamp, ".TextBuffer.dump"); 3269 fVisibleTextBuffer->MakeLinesSnapshots(timeStamp, ".VisualTextBuffer.dump"); 3270 } 3271 3272 3273 void 3274 TermView::StartStopDebugCapture() 3275 { 3276 BAutolock _(fTextBuffer); 3277 fTextBuffer->StartStopDebugCapture(); 3278 } 3279 3280 #endif 3281