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