xref: /haiku/src/apps/terminal/TermView.cpp (revision 49d7857e32a5c34fe63a11e46a41a774aa1b2728)
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 	{128, 128, 128, 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 	{255, 255, 255, 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 			long 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 		int32 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", specifier.FindString("property", i))) {
1866 				msg->FindInt32 ("data", &encodingID);
1867 				SetEncoding(encodingID);
1868 				msg->SendReply(B_REPLY);
1869 			} else {
1870 				BView::MessageReceived(msg);
1871 			}
1872 			break;
1873 		}
1874 
1875 		case B_GET_PROPERTY:
1876 		{
1877 			int32 i;
1878 			BMessage specifier;
1879 			if (msg->GetCurrentSpecifier(&i, &specifier) == B_OK
1880 				&& !strcmp("encoding", specifier.FindString("property", i))) {
1881 				BMessage reply(B_REPLY);
1882 				reply.AddInt32("result", Encoding());
1883 				msg->SendReply(&reply);
1884 			} else if (!strcmp("tty", specifier.FindString("property", i))) {
1885 				BMessage reply(B_REPLY);
1886 				reply.AddString("result", TerminalName());
1887 				msg->SendReply(&reply);
1888 			} else {
1889 				BView::MessageReceived(msg);
1890 			}
1891 			break;
1892 		}
1893 
1894 		case B_INPUT_METHOD_EVENT:
1895 		{
1896 			int32 opcode;
1897 			if (msg->FindInt32("be:opcode", &opcode) == B_OK) {
1898 				switch (opcode) {
1899 					case B_INPUT_METHOD_STARTED:
1900 					{
1901 						BMessenger messenger;
1902 						if (msg->FindMessenger("be:reply_to",
1903 								&messenger) == B_OK) {
1904 							fInline = new (std::nothrow)
1905 								InlineInput(messenger);
1906 						}
1907 						break;
1908 					}
1909 
1910 					case B_INPUT_METHOD_STOPPED:
1911 						delete fInline;
1912 						fInline = NULL;
1913 						break;
1914 
1915 					case B_INPUT_METHOD_CHANGED:
1916 						if (fInline != NULL)
1917 							_HandleInputMethodChanged(msg);
1918 						break;
1919 
1920 					case B_INPUT_METHOD_LOCATION_REQUEST:
1921 						if (fInline != NULL)
1922 							_HandleInputMethodLocationRequest();
1923 						break;
1924 
1925 					default:
1926 						break;
1927 				}
1928 			}
1929 			break;
1930 		}
1931 
1932 		case B_MOUSE_WHEEL_CHANGED:
1933 		{
1934 			// overridden to allow scrolling emulation in alternative screen
1935 			// mode
1936 			BAutolock locker(fTextBuffer);
1937 			float deltaY = 0;
1938 			if (fTextBuffer->IsAlternateScreenActive()
1939 				&& msg->FindFloat("be:wheel_delta_y", &deltaY) == B_OK
1940 				&& deltaY != 0) {
1941 				// We are in alternative screen mode and have a vertical delta
1942 				// we can work with -- emulate scrolling via terminal escape
1943 				// sequences.
1944 				locker.Unlock();
1945 
1946 				// scroll pagewise, if one of Option, Command, or Control is
1947 				// pressed
1948 				int32 steps;
1949 				const char* stepString;
1950 				if ((modifiers()
1951 						& (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
1952 						!= 0) {
1953 					// pagewise
1954 					stepString = deltaY > 0
1955 						? PAGE_DOWN_KEY_CODE : PAGE_UP_KEY_CODE;
1956 					steps = abs((int)deltaY);
1957 				} else {
1958 					// three lines per step
1959 					stepString = deltaY > 0
1960 						? DOWN_ARROW_KEY_CODE : UP_ARROW_KEY_CODE;
1961 					steps = 3 * abs((int)deltaY);
1962 				}
1963 
1964 				// We want to do only a single write(), so compose a string
1965 				// repeating the sequence as often as required by the delta.
1966 				BString toWrite;
1967 				for (int32 i = 0; i <steps; i++)
1968 					toWrite << stepString;
1969 
1970 				_WritePTY(toWrite.String(), toWrite.Length());
1971 			} else {
1972 				// let the BView's implementation handle the standard scrolling
1973 				locker.Unlock();
1974 				BView::MessageReceived(msg);
1975 			}
1976 
1977 			break;
1978 		}
1979 
1980 		case MENU_CLEAR_ALL:
1981 			Clear();
1982 			fShell->Write(ctrl_l, 1);
1983 			break;
1984 		case kBlinkCursor:
1985 			_BlinkCursor();
1986 			break;
1987 		case kUpdateSigWinch:
1988 			_UpdateSIGWINCH();
1989 			break;
1990 		case kAutoScroll:
1991 			_AutoScrollUpdate();
1992 			break;
1993 		case kSecondaryMouseDropAction:
1994 			_DoSecondaryMouseDropAction(msg);
1995 			break;
1996 		case MSG_TERMINAL_BUFFER_CHANGED:
1997 		{
1998 			BAutolock _(fTextBuffer);
1999 			_SynchronizeWithTextBuffer(0, -1);
2000 			break;
2001 		}
2002 		case MSG_SET_TERMNAL_TITLE:
2003 		{
2004 			const char* title;
2005 			if (msg->FindString("title", &title) == B_OK) {
2006 				if (fListener != NULL)
2007 					fListener->SetTermViewTitle(this, title);
2008 			}
2009 			break;
2010 		}
2011 		case MSG_REPORT_MOUSE_EVENT:
2012 		{
2013 			bool report;
2014 			if (msg->FindBool("reportX10MouseEvent", &report) == B_OK)
2015 				fReportX10MouseEvent = report;
2016 
2017 			if (msg->FindBool("reportNormalMouseEvent", &report) == B_OK)
2018 				fReportNormalMouseEvent = report;
2019 
2020 			if (msg->FindBool("reportButtonMouseEvent", &report) == B_OK)
2021 				fReportButtonMouseEvent = report;
2022 
2023 			if (msg->FindBool("reportAnyMouseEvent", &report) == B_OK)
2024 				fReportAnyMouseEvent = report;
2025 			break;
2026 		}
2027 		case MSG_REMOVE_RESIZE_VIEW_IF_NEEDED:
2028 		{
2029 			BPoint point;
2030 			uint32 buttons;
2031 			GetMouse(&point, &buttons, false);
2032 			if (buttons != 0)
2033 				break;
2034 
2035 			if (fResizeView != NULL) {
2036 				fResizeView->RemoveSelf();
2037 				delete fResizeView;
2038 				fResizeView = NULL;
2039 			}
2040 			delete fResizeRunner;
2041 			fResizeRunner = NULL;
2042 			break;
2043 		}
2044 
2045 		case MSG_QUIT_TERMNAL:
2046 		{
2047 			int32 reason;
2048 			if (msg->FindInt32("reason", &reason) != B_OK)
2049 				reason = 0;
2050 			if (fListener != NULL)
2051 				fListener->NotifyTermViewQuit(this, reason);
2052 			break;
2053 		}
2054 		default:
2055 			BView::MessageReceived(msg);
2056 			break;
2057 	}
2058 }
2059 
2060 
2061 status_t
2062 TermView::GetSupportedSuites(BMessage *message)
2063 {
2064 	BPropertyInfo propInfo(sPropList);
2065 	message->AddString("suites", "suite/vnd.naan-termview");
2066 	message->AddFlat("messages", &propInfo);
2067 	return BView::GetSupportedSuites(message);
2068 }
2069 
2070 
2071 void
2072 TermView::ScrollTo(BPoint where)
2073 {
2074 //debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y);
2075 	float diff = where.y - fScrollOffset;
2076 	if (diff == 0)
2077 		return;
2078 
2079 	float bottom = Bounds().bottom;
2080 	int32 oldFirstLine = _LineAt(0);
2081 	int32 oldLastLine = _LineAt(bottom);
2082 	int32 newFirstLine = _LineAt(diff);
2083 	int32 newLastLine = _LineAt(bottom + diff);
2084 
2085 	fScrollOffset = where.y;
2086 
2087 	// invalidate the current cursor position before scrolling
2088 	_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2089 
2090 	// scroll contents
2091 	BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop()));
2092 	BRect sourceRect(destRect.OffsetByCopy(0, diff));
2093 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2094 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2095 //destRect.left, destRect.top, destRect.right, destRect.bottom);
2096 	CopyBits(sourceRect, destRect);
2097 
2098 	// sync visible text buffer with text buffer
2099 	if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) {
2100 		if (newFirstLine != oldFirstLine)
2101 {
2102 //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
2103 			fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
2104 }
2105 		BAutolock _(fTextBuffer);
2106 		if (diff < 0)
2107 			_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
2108 		else
2109 			_SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine);
2110 	}
2111 }
2112 
2113 
2114 void
2115 TermView::TargetedByScrollView(BScrollView *scrollView)
2116 {
2117 	BView::TargetedByScrollView(scrollView);
2118 
2119 	SetScrollBar(scrollView ? scrollView->ScrollBar(B_VERTICAL) : NULL);
2120 }
2121 
2122 
2123 BHandler*
2124 TermView::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
2125 	int32 what, const char* property)
2126 {
2127 	BHandler* target = this;
2128 	BPropertyInfo propInfo(sPropList);
2129 	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK) {
2130 		target = BView::ResolveSpecifier(message, index, specifier, what,
2131 			property);
2132 	}
2133 
2134 	return target;
2135 }
2136 
2137 
2138 void
2139 TermView::_SecondaryMouseButtonDropped(BMessage* msg)
2140 {
2141 	// Launch menu to choose what is to do with the msg data
2142 	BPoint point;
2143 	if (msg->FindPoint("_drop_point_", &point) != B_OK)
2144 		return;
2145 
2146 	BMessage* insertMessage = new BMessage(*msg);
2147 	insertMessage->what = kSecondaryMouseDropAction;
2148 	insertMessage->AddInt8("action", kInsert);
2149 
2150 	BMessage* cdMessage = new BMessage(*msg);
2151 	cdMessage->what = kSecondaryMouseDropAction;
2152 	cdMessage->AddInt8("action", kChangeDirectory);
2153 
2154 	BMessage* lnMessage = new BMessage(*msg);
2155 	lnMessage->what = kSecondaryMouseDropAction;
2156 	lnMessage->AddInt8("action", kLinkFiles);
2157 
2158 	BMessage* mvMessage = new BMessage(*msg);
2159 	mvMessage->what = kSecondaryMouseDropAction;
2160 	mvMessage->AddInt8("action", kMoveFiles);
2161 
2162 	BMessage* cpMessage = new BMessage(*msg);
2163 	cpMessage->what = kSecondaryMouseDropAction;
2164 	cpMessage->AddInt8("action", kCopyFiles);
2165 
2166 	BMenuItem* insertItem = new BMenuItem(
2167 		B_TRANSLATE("Insert path"), insertMessage);
2168 	BMenuItem* cdItem = new BMenuItem(
2169 		B_TRANSLATE("Change directory"), cdMessage);
2170 	BMenuItem* lnItem = new BMenuItem(
2171 		B_TRANSLATE("Create link here"), lnMessage);
2172 	BMenuItem* mvItem = new BMenuItem(B_TRANSLATE("Move here"), mvMessage);
2173 	BMenuItem* cpItem = new BMenuItem(B_TRANSLATE("Copy here"), cpMessage);
2174 	BMenuItem* chItem = new BMenuItem(B_TRANSLATE("Cancel"), NULL);
2175 
2176 	// if the refs point to different directorys disable the cd menu item
2177 	bool differentDirs = false;
2178 	BDirectory firstDir;
2179 	entry_ref ref;
2180 	int i = 0;
2181 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
2182 		BNode node(&ref);
2183 		BEntry entry(&ref);
2184 		BDirectory dir;
2185 		if (node.IsDirectory())
2186 			dir.SetTo(&ref);
2187 		else
2188 			entry.GetParent(&dir);
2189 
2190 		if (i == 1) {
2191 			node_ref nodeRef;
2192 			dir.GetNodeRef(&nodeRef);
2193 			firstDir.SetTo(&nodeRef);
2194 		} else if (firstDir != dir) {
2195 			differentDirs = true;
2196 			break;
2197 		}
2198 	}
2199 	if (differentDirs)
2200 		cdItem->SetEnabled(false);
2201 
2202 	BPopUpMenu *menu = new BPopUpMenu(
2203 		"Secondary mouse button drop menu");
2204 	menu->SetAsyncAutoDestruct(true);
2205 	menu->AddItem(insertItem);
2206 	menu->AddSeparatorItem();
2207 	menu->AddItem(cdItem);
2208 	menu->AddItem(lnItem);
2209 	menu->AddItem(mvItem);
2210 	menu->AddItem(cpItem);
2211 	menu->AddSeparatorItem();
2212 	menu->AddItem(chItem);
2213 	menu->SetTargetForItems(this);
2214 	menu->Go(point, true, true, true);
2215 }
2216 
2217 
2218 void
2219 TermView::_DoSecondaryMouseDropAction(BMessage* msg)
2220 {
2221 	int8 action = -1;
2222 	msg->FindInt8("action", &action);
2223 
2224 	BString outString = "";
2225 	BString itemString = "";
2226 
2227 	switch (action) {
2228 		case kInsert:
2229 			break;
2230 		case kChangeDirectory:
2231 			outString = "cd ";
2232 			break;
2233 		case kLinkFiles:
2234 			outString = "ln -s ";
2235 			break;
2236 		case kMoveFiles:
2237 			outString = "mv ";
2238 			break;
2239 		case kCopyFiles:
2240 			outString = "cp ";
2241 			break;
2242 
2243 		default:
2244 			return;
2245 	}
2246 
2247 	bool listContainsDirectory = false;
2248 	entry_ref ref;
2249 	int32 i = 0;
2250 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
2251 		BEntry ent(&ref);
2252 		BNode node(&ref);
2253 		BPath path(&ent);
2254 		BString string(path.Path());
2255 
2256 		if (node.IsDirectory())
2257 			listContainsDirectory = true;
2258 
2259 		if (i > 1)
2260 			itemString += " ";
2261 
2262 		if (action == kChangeDirectory) {
2263 			if (!node.IsDirectory()) {
2264 				int32 slash = string.FindLast("/");
2265 				string.Truncate(slash);
2266 			}
2267 			string.CharacterEscape(kEscapeCharacters, '\\');
2268 			itemString += string;
2269 			break;
2270 		}
2271 		string.CharacterEscape(kEscapeCharacters, '\\');
2272 		itemString += string;
2273 	}
2274 
2275 	if (listContainsDirectory && action == kCopyFiles)
2276 		outString += "-R ";
2277 
2278 	outString += itemString;
2279 
2280 	if (action == kLinkFiles || action == kMoveFiles || action == kCopyFiles)
2281 		outString += " .";
2282 
2283 	if (action != kInsert)
2284 		outString += "\n";
2285 
2286 	_WritePTY(outString.String(), outString.Length());
2287 }
2288 
2289 
2290 //! Gets dropped file full path and display it at cursor position.
2291 void
2292 TermView::_DoFileDrop(entry_ref& ref)
2293 {
2294 	BEntry ent(&ref);
2295 	BPath path(&ent);
2296 	BString string(path.Path());
2297 
2298 	string.CharacterEscape(kEscapeCharacters, '\\');
2299 	_WritePTY(string.String(), string.Length());
2300 }
2301 
2302 
2303 /*!	Text buffer must already be locked.
2304 */
2305 void
2306 TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
2307 	int32 visibleDirtyBottom)
2308 {
2309 	TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo();
2310 	int32 linesScrolled = info.linesScrolled;
2311 
2312 //debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, "
2313 //"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom,
2314 //info.linesScrolled, visibleDirtyTop, visibleDirtyBottom);
2315 
2316 	bigtime_t now = system_time();
2317 	bigtime_t timeElapsed = now - fLastSyncTime;
2318 	if (timeElapsed > 2 * kSyncUpdateGranularity) {
2319 		// last sync was ages ago
2320 		fLastSyncTime = now;
2321 		fScrolledSinceLastSync = linesScrolled;
2322 	}
2323 
2324 	if (fSyncRunner == NULL) {
2325 		// We consider clocked syncing when more than a full screen height has
2326 		// been scrolled in less than a sync update period. Once we're
2327 		// actively considering it, the same condition will convince us to
2328 		// actually do it.
2329 		if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2330 			// Condition doesn't hold yet. Reset if time is up, or otherwise
2331 			// keep counting.
2332 			if (timeElapsed > kSyncUpdateGranularity) {
2333 				fConsiderClockedSync = false;
2334 				fLastSyncTime = now;
2335 				fScrolledSinceLastSync = linesScrolled;
2336 			} else
2337 				fScrolledSinceLastSync += linesScrolled;
2338 		} else if (fConsiderClockedSync) {
2339 			// We are convinced -- create the sync runner.
2340 			fLastSyncTime = now;
2341 			fScrolledSinceLastSync = 0;
2342 
2343 			BMessage message(MSG_TERMINAL_BUFFER_CHANGED);
2344 			fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this),
2345 				&message, kSyncUpdateGranularity);
2346 			if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK)
2347 				return;
2348 
2349 			delete fSyncRunner;
2350 			fSyncRunner = NULL;
2351 		} else {
2352 			// Looks interesting so far. Reset the counts and consider clocked
2353 			// syncing.
2354 			fConsiderClockedSync = true;
2355 			fLastSyncTime = now;
2356 			fScrolledSinceLastSync = 0;
2357 		}
2358 	} else if (timeElapsed < kSyncUpdateGranularity) {
2359 		// sync time not passed yet -- keep counting
2360 		fScrolledSinceLastSync += linesScrolled;
2361 		return;
2362 	} else if (fScrolledSinceLastSync + linesScrolled <= fRows) {
2363 		// time's up, but not enough happened
2364 		delete fSyncRunner;
2365 		fSyncRunner = NULL;
2366 		fLastSyncTime = now;
2367 		fScrolledSinceLastSync = linesScrolled;
2368 	} else {
2369 		// Things are still rolling, but the sync time's up.
2370 		fLastSyncTime = now;
2371 		fScrolledSinceLastSync = 0;
2372 	}
2373 
2374 	// Simple case first -- complete invalidation.
2375 	if (info.invalidateAll) {
2376 		Invalidate();
2377 		_UpdateScrollBarRange();
2378 		_Deselect();
2379 
2380 		fCursor = fTextBuffer->Cursor();
2381 		_ActivateCursor(false);
2382 
2383 		int32 offset = _LineAt(0);
2384 		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
2385 			offset + fTextBuffer->Height() + 2);
2386 
2387 		info.Reset();
2388 		return;
2389 	}
2390 
2391 	BRect bounds = Bounds();
2392 	int32 firstVisible = _LineAt(0);
2393 	int32 lastVisible = _LineAt(bounds.bottom);
2394 	int32 historySize = fTextBuffer->HistorySize();
2395 
2396 	bool doScroll = false;
2397 	if (linesScrolled > 0) {
2398 		_UpdateScrollBarRange();
2399 
2400 		visibleDirtyTop -= linesScrolled;
2401 		visibleDirtyBottom -= linesScrolled;
2402 
2403 		if (firstVisible < 0) {
2404 			firstVisible -= linesScrolled;
2405 			lastVisible -= linesScrolled;
2406 
2407 			float scrollOffset;
2408 			if (firstVisible < -historySize) {
2409 				firstVisible = -historySize;
2410 				doScroll = true;
2411 				scrollOffset = -historySize * fFontHeight;
2412 				// We need to invalidate the lower linesScrolled lines of the
2413 				// visible text buffer, since those will be scrolled up and
2414 				// need to be replaced. We just use visibleDirty{Top,Bottom}
2415 				// for that purpose. Unless invoked from ScrollTo() (i.e.
2416 				// user-initiated scrolling) those are unused. In the unlikely
2417 				// case that the user is scrolling at the same time we may
2418 				// invalidate too many lines, since we have to extend the given
2419 				// region.
2420 				// Note that in the firstVisible == 0 case the new lines are
2421 				// already in the dirty region, so they will be updated anyway.
2422 				if (visibleDirtyTop <= visibleDirtyBottom) {
2423 					if (lastVisible < visibleDirtyTop)
2424 						visibleDirtyTop = lastVisible;
2425 					if (visibleDirtyBottom < lastVisible + linesScrolled)
2426 						visibleDirtyBottom = lastVisible + linesScrolled;
2427 				} else {
2428 					visibleDirtyTop = lastVisible + 1;
2429 					visibleDirtyBottom = lastVisible + linesScrolled;
2430 				}
2431 			} else
2432 				scrollOffset = fScrollOffset - linesScrolled * fFontHeight;
2433 
2434 			_ScrollTo(scrollOffset, false);
2435 		} else
2436 			doScroll = true;
2437 
2438 		if (doScroll && lastVisible >= firstVisible
2439 			&& !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop
2440 				&& lastVisible <= info.dirtyBottom)) {
2441 			// scroll manually
2442 			float scrollBy = linesScrolled * fFontHeight;
2443 			BRect destRect(Frame().OffsetToCopy(B_ORIGIN));
2444 			BRect sourceRect(destRect.OffsetByCopy(0, scrollBy));
2445 
2446 			// invalidate the current cursor position before scrolling
2447 			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2448 
2449 //debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n",
2450 //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom,
2451 //destRect.left, destRect.top, destRect.right, destRect.bottom);
2452 			CopyBits(sourceRect, destRect);
2453 
2454 			fVisibleTextBuffer->ScrollBy(linesScrolled);
2455 		}
2456 
2457 		// move selection
2458 		if (fSelStart != fSelEnd) {
2459 			fSelStart.y -= linesScrolled;
2460 			fSelEnd.y -= linesScrolled;
2461 			fInitialSelectionStart.y -= linesScrolled;
2462 			fInitialSelectionEnd.y -= linesScrolled;
2463 
2464 			if (fSelStart.y < -historySize)
2465 				_Deselect();
2466 		}
2467 	}
2468 
2469 	// invalidate dirty region
2470 	if (info.IsDirtyRegionValid()) {
2471 		_InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1,
2472 			info.dirtyBottom);
2473 
2474 		// clear the selection, if affected
2475 		if (fSelStart != fSelEnd) {
2476 			// TODO: We're clearing the selection more often than necessary --
2477 			// to avoid that, we'd also need to track the x coordinates of the
2478 			// dirty range.
2479 			int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1;
2480 			if (fSelStart.y <= info.dirtyBottom
2481 				&& info.dirtyTop <= selectionBottom) {
2482 				_Deselect();
2483 			}
2484 		}
2485 	}
2486 
2487 	if (visibleDirtyTop <= visibleDirtyBottom)
2488 		info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom);
2489 
2490 	if (linesScrolled != 0 || info.IsDirtyRegionValid()) {
2491 		fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible,
2492 			info.dirtyTop, info.dirtyBottom);
2493 	}
2494 
2495 	// invalidate cursor, if it changed
2496 	TermPos cursor = fTextBuffer->Cursor();
2497 	if (fCursor != cursor || linesScrolled != 0) {
2498 		// Before we scrolled we did already invalidate the old cursor.
2499 		if (!doScroll)
2500 			_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2501 		fCursor = cursor;
2502 		_InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y);
2503 		_ActivateCursor(false);
2504 	}
2505 
2506 	info.Reset();
2507 }
2508 
2509 
2510 /*!	Write strings to PTY device. If encoding system isn't UTF8, change
2511 	encoding to UTF8 before writing PTY.
2512 */
2513 void
2514 TermView::_WritePTY(const char* text, int32 numBytes)
2515 {
2516 	if (fEncoding != M_UTF8) {
2517 		while (numBytes > 0) {
2518 			char buffer[1024];
2519 			int32 bufferSize = sizeof(buffer);
2520 			int32 sourceSize = numBytes;
2521 			int32 state = 0;
2522 			if (convert_to_utf8(fEncoding, text, &sourceSize, buffer,
2523 					&bufferSize, &state) != B_OK || bufferSize == 0) {
2524 				break;
2525 			}
2526 
2527 			fShell->Write(buffer, bufferSize);
2528 			text += sourceSize;
2529 			numBytes -= sourceSize;
2530 		}
2531 	} else {
2532 		fShell->Write(text, numBytes);
2533 	}
2534 }
2535 
2536 
2537 //! Returns the square of the actual pixel distance between both points
2538 float
2539 TermView::_MouseDistanceSinceLastClick(BPoint where)
2540 {
2541 	return (fLastClickPoint.x - where.x) * (fLastClickPoint.x - where.x)
2542 		+ (fLastClickPoint.y - where.y) * (fLastClickPoint.y - where.y);
2543 }
2544 
2545 
2546 void
2547 TermView::_SendMouseEvent(int32 buttons, int32 mode, int32 x, int32 y,
2548 	bool motion)
2549 {
2550 	char xtermButtons;
2551 	if (buttons == B_PRIMARY_MOUSE_BUTTON)
2552 		xtermButtons = 32 + 0;
2553  	else if (buttons == B_SECONDARY_MOUSE_BUTTON)
2554 		xtermButtons = 32 + 1;
2555 	else if (buttons == B_TERTIARY_MOUSE_BUTTON)
2556 		xtermButtons = 32 + 2;
2557 	else
2558 		xtermButtons = 32 + 3;
2559 
2560 	if (motion)
2561 		xtermButtons += 32;
2562 
2563 	char xtermX = x + 1 + 32;
2564 	char xtermY = y + 1 + 32;
2565 
2566 	char destBuffer[6];
2567 	destBuffer[0] = '\033';
2568 	destBuffer[1] = '[';
2569 	destBuffer[2] = 'M';
2570 	destBuffer[3] = xtermButtons;
2571 	destBuffer[4] = xtermX;
2572 	destBuffer[5] = xtermY;
2573 	fShell->Write(destBuffer, 6);
2574 }
2575 
2576 
2577 void
2578 TermView::MouseDown(BPoint where)
2579 {
2580 	if (!IsFocus())
2581 		MakeFocus();
2582 
2583 	int32 buttons;
2584 	int32 modifier;
2585 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2586 	Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2587 
2588 	fMouseButtons = buttons;
2589 
2590 	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2591 		|| fReportNormalMouseEvent || fReportX10MouseEvent) {
2592   		TermPos clickPos = _ConvertToTerminal(where);
2593   		_SendMouseEvent(buttons, modifier, clickPos.x, clickPos.y, false);
2594 		return;
2595 	}
2596 
2597 	// paste button
2598 	if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
2599 		Paste(fMouseClipboard);
2600 		fLastClickPoint = where;
2601 		return;
2602 	}
2603 
2604 	// Select Region
2605 	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
2606 		int32 clicks;
2607 		Window()->CurrentMessage()->FindInt32("clicks", &clicks);
2608 
2609 		if (_HasSelection()) {
2610 			TermPos inPos = _ConvertToTerminal(where);
2611 			if (_CheckSelectedRegion(inPos)) {
2612 				if (modifier & B_CONTROL_KEY) {
2613 					BPoint p;
2614 					uint32 bt;
2615 					do {
2616 						GetMouse(&p, &bt);
2617 
2618 						if (bt == 0) {
2619 							_Deselect();
2620 							return;
2621 						}
2622 
2623 						snooze(40000);
2624 
2625 					} while (abs((int)(where.x - p.x)) < 4
2626 						&& abs((int)(where.y - p.y)) < 4);
2627 
2628 					InitiateDrag();
2629 					return;
2630 				}
2631 			}
2632 		}
2633 
2634 		// If mouse has moved too much, disable double/triple click.
2635 		if (_MouseDistanceSinceLastClick(where) > 8)
2636 			clicks = 1;
2637 
2638 		SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
2639 			B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
2640 
2641 		TermPos clickPos = _ConvertToTerminal(where);
2642 
2643 		if (modifier & B_SHIFT_KEY) {
2644 			fInitialSelectionStart = clickPos;
2645 			fInitialSelectionEnd = clickPos;
2646 			_ExtendSelection(fInitialSelectionStart, true, false);
2647 		} else {
2648 			_Deselect();
2649 			fInitialSelectionStart = clickPos;
2650 			fInitialSelectionEnd = clickPos;
2651 		}
2652 
2653 		// If clicks larger than 3, reset mouse click counter.
2654 		clicks = (clicks - 1) % 3 + 1;
2655 
2656 		switch (clicks) {
2657 			case 1:
2658 				fCheckMouseTracking = true;
2659 				fSelectGranularity = SELECT_CHARS;
2660 				break;
2661 
2662 			case 2:
2663 				_SelectWord(where, (modifier & B_SHIFT_KEY) != 0, false);
2664 				fMouseTracking = true;
2665 				fSelectGranularity = SELECT_WORDS;
2666 				break;
2667 
2668 			case 3:
2669 				_SelectLine(where, (modifier & B_SHIFT_KEY) != 0, false);
2670 				fMouseTracking = true;
2671 				fSelectGranularity = SELECT_LINES;
2672 				break;
2673 		}
2674 	}
2675 	fLastClickPoint = where;
2676 }
2677 
2678 
2679 void
2680 TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
2681 {
2682 	if (fReportAnyMouseEvent || fReportButtonMouseEvent) {
2683 		int32 modifier;
2684 		Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
2685 
2686   		TermPos clickPos = _ConvertToTerminal(where);
2687 
2688   		if (fReportButtonMouseEvent) {
2689   			if (fPrevPos.x != clickPos.x || fPrevPos.y != clickPos.y) {
2690 		  		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y,
2691 					true);
2692   			}
2693   			fPrevPos = clickPos;
2694   			return;
2695   		}
2696   		_SendMouseEvent(fMouseButtons, modifier, clickPos.x, clickPos.y, true);
2697 		return;
2698 	}
2699 
2700 	if (fCheckMouseTracking) {
2701 		if (_MouseDistanceSinceLastClick(where) > 9)
2702 			fMouseTracking = true;
2703 	}
2704 	if (!fMouseTracking)
2705 		return;
2706 
2707 	bool doAutoScroll = false;
2708 
2709 	if (where.y < 0) {
2710 		doAutoScroll = true;
2711 		fAutoScrollSpeed = where.y;
2712 		where.x = 0;
2713 		where.y = 0;
2714 	}
2715 
2716 	BRect bounds(Bounds());
2717 	if (where.y > bounds.bottom) {
2718 		doAutoScroll = true;
2719 		fAutoScrollSpeed = where.y - bounds.bottom;
2720 		where.x = bounds.right;
2721 		where.y = bounds.bottom;
2722 	}
2723 
2724 	if (doAutoScroll) {
2725 		if (fAutoScrollRunner == NULL) {
2726 			BMessage message(kAutoScroll);
2727 			fAutoScrollRunner = new (std::nothrow) BMessageRunner(
2728 				BMessenger(this), &message, 10000);
2729 		}
2730 	} else {
2731 		delete fAutoScrollRunner;
2732 		fAutoScrollRunner = NULL;
2733 	}
2734 
2735 	switch (fSelectGranularity) {
2736 		case SELECT_CHARS:
2737 		{
2738 			// If we just start selecting, we first select the initially
2739 			// hit char, so that we get a proper initial selection -- the char
2740 			// in question, which will thus always be selected, regardless of
2741 			// whether selecting forward or backward.
2742 			if (fInitialSelectionStart == fInitialSelectionEnd) {
2743 				_Select(fInitialSelectionStart, fInitialSelectionEnd, true,
2744 					true);
2745 			}
2746 
2747 			_ExtendSelection(_ConvertToTerminal(where), true, true);
2748 			break;
2749 		}
2750 		case SELECT_WORDS:
2751 			_SelectWord(where, true, true);
2752 			break;
2753 		case SELECT_LINES:
2754 			_SelectLine(where, true, true);
2755 			break;
2756 	}
2757 }
2758 
2759 
2760 void
2761 TermView::MouseUp(BPoint where)
2762 {
2763 	fCheckMouseTracking = false;
2764 	fMouseTracking = false;
2765 
2766 	if (fAutoScrollRunner != NULL) {
2767 		delete fAutoScrollRunner;
2768 		fAutoScrollRunner = NULL;
2769 	}
2770 
2771 	// When releasing the first mouse button, we copy the selected text to the
2772 	// clipboard.
2773 	int32 buttons;
2774 	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
2775 
2776 	if (fReportAnyMouseEvent || fReportButtonMouseEvent
2777 		|| fReportNormalMouseEvent) {
2778 	  	TermPos clickPos = _ConvertToTerminal(where);
2779 	  	_SendMouseEvent(0, 0, clickPos.x, clickPos.y, false);
2780 	} else {
2781 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0
2782 			&& (fMouseButtons & B_PRIMARY_MOUSE_BUTTON) != 0) {
2783 			Copy(fMouseClipboard);
2784 		}
2785 
2786 	}
2787 	fMouseButtons = buttons;
2788 }
2789 
2790 
2791 //! Select a range of text.
2792 void
2793 TermView::_Select(TermPos start, TermPos end, bool inclusive,
2794 	bool setInitialSelection)
2795 {
2796 	BAutolock _(fTextBuffer);
2797 
2798 	_SynchronizeWithTextBuffer(0, -1);
2799 
2800 	if (end < start)
2801 		std::swap(start, end);
2802 
2803 	if (inclusive)
2804 		end.x++;
2805 
2806 //debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x,
2807 //start.y, end.x, end.y);
2808 
2809 	if (start.x < 0)
2810 		start.x = 0;
2811 	if (end.x >= fColumns)
2812 		end.x = fColumns;
2813 
2814 	TermPos minPos(0, -fTextBuffer->HistorySize());
2815 	TermPos maxPos(0, fTextBuffer->Height());
2816 	start = restrict_value(start, minPos, maxPos);
2817 	end = restrict_value(end, minPos, maxPos);
2818 
2819 	// if the end is past the end of the line, select the line break, too
2820 	if (fTextBuffer->LineLength(end.y) < end.x
2821 			&& end.y < fTextBuffer->Height()) {
2822 		end.y++;
2823 		end.x = 0;
2824 	}
2825 
2826 	if (fTextBuffer->IsFullWidthChar(start.y, start.x)) {
2827 		start.x--;
2828 		if (start.x < 0)
2829 			start.x = 0;
2830 	}
2831 
2832 	if (fTextBuffer->IsFullWidthChar(end.y, end.x)) {
2833 		end.x++;
2834 		if (end.x >= fColumns)
2835 			end.x = fColumns;
2836 	}
2837 
2838 	if (fSelStart != fSelEnd)
2839 		_InvalidateTextRange(fSelStart, fSelEnd);
2840 
2841 	fSelStart = start;
2842 	fSelEnd = end;
2843 
2844 	if (setInitialSelection) {
2845 		fInitialSelectionStart = fSelStart;
2846 		fInitialSelectionEnd = fSelEnd;
2847 	}
2848 
2849 	_InvalidateTextRange(fSelStart, fSelEnd);
2850 }
2851 
2852 
2853 //! Extend selection (shift + mouse click).
2854 void
2855 TermView::_ExtendSelection(TermPos pos, bool inclusive,
2856 	bool useInitialSelection)
2857 {
2858 	if (!useInitialSelection && !_HasSelection())
2859 		return;
2860 
2861 	TermPos start = fSelStart;
2862 	TermPos end = fSelEnd;
2863 
2864 	if (useInitialSelection) {
2865 		start = fInitialSelectionStart;
2866 		end = fInitialSelectionEnd;
2867 	}
2868 
2869 	if (inclusive) {
2870 		if (pos >= start && pos >= end)
2871 			pos.x++;
2872 	}
2873 
2874 	if (pos < start)
2875 		_Select(pos, end, false, !useInitialSelection);
2876 	else if (pos > end)
2877 		_Select(start, pos, false, !useInitialSelection);
2878 	else if (useInitialSelection)
2879 		_Select(start, end, false, false);
2880 }
2881 
2882 
2883 // clear the selection.
2884 void
2885 TermView::_Deselect()
2886 {
2887 //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
2888 	if (!_HasSelection())
2889 		return;
2890 
2891 	_InvalidateTextRange(fSelStart, fSelEnd);
2892 
2893 	fSelStart.SetTo(0, 0);
2894 	fSelEnd.SetTo(0, 0);
2895 	fInitialSelectionStart.SetTo(0, 0);
2896 	fInitialSelectionEnd.SetTo(0, 0);
2897 }
2898 
2899 
2900 bool
2901 TermView::_HasSelection() const
2902 {
2903 	return fSelStart != fSelEnd;
2904 }
2905 
2906 
2907 void
2908 TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
2909 {
2910 	BAutolock _(fTextBuffer);
2911 
2912 	TermPos pos = _ConvertToTerminal(where);
2913 	TermPos start, end;
2914 	if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end))
2915 		return;
2916 
2917 	if (extend) {
2918 		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2919 			_ExtendSelection(start, false, useInitialSelection);
2920 		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2921 			_ExtendSelection(end, false, useInitialSelection);
2922 		else if (useInitialSelection)
2923 			_Select(start, end, false, false);
2924 	} else
2925 		_Select(start, end, false, !useInitialSelection);
2926 }
2927 
2928 
2929 void
2930 TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
2931 {
2932 	TermPos start = TermPos(0, _ConvertToTerminal(where).y);
2933 	TermPos end = TermPos(0, start.y + 1);
2934 
2935 	if (extend) {
2936 		if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
2937 			_ExtendSelection(start, false, useInitialSelection);
2938 		else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
2939 			_ExtendSelection(end, false, useInitialSelection);
2940 		else if (useInitialSelection)
2941 			_Select(start, end, false, false);
2942 	} else
2943 		_Select(start, end, false, !useInitialSelection);
2944 }
2945 
2946 
2947 void
2948 TermView::_AutoScrollUpdate()
2949 {
2950 	if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) {
2951 		float value = fScrollBar->Value();
2952 		_ScrollTo(value + fAutoScrollSpeed, true);
2953 		if (fAutoScrollSpeed < 0) {
2954 			_ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true);
2955 		} else {
2956 			_ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true,
2957 				true);
2958 		}
2959 	}
2960 }
2961 
2962 
2963 bool
2964 TermView::_CheckSelectedRegion(const TermPos &pos) const
2965 {
2966 	return pos >= fSelStart && pos < fSelEnd;
2967 }
2968 
2969 
2970 bool
2971 TermView::_CheckSelectedRegion(int32 row, int32 firstColumn,
2972 	int32& lastColumn) const
2973 {
2974 	if (fSelStart == fSelEnd)
2975 		return false;
2976 
2977 	if (row == fSelStart.y && firstColumn < fSelStart.x
2978 			&& lastColumn >= fSelStart.x) {
2979 		// region starts before the selection, but intersects with it
2980 		lastColumn = fSelStart.x - 1;
2981 		return false;
2982 	}
2983 
2984 	if (row == fSelEnd.y && firstColumn < fSelEnd.x
2985 			&& lastColumn >= fSelEnd.x) {
2986 		// region starts in the selection, but exceeds the end
2987 		lastColumn = fSelEnd.x - 1;
2988 		return true;
2989 	}
2990 
2991 	TermPos pos(firstColumn, row);
2992 	return pos >= fSelStart && pos < fSelEnd;
2993 }
2994 
2995 
2996 void
2997 TermView::GetFrameSize(float *width, float *height)
2998 {
2999 	int32 historySize;
3000 	{
3001 		BAutolock _(fTextBuffer);
3002 		historySize = fTextBuffer->HistorySize();
3003 	}
3004 
3005 	if (width != NULL)
3006 		*width = fColumns * fFontWidth;
3007 
3008 	if (height != NULL)
3009 		*height = (fRows + historySize) * fFontHeight;
3010 }
3011 
3012 
3013 // Find a string, and select it if found
3014 bool
3015 TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
3016 	bool matchWord)
3017 {
3018 	BAutolock _(fTextBuffer);
3019 	_SynchronizeWithTextBuffer(0, -1);
3020 
3021 	TermPos start;
3022 	if (_HasSelection()) {
3023 		if (forwardSearch)
3024 			start = fSelEnd;
3025 		else
3026 			start = fSelStart;
3027 	} else {
3028 		// search from the very beginning/end
3029 		if (forwardSearch)
3030 			start = TermPos(0, -fTextBuffer->HistorySize());
3031 		else
3032 			start = TermPos(0, fTextBuffer->Height());
3033 	}
3034 
3035 	TermPos matchStart, matchEnd;
3036 	if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase,
3037 			matchWord, matchStart, matchEnd)) {
3038 		return false;
3039 	}
3040 
3041 	_Select(matchStart, matchEnd, false, true);
3042 	_ScrollToRange(fSelStart, fSelEnd);
3043 
3044 	return true;
3045 }
3046 
3047 
3048 //! Get the selected text and copy to str
3049 void
3050 TermView::GetSelection(BString &str)
3051 {
3052 	str.SetTo("");
3053 	BAutolock _(fTextBuffer);
3054 	fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
3055 }
3056 
3057 
3058 bool
3059 TermView::CheckShellGone() const
3060 {
3061 	if (!fShell)
3062 		return false;
3063 
3064 	// check, if the shell does still live
3065 	pid_t pid = fShell->ProcessID();
3066 	team_info info;
3067 	return get_team_info(pid, &info) == B_BAD_TEAM_ID;
3068 }
3069 
3070 
3071 void
3072 TermView::InitiateDrag()
3073 {
3074 	BAutolock _(fTextBuffer);
3075 
3076 	BString copyStr("");
3077 	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
3078 
3079 	BMessage message(B_MIME_DATA);
3080 	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
3081 		copyStr.Length());
3082 
3083 	BPoint start = _ConvertFromTerminal(fSelStart);
3084 	BPoint end = _ConvertFromTerminal(fSelEnd);
3085 
3086 	BRect rect;
3087 	if (fSelStart.y == fSelEnd.y)
3088 		rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
3089 	else
3090 		rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
3091 
3092 	rect = rect & Bounds();
3093 
3094 	DragMessage(&message, rect);
3095 }
3096 
3097 
3098 void
3099 TermView::_ScrollTo(float y, bool scrollGfx)
3100 {
3101 	if (!scrollGfx)
3102 		fScrollOffset = y;
3103 
3104 	if (fScrollBar != NULL)
3105 		fScrollBar->SetValue(y);
3106 	else
3107 		ScrollTo(BPoint(0, y));
3108 }
3109 
3110 
3111 void
3112 TermView::_ScrollToRange(TermPos start, TermPos end)
3113 {
3114 	if (start > end)
3115 		std::swap(start, end);
3116 
3117 	float startY = _LineOffset(start.y);
3118 	float endY = _LineOffset(end.y) + fFontHeight - 1;
3119 	float height = Bounds().Height();
3120 
3121 	if (endY - startY > height) {
3122 		// The range is greater than the height. Scroll to the closest border.
3123 
3124 		// already as good as it gets?
3125 		if (startY <= 0 && endY >= height)
3126 			return;
3127 
3128 		if (startY > 0) {
3129 			// scroll down to align the start with the top of the view
3130 			_ScrollTo(fScrollOffset + startY, true);
3131 		} else {
3132 			// scroll up to align the end with the bottom of the view
3133 			_ScrollTo(fScrollOffset + endY - height, true);
3134 		}
3135 	} else {
3136 		// The range is smaller than the height.
3137 
3138 		// already visible?
3139 		if (startY >= 0 && endY <= height)
3140 			return;
3141 
3142 		if (startY < 0) {
3143 			// scroll up to make the start visible
3144 			_ScrollTo(fScrollOffset + startY, true);
3145 		} else {
3146 			// scroll down to make the end visible
3147 			_ScrollTo(fScrollOffset + endY - height, true);
3148 		}
3149 	}
3150 }
3151 
3152 
3153 void
3154 TermView::DisableResizeView(int32 disableCount)
3155 {
3156 	fResizeViewDisableCount += disableCount;
3157 }
3158 
3159 
3160 void
3161 TermView::_DrawInlineMethodString()
3162 {
3163 	if (!fInline || !fInline->String())
3164 		return;
3165 
3166 	const int32 numChars = BString(fInline->String()).CountChars();
3167 
3168 	BPoint startPoint = _ConvertFromTerminal(fCursor);
3169 	BPoint endPoint = startPoint;
3170 	endPoint.x += fFontWidth * numChars;
3171 	endPoint.y += fFontHeight + 1;
3172 
3173 	BRect eraseRect(startPoint, endPoint);
3174 
3175 	PushState();
3176 	SetHighColor(kTermColorTable[7]);
3177 	FillRect(eraseRect);
3178 	PopState();
3179 
3180 	BPoint loc = _ConvertFromTerminal(fCursor);
3181 	loc.y += fFontHeight;
3182 	SetFont(&fHalfFont);
3183 	SetHighColor(kTermColorTable[0]);
3184 	SetLowColor(kTermColorTable[7]);
3185 	DrawString(fInline->String(), loc);
3186 }
3187 
3188 
3189 void
3190 TermView::_HandleInputMethodChanged(BMessage *message)
3191 {
3192 	const char *string = NULL;
3193 	if (message->FindString("be:string", &string) < B_OK || string == NULL)
3194 		return;
3195 
3196 	_ActivateCursor(false);
3197 
3198 	if (IsFocus())
3199 		be_app->ObscureCursor();
3200 
3201 	// If we find the "be:confirmed" boolean (and the boolean is true),
3202 	// it means it's over for now, so the current InlineInput object
3203 	// should become inactive. We will probably receive a
3204 	// B_INPUT_METHOD_STOPPED message after this one.
3205 	bool confirmed;
3206 	if (message->FindBool("be:confirmed", &confirmed) != B_OK)
3207 		confirmed = false;
3208 
3209 	fInline->SetString("");
3210 
3211 	Invalidate();
3212 	// TODO: Debug only
3213 	snooze(100000);
3214 
3215 	fInline->SetString(string);
3216 	fInline->ResetClauses();
3217 
3218 	if (!confirmed && !fInline->IsActive())
3219 		fInline->SetActive(true);
3220 
3221 	// Get the clauses, and pass them to the InlineInput object
3222 	// TODO: Find out if what we did it's ok, currently we don't consider
3223 	// clauses at all, while the bebook says we should; though the visual
3224 	// effect we obtained seems correct. Weird.
3225 	int32 clauseCount = 0;
3226 	int32 clauseStart;
3227 	int32 clauseEnd;
3228 	while (message->FindInt32("be:clause_start", clauseCount, &clauseStart)
3229 			== B_OK
3230 		&& message->FindInt32("be:clause_end", clauseCount, &clauseEnd)
3231 			== B_OK) {
3232 		if (!fInline->AddClause(clauseStart, clauseEnd))
3233 			break;
3234 		clauseCount++;
3235 	}
3236 
3237 	if (confirmed) {
3238 		fInline->SetString("");
3239 		_ActivateCursor(true);
3240 
3241 		// now we need to feed ourselves the individual characters as if the
3242 		// user would have pressed them now - this lets KeyDown() pick out all
3243 		// the special characters like B_BACKSPACE, cursor keys and the like:
3244 		const char* currPos = string;
3245 		const char* prevPos = currPos;
3246 		while (*currPos != '\0') {
3247 			if ((*currPos & 0xC0) == 0xC0) {
3248 				// found the start of an UTF-8 char, we collect while it lasts
3249 				++currPos;
3250 				while ((*currPos & 0xC0) == 0x80)
3251 					++currPos;
3252 			} else if ((*currPos & 0xC0) == 0x80) {
3253 				// illegal: character starts with utf-8 intermediate byte, skip it
3254 				prevPos = ++currPos;
3255 			} else {
3256 				// single byte character/code, just feed that
3257 				++currPos;
3258 			}
3259 			KeyDown(prevPos, currPos - prevPos);
3260 			prevPos = currPos;
3261 		}
3262 
3263 		Invalidate();
3264 	} else {
3265 		// temporarily show transient state of inline input
3266 		int32 selectionStart = 0;
3267 		int32 selectionEnd = 0;
3268 		message->FindInt32("be:selection", 0, &selectionStart);
3269 		message->FindInt32("be:selection", 1, &selectionEnd);
3270 
3271 		fInline->SetSelectionOffset(selectionStart);
3272 		fInline->SetSelectionLength(selectionEnd - selectionStart);
3273 		Invalidate();
3274 	}
3275 }
3276 
3277 
3278 void
3279 TermView::_HandleInputMethodLocationRequest()
3280 {
3281 	BMessage message(B_INPUT_METHOD_EVENT);
3282 	message.AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
3283 
3284 	BString string(fInline->String());
3285 
3286 	const int32 &limit = string.CountChars();
3287 	BPoint where = _ConvertFromTerminal(fCursor);
3288 	where.y += fFontHeight;
3289 
3290 	for (int32 i = 0; i < limit; i++) {
3291 		// Add the location of the UTF8 characters
3292 
3293 		where.x += fFontWidth;
3294 		ConvertToScreen(&where);
3295 
3296 		message.AddPoint("be:location_reply", where);
3297 		message.AddFloat("be:height_reply", fFontHeight);
3298 	}
3299 
3300 	fInline->Method()->SendMessage(&message);
3301 }
3302 
3303 
3304 
3305 void
3306 TermView::_CancelInputMethod()
3307 {
3308 	if (!fInline)
3309 		return;
3310 
3311 	InlineInput *inlineInput = fInline;
3312 	fInline = NULL;
3313 
3314 	if (inlineInput->IsActive() && Window()) {
3315 		Invalidate();
3316 
3317 		BMessage message(B_INPUT_METHOD_EVENT);
3318 		message.AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
3319 		inlineInput->Method()->SendMessage(&message);
3320 	}
3321 
3322 	delete inlineInput;
3323 }
3324 
3325 
3326 // #pragma mark - Listener
3327 
3328 
3329 TermView::Listener::~Listener()
3330 {
3331 }
3332 
3333 
3334 void
3335 TermView::Listener::NotifyTermViewQuit(TermView* view, int32 reason)
3336 {
3337 }
3338 
3339 
3340 void
3341 TermView::Listener::SetTermViewTitle(TermView* view, const char* title)
3342 {
3343 }
3344 
3345 
3346 void
3347 TermView::Listener::PreviousTermView(TermView* view)
3348 {
3349 }
3350 
3351 
3352 void
3353 TermView::Listener::NextTermView(TermView* view)
3354 {
3355 }
3356