xref: /haiku/src/apps/terminal/TermViewStates.cpp (revision bda35ef5dc71c1c4a4e831233f937bcb130bd284)
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