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