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