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