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