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