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