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