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 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 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 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* 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 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 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 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 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 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 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 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 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