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
21 enum {
22 MSG_CLEAR_OUTPUT = 'clou',
23 MSG_POST_OUTPUT = 'poou'
24 };
25
26
27 static const bigtime_t kOutputWaitInterval = 10000;
28
29
30 // #pragma mark - ConsoleOutputView::OutputInfo
31
32
33 struct ConsoleOutputView::OutputInfo {
34 int32 fd;
35 BString text;
36
OutputInfoConsoleOutputView::OutputInfo37 OutputInfo(int32 fd, const BString& text)
38 :
39 fd(fd),
40 text(text)
41 {
42 }
43 };
44
45
46 // #pragma mark - ConsoleOutputView
47
48
ConsoleOutputView()49 ConsoleOutputView::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
~ConsoleOutputView()64 ConsoleOutputView::~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*
Create()77 ConsoleOutputView::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
92 void
ConsoleOutputReceived(int32 fd,const BString & output)93 ConsoleOutputView::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
112 void
MessageReceived(BMessage * message)113 ConsoleOutputView::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
138 void
AttachedToWindow()139 ConsoleOutputView::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
149 void
LoadSettings(const BMessage & settings)150 ConsoleOutputView::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
159 status_t
SaveSettings(BMessage & settings)160 ConsoleOutputView::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
174 void
_Init()175 ConsoleOutputView::_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
212 int32
_OutputWorker(void * arg)213 ConsoleOutputView::_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
232 void
_HandleConsoleOutput(OutputInfo * info)233 ConsoleOutputView::_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