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