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