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