TermView.cpp revision 901e5756
1/*
2 * Copyright 2001-2008, 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 */
12
13
14#include "TermView.h"
15
16#include "CodeConv.h"
17#include "Shell.h"
18#include "TermBuffer.h"
19#include "TermConst.h"
20#include "VTkeymap.h"
21
22#include <Alert.h>
23#include <Beep.h>
24#include <Clipboard.h>
25#include <Debug.h>
26#include <Dragger.h>
27#include <Input.h>
28#include <Message.h>
29#include <MessageRunner.h>
30#include <Path.h>
31#include <PopUpMenu.h>
32#include <PropertyInfo.h>
33#include <Roster.h>
34#include <ScrollBar.h>
35#include <String.h>
36#include <Window.h>
37
38#include <ctype.h>
39#include <signal.h>
40#include <stdlib.h>
41#include <string.h>
42#include <termios.h>
43
44#include <new>
45#include <algorithm>
46
47// defined VTKeyTbl.c
48extern int function_keycode_table[];
49extern char *function_key_char_table[];
50
51const static rgb_color kTermColorTable[8] = {
52	{ 40,  40,  40, 0},	// black
53	{204,   0,   0, 0},	// red
54	{ 78, 154,   6, 0},	// green
55	{218, 168,   0, 0},	// yellow
56	{ 51, 102, 152, 0},	// blue
57	{115,  68, 123, 0},	// magenta
58	{  6, 152, 154, 0},	// cyan
59	{245, 245, 245, 0},	// white
60};
61
62// Space at the borders of the view
63const int32 kOffset = 3;
64
65#define ROWS_DEFAULT 25
66#define COLUMNS_DEFAULT 80
67
68
69static property_info sPropList[] = {
70	{ "encoding",
71	{B_GET_PROPERTY, 0},
72	{B_DIRECT_SPECIFIER, 0},
73	"get terminal encoding"},
74	{ "encoding",
75	{B_SET_PROPERTY, 0},
76	{B_DIRECT_SPECIFIER, 0},
77	"set terminal encoding"},
78	{ "tty",
79	{B_GET_PROPERTY, 0},
80	{B_DIRECT_SPECIFIER, 0},
81	"get tty name."},
82	{ 0  }
83};
84
85
86const static uint32 kUpdateSigWinch = 'Rwin';
87
88const static rgb_color kBlackColor = { 0, 0, 0, 255 };
89const static rgb_color kWhiteColor = { 255, 255, 255, 255 };
90
91
92
93TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize)
94	: BView(frame, "termview", B_FOLLOW_ALL,
95		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE| B_PULSE_NEEDED),
96	fShell(NULL),
97	fWinchRunner(NULL),
98	fFontWidth(0),
99	fFontHeight(0),
100	fFontAscent(0),
101	fUpdateFlag(false),
102	fInsertModeFlag(MODE_OVER),
103	fScrollUpCount(0),
104	fScrollBarRange(0),
105	fFrameResized(false),
106	fLastCursorTime(0),
107	fCursorDrawFlag(true),
108	fCursorStatus(true),
109	fCursorBlinkingFlag(true),
110	fCursorRedrawFlag(true),
111	fCursorHeight(0),
112	fCurPos(0, 0),
113	fCurStack(0, 0),
114	fBufferStartPos(-1),
115	fTermRows(ROWS_DEFAULT),
116	fTermColumns(COLUMNS_DEFAULT),
117	fEncoding(M_UTF8),
118	fTop(0),
119	fTextBuffer(NULL),
120	fScrollBar(NULL),
121	fTextForeColor(kBlackColor),
122	fTextBackColor(kWhiteColor),
123	fCursorForeColor(kWhiteColor),
124	fCursorBackColor(kBlackColor),
125	fSelectForeColor(kWhiteColor),
126	fSelectBackColor(kBlackColor),
127	fScrTop(0),
128	fScrBot(fTermRows - 1),
129	fScrBufSize(historySize),
130	fScrRegionSet(0),
131	fClickPoint(0, 0),
132	fSelStart(-1, -1),
133	fSelEnd(-1, -1),
134	fMouseTracking(false),
135	fIMflag(false)
136{
137	_InitObject(argc, argv);
138}
139
140
141TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 historySize)
142	: BView(BRect(0, 0, 0, 0), "termview", B_FOLLOW_ALL,
143		B_WILL_DRAW | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE| B_PULSE_NEEDED),
144	fShell(NULL),
145	fWinchRunner(NULL),
146	fFontWidth(0),
147	fFontHeight(0),
148	fFontAscent(0),
149	fUpdateFlag(false),
150	fInsertModeFlag(MODE_OVER),
151	fScrollUpCount(0),
152	fScrollBarRange(0),
153	fFrameResized(false),
154	fLastCursorTime(0),
155	fCursorDrawFlag(true),
156	fCursorStatus(true),
157	fCursorBlinkingFlag(true),
158	fCursorRedrawFlag(true),
159	fCursorHeight(0),
160	fCurPos(0, 0),
161	fCurStack(0, 0),
162	fBufferStartPos(-1),
163	fTermRows(rows),
164	fTermColumns(columns),
165	fEncoding(M_UTF8),
166	fTop(0),
167	fTextBuffer(NULL),
168	fScrollBar(NULL),
169	fTextForeColor(kBlackColor),
170	fTextBackColor(kWhiteColor),
171	fCursorForeColor(kWhiteColor),
172	fCursorBackColor(kBlackColor),
173	fSelectForeColor(kWhiteColor),
174	fSelectBackColor(kBlackColor),
175	fScrTop(0),
176	fScrBot(fTermRows - 1),
177	fScrBufSize(historySize),
178	fScrRegionSet(0),
179	fClickPoint(0, 0),
180	fSelStart(-1, -1),
181	fSelEnd(-1, -1),
182	fMouseTracking(false),
183	fIMflag(false)
184{
185	_InitObject(argc, argv);
186	SetTermSize(fTermRows, fTermColumns, true);
187
188	BRect rect(0, 0, 16, 16);
189	rect.OffsetTo(Bounds().right - rect.Width(),
190				Bounds().bottom - rect.Height());
191
192	SetFlags(Flags() | B_DRAW_ON_CHILDREN | B_FOLLOW_ALL);
193	AddChild(new BDragger(rect, this,
194		B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM, B_WILL_DRAW));
195}
196
197
198TermView::TermView(BMessage *archive)
199	:
200	BView(archive),
201	fShell(NULL),
202	fWinchRunner(NULL),
203	fFontWidth(0),
204	fFontHeight(0),
205	fFontAscent(0),
206	fUpdateFlag(false),
207	fInsertModeFlag(MODE_OVER),
208	fScrollUpCount(0),
209	fScrollBarRange(0),
210	fFrameResized(false),
211	fLastCursorTime(0),
212	fCursorDrawFlag(true),
213	fCursorStatus(true),
214	fCursorBlinkingFlag(true),
215	fCursorRedrawFlag(true),
216	fCursorHeight(0),
217	fCurPos(0, 0),
218	fCurStack(0, 0),
219	fBufferStartPos(-1),
220	fTermRows(ROWS_DEFAULT),
221	fTermColumns(COLUMNS_DEFAULT),
222	fEncoding(M_UTF8),
223	fTop(0),
224	fTextBuffer(NULL),
225	fScrollBar(NULL),
226	fTextForeColor(kBlackColor),
227	fTextBackColor(kWhiteColor),
228	fCursorForeColor(kWhiteColor),
229	fCursorBackColor(kBlackColor),
230	fSelectForeColor(kWhiteColor),
231	fSelectBackColor(kBlackColor),
232	fScrTop(0),
233	fScrBot(fTermRows - 1),
234	fScrBufSize(1000),
235	fScrRegionSet(0),
236	fClickPoint(0, 0),
237	fSelStart(-1, -1),
238	fSelEnd(-1, -1),
239	fMouseTracking(false),
240	fIMflag(false)
241{
242	// We need this
243	SetFlags(Flags() | B_WILL_DRAW | B_PULSE_NEEDED);
244
245	if (archive->FindInt32("encoding", (int32 *)&fEncoding) < B_OK)
246		fEncoding = M_UTF8;
247	if (archive->FindInt32("columns", (int32 *)&fTermColumns) < B_OK)
248		fTermColumns = COLUMNS_DEFAULT;
249	if (archive->FindInt32("rows", (int32 *)&fTermRows) < B_OK)
250		fTermRows = ROWS_DEFAULT;
251
252	int32 argc = 0;
253	if (archive->HasInt32("argc"))
254		archive->FindInt32("argc", &argc);
255
256	const char **argv = new const char*[argc];
257	for (int32 i = 0; i < argc; i++) {
258		archive->FindString("argv", i, (const char **)&argv[i]);
259	}
260
261	// TODO: Retrieve colors, history size, etc. from archive
262	_InitObject(argc, argv);
263
264	delete[] argv;
265}
266
267
268status_t
269TermView::_InitObject(int32 argc, const char **argv)
270{
271	fTextBuffer = new (std::nothrow) TermBuffer(fTermRows, fTermColumns, fScrBufSize);
272	if (fTextBuffer == NULL)
273		return B_NO_MEMORY;
274
275	fShell = new (std::nothrow) Shell();
276	if (fShell == NULL)
277		return B_NO_MEMORY;
278
279	SetTermFont(be_fixed_font);
280	SetTermSize(fTermRows, fTermColumns, false);
281	//SetIMAware(false);
282
283	status_t status = fShell->Open(fTermRows, fTermColumns,
284								EncodingAsShortString(fEncoding),
285								argc, argv);
286
287	if (status < B_OK)
288		return status;
289
290	status = _AttachShell(fShell);
291	if (status < B_OK)
292		return status;
293
294
295	SetLowColor(fTextBackColor);
296	SetViewColor(fTextBackColor);
297
298	return B_OK;
299}
300
301
302TermView::~TermView()
303{
304	Shell *shell = fShell;
305		// _DetachShell sets fShell to NULL
306
307	_DetachShell();
308
309	delete fTextBuffer;
310	delete shell;
311}
312
313
314/* static */
315BArchivable *
316TermView::Instantiate(BMessage* data)
317{
318	if (validate_instantiation(data, "TermView"))
319		return new (std::nothrow) TermView(data);
320
321	return NULL;
322}
323
324
325status_t
326TermView::Archive(BMessage* data, bool deep) const
327{
328	status_t status = BView::Archive(data, deep);
329	if (status == B_OK)
330		status = data->AddString("add_on", TERM_SIGNATURE);
331	if (status == B_OK)
332		status = data->AddInt32("encoding", (int32)fEncoding);
333	if (status == B_OK)
334		status = data->AddInt32("columns", (int32)fTermColumns);
335	if (status == B_OK)
336		status = data->AddInt32("rows", (int32)fTermRows);
337
338	if (data->ReplaceString("class", "TermView") != B_OK)
339		data->AddString("class", "TermView");
340
341	return status;
342}
343
344
345void
346TermView::GetPreferredSize(float *width, float *height)
347{
348	if (width)
349		*width = fTermColumns * fFontWidth + 2 * kOffset;
350	if (height)
351		*height = fTermRows * fFontHeight + 2 * kOffset;
352}
353
354
355const char *
356TermView::TerminalName() const
357{
358	if (fShell == NULL)
359		return NULL;
360
361	return fShell->TTYName();
362}
363
364
365//! Get width and height for terminal font
366void
367TermView::GetFontSize(int* _width, int* _height)
368{
369	*_width = fFontWidth;
370	*_height = fFontHeight;
371}
372
373
374//! Set number of rows and columns in terminal
375BRect
376TermView::SetTermSize(int rows, int cols, bool resize)
377{
378	if (rows > 0)
379		fTermRows = rows;
380	if (cols > 0)
381		fTermColumns = cols;
382
383	fTextBuffer->ResizeTo(fTermRows, fTermColumns, 0);
384
385	fScrTop = 0;
386	fScrBot = fTermRows - 1;
387
388	BRect rect(0, 0, fTermColumns * fFontWidth + 2 * kOffset,
389		fTermRows * fFontHeight + 2 * kOffset);
390
391	if (resize)
392		ResizeTo(rect.Width(), rect.Height());
393
394	return rect;
395}
396
397
398void
399TermView::SetTextColor(rgb_color fore, rgb_color back)
400{
401	fTextForeColor = fore;
402	fTextBackColor = back;
403
404	SetLowColor(fTextBackColor);
405	SetViewColor(fTextBackColor);
406}
407
408
409void
410TermView::SetSelectColor(rgb_color fore, rgb_color back)
411{
412	fSelectForeColor = fore;
413	fSelectBackColor = back;
414}
415
416
417void
418TermView::SetCursorColor(rgb_color fore, rgb_color back)
419{
420	fCursorForeColor = fore;
421	fCursorBackColor = back;
422}
423
424
425int
426TermView::Encoding() const
427{
428	return fEncoding;
429}
430
431
432void
433TermView::SetEncoding(int encoding)
434{
435	// TODO: Shell::_Spawn() sets the "TTYPE" environment variable using
436	// the string value of encoding. But when this function is called and
437	// the encoding changes, the new value is never passed to Shell.
438	fEncoding = encoding;
439}
440
441
442void
443TermView::GetTermFont(BFont *font) const
444{
445	if (font != NULL)
446		*font = fHalfFont;
447}
448
449
450//! Sets font for terminal
451void
452TermView::SetTermFont(const BFont *font)
453{
454	int halfWidth = 0;
455
456	fHalfFont = font;
457
458	_FixFontAttributes(fHalfFont);
459
460	// calculate half font's max width
461	// Not Bounding, check only A-Z(For case of fHalfFont is KanjiFont. )
462	for (int c = 0x20 ; c <= 0x7e; c++){
463		char buf[4];
464		sprintf(buf, "%c", c);
465		int tmpWidth = (int)fHalfFont.StringWidth(buf);
466		if (tmpWidth > halfWidth)
467			halfWidth = tmpWidth;
468	}
469
470	fFontWidth = halfWidth;
471
472	font_height hh;
473	fHalfFont.GetHeight(&hh);
474
475	int font_ascent = (int)hh.ascent;
476	int font_descent =(int)hh.descent;
477	int font_leading =(int)hh.leading;
478
479	if (font_leading == 0)
480		font_leading = 1;
481
482	if (fTop)
483		fTop = fTop / fFontHeight;
484
485	fFontAscent = font_ascent;
486	fFontHeight = font_ascent + font_descent + font_leading + 1;
487
488	fTop = fTop * fFontHeight;
489
490	fCursorHeight = fFontHeight;
491
492	if (fScrollBar != NULL) {
493		fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows);
494	}
495}
496
497
498void
499TermView::SetScrollBar(BScrollBar *scrollBar)
500{
501	fScrollBar = scrollBar;
502	if (fScrollBar != NULL) {
503		fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows);
504	}
505}
506
507
508void
509TermView::SetTitle(const char *title)
510{
511	// TODO: Do something different in case we're a replicant,
512	// or in case we are inside a BTabView ?
513	if (Window())
514		Window()->SetTitle(title);
515}
516
517
518void
519TermView::Copy(BClipboard *clipboard)
520{
521	if (!_HasSelection())
522		return;
523
524	BString copyStr;
525	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
526
527	if (clipboard->Lock()) {
528		BMessage *clipMsg = NULL;
529		clipboard->Clear();
530
531		if ((clipMsg = clipboard->Data()) != NULL) {
532			clipMsg->AddData("text/plain", B_MIME_TYPE, copyStr.String(),
533				copyStr.Length());
534			clipboard->Commit();
535		}
536		clipboard->Unlock();
537	}
538
539	// Deselecting the current selection is not the behavior that
540	// R5's Terminal app displays. We want to mimic the behavior, so we will
541	// no longer do the deselection
542//	if (!fMouseTracking)
543//		_DeSelect();
544}
545
546
547void
548TermView::Paste(BClipboard *clipboard)
549{
550	if (clipboard->Lock()) {
551		BMessage *clipMsg = clipboard->Data();
552		char *text;
553		ssize_t numBytes;
554		if (clipMsg->FindData("text/plain", B_MIME_TYPE,
555				(const void **)&text, &numBytes) == B_OK ) {
556			// Clipboard text doesn't attached EOF?
557			text[numBytes] = '\0';
558			_WritePTY((uchar *)text, numBytes);
559		}
560
561		clipboard->Unlock();
562	}
563}
564
565
566void
567TermView::SelectAll()
568{
569	int screen_top = fTop / fFontHeight;
570	int viewheight = fTermRows;
571
572	int start_pos = screen_top -(fScrBufSize - viewheight * 2);
573
574	CurPos start, end;
575	start.x = 0;
576	end.x = fTermColumns -1;
577
578	if (start_pos > 0)
579		start.y = start_pos;
580	else
581		start.y = 0;
582
583	end.y = fCurPos.y  + screen_top;
584
585	_Select(start, end);
586}
587
588
589void
590TermView::Clear()
591{
592	_DeSelect();
593	fTextBuffer->Clear();
594
595	fTop = 0;
596	ScrollTo(0, 0);
597
598	if (LockLooper()) {
599		SetHighColor(fTextBackColor);
600		FillRect(Bounds());
601		SetHighColor(fTextForeColor);
602		UnlockLooper();
603	}
604
605	// reset cursor pos
606	SetCurPos(0, 0);
607
608	if (fScrollBar) {
609		fScrollBar->SetRange(0, 0);
610		fScrollBar->SetProportion(1);
611	}
612}
613
614
615//! Print one character
616void
617TermView::Insert(uchar *string, ushort attr)
618{
619	int width = CodeConv::UTF8GetFontWidth((char*)string);
620	if (width == FULL_WIDTH)
621		attr |= A_WIDTH;
622
623	// check column over flow.
624	if (fCurPos.x + width > fTermColumns) {
625		UpdateLine();
626		fCurPos.x = 0;
627
628		if (fCurPos.y == fTermRows -1)
629			ScrollScreen();
630		else
631			fCurPos.y++;
632	}
633
634	if (fInsertModeFlag == MODE_INSERT)
635		fTextBuffer->InsertSpace(fCurPos, width);
636
637	fTextBuffer->WriteChar(fCurPos, string, attr);
638
639	if (!fUpdateFlag)
640		fBufferStartPos = fCurPos.x;
641
642	fCurPos.x += width;
643	fUpdateFlag = true;
644}
645
646
647//! Print a CR and move the cursor
648void
649TermView::InsertCR()
650{
651	UpdateLine();
652	fTextBuffer->WriteCR(fCurPos);
653	fCurPos.x = 0;
654}
655
656
657//! Print a LF and move the cursor
658void
659TermView::InsertLF()
660{
661	UpdateLine();
662
663	if (fScrRegionSet) {
664		if (fCurPos.y == fScrBot) {
665			ScrollRegion(-1, -1, SCRUP, 1);
666			return;
667		}
668	}
669
670	if (fCurPos.x != fTermColumns){
671		if (fCurPos.y == fTermRows -1)
672			ScrollScreen();
673		else
674			fCurPos.y++;
675	}
676}
677
678
679//! Print a NL and move the cursor
680void
681TermView::InsertNewLine(int num)
682{
683	ScrollRegion(fCurPos.y, -1, SCRDOWN, num);
684}
685
686
687//! Print a space
688void
689TermView::InsertSpace(int num)
690{
691	UpdateLine();
692
693	fTextBuffer->InsertSpace(fCurPos, num);
694	_TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y));
695}
696
697
698//! Set or reset Insert mode
699void
700TermView::SetInsertMode(int flag)
701{
702	UpdateLine();
703	fInsertModeFlag = flag;
704}
705
706
707//! Draw region
708inline int
709TermView::_TermDraw(const CurPos &start, const CurPos &end)
710{
711	int x1 = start.x;
712	int y1 = start.y;
713	int x2 = end.x;
714	int y2 = end.y;
715
716	_Redraw(x1, y1 + fTop / fFontHeight,
717		x2, y2 + fTop / fFontHeight);
718
719	return 0;
720}
721
722
723//! Draw region
724int
725TermView::_TermDrawSelectedRegion(CurPos start, CurPos end)
726{
727	CurPos inPos;
728
729	if (end < start) {
730		inPos = start;
731		start = end;
732		end = inPos;
733	}
734
735	if (start.y == end.y) {
736		_Redraw(start.x, start.y, end.x, end.y);
737	} else {
738		_Redraw(start.x, start.y, fTermColumns, start.y);
739
740		if (end.y - start.y > 0)
741			_Redraw(0, start.y + 1, fTermColumns, end.y - 1);
742
743		_Redraw(0, end.y, end.x, end.y);
744	}
745
746	return 0;
747}
748
749
750//! Draw region
751int
752TermView::_TermDrawRegion(CurPos start, CurPos end)
753{
754	CurPos inPos;
755	int top = fTop / fFontHeight;
756
757	if (end < start) {
758		inPos = start;
759		start = end;
760		end = inPos;
761	}
762
763	start.y += top;
764	end.y += top;
765
766	if (start.y == end.y) {
767		_Redraw(start.x, start.y, end.x, end.y);
768	} else {
769		_Redraw(start.x, start.y, fTermColumns - 1, start.y);
770
771		if (end.y - start.y > 0) {
772			_Redraw(0, start.y + 1, fTermColumns - 1, end.y - 1);
773		}
774		_Redraw(0, end.y, end.x, end.y);
775	}
776
777	return 0;
778}
779
780
781//! Erase below cursor below.
782void
783TermView::EraseBelow()
784{
785	UpdateLine();
786
787	fTextBuffer->EraseBelow(fCurPos);
788	_TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y));
789	if (fCurPos.y != fTermRows - 1)
790		_TermDraw(CurPos(0, fCurPos.y + 1), CurPos(fTermColumns - 1, fTermRows - 1));
791}
792
793
794//! Delete num characters from current position.
795void
796TermView::DeleteChar(int num)
797{
798	UpdateLine();
799
800	fTextBuffer->DeleteChar(fCurPos, num);
801	_TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y));
802}
803
804
805//! Delete cursor right characters.
806void
807TermView::DeleteColumns()
808{
809	UpdateLine();
810
811	fTextBuffer->DeleteChar(fCurPos, fTermColumns - fCurPos.x);
812	_TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y));
813}
814
815
816//! Delete 'num' lines from current position with scrolling.
817void
818TermView::DeleteLine(int num)
819{
820	ScrollRegion(fCurPos.y, -1, SCRUP, num);
821}
822
823
824//! Sets cursor position
825void
826TermView::SetCurPos(int x, int y)
827{
828	UpdateLine();
829
830	if (x >= 0 && x < fTermColumns)
831		fCurPos.x = x;
832	if (y >= 0 && y < fTermRows)
833		fCurPos.y = y;
834}
835
836
837//! Sets cursor x position
838void
839TermView::SetCurX(int x)
840{
841	if (x >= 0 && x < fTermColumns) {
842		UpdateLine();
843		fCurPos.x = x;
844	}
845}
846
847
848//! Sets cursor y position
849void
850TermView::SetCurY(int y)
851{
852	if (y >= 0 && y < fTermRows) {
853		UpdateLine();
854		fCurPos.y = y;
855	}
856}
857
858
859//! Gets cursor position
860void
861TermView::GetCurPos(CurPos *inCurPos)
862{
863	inCurPos->x = fCurPos.x;
864	inCurPos->y = fCurPos.y;
865}
866
867
868//! Gets cursor x position
869int
870TermView::GetCurX()
871{
872	return fCurPos.x;
873}
874
875
876//! Gets cursor y position
877int
878TermView::GetCurY()
879{
880	return fCurPos.y;
881}
882
883
884//! Saves cursor position
885void
886TermView::SaveCursor()
887{
888	fCurStack = fCurPos;
889}
890
891
892//! Restores cursor position
893void
894TermView::RestoreCursor()
895{
896	UpdateLine();
897	fCurPos = fCurStack;
898}
899
900
901//! Move cursor right by 'num' steps.
902void
903TermView::MoveCurRight(int num)
904{
905	UpdateLine();
906
907	if (fCurPos.x + num >= fTermColumns) {
908		// Wrap around
909		fCurPos.x = 0;
910		InsertCR();
911		InsertLF();
912	} else
913		fCurPos.x += num;
914}
915
916
917//! Move cursor left by 'num' steps.
918void
919TermView::MoveCurLeft(int num)
920{
921	UpdateLine();
922
923	fCurPos.x -= num;
924	if (fCurPos.x < 0)
925		fCurPos.x = 0;
926}
927
928
929//! Move cursor up by 'num' steps.
930void
931TermView::MoveCurUp(int num)
932{
933	UpdateLine();
934
935	fCurPos.y -= num;
936
937	if (fCurPos.y < 0)
938		fCurPos.y = 0;
939}
940
941
942//! Move cursor down by 'num' steps.
943void
944TermView::MoveCurDown(int num)
945{
946	UpdateLine();
947
948	fCurPos.y += num;
949
950	if (fCurPos.y >= fTermRows)
951		fCurPos.y = fTermRows - 1;
952}
953
954
955// TODO: Cleanup the next 3 functions!!!
956void
957TermView::DrawCursor()
958{
959	BRect rect(fFontWidth * fCurPos.x + kOffset,
960		fFontHeight * fCurPos.y + fTop + kOffset,
961		fFontWidth * (fCurPos.x + 1) - 1 + kOffset,
962		fFontHeight * fCurPos.y + fTop + fCursorHeight - 1 + kOffset);
963
964	uchar buf[4];
965	ushort attr;
966
967	int top = (fTop + kOffset) / fFontHeight;
968	bool m_flag = _CheckSelectedRegion(CurPos(fCurPos.x, fCurPos.y + top));
969	if (fTextBuffer->GetChar(fCurPos.y + top, fCurPos.x, buf, &attr) == A_CHAR) {
970		int width;
971		if (IS_WIDTH(attr))
972			width = 2;
973		else
974			width = 1;
975
976		_DrawLines(fCurPos.x * fFontWidth,
977			fCurPos.y * fFontHeight + fTop,
978			attr, buf, width, m_flag, true, this);
979	} else {
980		if (m_flag)
981			SetHighColor(fSelectBackColor);
982		else
983			SetHighColor(fCursorBackColor);
984
985		FillRect(rect);
986	}
987
988	Sync();
989}
990
991
992void
993TermView::BlinkCursor()
994{
995	if (fCursorDrawFlag
996		&& fCursorBlinkingFlag
997		&& Window()->IsActive()) {
998		if (fCursorStatus)
999			_TermDraw(fCurPos, fCurPos);
1000		else
1001			DrawCursor();
1002
1003		fCursorStatus = !fCursorStatus;
1004		fLastCursorTime = system_time();
1005	}
1006}
1007
1008
1009//! Draw / Clear cursor.
1010void
1011TermView::SetCurDraw(bool flag)
1012{
1013	if (!flag) {
1014		if (fCursorStatus)
1015			_TermDraw(fCurPos, fCurPos);
1016
1017		fCursorStatus = false;
1018		fCursorDrawFlag = false;
1019	} else {
1020		if (!fCursorDrawFlag) {
1021			fCursorDrawFlag = true;
1022			fCursorStatus = true;
1023
1024			if (LockLooper()) {
1025				DrawCursor();
1026				UnlockLooper();
1027			}
1028		}
1029	}
1030}
1031
1032
1033//! Sets cursor Blinking flag.
1034void
1035TermView::SetCurBlinking(bool flag)
1036{
1037	fCursorBlinkingFlag = flag;
1038}
1039
1040
1041//! Scroll terminal dir directory by 'num' steps.
1042void
1043TermView::ScrollRegion(int top, int bot, int dir, int num)
1044{
1045	UpdateLine();
1046
1047	if (top == -1)
1048		top = fScrTop;
1049
1050	if (bot == -1)
1051		bot = fScrBot;
1052
1053	if (top < fScrTop)
1054		top = fScrTop;
1055
1056	if (bot > fScrBot)
1057		bot = fScrBot;
1058
1059	fTextBuffer->ScrollRegion(top, bot , dir ,num);
1060	_TermDraw(CurPos(0, top), CurPos(fTermColumns - 1, bot));
1061}
1062
1063
1064//! Sets terminal scroll region.
1065void
1066TermView::SetScrollRegion(int top, int bot)
1067{
1068	if (top >= 0 && top < fTermRows) {
1069		if (bot >= 0 && bot < fTermRows) {
1070			if (top > bot) {
1071				fScrTop = bot;
1072				fScrBot = top;
1073			} else if (top < bot ) {
1074				fScrTop = top;
1075				fScrBot = bot;
1076			}
1077		}
1078	}
1079
1080	if (fScrTop != 0 || fScrBot != fTermRows -1 )
1081		fScrRegionSet = 1;
1082	else
1083		fScrRegionSet = 0;
1084}
1085
1086
1087//! Scroll to cursor position.
1088void
1089TermView::ScrollAtCursor()
1090{
1091	if (LockLooper()) {
1092		_ResizeScrBarRange();
1093		fScrollUpCount = 0;
1094		ScrollTo(0, fTop);
1095		UnlockLooper();
1096	}
1097}
1098
1099
1100status_t
1101TermView::_AttachShell(Shell *shell)
1102{
1103	if (shell == NULL)
1104		return B_BAD_VALUE;
1105
1106	fShell = shell;
1107	fShell->ViewAttached(this);
1108
1109	return B_OK;
1110}
1111
1112
1113void
1114TermView::_DetachShell()
1115{
1116	fShell->ViewDetached();
1117	fShell = NULL;
1118}
1119
1120
1121//! Draw character on offscreen bitmap.
1122void
1123TermView::_DrawLines(int x1, int y1, ushort attr, uchar *buf,
1124	int width, int mouse, int cursor, BView *inView)
1125{
1126	rgb_color rgb_fore = fTextForeColor, rgb_back = fTextBackColor;
1127
1128	inView->SetFont(&fHalfFont);
1129
1130	// Move the whole thing to the right
1131	x1 += kOffset;
1132	y1 += kOffset;
1133
1134	// Set pen point
1135	int x2 = x1 + fFontWidth * width;
1136	int y2 = y1 + fFontHeight;
1137
1138	// color attribute
1139	int forecolor = IS_FORECOLOR(attr);
1140	int backcolor = IS_BACKCOLOR(attr);
1141
1142	if (IS_FORESET(attr))
1143		rgb_fore = kTermColorTable[forecolor];
1144
1145	if (IS_BACKSET(attr))
1146		rgb_back = kTermColorTable[backcolor];
1147
1148	// Selection check.
1149	if (cursor) {
1150		rgb_fore = fCursorForeColor;
1151		rgb_back = fCursorBackColor;
1152	} else if (mouse) {
1153		rgb_fore = fSelectForeColor;
1154		rgb_back = fSelectBackColor;
1155	} else {
1156		// Reverse attribute(If selected area, don't reverse color).
1157		if (IS_INVERSE(attr)) {
1158			rgb_color rgb_tmp = rgb_fore;
1159			rgb_fore = rgb_back;
1160			rgb_back = rgb_tmp;
1161		}
1162	}
1163
1164	// Fill color at Background color and set low color.
1165	inView->SetHighColor(rgb_back);
1166	inView->FillRect(BRect(x1, y1, x2 - 1, y2 - 1));
1167	inView->SetLowColor(rgb_back);
1168
1169	inView->SetHighColor(rgb_fore);
1170
1171	// Draw character.
1172	inView->MovePenTo(x1, y1 + fFontAscent);
1173	inView->DrawString((char *) buf);
1174
1175	// bold attribute.
1176	if (IS_BOLD(attr)) {
1177		inView->MovePenTo(x1 + 1, y1 + fFontAscent);
1178
1179		inView->SetDrawingMode(B_OP_OVER);
1180		inView->DrawString((char *)buf);
1181		inView->SetDrawingMode(B_OP_COPY);
1182	}
1183
1184	// underline attribute
1185	if (IS_UNDER(attr)) {
1186		inView->MovePenTo(x1, y1 + fFontAscent);
1187		inView->StrokeLine(BPoint(x1 , y1 + fFontAscent),
1188			BPoint(x2 , y1 + fFontAscent));
1189	}
1190}
1191
1192
1193//! Resize scroll bar range and knob size.
1194void
1195TermView::_ResizeScrBarRange()
1196{
1197	if (fScrollBar == NULL)
1198		return;
1199
1200	float viewheight = fTermRows * fFontHeight;
1201	float start_pos = fTop -(fScrBufSize - fTermRows * 2) * fFontHeight;
1202
1203	if (start_pos > 0) {
1204		fScrollBar->SetRange(start_pos, viewheight + fTop - fFontHeight);
1205	} else {
1206		fScrollBar->SetRange(0, viewheight + fTop - fFontHeight);
1207		fScrollBar->SetProportion( viewheight /(viewheight + fTop));
1208	}
1209}
1210
1211
1212//! Scrolls screen.
1213void
1214TermView::ScrollScreen()
1215{
1216	fTop += fFontHeight;
1217	fScrollUpCount++;
1218	fTextBuffer->ScrollLine();
1219
1220	if (fScrollUpCount > fTermRows ) {
1221		if (LockLooper()) {
1222			_ResizeScrBarRange();
1223			fScrollBarRange += fScrollUpCount;
1224			fScrollUpCount = 0;
1225			ScrollTo(0, fTop);
1226			UnlockLooper();
1227		}
1228	}
1229}
1230
1231
1232//! Scrolls screen.
1233void
1234TermView::ScrollScreenDraw()
1235{
1236	if (fScrollUpCount){
1237		if (LockLooper()) {
1238			_ResizeScrBarRange();
1239
1240			fScrollBarRange += fScrollUpCount;
1241			fScrollUpCount = 0;
1242			ScrollTo(0, fTop);
1243			UnlockLooper();
1244		}
1245	}
1246}
1247
1248
1249//!	Handler for SIGWINCH
1250void
1251TermView::_UpdateSIGWINCH()
1252{
1253	if (fFrameResized) {
1254		if (_HasSelection())
1255			_TermDrawSelectedRegion(fSelStart, fSelEnd);
1256		ScrollTo(0, fTop);
1257		_ResizeScrBarRange();
1258
1259		fShell->UpdateWindowSize(fTermRows, fTermColumns);
1260
1261		fFrameResized = false;
1262		if (fScrRegionSet == 0)
1263			fScrBot = fTermRows - 1;
1264	}
1265}
1266
1267
1268//!	Device Status.
1269void
1270TermView::DeviceStatusReport(int n)
1271{
1272	char sbuf[16] ;
1273	int len;
1274
1275	switch (n) {
1276		case 5:
1277			len = sprintf(sbuf,"\033[0n") ;
1278			fShell->Write(sbuf, len);
1279			break ;
1280		case 6:
1281			len = sprintf(sbuf,"\033[%d;%dR", fTermRows, fTermColumns) ;
1282			fShell->Write(sbuf, len);
1283			break ;
1284		default:
1285			return;
1286	}
1287}
1288
1289
1290//!	Update line buffer.
1291void
1292TermView::UpdateLine()
1293{
1294	if (fUpdateFlag == true) {
1295		if (fInsertModeFlag == MODE_INSERT) {
1296			_TermDraw(CurPos(fBufferStartPos, fCurPos.y),
1297				CurPos(fTermColumns - 1, fCurPos.y));
1298		} else {
1299			_TermDraw(CurPos(fBufferStartPos, fCurPos.y),
1300				CurPos(fCurPos.x - 1, fCurPos.y));
1301		}
1302		fUpdateFlag = false;
1303	}
1304}
1305
1306
1307void
1308TermView::AttachedToWindow()
1309{
1310	MakeFocus(true);
1311	if (fScrollBar)
1312		fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows);
1313
1314	BMessage message(kUpdateSigWinch);
1315	fWinchRunner = new (std::nothrow) BMessageRunner(BMessenger(this), &message, 500000);
1316
1317	// TODO: Since we can also be a replicant, messing
1318	// with the window, which is not ours, is not nice:
1319	// Switch to using a BMessageRunner for the
1320	// blinking caret too.
1321	Window()->SetPulseRate(1000000);
1322}
1323
1324
1325void
1326TermView::DetachedFromWindow()
1327{
1328	delete fWinchRunner;
1329	fWinchRunner = NULL;
1330}
1331
1332
1333void
1334TermView::Pulse()
1335{
1336	//if (system_time() > fLastCursorTime + 1000000)
1337		BlinkCursor();
1338}
1339
1340
1341void
1342TermView::Draw(BRect updateRect)
1343{
1344	if (IsPrinting()) {
1345		_DoPrint(updateRect);
1346		return;
1347	}
1348
1349	int x1 =(int)(updateRect.left - kOffset) / fFontWidth;
1350	int x2 =(int)(updateRect.right - kOffset) / fFontWidth;
1351
1352	int y1 =(int)(updateRect.top - kOffset) / fFontHeight;
1353	int y2 =(int)(updateRect.bottom - kOffset) / fFontHeight;
1354
1355	Window()->BeginViewTransaction();
1356
1357	for (int j = y1; j <= y2; j++) {
1358		// If(x1, y1) Buffer is in string full width character,
1359		// alignment start position.
1360
1361		int k = x1;
1362		uchar buf[256];
1363
1364		ushort attr;
1365		if (fTextBuffer->GetChar(j, k, buf, &attr) == IN_STRING)
1366			k--;
1367
1368		if (k < 0)
1369			k = 0;
1370
1371		for (int i = k; i <= x2;) {
1372			int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1373			bool insideSelection = _CheckSelectedRegion(CurPos(i, j));
1374
1375			if (count < 0) {
1376				if (insideSelection) {
1377					BRect eraseRect;
1378					eraseRect.Set(fFontWidth * i + kOffset,
1379					    fFontHeight * j + kOffset,
1380					    fFontWidth * (i - count) -1 + kOffset,
1381					    fFontHeight * (j + 1) -1 + kOffset);
1382
1383					SetHighColor(fSelectBackColor);
1384					FillRect(eraseRect);
1385				}
1386				i += abs(count);
1387				continue;
1388			}
1389
1390			_DrawLines(fFontWidth * i, fFontHeight * j,
1391				attr, buf, count, insideSelection, false, this);
1392			i += count;
1393			if (i >= fTermColumns)
1394				break;
1395		}
1396	}
1397
1398	if (fCursorStatus)
1399		DrawCursor();
1400
1401	Window()->EndViewTransaction();
1402}
1403
1404
1405void
1406TermView::_DoPrint(BRect updateRect)
1407{
1408	ushort attr;
1409	uchar buf[256];
1410
1411	const int numLines = (int)((updateRect.Height()) / fFontHeight);
1412
1413	int y1 = (int)(updateRect.top - kOffset) / fFontHeight;
1414	y1 = y1 -(fScrBufSize - numLines * 2);
1415	if (y1 < 0)
1416		y1 = 0;
1417
1418	const int y2 = y1 + numLines -1;
1419
1420	const int x1 = (int)(updateRect.left - kOffset) / fFontWidth;
1421	const int x2 = (int)(updateRect.right - kOffset) / fFontWidth;
1422
1423	for (int j = y1; j <= y2; j++) {
1424		// If(x1, y1) Buffer is in string full width character,
1425		// alignment start position.
1426
1427		int k = x1;
1428		if (fTextBuffer->GetChar(j, k, buf, &attr) == IN_STRING)
1429			k--;
1430
1431		if (k < 0)
1432			k = 0;
1433
1434		for (int i = k; i <= x2;) {
1435			int count = fTextBuffer->GetString(j, i, x2, buf, &attr);
1436			if (count < 0) {
1437				i += abs(count);
1438				continue;
1439			}
1440
1441			_DrawLines(fFontWidth * i, fFontHeight * j,
1442				attr, buf, count, false, false, this);
1443			i += count;
1444		}
1445	}
1446}
1447
1448
1449void
1450TermView::WindowActivated(bool active)
1451{
1452	BView::WindowActivated(active);
1453	if (active == false) {
1454		// DoIMConfirm();
1455	}
1456}
1457
1458
1459void
1460TermView::KeyDown(const char *bytes, int32 numBytes)
1461{
1462	if (fIMflag)
1463		return;
1464
1465	int32 key, mod, rawChar;
1466	BMessage *currentMessage = Looper()->CurrentMessage();
1467	if (currentMessage == NULL)
1468		return;
1469
1470	currentMessage->FindInt32("modifiers", &mod);
1471	currentMessage->FindInt32("key", &key);
1472	currentMessage->FindInt32("raw_char", &rawChar);
1473
1474	// Terminal filters RET, ENTER, F1...F12, and ARROW key code.
1475	// TODO: Cleanup
1476	if (numBytes == 1) {
1477		const char *toWrite = NULL;
1478		switch (*bytes) {
1479			case B_RETURN:
1480				if (rawChar == B_RETURN)
1481					toWrite = "\r";
1482				break;
1483
1484			case B_LEFT_ARROW:
1485				if (rawChar == B_LEFT_ARROW) {
1486					if (mod & B_CONTROL_KEY)
1487						toWrite = CTRL_LEFT_ARROW_KEY_CODE;
1488					else
1489						toWrite = LEFT_ARROW_KEY_CODE;
1490				}
1491				break;
1492
1493			case B_RIGHT_ARROW:
1494				if (rawChar == B_RIGHT_ARROW) {
1495					if (mod & B_CONTROL_KEY)
1496						toWrite = CTRL_RIGHT_ARROW_KEY_CODE;
1497					else
1498						toWrite = RIGHT_ARROW_KEY_CODE;
1499				}
1500				break;
1501
1502			case B_UP_ARROW:
1503				if (mod & B_SHIFT_KEY) {
1504					if (Bounds().top > 0) {
1505						ScrollBy(0, -fFontHeight);
1506						Window()->UpdateIfNeeded();
1507					}
1508					return;
1509				}
1510				if (rawChar == B_UP_ARROW) {
1511					if (mod & B_CONTROL_KEY)
1512						toWrite = CTRL_UP_ARROW_KEY_CODE;
1513					else
1514						toWrite = UP_ARROW_KEY_CODE;
1515				}
1516				break;
1517
1518			case B_DOWN_ARROW:
1519				if (mod & B_SHIFT_KEY) {
1520					ScrollBy(0, fFontHeight);
1521					Window()->UpdateIfNeeded();
1522					return;
1523				}
1524
1525				if (rawChar == B_DOWN_ARROW) {
1526					if (mod & B_CONTROL_KEY)
1527						toWrite = CTRL_DOWN_ARROW_KEY_CODE;
1528					else
1529						toWrite = DOWN_ARROW_KEY_CODE;
1530				}
1531				break;
1532
1533			case B_INSERT:
1534				if (rawChar == B_INSERT)
1535					toWrite = INSERT_KEY_CODE;
1536				break;
1537
1538			case B_HOME:
1539				if (rawChar == B_HOME)
1540					toWrite = HOME_KEY_CODE;
1541				break;
1542
1543			case B_END:
1544				if (rawChar == B_END)
1545					toWrite = END_KEY_CODE;
1546				break;
1547
1548			case B_PAGE_UP:
1549				if (mod & B_SHIFT_KEY) {
1550					if (Bounds().top > 0) {
1551						ScrollBy(0, -fFontHeight * fTermRows );
1552						Window()->UpdateIfNeeded();
1553					}
1554					return;
1555				}
1556				if (rawChar == B_PAGE_UP)
1557					toWrite = PAGE_UP_KEY_CODE;
1558				break;
1559
1560			case B_PAGE_DOWN:
1561				if (mod & B_SHIFT_KEY) {
1562					ScrollBy(0, fFontHeight * fTermRows);
1563					Window()->UpdateIfNeeded();
1564					return;
1565				}
1566
1567				if (rawChar == B_PAGE_DOWN)
1568					toWrite = PAGE_DOWN_KEY_CODE;
1569				break;
1570
1571			case B_FUNCTION_KEY:
1572				// TODO: Why not just fShell->Write(key) ?
1573				for (int32 i = 0; i < 12; i++) {
1574					if (key == function_keycode_table[i]) {
1575						fShell->Write(function_key_char_table[i], 5);
1576						return;
1577					}
1578				}
1579				break;
1580			default:
1581				break;
1582		}
1583		if (toWrite) {
1584			fShell->Write(toWrite, strlen(toWrite));
1585			return;
1586		}
1587	} else {
1588		// input multibyte character
1589		if (fEncoding != M_UTF8) {
1590			char destBuffer[16];
1591			int cnum = CodeConv::ConvertFromInternal(bytes, numBytes,
1592				(char *)destBuffer, fEncoding);
1593			fShell->Write(destBuffer, cnum);
1594			return;
1595		}
1596	}
1597
1598	fShell->Write(bytes, numBytes);
1599}
1600
1601
1602void
1603TermView::FrameResized(float width, float height)
1604{
1605	const int cols = ((int)width + 1 - 2 * kOffset) / fFontWidth;
1606	const int rows = ((int)height + 1 - 2 * kOffset) / fFontHeight;
1607
1608	int offset = 0;
1609
1610	if (rows < fCurPos.y + 1) {
1611		fTop += (fCurPos.y  + 1 - rows) * fFontHeight;
1612		offset = fCurPos.y + 1 - rows;
1613		fCurPos.y = rows - 1;
1614	}
1615	fTextBuffer->ResizeTo(rows, cols, offset);
1616	fTermRows = rows;
1617	fTermColumns = cols;
1618
1619	fFrameResized = true;
1620
1621	if (fScrollBar != NULL)
1622		fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows);
1623}
1624
1625
1626void
1627TermView::MessageReceived(BMessage *msg)
1628{
1629	entry_ref ref;
1630	char *ctrl_l = "\x0c";
1631
1632	// first check for any dropped message
1633	if (msg->WasDropped()) {
1634		char *text;
1635		int32 numBytes;
1636		//rgb_color *color;
1637
1638		int32 i = 0;
1639
1640		if (msg->FindRef("refs", i++, &ref) == B_OK) {
1641			_DoFileDrop(ref);
1642
1643			while (msg->FindRef("refs", i++, &ref) == B_OK) {
1644				_WritePTY((const uchar*)" ", 1);
1645				_DoFileDrop(ref);
1646			}
1647			return;
1648#if 0
1649		} else if (msg->FindData("RGBColor", B_RGB_COLOR_TYPE,
1650				(const void **)&color, &numBytes) == B_OK
1651				 && numBytes == sizeof(color)) {
1652			// TODO: handle color drop
1653			// maybe only on replicants ?
1654			return;
1655#endif
1656		} else if (msg->FindData("text/plain", B_MIME_TYPE,
1657			 	(const void **)&text, &numBytes) == B_OK) {
1658			_WritePTY((uchar *)text, numBytes);
1659			return;
1660		}
1661	}
1662
1663	switch (msg->what){
1664		case B_ABOUT_REQUESTED:
1665			// (replicant) about box requested
1666			_AboutRequested();
1667			break;
1668
1669		case B_SIMPLE_DATA:
1670		case B_REFS_RECEIVED:
1671		{
1672			// handle refs if they weren't dropped
1673			int32 i = 0;
1674			if (msg->FindRef("refs", i++, &ref) == B_OK) {
1675				_DoFileDrop(ref);
1676
1677				while (msg->FindRef("refs", i++, &ref) == B_OK) {
1678					_WritePTY((const uchar*)" ", 1);
1679					_DoFileDrop(ref);
1680				}
1681			} else
1682				BView::MessageReceived(msg);
1683			break;
1684		}
1685
1686		case B_COPY:
1687			Copy(be_clipboard);
1688			break;
1689
1690		case B_PASTE:
1691		{
1692			int32 code;
1693			if (msg->FindInt32("index", &code) == B_OK)
1694				Paste(be_clipboard);
1695			break;
1696		}
1697
1698		case B_SELECT_ALL:
1699			SelectAll();
1700			break;
1701
1702		case B_SET_PROPERTY:
1703		{
1704			int32 i;
1705			int32 encodingID;
1706			BMessage specifier;
1707			msg->GetCurrentSpecifier(&i, &specifier);
1708			if (!strcmp("encoding", specifier.FindString("property", i))){
1709				msg->FindInt32 ("data", &encodingID);
1710				SetEncoding(encodingID);
1711				msg->SendReply(B_REPLY);
1712			} else {
1713				BView::MessageReceived(msg);
1714			}
1715			break;
1716		}
1717
1718		case B_GET_PROPERTY:
1719		{
1720			int32 i;
1721			BMessage specifier;
1722			msg->GetCurrentSpecifier(&i, &specifier);
1723			if (!strcmp("encoding", specifier.FindString("property", i))){
1724				BMessage reply(B_REPLY);
1725				reply.AddInt32("result", Encoding());
1726				msg->SendReply(&reply);
1727			} else if (!strcmp("tty", specifier.FindString("property", i))) {
1728				BMessage reply(B_REPLY);
1729				reply.AddString("result", TerminalName());
1730				msg->SendReply(&reply);
1731			} else {
1732				BView::MessageReceived(msg);
1733			}
1734			break;
1735		}
1736
1737		case MENU_CLEAR_ALL:
1738			Clear();
1739			fShell->Write(ctrl_l, 1);
1740			break;
1741
1742
1743//  case B_INPUT_METHOD_EVENT:
1744//    {
1745   //   int32 op;
1746  //    msg->FindInt32("be:opcode", &op);
1747   //   switch (op){
1748   //   case B_INPUT_METHOD_STARTED:
1749	//DoIMStart(msg);
1750//	break;
1751
1752//      case B_INPUT_METHOD_STOPPED:
1753//	DoIMStop(msg);
1754//	break;
1755
1756//      case B_INPUT_METHOD_CHANGED:
1757//	DoIMChange(msg);
1758//	break;
1759
1760//      case B_INPUT_METHOD_LOCATION_REQUEST:
1761//	DoIMLocation(msg);
1762//	break;
1763    //  }
1764   // }
1765		case kUpdateSigWinch:
1766			_UpdateSIGWINCH();
1767			break;
1768		default:
1769			BView::MessageReceived(msg);
1770			break;
1771	}
1772}
1773
1774
1775status_t
1776TermView::GetSupportedSuites(BMessage *message)
1777{
1778	BPropertyInfo propInfo(sPropList);
1779	message->AddString("suites", "suite/vnd.naan-termview");
1780	message->AddFlat("messages", &propInfo);
1781	return BView::GetSupportedSuites(message);
1782}
1783
1784
1785BHandler*
1786TermView::ResolveSpecifier(BMessage *message, int32 index, BMessage *specifier,
1787				int32 what, const char *property)
1788{
1789	BHandler *target = this;
1790	BPropertyInfo propInfo(sPropList);
1791	if (propInfo.FindMatch(message, index, specifier, what, property) < B_OK)
1792		target = BView::ResolveSpecifier(message, index, specifier, what, property);
1793
1794	return target;
1795}
1796
1797
1798//! Gets dropped file full path and display it at cursor position.
1799void
1800TermView::_DoFileDrop(entry_ref &ref)
1801{
1802	BEntry ent(&ref);
1803	BPath path(&ent);
1804	BString string(path.Path());
1805
1806	string.CharacterEscape(" ~`#$&*()\\|[]{};'\"<>?!",'\\');
1807	_WritePTY((const uchar *)string.String(), string.Length());
1808}
1809
1810
1811/*!	Write strings to PTY device. If encoding system isn't UTF8, change
1812	encoding to UTF8 before writing PTY.
1813*/
1814void
1815TermView::_WritePTY(const uchar *text, int numBytes)
1816{
1817	if (fEncoding != M_UTF8) {
1818		uchar *destBuffer = (uchar *)malloc(numBytes * 3);
1819		numBytes = CodeConv::ConvertFromInternal((char*)text, numBytes,
1820			(char*)destBuffer, fEncoding);
1821		fShell->Write(destBuffer, numBytes);
1822		free(destBuffer);
1823	} else {
1824		fShell->Write(text, numBytes);
1825	}
1826}
1827
1828
1829void
1830TermView::MouseDown(BPoint where)
1831{
1832	if (!IsFocus())
1833		MakeFocus();
1834
1835	int32 buttons;
1836	Window()->CurrentMessage()->FindInt32("buttons", &buttons);
1837
1838	// paste button
1839	if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
1840		if (_HasSelection()) {
1841			// copy text from region
1842			BString copy;
1843			fTextBuffer->GetStringFromRegion(copy, fSelStart, fSelEnd);
1844			_WritePTY((uchar *)copy.String(), copy.Length());
1845		} else {
1846			// copy text from clipboard.
1847			Paste(be_clipboard);
1848		}
1849		return;
1850	}
1851
1852	// Select Region
1853	if (buttons == B_PRIMARY_MOUSE_BUTTON) {
1854		int32 mod, clicks;
1855		Window()->CurrentMessage()->FindInt32("modifiers", &mod);
1856		Window()->CurrentMessage()->FindInt32("clicks", &clicks);
1857
1858		if (_HasSelection()) {
1859			CurPos inPos = _ConvertToTerminal(where);
1860			if (_CheckSelectedRegion(inPos)) {
1861				if (mod & B_CONTROL_KEY) {
1862					BPoint p;
1863					uint32 bt;
1864					do {
1865						GetMouse(&p, &bt);
1866
1867						if (bt == 0) {
1868							_DeSelect();
1869							return;
1870						}
1871
1872						snooze(40000);
1873
1874					} while (abs((int)(where.x - p.x)) < 4
1875						&& abs((int)(where.y - p.y)) < 4);
1876
1877					InitiateDrag();
1878					return;
1879				}
1880			}
1881		}
1882
1883		// If mouse has a lot of movement, disable double/triple click.
1884		/*BPoint inPoint = fClickPoint - where;
1885		if (abs((int)inPoint.x) > 16 || abs((int)inPoint.y) > 16)
1886			clicks = 1;
1887		*/
1888
1889		SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS,
1890				B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS);
1891
1892		fClickPoint = where;
1893
1894		if (mod & B_SHIFT_KEY)
1895			_AddSelectRegion(_ConvertToTerminal(where));
1896		else
1897			_DeSelect();
1898
1899
1900		// If clicks larger than 3, reset mouse click counter.
1901		clicks = clicks % 3;
1902		if (clicks == 0)
1903			clicks = 3;
1904
1905		switch (clicks) {
1906			case 1:
1907				fMouseTracking = true;
1908	      			break;
1909
1910			case 2:
1911				_SelectWord(where, mod);
1912				break;
1913
1914			case 3:
1915	 			_SelectLine(where, mod);
1916				break;
1917		}
1918		return;
1919  	}
1920
1921	BView::MouseDown(where);
1922}
1923
1924
1925void
1926TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
1927{
1928	BView::MouseMoved(where, transit, message);
1929	if (!fMouseTracking)
1930		return;
1931
1932	CurPos startPos = _ConvertToTerminal(fClickPoint);
1933	CurPos endPos = _ConvertToTerminal(where);
1934	if (endPos.y < 0)
1935		return;
1936
1937	_DeSelect();
1938	_Select(startPos, endPos);
1939
1940	// Scroll check
1941	if (fScrollBar != NULL) {
1942		// Get now scroll point
1943		float scrollStart, scrollEnd;
1944		fScrollBar->GetRange(&scrollStart, &scrollEnd);
1945		float scrollPos = fScrollBar->Value();
1946
1947		if (where.y < Bounds().LeftTop().y ) {
1948			// mouse point left of window
1949			if (scrollPos != scrollStart)
1950				ScrollTo(0, where.y);
1951		}
1952
1953		if (where.y > Bounds().LeftBottom().y) {
1954			// mouse point left of window
1955			if (scrollPos != scrollEnd)
1956				ScrollTo(0, where.y);
1957		}
1958	}
1959}
1960
1961
1962void
1963TermView::MouseUp(BPoint where)
1964{
1965	BView::MouseUp(where);
1966	fMouseTracking = false;
1967}
1968
1969
1970// Select a range of text
1971void
1972TermView::_Select(CurPos start, CurPos end)
1973{
1974	if (end < start)
1975		std::swap(start, end);
1976
1977	if (start.x < 0)
1978		start.x = 0;
1979	if (end.x >= fTermColumns)
1980		end.x = fTermColumns - 1;
1981
1982	uchar buf[4];
1983	ushort attr;
1984	if (fTextBuffer->GetChar(start.y, start.x, buf, &attr) == IN_STRING) {
1985		start.x--;
1986		if (start.x < 0)
1987			start.x = 0;
1988	}
1989
1990	if (fTextBuffer->GetChar(end.y, end.x, buf, &attr) == IN_STRING) {
1991		end.x++;
1992		if (end.x >= fTermColumns)
1993			end.x = fTermColumns;
1994	}
1995
1996	fSelStart = start;
1997	fSelEnd = end;
1998
1999	fTextBuffer->Select(fSelStart, fSelEnd);
2000	_TermDrawSelectedRegion(fSelStart, fSelEnd);
2001}
2002
2003
2004// Add select region(shift + mouse click)
2005void
2006TermView::_AddSelectRegion(CurPos pos)
2007{
2008	if (!_HasSelection())
2009		return;
2010
2011	// error check, and if mouse point to a plase full width character,
2012	// select point decliment.
2013	if (pos.x >= fTermColumns)
2014		pos.x = fTermColumns - 1;
2015	else if (pos.x < 0)
2016		pos.x = 0;
2017
2018	if (pos.y < 0)
2019		pos.y = 0;
2020
2021	uchar buf[4];
2022	ushort attr;
2023	if (fTextBuffer->GetChar(pos.y, pos.x, buf, &attr) == IN_STRING) {
2024		pos.x++;
2025		if (pos.x >= fTermColumns)
2026			pos.x = fTermColumns - 1;
2027	}
2028
2029	CurPos start = fSelStart;
2030	CurPos end = fSelEnd;
2031	CurPos inPos;
2032
2033	// Mouse point is same as selected line.
2034	if (pos.y == fSelStart.y && pos.y == fSelEnd.y) {
2035
2036		if (abs(pos.x - start.x) > abs(pos.x - end.x)) {
2037
2038			fSelStart = start;
2039			fSelEnd = pos;
2040			inPos = end;
2041
2042		} else {
2043
2044			fSelStart = end;
2045			fSelEnd = pos;
2046			inPos = start;
2047		}
2048		// else, End point set to near the start or end point.
2049	} else if (abs(pos.y - start.y) > abs(pos.y - end.y)) {
2050
2051		fSelStart = start;
2052		fSelEnd = pos;
2053		inPos = end;
2054	} else if (abs(pos.y - start.y) > abs(pos.y - end.y)) {
2055		fSelStart = end;
2056		fSelEnd = pos;
2057		inPos = start;
2058
2059	} else {
2060		if (start > end) {
2061			inPos = start;
2062			start = end;
2063			end = inPos;
2064		}
2065
2066		if (pos.y < start.y) {
2067			fSelStart = end;
2068			fSelEnd = pos;
2069			inPos = start;
2070		} else {
2071			fSelStart = start;
2072			fSelEnd = pos;
2073			inPos = end;
2074		}
2075	}
2076
2077	fTextBuffer->Select(fSelStart, fSelEnd);
2078	_TermDrawSelectedRegion(inPos, fSelEnd);
2079}
2080
2081
2082// Resize select region (mouse drag)
2083void
2084TermView::_ResizeSelectRegion(CurPos pos)
2085{
2086	//TODO: Broken. Selecting from right to left doesn't work.
2087
2088	CurPos inPos = fSelEnd;
2089
2090	// error check, and if mouse point to a plase full width character,
2091	// select point decliment.
2092	if (pos.x >= fTermColumns)
2093		pos.x = fTermColumns - 1;
2094	else if (pos.x < 0)
2095		pos.x = 0;
2096
2097	if (pos.y < 0)
2098		pos.y = 0;
2099
2100	uchar buf[4];
2101	ushort attr;
2102	if (fTextBuffer->GetChar(pos.y, pos.x, buf, &attr) == IN_STRING) {
2103		pos.x++;
2104
2105		if (pos == inPos)
2106			return;
2107
2108		if (pos.x >= fTermColumns)
2109			pos.x = fTermColumns - 1;
2110	}
2111
2112	fSelEnd = pos;
2113
2114	fTextBuffer->Select(fSelStart, fSelEnd);
2115	_TermDrawSelectedRegion(inPos, pos);
2116}
2117
2118
2119// DeSelect a range of text
2120void
2121TermView::_DeSelect(void)
2122{
2123	if (!_HasSelection())
2124		return;
2125
2126	fTextBuffer->DeSelect();
2127
2128	CurPos start = fSelStart;
2129	CurPos end = fSelEnd;
2130
2131	fSelStart.Set(-1, -1);
2132	fSelEnd.Set(-1, -1);
2133
2134	_TermDrawSelectedRegion(start, end);
2135}
2136
2137
2138bool
2139TermView::_HasSelection() const
2140{
2141	return fSelStart != fSelEnd;
2142}
2143
2144
2145void
2146TermView::_SelectWord(BPoint where, int mod)
2147{
2148	CurPos start, end, pos;
2149	bool flag;
2150
2151	pos = _ConvertToTerminal(where);
2152	flag = fTextBuffer->FindWord(pos, &start, &end);
2153	fTextBuffer->Select(start, end);
2154
2155	if (mod & B_SHIFT_KEY) {
2156		if (flag) {
2157			if (start < fSelStart)
2158				_AddSelectRegion(start);
2159			else if (end > fSelEnd)
2160				_AddSelectRegion(end);
2161		} else
2162			_AddSelectRegion(pos);
2163	} else {
2164		_DeSelect();
2165		if (flag)
2166			_Select(start, end);
2167	}
2168}
2169
2170
2171void
2172TermView::_SelectLine(BPoint where, int mod)
2173{
2174	CurPos pos = _ConvertToTerminal(where);
2175
2176	if (mod & B_SHIFT_KEY) {
2177
2178		CurPos start = CurPos(0, pos.y);
2179		CurPos end = CurPos(fTermColumns - 1, pos.y);
2180
2181		if (start < fSelStart)
2182			_AddSelectRegion(start);
2183		else if (end > fSelEnd)
2184			_AddSelectRegion(end);
2185
2186	} else {
2187		_DeSelect();
2188		_Select(CurPos(0, pos.y), CurPos(fTermColumns - 1, pos.y));
2189	}
2190}
2191
2192
2193// Convert View visible area corrdination to cursor position.
2194CurPos
2195TermView::_ConvertToTerminal(const BPoint &p)
2196{
2197	return CurPos((p.x - kOffset) / fFontWidth, (p.y - kOffset) / fFontHeight);
2198}
2199
2200
2201// Convert cursor position to view coordination.
2202BPoint
2203TermView::_ConvertFromTerminal(const CurPos &pos)
2204{
2205	return BPoint(fFontWidth * pos.x + kOffset,
2206		pos.y * fFontHeight + fTop + kOffset);
2207}
2208
2209
2210bool
2211TermView::_CheckSelectedRegion(const CurPos &pos)
2212{
2213	CurPos start, end;
2214
2215	if (fSelStart > fSelEnd) {
2216		start = fSelEnd;
2217		end = fSelStart;
2218	} else {
2219		start = fSelStart;
2220		end = fSelEnd;
2221	}
2222
2223	if (pos >= start && pos <= end)
2224		return true;
2225
2226	return false;
2227
2228}
2229
2230void
2231TermView::GetFrameSize(float *width, float *height)
2232{
2233	if (width != NULL)
2234		*width = 2 * kOffset + fTermColumns * fFontWidth;
2235
2236	if (height == NULL)
2237		return;
2238
2239	if (!fTop) {
2240		*height = 2 * kOffset + fTermRows * fFontHeight;
2241		return;
2242	}
2243
2244	if (fTop - fTermRows * fFontHeight > fScrBufSize * fFontHeight) {
2245
2246		*height = fScrBufSize * fFontHeight + 2 * kOffset;
2247		return;
2248	}
2249
2250	*height = kOffset + fTop + fTermRows * fFontHeight;
2251}
2252
2253
2254// Find a string, and select it if found
2255bool
2256TermView::Find(const BString &str, bool forwardSearch, bool matchCase, bool matchWord)
2257{
2258	//Get the buffer contents
2259	BString buffer;
2260	fTextBuffer->ToString(buffer);
2261
2262	CurPos selectionstart = fSelStart;
2263	CurPos selectionend = fSelEnd;
2264
2265	int offset = 0;
2266	if (selectionstart.x >= 0 || selectionstart.y >= 0) {
2267		if (forwardSearch)
2268			//Set the offset to the end of the selection
2269			offset = (selectionend.y) * fTermColumns + selectionend.x;
2270		else
2271			offset = (selectionstart.y) * fTermColumns + selectionstart.x;
2272	}
2273
2274	int initialresult = -1;
2275	int result = B_ERROR;
2276
2277	for (;;) {
2278		//Actual search
2279		if (forwardSearch) {
2280			if (matchCase)
2281				result = buffer.FindFirst(str, offset);
2282			else
2283				result = buffer.IFindFirst(str, offset);
2284		} else {
2285			if (matchCase)
2286				result = buffer.FindLast(str, offset);
2287			else
2288				result = buffer.IFindLast(str, offset);
2289		}
2290
2291		if (result == B_ERROR) { //Wrap search like Be's Terminal
2292			if (forwardSearch) {
2293				if (matchCase)
2294					result = buffer.FindFirst(str, 0);
2295				else
2296					result = buffer.IFindFirst(str, 0);
2297			} else {
2298				if (matchCase)
2299					result = buffer.FindLast(str, buffer.Length());
2300				else
2301					result = buffer.IFindLast(str, buffer.Length());
2302			}
2303		}
2304
2305		if (result < 0)
2306			return false;
2307
2308		if (matchWord) {
2309			if (isalnum(buffer.ByteAt(result - 1)) || isalnum(buffer.ByteAt(result + str.Length()))) {
2310				if (initialresult == -1) //Set the initial offset to the first result to aid word matching
2311					initialresult = result;
2312				else if (initialresult == result) //We went round the buffer, nothing found
2313					return false;
2314				if (forwardSearch)
2315					offset = result + str.Length();
2316				else
2317					offset = result;
2318				continue;
2319			}
2320			else
2321				break;
2322		}
2323		else
2324			break;
2325	}
2326
2327	//Select the found text
2328	selectionstart.y = result / fTermColumns;
2329	selectionstart.x = result % fTermColumns;
2330	//Length -1 since it seems to count the \0 as well
2331	selectionend.y = (result + str.Length() - 1) / fTermColumns;
2332	selectionend.x = (result + str.Length() - 1) % fTermColumns;
2333	//Update the contents of the view
2334	_DeSelect();
2335	_Select(selectionstart, selectionend);
2336
2337	return true;
2338}
2339
2340
2341//! Get the selected text and copy to str
2342void
2343TermView::GetSelection(BString &str)
2344{
2345	str.SetTo("");
2346	fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
2347}
2348
2349
2350void
2351TermView::NotifyQuit(int32 reason)
2352{
2353	// implemented in subclasses
2354}
2355
2356
2357void
2358TermView::CheckShellGone()
2359{
2360	if (!fShell)
2361		return;
2362
2363	// check, if the shell does still live
2364	pid_t pid = fShell->ProcessID();
2365	team_info info;
2366	if (get_team_info(pid, &info) == B_BAD_TEAM_ID) {
2367		// the shell is gone
2368		NotifyQuit(0);
2369	}
2370}
2371
2372
2373void
2374TermView::InitiateDrag()
2375{
2376	BString copyStr("");
2377	fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
2378
2379	BMessage message(B_MIME_DATA);
2380	message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), copyStr.Length());
2381
2382	BPoint start = _ConvertFromTerminal(fSelStart);
2383	BPoint end = _ConvertFromTerminal(fSelEnd);
2384
2385	BRect rect;
2386	if (fSelStart.y == fSelEnd.y) {
2387		rect.Set(start.x, start.y - fTop, end.x + fFontWidth,
2388			end.y + fFontHeight - fTop);
2389
2390	} else {
2391
2392		rect.Set(0, start.y - fTop, fTermColumns * fFontWidth,
2393			end.y + fFontHeight - fTop);
2394	}
2395
2396	rect = rect & Bounds();
2397
2398	DragMessage(&message, rect);
2399}
2400
2401
2402inline void
2403TermView::_Redraw(int x1, int y1, int x2, int y2)
2404{
2405	BRect rect(x1 * fFontWidth + kOffset,
2406	    y1 * fFontHeight + kOffset,
2407	    (x2 + 1) * fFontWidth + kOffset - 1,
2408	    (y2 + 1) * fFontHeight -1 + kOffset);
2409
2410	if (LockLooper()) {
2411		Invalidate(rect);
2412		UnlockLooper();
2413	}
2414}
2415
2416
2417/* static */
2418void
2419TermView::_FixFontAttributes(BFont &font)
2420{
2421	font.SetSpacing(B_FIXED_SPACING);
2422}
2423
2424
2425void
2426TermView::_AboutRequested()
2427{
2428	BAlert *alert = new (std::nothrow) BAlert("about",
2429					"Terminal\n"
2430					"\twritten by Kazuho Okui and Takashi Murai\n"
2431					"\tupdated by Kian Duffy and others\n\n"
2432					"\tCopyright " B_UTF8_COPYRIGHT "2003-2007, Haiku.\n", "Ok");
2433	if (alert != NULL)
2434		alert->Go();
2435}
2436
2437
2438