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