1/*
2 * Copyright 2013-2014, Rene Gollent, rene@gollent.com.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "ConsoleOutputView.h"
8
9#include <new>
10
11#include <Button.h>
12#include <CheckBox.h>
13#include <LayoutBuilder.h>
14#include <ScrollView.h>
15#include <String.h>
16#include <TextView.h>
17
18#include <AutoDeleter.h>
19
20
21enum {
22	MSG_CLEAR_OUTPUT	= 'clou',
23	MSG_POST_OUTPUT		= 'poou'
24};
25
26
27static const bigtime_t kOutputWaitInterval = 10000;
28
29
30// #pragma mark - ConsoleOutputView::OutputInfo
31
32
33struct ConsoleOutputView::OutputInfo {
34	int32 	fd;
35	BString	text;
36
37	OutputInfo(int32 fd, const BString& text)
38		:
39		fd(fd),
40		text(text)
41	{
42	}
43};
44
45
46// #pragma mark - ConsoleOutputView
47
48
49ConsoleOutputView::ConsoleOutputView()
50	:
51	BGroupView(B_VERTICAL, 0.0f),
52	fStdoutEnabled(NULL),
53	fStderrEnabled(NULL),
54	fConsoleOutput(NULL),
55	fClearButton(NULL),
56	fPendingOutput(NULL),
57	fWorkToDoSem(-1),
58	fOutputWorker(-1)
59{
60	SetName("ConsoleOutput");
61}
62
63
64ConsoleOutputView::~ConsoleOutputView()
65{
66	if (fWorkToDoSem > 0)
67		delete_sem(fWorkToDoSem);
68
69	if (fOutputWorker > 0)
70		wait_for_thread(fOutputWorker, NULL);
71
72	delete fPendingOutput;
73}
74
75
76/*static*/ ConsoleOutputView*
77ConsoleOutputView::Create()
78{
79	ConsoleOutputView* self = new ConsoleOutputView();
80
81	try {
82		self->_Init();
83	} catch (...) {
84		delete self;
85		throw;
86	}
87
88	return self;
89}
90
91
92void
93ConsoleOutputView::ConsoleOutputReceived(int32 fd, const BString& output)
94{
95	if (fd == 1 && fStdoutEnabled->Value() != B_CONTROL_ON)
96		return;
97	else if (fd == 2 && fStderrEnabled->Value() != B_CONTROL_ON)
98		return;
99
100	OutputInfo* info = new(std::nothrow) OutputInfo(fd, output);
101	if (info == NULL)
102		return;
103
104	ObjectDeleter<OutputInfo> infoDeleter(info);
105	if (fPendingOutput->AddItem(info)) {
106		infoDeleter.Detach();
107		release_sem(fWorkToDoSem);
108	}
109}
110
111
112void
113ConsoleOutputView::MessageReceived(BMessage* message)
114{
115	switch (message->what) {
116		case MSG_CLEAR_OUTPUT:
117		{
118			fConsoleOutput->SetText("");
119			fPendingOutput->MakeEmpty();
120			break;
121		}
122		case MSG_POST_OUTPUT:
123		{
124			OutputInfo* info = fPendingOutput->RemoveItemAt(0);
125			if (info == NULL)
126				break;
127
128			ObjectDeleter<OutputInfo> infoDeleter(info);
129			_HandleConsoleOutput(info);
130		}
131		default:
132			BGroupView::MessageReceived(message);
133			break;
134	}
135}
136
137
138void
139ConsoleOutputView::AttachedToWindow()
140{
141	BGroupView::AttachedToWindow();
142
143	fStdoutEnabled->SetValue(B_CONTROL_ON);
144	fStderrEnabled->SetValue(B_CONTROL_ON);
145	fClearButton->SetTarget(this);
146}
147
148
149void
150ConsoleOutputView::LoadSettings(const BMessage& settings)
151{
152	fStdoutEnabled->SetValue(settings.GetBool("showStdout", true)
153			? B_CONTROL_ON : B_CONTROL_OFF);
154	fStderrEnabled->SetValue(settings.GetBool("showStderr", true)
155			? B_CONTROL_ON : B_CONTROL_OFF);
156}
157
158
159status_t
160ConsoleOutputView::SaveSettings(BMessage& settings)
161{
162	bool value = fStdoutEnabled->Value() == B_CONTROL_ON;
163	if (settings.AddBool("showStdout", value) != B_OK)
164		return B_NO_MEMORY;
165
166	value = fStderrEnabled->Value() == B_CONTROL_ON;
167	if (settings.AddBool("showStderr", value) != B_OK)
168		return B_NO_MEMORY;
169
170	return B_OK;
171}
172
173
174void
175ConsoleOutputView::_Init()
176{
177	fPendingOutput = new OutputInfoList(10, true);
178
179	fWorkToDoSem = create_sem(0, "output_work_available");
180	if (fWorkToDoSem < 0)
181		throw std::bad_alloc();
182
183	fOutputWorker = spawn_thread(_OutputWorker, "output worker", B_LOW_PRIORITY, this);
184	if (fOutputWorker < 0)
185		throw std::bad_alloc();
186
187	resume_thread(fOutputWorker);
188
189	BScrollView* consoleScrollView;
190
191	BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0.0f)
192		.Add(consoleScrollView = new BScrollView("console scroll", NULL, 0,
193			true, true), 3.0f)
194		.AddGroup(B_VERTICAL, 0.0f)
195			.SetInsets(B_USE_SMALL_SPACING)
196			.Add(fStdoutEnabled = new BCheckBox("Stdout"))
197			.Add(fStderrEnabled = new BCheckBox("Stderr"))
198			.Add(fClearButton = new BButton("Clear"))
199			.AddGlue()
200		.End()
201	.End();
202
203	consoleScrollView->SetTarget(fConsoleOutput = new BTextView("Console"));
204
205	fClearButton->SetMessage(new BMessage(MSG_CLEAR_OUTPUT));
206	fConsoleOutput->MakeEditable(false);
207	fConsoleOutput->SetStylable(true);
208	fConsoleOutput->SetDoesUndo(false);
209}
210
211
212int32
213ConsoleOutputView::_OutputWorker(void* arg)
214{
215	ConsoleOutputView* view = (ConsoleOutputView*)arg;
216
217	for (;;) {
218		status_t error = acquire_sem(view->fWorkToDoSem);
219		if (error == B_INTERRUPTED)
220			continue;
221		else if (error != B_OK)
222			break;
223
224		BMessenger(view).SendMessage(MSG_POST_OUTPUT);
225		snooze(kOutputWaitInterval);
226	}
227
228	return B_OK;
229}
230
231
232void
233ConsoleOutputView::_HandleConsoleOutput(OutputInfo* info)
234{
235	if (info->fd == 1 && fStdoutEnabled->Value() != B_CONTROL_ON)
236		return;
237	else if (info->fd == 2 && fStderrEnabled->Value() != B_CONTROL_ON)
238		return;
239
240	text_run_array run;
241	run.count = 1;
242	run.runs[0].font = be_fixed_font;
243	run.runs[0].offset = 0;
244	run.runs[0].color.red = info->fd == 1 ? 0 : 192;
245	run.runs[0].color.green = 0;
246	run.runs[0].color.blue = 0;
247	run.runs[0].color.alpha = 255;
248
249	bool autoScroll = false;
250	BScrollBar* scroller = fConsoleOutput->ScrollBar(B_VERTICAL);
251	float min, max;
252	scroller->GetRange(&min, &max);
253	if (min == max || scroller->Value() == max)
254		autoScroll = true;
255
256	fConsoleOutput->Insert(fConsoleOutput->TextLength(), info->text,
257		info->text.Length(), &run);
258	if (autoScroll) {
259		scroller->GetRange(&min, &max);
260		fConsoleOutput->ScrollTo(0.0, max);
261	}
262}
263