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