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