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