1 /* 2 * Copyright 2001-2013, 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 * Siarzhuk Zharski, zharik@gmx.li 14 */ 15 16 17 #include "TermViewStates.h" 18 19 #include <MessageRunner.h> 20 #include <ScrollBar.h> 21 #include <UTF8.h> 22 #include <Window.h> 23 24 #include "Shell.h" 25 #include "TermConst.h" 26 #include "VTkeymap.h" 27 #include "VTKeyTbl.h" 28 29 30 // selection granularity 31 enum { 32 SELECT_CHARS, 33 SELECT_WORDS, 34 SELECT_LINES 35 }; 36 37 static const uint32 kAutoScroll = 'AScr'; 38 39 40 // #pragma mark - State 41 42 43 TermView::State::State(TermView* view) 44 : 45 fView(view) 46 { 47 } 48 49 50 TermView::State::~State() 51 { 52 } 53 54 55 void 56 TermView::State::Entered() 57 { 58 } 59 60 61 void 62 TermView::State::Exited() 63 { 64 } 65 66 67 bool 68 TermView::State::MessageReceived(BMessage* message) 69 { 70 return false; 71 } 72 73 74 void 75 TermView::State::KeyDown(const char* bytes, int32 numBytes) 76 { 77 } 78 79 80 void 81 TermView::State::MouseDown(BPoint where, int32 buttons, int32 modifiers) 82 { 83 } 84 85 86 void 87 TermView::State::MouseMoved(BPoint where, uint32 transit, 88 const BMessage* message) 89 { 90 } 91 92 93 void 94 TermView::State::MouseUp(BPoint where, int32 buttons) 95 { 96 } 97 98 99 // #pragma mark - StandardBaseState 100 101 102 TermView::StandardBaseState::StandardBaseState(TermView* view) 103 : 104 State(view) 105 { 106 } 107 108 109 bool 110 TermView::StandardBaseState::_StandardMouseMoved(BPoint where) 111 { 112 if (!fView->fReportAnyMouseEvent && !fView->fReportButtonMouseEvent) 113 return false; 114 115 int32 modifier; 116 fView->Window()->CurrentMessage()->FindInt32("modifiers", &modifier); 117 118 TermPos clickPos = fView->_ConvertToTerminal(where); 119 120 if (fView->fReportButtonMouseEvent) { 121 if (fView->fPrevPos.x != clickPos.x 122 || fView->fPrevPos.y != clickPos.y) { 123 fView->_SendMouseEvent(fView->fMouseButtons, modifier, 124 clickPos.x, clickPos.y, true); 125 } 126 fView->fPrevPos = clickPos; 127 } else { 128 fView->_SendMouseEvent(fView->fMouseButtons, modifier, clickPos.x, 129 clickPos.y, true); 130 } 131 132 return true; 133 } 134 135 136 // #pragma mark - DefaultState 137 138 139 TermView::DefaultState::DefaultState(TermView* view) 140 : 141 StandardBaseState(view) 142 { 143 } 144 145 146 void 147 TermView::DefaultState::KeyDown(const char* bytes, int32 numBytes) 148 { 149 int32 key, mod, rawChar; 150 BMessage *currentMessage = fView->Looper()->CurrentMessage(); 151 if (currentMessage == NULL) 152 return; 153 154 currentMessage->FindInt32("modifiers", &mod); 155 currentMessage->FindInt32("key", &key); 156 currentMessage->FindInt32("raw_char", &rawChar); 157 158 fView->_ActivateCursor(true); 159 160 // handle multi-byte chars 161 if (numBytes > 1) { 162 if (fView->fEncoding != M_UTF8) { 163 char destBuffer[16]; 164 int32 destLen = sizeof(destBuffer); 165 int32 state = 0; 166 convert_from_utf8(fView->fEncoding, bytes, &numBytes, destBuffer, 167 &destLen, &state, '?'); 168 fView->_ScrollTo(0, true); 169 fView->fShell->Write(destBuffer, destLen); 170 return; 171 } 172 173 fView->_ScrollTo(0, true); 174 fView->fShell->Write(bytes, numBytes); 175 return; 176 } 177 178 // Terminal filters RET, ENTER, F1...F12, and ARROW key code. 179 const char *toWrite = NULL; 180 181 switch (*bytes) { 182 case B_RETURN: 183 if (rawChar == B_RETURN) 184 toWrite = "\r"; 185 break; 186 187 case B_DELETE: 188 toWrite = DELETE_KEY_CODE; 189 break; 190 191 case B_BACKSPACE: 192 // Translate only the actual backspace key to the backspace 193 // code. CTRL-H shall just be echoed. 194 if (!((mod & B_CONTROL_KEY) && rawChar == 'h')) 195 toWrite = BACKSPACE_KEY_CODE; 196 break; 197 198 case B_LEFT_ARROW: 199 if (rawChar == B_LEFT_ARROW) { 200 if ((mod & B_SHIFT_KEY) != 0) { 201 if (fView->fListener != NULL) 202 fView->fListener->PreviousTermView(fView); 203 return; 204 } 205 if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) 206 toWrite = CTRL_LEFT_ARROW_KEY_CODE; 207 else 208 toWrite = LEFT_ARROW_KEY_CODE; 209 } 210 break; 211 212 case B_RIGHT_ARROW: 213 if (rawChar == B_RIGHT_ARROW) { 214 if ((mod & B_SHIFT_KEY) != 0) { 215 if (fView->fListener != NULL) 216 fView->fListener->NextTermView(fView); 217 return; 218 } 219 if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY)) 220 toWrite = CTRL_RIGHT_ARROW_KEY_CODE; 221 else 222 toWrite = RIGHT_ARROW_KEY_CODE; 223 } 224 break; 225 226 case B_UP_ARROW: 227 if (mod & B_SHIFT_KEY) { 228 fView->_ScrollTo(fView->fScrollOffset - fView->fFontHeight, 229 true); 230 return; 231 } 232 if (rawChar == B_UP_ARROW) { 233 if (mod & B_CONTROL_KEY) 234 toWrite = CTRL_UP_ARROW_KEY_CODE; 235 else 236 toWrite = UP_ARROW_KEY_CODE; 237 } 238 break; 239 240 case B_DOWN_ARROW: 241 if (mod & B_SHIFT_KEY) { 242 fView->_ScrollTo(fView->fScrollOffset + fView->fFontHeight, 243 true); 244 return; 245 } 246 247 if (rawChar == B_DOWN_ARROW) { 248 if (mod & B_CONTROL_KEY) 249 toWrite = CTRL_DOWN_ARROW_KEY_CODE; 250 else 251 toWrite = DOWN_ARROW_KEY_CODE; 252 } 253 break; 254 255 case B_INSERT: 256 if (rawChar == B_INSERT) 257 toWrite = INSERT_KEY_CODE; 258 break; 259 260 case B_HOME: 261 if (rawChar == B_HOME) 262 toWrite = HOME_KEY_CODE; 263 break; 264 265 case B_END: 266 if (rawChar == B_END) 267 toWrite = END_KEY_CODE; 268 break; 269 270 case B_PAGE_UP: 271 if (mod & B_SHIFT_KEY) { 272 fView->_ScrollTo( 273 fView->fScrollOffset - fView->fFontHeight * fView->fRows, 274 true); 275 return; 276 } 277 if (rawChar == B_PAGE_UP) 278 toWrite = PAGE_UP_KEY_CODE; 279 break; 280 281 case B_PAGE_DOWN: 282 if (mod & B_SHIFT_KEY) { 283 fView->_ScrollTo( 284 fView->fScrollOffset + fView->fFontHeight * fView->fRows, 285 true); 286 return; 287 } 288 if (rawChar == B_PAGE_DOWN) 289 toWrite = PAGE_DOWN_KEY_CODE; 290 break; 291 292 case B_FUNCTION_KEY: 293 for (int32 i = 0; i < 12; i++) { 294 if (key == function_keycode_table[i]) { 295 toWrite = function_key_char_table[i]; 296 break; 297 } 298 } 299 break; 300 } 301 302 // If the above code proposed an alternative string to write, we get it's 303 // length. Otherwise we write exactly the bytes passed to this method. 304 size_t toWriteLen; 305 if (toWrite != NULL) { 306 toWriteLen = strlen(toWrite); 307 } else { 308 toWrite = bytes; 309 toWriteLen = numBytes; 310 } 311 312 fView->_ScrollTo(0, true); 313 fView->fShell->Write(toWrite, toWriteLen); 314 } 315 316 317 void 318 TermView::DefaultState::MouseDown(BPoint where, int32 buttons, int32 modifiers) 319 { 320 if (fView->fReportAnyMouseEvent || fView->fReportButtonMouseEvent 321 || fView->fReportNormalMouseEvent || fView->fReportX10MouseEvent) { 322 TermPos clickPos = fView->_ConvertToTerminal(where); 323 fView->_SendMouseEvent(buttons, modifiers, clickPos.x, clickPos.y, 324 false); 325 return; 326 } 327 328 // paste button 329 if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) { 330 fView->Paste(fView->fMouseClipboard); 331 return; 332 } 333 334 // Select Region 335 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 336 fView->fSelectState->Prepare(where, modifiers); 337 fView->_NextState(fView->fSelectState); 338 } 339 } 340 341 342 void 343 TermView::DefaultState::MouseMoved(BPoint where, uint32 transit, 344 const BMessage* message) 345 { 346 _StandardMouseMoved(where); 347 } 348 349 350 // #pragma mark - SelectState 351 352 353 TermView::SelectState::SelectState(TermView* view) 354 : 355 StandardBaseState(view), 356 fSelectGranularity(SELECT_CHARS), 357 fCheckMouseTracking(false), 358 fMouseTracking(false) 359 { 360 } 361 362 363 void 364 TermView::SelectState::Prepare(BPoint where, int32 modifiers) 365 { 366 int32 clicks; 367 fView->Window()->CurrentMessage()->FindInt32("clicks", &clicks); 368 369 if (fView->_HasSelection()) { 370 TermPos inPos = fView->_ConvertToTerminal(where); 371 if (fView->_CheckSelectedRegion(inPos)) { 372 if (modifiers & B_CONTROL_KEY) { 373 BPoint p; 374 uint32 bt; 375 do { 376 fView->GetMouse(&p, &bt); 377 378 if (bt == 0) { 379 fView->_Deselect(); 380 return; 381 } 382 383 snooze(40000); 384 385 } while (abs((int)(where.x - p.x)) < 4 386 && abs((int)(where.y - p.y)) < 4); 387 388 fView->InitiateDrag(); 389 return; 390 } 391 } 392 } 393 394 // If mouse has moved too much, disable double/triple click. 395 if (fView->_MouseDistanceSinceLastClick(where) > 8) 396 clicks = 1; 397 398 fView->SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, 399 B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); 400 401 TermPos clickPos = fView->_ConvertToTerminal(where); 402 403 if (modifiers & B_SHIFT_KEY) { 404 fView->fInitialSelectionStart = clickPos; 405 fView->fInitialSelectionEnd = clickPos; 406 fView->_ExtendSelection(fView->fInitialSelectionStart, true, false); 407 } else { 408 fView->_Deselect(); 409 fView->fInitialSelectionStart = clickPos; 410 fView->fInitialSelectionEnd = clickPos; 411 } 412 413 // If clicks larger than 3, reset mouse click counter. 414 clicks = (clicks - 1) % 3 + 1; 415 416 switch (clicks) { 417 case 1: 418 fCheckMouseTracking = true; 419 fSelectGranularity = SELECT_CHARS; 420 break; 421 422 case 2: 423 fView->_SelectWord(where, (modifiers & B_SHIFT_KEY) != 0, false); 424 fMouseTracking = true; 425 fSelectGranularity = SELECT_WORDS; 426 break; 427 428 case 3: 429 fView->_SelectLine(where, (modifiers & B_SHIFT_KEY) != 0, false); 430 fMouseTracking = true; 431 fSelectGranularity = SELECT_LINES; 432 break; 433 } 434 } 435 436 437 bool 438 TermView::SelectState::MessageReceived(BMessage* message) 439 { 440 if (message->what == kAutoScroll) { 441 _AutoScrollUpdate(); 442 return true; 443 } 444 445 return false; 446 } 447 448 449 void 450 TermView::SelectState::MouseMoved(BPoint where, uint32 transit, 451 const BMessage* message) 452 { 453 if (_StandardMouseMoved(where)) 454 return; 455 456 if (fCheckMouseTracking) { 457 if (fView->_MouseDistanceSinceLastClick(where) > 9) 458 fMouseTracking = true; 459 } 460 if (!fMouseTracking) 461 return; 462 463 bool doAutoScroll = false; 464 465 if (where.y < 0) { 466 doAutoScroll = true; 467 fView->fAutoScrollSpeed = where.y; 468 where.x = 0; 469 where.y = 0; 470 } 471 472 BRect bounds(fView->Bounds()); 473 if (where.y > bounds.bottom) { 474 doAutoScroll = true; 475 fView->fAutoScrollSpeed = where.y - bounds.bottom; 476 where.x = bounds.right; 477 where.y = bounds.bottom; 478 } 479 480 if (doAutoScroll) { 481 if (fView->fAutoScrollRunner == NULL) { 482 BMessage message(kAutoScroll); 483 fView->fAutoScrollRunner = new (std::nothrow) BMessageRunner( 484 BMessenger(fView), &message, 10000); 485 } 486 } else { 487 delete fView->fAutoScrollRunner; 488 fView->fAutoScrollRunner = NULL; 489 } 490 491 switch (fSelectGranularity) { 492 case SELECT_CHARS: 493 { 494 // If we just start selecting, we first select the initially 495 // hit char, so that we get a proper initial selection -- the char 496 // in question, which will thus always be selected, regardless of 497 // whether selecting forward or backward. 498 if (fView->fInitialSelectionStart == fView->fInitialSelectionEnd) { 499 fView->_Select(fView->fInitialSelectionStart, 500 fView->fInitialSelectionEnd, true, true); 501 } 502 503 fView->_ExtendSelection(fView->_ConvertToTerminal(where), true, 504 true); 505 break; 506 } 507 case SELECT_WORDS: 508 fView->_SelectWord(where, true, true); 509 break; 510 case SELECT_LINES: 511 fView->_SelectLine(where, true, true); 512 break; 513 } 514 } 515 516 517 void 518 TermView::SelectState::MouseUp(BPoint where, int32 buttons) 519 { 520 fCheckMouseTracking = false; 521 fMouseTracking = false; 522 523 if (fView->fAutoScrollRunner != NULL) { 524 delete fView->fAutoScrollRunner; 525 fView->fAutoScrollRunner = NULL; 526 } 527 528 // When releasing the first mouse button, we copy the selected text to the 529 // clipboard. 530 531 if (fView->fReportAnyMouseEvent || fView->fReportButtonMouseEvent 532 || fView->fReportNormalMouseEvent) { 533 TermPos clickPos = fView->_ConvertToTerminal(where); 534 fView->_SendMouseEvent(0, 0, clickPos.x, clickPos.y, false); 535 } else { 536 if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0 537 && (fView->fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) { 538 fView->Copy(fView->fMouseClipboard); 539 } 540 541 } 542 543 fView->_NextState(fView->fDefaultState); 544 } 545 546 547 void 548 TermView::SelectState::_AutoScrollUpdate() 549 { 550 if (fMouseTracking && fView->fAutoScrollRunner != NULL 551 && fView->fScrollBar != NULL) { 552 float value = fView->fScrollBar->Value(); 553 fView->_ScrollTo(value + fView->fAutoScrollSpeed, true); 554 if (fView->fAutoScrollSpeed < 0) { 555 fView->_ExtendSelection( 556 fView->_ConvertToTerminal(BPoint(0, 0)), true, true); 557 } else { 558 fView->_ExtendSelection( 559 fView->_ConvertToTerminal(fView->Bounds().RightBottom()), true, 560 true); 561 } 562 } 563 } 564