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