xref: /haiku/src/apps/codycam/CodyCam.cpp (revision 495060760727dd782c9f8a90db71e5d727f19748)
1 #include "CodyCam.h"
2 
3 #include <stdio.h>
4 #include <string.h>
5 #include <unistd.h>
6 
7 #include <Alert.h>
8 #include <Button.h>
9 #include <Catalog.h>
10 #include <FindDirectory.h>
11 #include <LayoutBuilder.h>
12 #include <MediaDefs.h>
13 #include <MediaNode.h>
14 #include <MediaRoster.h>
15 #include <MediaTheme.h>
16 #include <Menu.h>
17 #include <MenuBar.h>
18 #include <MenuItem.h>
19 #include <Path.h>
20 #include <PopUpMenu.h>
21 #include <scheduler.h>
22 #include <TabView.h>
23 #include <TextControl.h>
24 #include <TimeSource.h>
25 #include <TranslationUtils.h>
26 
27 #undef B_TRANSLATION_CONTEXT
28 #define B_TRANSLATION_CONTEXT "CodyCam"
29 
30 #define VIDEO_SIZE_X 320
31 #define VIDEO_SIZE_Y 240
32 
33 #define WINDOW_SIZE_X (VIDEO_SIZE_X + 80)
34 #define WINDOW_SIZE_Y (VIDEO_SIZE_Y + 230)
35 
36 #define WINDOW_OFFSET_X 28
37 #define WINDOW_OFFSET_Y 28
38 
39 #define	CALL		printf
40 #define ERROR		printf
41 #define FTPINFO		printf
42 #define	INFO		printf
43 
44 
45 // Utility functions
46 
47 namespace {
48 
49 // functions for EnumeratedStringValueSettings
50 
51 const char*
52 CaptureRateAt(int32 i)
53 {
54 	return (i >= 0 && i < kCaptureRatesCount) ? kCaptureRates[i].name : NULL;
55 }
56 
57 
58 const char*
59 UploadClientAt(int32 i)
60 {
61 	return (i >= 0 && i < kUploadClientsCount) ? kUploadClients[i] : NULL;
62 }
63 
64 
65 }; // end anonymous namespace
66 
67 
68 
69 //	#pragma mark -
70 
71 
72 CodyCam::CodyCam()
73 	:
74 	BApplication("application/x-vnd.Haiku-CodyCam"),
75 	fMediaRoster(NULL),
76 	fVideoConsumer(NULL),
77 	fWindow(NULL),
78 	fPort(0),
79 	fVideoControlWindow(NULL)
80 {
81 	int32 index = 0;
82 	kCaptureRates[index++].name = B_TRANSLATE("Every 15 seconds");
83 	kCaptureRates[index++].name = B_TRANSLATE("Every 30 seconds");
84 	kCaptureRates[index++].name = B_TRANSLATE("Every minute");
85 	kCaptureRates[index++].name = B_TRANSLATE("Every 5 minutes");
86 	kCaptureRates[index++].name = B_TRANSLATE("Every 10 minutes");
87 	kCaptureRates[index++].name = B_TRANSLATE("Every 15 minutes");
88 	kCaptureRates[index++].name = B_TRANSLATE("Every 30 minutes");
89 	kCaptureRates[index++].name = B_TRANSLATE("Every hour");
90 	kCaptureRates[index++].name = B_TRANSLATE("Every 2 hours");
91 	kCaptureRates[index++].name = B_TRANSLATE("Every 4 hours");
92 	kCaptureRates[index++].name = B_TRANSLATE("Every 8 hours");
93 	kCaptureRates[index++].name = B_TRANSLATE("Every 24 hours");
94 	kCaptureRates[index++].name = B_TRANSLATE("Never");
95 
96 	index = 0;
97 	kUploadClients[index++] = B_TRANSLATE("FTP");
98 	kUploadClients[index++] = B_TRANSLATE("SFTP");
99 	kUploadClients[index++] = B_TRANSLATE("Local");
100 
101 	BPath homeDir;
102 	if (find_directory(B_USER_DIRECTORY, &homeDir) != B_OK)
103 		homeDir.SetTo("/boot/home");
104 
105 	chdir(homeDir.Path());
106 }
107 
108 
109 CodyCam::~CodyCam()
110 {
111 	CALL("CodyCam::~CodyCam\n");
112 
113 	// release the video consumer node
114 	// the consumer node cleans up the window
115 	if (fVideoConsumer) {
116 		fVideoConsumer->Release();
117 		fVideoConsumer = NULL;
118 	}
119 
120 	CALL("CodyCam::~CodyCam - EXIT\n");
121 }
122 
123 
124 void
125 CodyCam::ReadyToRun()
126 {
127 	fWindow = new VideoWindow(
128 		(const char*) B_TRANSLATE_SYSTEM_NAME("CodyCam"), B_TITLED_WINDOW,
129 		B_NOT_ZOOMABLE | B_NOT_V_RESIZABLE
130 		| B_AUTO_UPDATE_SIZE_LIMITS, &fPort);
131 
132 	if (_SetUpNodes() != B_OK)
133 		fWindow->ToggleMenuOnOff();
134 
135 	((VideoWindow*)fWindow)->ApplyControls();
136 }
137 
138 
139 bool
140 CodyCam::QuitRequested()
141 {
142 	_TearDownNodes();
143 	snooze(100000);
144 
145 	return true;
146 }
147 
148 
149 void
150 CodyCam::MessageReceived(BMessage *message)
151 {
152 	switch (message->what) {
153 		case msg_start:
154 		{
155 			BTimeSource* timeSource = fMediaRoster->MakeTimeSourceFor(
156 				fTimeSourceNode);
157 			bigtime_t real = BTimeSource::RealTime();
158 			bigtime_t perf = timeSource->PerformanceTimeFor(real) + 10000;
159 			status_t status = fMediaRoster->StartNode(fProducerNode, perf);
160 			if (status != B_OK)
161 				ERROR("error starting producer!");
162 			timeSource->Release();
163 			break;
164 		}
165 
166 		case msg_stop:
167 			fMediaRoster->StopNode(fProducerNode, 0, true);
168 			break;
169 
170 		case msg_video:
171 		{
172 			if (fVideoControlWindow) {
173 				fVideoControlWindow->Activate();
174 				break;
175 			}
176 			BParameterWeb* web = NULL;
177 			BView* view = NULL;
178 			media_node node = fProducerNode;
179 			status_t err = fMediaRoster->GetParameterWebFor(node, &web);
180 			if (err >= B_OK && web != NULL) {
181 				view = BMediaTheme::ViewFor(web);
182 				fVideoControlWindow = new ControlWindow(view, node);
183 				fMediaRoster->StartWatching(BMessenger(NULL,
184 					fVideoControlWindow), node,	B_MEDIA_WEB_CHANGED);
185 				fVideoControlWindow->Show();
186 			}
187 			break;
188 		}
189 
190 		case msg_control_win:
191 			// our control window is being asked to go away
192 			// set our pointer to NULL
193 			fVideoControlWindow = NULL;
194 			break;
195 
196 		default:
197 			BApplication::MessageReceived(message);
198 			break;
199 	}
200 }
201 
202 
203 status_t
204 CodyCam::_SetUpNodes()
205 {
206 	status_t status = B_OK;
207 
208 	/* find the media roster */
209 	fMediaRoster = BMediaRoster::Roster(&status);
210 	if (status != B_OK) {
211 		fWindow->ErrorAlert(B_TRANSLATE("Cannot find the media roster"),
212 			status);
213 		return status;
214 	}
215 
216 	/* find the time source */
217 	status = fMediaRoster->GetTimeSource(&fTimeSourceNode);
218 	if (status != B_OK) {
219 		fWindow->ErrorAlert(B_TRANSLATE("Cannot get a time source"), status);
220 		return status;
221 	}
222 
223 	/* find a video producer node */
224 	INFO("CodyCam acquiring VideoInput node\n");
225 	status = fMediaRoster->GetVideoInput(&fProducerNode);
226 	if (status != B_OK) {
227 		fWindow->ErrorAlert(B_TRANSLATE("Cannot find a video source.\n"
228 			"You need a webcam to use CodyCam."), status);
229 		return status;
230 	}
231 
232 	/* create the video consumer node */
233 	fVideoConsumer = new VideoConsumer("CodyCam",
234 		((VideoWindow*)fWindow)->VideoView(),
235 		((VideoWindow*)fWindow)->StatusLine(), NULL, 0);
236 	if (!fVideoConsumer) {
237 		fWindow->ErrorAlert(B_TRANSLATE("Cannot create a video window"),
238 			B_ERROR);
239 		return B_ERROR;
240 	}
241 
242 	/* register the node */
243 	status = fMediaRoster->RegisterNode(fVideoConsumer);
244 	if (status != B_OK) {
245 		fWindow->ErrorAlert(B_TRANSLATE("Cannot register the video window"),
246 			status);
247 		return status;
248 	}
249 	fPort = fVideoConsumer->ControlPort();
250 
251 	/* find free producer output */
252 	int32 cnt = 0;
253 	status = fMediaRoster->GetFreeOutputsFor(fProducerNode, &fProducerOut, 1,
254 		&cnt, B_MEDIA_RAW_VIDEO);
255 	if (status != B_OK || cnt < 1) {
256 		status = B_RESOURCE_UNAVAILABLE;
257 		fWindow->ErrorAlert(B_TRANSLATE("Cannot find an available video stream"),
258 			status);
259 		return status;
260 	}
261 
262 	/* find free consumer input */
263 	cnt = 0;
264 	status = fMediaRoster->GetFreeInputsFor(fVideoConsumer->Node(),
265 		&fConsumerIn, 1, &cnt, B_MEDIA_RAW_VIDEO);
266 	if (status != B_OK || cnt < 1) {
267 		status = B_RESOURCE_UNAVAILABLE;
268 		fWindow->ErrorAlert(B_TRANSLATE("Can't find an available connection to "
269 			"the video window"), status);
270 		return status;
271 	}
272 
273 	/* Connect The Nodes!!! */
274 	media_format format;
275 	format.type = B_MEDIA_RAW_VIDEO;
276 	media_raw_video_format vid_format = {0, 1, 0, 239, B_VIDEO_TOP_LEFT_RIGHT,
277 		1, 1, {B_RGB32, VIDEO_SIZE_X, VIDEO_SIZE_Y, VIDEO_SIZE_X * 4, 0, 0}};
278 	format.u.raw_video = vid_format;
279 
280 	/* connect producer to consumer */
281 	status = fMediaRoster->Connect(fProducerOut.source,
282 		fConsumerIn.destination, &format, &fProducerOut, &fConsumerIn);
283 	if (status != B_OK) {
284 		fWindow->ErrorAlert(B_TRANSLATE("Cannot connect the video source to "
285 			"the video window"), status);
286 		return status;
287 	}
288 
289 
290 	/* set time sources */
291 	status = fMediaRoster->SetTimeSourceFor(fProducerNode.node,
292 		fTimeSourceNode.node);
293 	if (status != B_OK) {
294 		fWindow->ErrorAlert(B_TRANSLATE("Cannot set the time source for the "
295 			"video source"), status);
296 		return status;
297 	}
298 
299 	status = fMediaRoster->SetTimeSourceFor(fVideoConsumer->ID(),
300 		fTimeSourceNode.node);
301 	if (status != B_OK) {
302 		fWindow->ErrorAlert(B_TRANSLATE("Cannot set the time source for the "
303 			"video window"), status);
304 		return status;
305 	}
306 
307 	/* figure out what recording delay to use */
308 	bigtime_t latency = 0;
309 	status = fMediaRoster->GetLatencyFor(fProducerNode, &latency);
310 	status = fMediaRoster->SetProducerRunModeDelay(fProducerNode, latency);
311 
312 	/* start the nodes */
313 	bigtime_t initLatency = 0;
314 	status = fMediaRoster->GetInitialLatencyFor(fProducerNode, &initLatency);
315 	if (status < B_OK) {
316 		fWindow->ErrorAlert(B_TRANSLATE("Error getting initial latency for the "
317 			"capture node"), status);
318 		return status;
319 	}
320 
321 	initLatency += estimate_max_scheduling_latency();
322 
323 	BTimeSource* timeSource = fMediaRoster->MakeTimeSourceFor(fProducerNode);
324 	bool running = timeSource->IsRunning();
325 
326 	/* workaround for people without sound cards */
327 	/* because the system time source won't be running */
328 	bigtime_t real = BTimeSource::RealTime();
329 	if (!running) {
330 		status = fMediaRoster->StartTimeSource(fTimeSourceNode, real);
331 		if (status != B_OK) {
332 			timeSource->Release();
333 			fWindow->ErrorAlert(B_TRANSLATE("Cannot start time source!"),
334 				status);
335 			return status;
336 		}
337 		status = fMediaRoster->SeekTimeSource(fTimeSourceNode, 0, real);
338 		if (status != B_OK) {
339 			timeSource->Release();
340 			fWindow->ErrorAlert(B_TRANSLATE("Cannot seek time source!"),
341 				status);
342 			return status;
343 		}
344 	}
345 
346 	bigtime_t perf = timeSource->PerformanceTimeFor(real + latency
347 		+ initLatency);
348 	timeSource->Release();
349 
350 	/* start the nodes */
351 	status = fMediaRoster->StartNode(fProducerNode, perf);
352 	if (status != B_OK) {
353 		fWindow->ErrorAlert(B_TRANSLATE("Cannot start the video source"),
354 			status);
355 		return status;
356 	}
357 	status = fMediaRoster->StartNode(fVideoConsumer->Node(), perf);
358 	if (status != B_OK) {
359 		fWindow->ErrorAlert(B_TRANSLATE("Cannot start the video window"),
360 			status);
361 		return status;
362 	}
363 
364 	return status;
365 }
366 
367 
368 void
369 CodyCam::_TearDownNodes()
370 {
371 	CALL("CodyCam::_TearDownNodes\n");
372 	if (!fMediaRoster)
373 		return;
374 
375 	if (fVideoConsumer) {
376 		/* stop */
377 		INFO("stopping nodes!\n");
378 //		fMediaRoster->StopNode(fProducerNode, 0, true);
379 		fMediaRoster->StopNode(fVideoConsumer->Node(), 0, true);
380 
381 		/* disconnect */
382 		fMediaRoster->Disconnect(fProducerOut.node.node, fProducerOut.source,
383 			fConsumerIn.node.node, fConsumerIn.destination);
384 
385 		if (fProducerNode != media_node::null) {
386 			INFO("CodyCam releasing fProducerNode\n");
387 			fMediaRoster->ReleaseNode(fProducerNode);
388 			fProducerNode = media_node::null;
389 		}
390 		fMediaRoster->ReleaseNode(fVideoConsumer->Node());
391 		fVideoConsumer = NULL;
392 	}
393 }
394 
395 
396 //	#pragma mark - Video Window Class
397 
398 
399 VideoWindow::VideoWindow(const char* title, window_type type,
400 		uint32 flags, port_id* consumerPort)
401 	:
402 	BWindow(BRect(50, 50, 50, 50), title, type, flags),
403 	fPortPtr(consumerPort),
404 	fVideoView(NULL)
405 {
406 	fFtpInfo.port = 0;
407 	fFtpInfo.rate = 0x7fffffff;
408 	fFtpInfo.imageFormat = 0;
409 	fFtpInfo.translator = 0;
410 	fFtpInfo.passiveFtp = true;
411 	fFtpInfo.uploadClient = 0;
412 	strcpy(fFtpInfo.fileNameText, "filename");
413 	strcpy(fFtpInfo.serverText, "server");
414 	strcpy(fFtpInfo.loginText, "login");
415 	strcpy(fFtpInfo.passwordText, "password");
416 	strcpy(fFtpInfo.directoryText, "directory");
417 
418 	_SetUpSettings("codycam", "");
419 
420 	BMenuBar* menuBar = new BMenuBar("menu bar");
421 
422 	BMenuItem* menuItem;
423 	fMenu = new BMenu(B_TRANSLATE("File"));
424 
425 	menuItem = new BMenuItem(B_TRANSLATE("Video settings"),
426 		new BMessage(msg_video), 'P');
427 	menuItem->SetTarget(be_app);
428 	fMenu->AddItem(menuItem);
429 
430 	fMenu->AddSeparatorItem();
431 
432 	menuItem = new BMenuItem(B_TRANSLATE("Start video"),
433 		new BMessage(msg_start), 'A');
434 	menuItem->SetTarget(be_app);
435 	fMenu->AddItem(menuItem);
436 
437 	menuItem = new BMenuItem(B_TRANSLATE("Stop video"),
438 		new BMessage(msg_stop), 'O');
439 	menuItem->SetTarget(be_app);
440 	fMenu->AddItem(menuItem);
441 
442 	fMenu->AddSeparatorItem();
443 
444 	menuItem = new BMenuItem(B_TRANSLATE("Quit"),
445 		new BMessage(B_QUIT_REQUESTED), 'Q');
446 	menuItem->SetTarget(be_app);
447 	fMenu->AddItem(menuItem);
448 
449 	menuBar->AddItem(fMenu);
450 
451 	/* add some controls */
452 	_BuildCaptureControls();
453 
454 	BBox* box = new BBox("box");
455 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
456 	box->SetLayout(layout);
457 	layout->SetInsets(2, 2, 2, 2);
458 	box->AddChild(fVideoView);
459 	box->AddChild(fErrorView);
460 
461 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
462 		.Add(menuBar)
463 		.AddGroup(B_VERTICAL)
464 			.SetInsets(B_USE_WINDOW_SPACING)
465 			.AddGroup(B_HORIZONTAL)
466 				.AddGlue()
467 				.Add(box)
468 				.AddGlue()
469 			.End()
470 			.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
471 				.Add(fCaptureSetupBox)
472 				.Add(fFtpSetupBox)
473 				.End()
474 			.Add(fStatusLine)
475 			.End()
476 		.AddGlue();
477 
478 	Show();
479 }
480 
481 
482 VideoWindow::~VideoWindow()
483 {
484 	_QuitSettings();
485 }
486 
487 
488 bool
489 VideoWindow::QuitRequested()
490 {
491 	be_app->PostMessage(B_QUIT_REQUESTED);
492 	return false;
493 }
494 
495 
496 void
497 VideoWindow::MessageReceived(BMessage* message)
498 {
499 	BControl* control = NULL;
500 	message->FindPointer((const char*)"source", (void **)&control);
501 
502 	switch (message->what) {
503 		case msg_filename:
504 			if (control != NULL) {
505 				strlcpy(fFtpInfo.fileNameText,
506 					((BTextControl*)control)->Text(), 64);
507 				FTPINFO("file is '%s'\n", fFtpInfo.fileNameText);
508 			}
509 			break;
510 
511 		case msg_rate_changed: {
512 			int32 seconds;
513 			message->FindInt32("seconds", &seconds);
514 			if (seconds == 0) {
515 				FTPINFO("never\n");
516 				fFtpInfo.rate = (bigtime_t)(B_INFINITE_TIMEOUT);
517 			} else {
518 				FTPINFO("%ld seconds\n", (long)seconds);
519 				fFtpInfo.rate = (bigtime_t)(seconds * 1000000LL);
520 			}
521 			break;
522 		}
523 
524 		case msg_translate:
525 			message->FindInt32("be:type", (int32*)&(fFtpInfo.imageFormat));
526 			message->FindInt32("be:translator", &(fFtpInfo.translator));
527 			break;
528 
529 		case msg_upl_client:
530 			message->FindInt32("client", &(fFtpInfo.uploadClient));
531 			FTPINFO("upl client = %" B_PRId32 "\n", fFtpInfo.uploadClient);
532 			_UploadClientChanged();
533 			break;
534 
535 		case msg_server:
536 			if (control != NULL) {
537 				strlcpy(fFtpInfo.serverText,
538 					((BTextControl*)control)->Text(), 64);
539 				FTPINFO("server = '%s'\n", fFtpInfo.serverText);
540 			}
541 			break;
542 
543 		case msg_login:
544 			if (control != NULL) {
545 				strlcpy(fFtpInfo.loginText,
546 					((BTextControl*)control)->Text(), 64);
547 				FTPINFO("login = '%s'\n", fFtpInfo.loginText);
548 			}
549 			break;
550 
551 		case msg_password:
552 			if (control != NULL) {
553 				strlcpy(fFtpInfo.passwordText,
554 					((BTextControl*)control)->Text(), 64);
555 				FTPINFO("password = '%s'\n", fFtpInfo.passwordText);
556 			}
557 			break;
558 
559 		case msg_directory:
560 			if (control != NULL) {
561 				strlcpy(fFtpInfo.directoryText,
562 					((BTextControl*)control)->Text(), 64);
563 				FTPINFO("directory = '%s'\n", fFtpInfo.directoryText);
564 			}
565 			break;
566 
567 		case msg_passiveftp:
568 			if (control != NULL) {
569 				fFtpInfo.passiveFtp = ((BCheckBox*)control)->Value();
570 				if (fFtpInfo.passiveFtp)
571 					FTPINFO("using passive ftp\n");
572 			}
573 			break;
574 
575 		default:
576 			BWindow::MessageReceived(message);
577 			return;
578 	}
579 
580 	if (*fPortPtr)
581 		write_port(*fPortPtr, FTP_INFO, (void*)&fFtpInfo, sizeof(ftp_msg_info));
582 
583 }
584 
585 
586 BView*
587 VideoWindow::VideoView()
588 {
589 	return fVideoView;
590 }
591 
592 
593 BStringView*
594 VideoWindow::StatusLine()
595 {
596 	return fStatusLine;
597 }
598 
599 
600 void
601 VideoWindow::_BuildCaptureControls()
602 {
603 	// a view to hold the video image
604 	fVideoView = new BView("Video preview", B_WILL_DRAW);
605 	fVideoView->SetExplicitMinSize(BSize(VIDEO_SIZE_X, VIDEO_SIZE_Y));
606 	fVideoView->SetExplicitMaxSize(BSize(VIDEO_SIZE_X, VIDEO_SIZE_Y));
607 
608 	fErrorView = new BTextView("error");
609 	fErrorView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
610 
611 	// Capture controls
612 	BGridLayout *controlsLayout = new BGridLayout(B_USE_DEFAULT_SPACING,
613 		B_USE_SMALL_SPACING);
614 	controlsLayout->SetInsets(B_USE_SMALL_SPACING);
615 
616 	BView* controlView = new BView("Controls", B_SUPPORTS_LAYOUT, NULL);
617 	controlView->SetLayout(controlsLayout);
618 
619 	fCaptureSetupBox = new BBox("Capture Controls", B_WILL_DRAW);
620 	fCaptureSetupBox->SetLabel(B_TRANSLATE("Capture controls"));
621 	fCaptureSetupBox->AddChild(controlView);
622 
623 	// file name
624 	fFileName = new BTextControl("File Name", B_TRANSLATE("File name:"),
625 		fFilenameSetting->Value(), new BMessage(msg_filename));
626 	fFileName->SetTarget(BMessenger(NULL, this));
627 
628 	// format menu
629 	fImageFormatMenu = new BPopUpMenu(B_TRANSLATE("Image Format Menu"));
630 	BTranslationUtils::AddTranslationItems(fImageFormatMenu, B_TRANSLATOR_BITMAP);
631 	fImageFormatMenu->SetTargetForItems(this);
632 
633 	if (fImageFormatSettings->Value()
634 		&& fImageFormatMenu->FindItem(fImageFormatSettings->Value()) != NULL) {
635 		fImageFormatMenu->FindItem(
636 			fImageFormatSettings->Value())->SetMarked(true);
637 	} else if (fImageFormatMenu->FindItem("JPEG image") != NULL)
638 		fImageFormatMenu->FindItem("JPEG image")->SetMarked(true);
639 	else
640 		fImageFormatMenu->ItemAt(0)->SetMarked(true);
641 
642 	fImageFormatSelector = new BMenuField("Format", B_TRANSLATE("Format:"),
643 		fImageFormatMenu);
644 
645 	// capture rate
646 	fCaptureRateMenu = new BPopUpMenu(B_TRANSLATE("Capture Rate Menu"));
647 	for (int32 i = 0; i < kCaptureRatesCount; i++) {
648 		BMessage* itemMessage = new BMessage(msg_rate_changed);
649 		itemMessage->AddInt32("seconds", kCaptureRates[i].seconds);
650 		fCaptureRateMenu->AddItem(new BMenuItem(kCaptureRates[i].name,
651 			itemMessage));
652 	}
653 	fCaptureRateMenu->SetTargetForItems(this);
654 	fCaptureRateMenu->FindItem(fCaptureRateSetting->Value())->SetMarked(true);
655 	fCaptureRateSelector = new BMenuField("Rate", B_TRANSLATE("Rate:"),
656 		fCaptureRateMenu);
657 
658 	BLayoutBuilder::Grid<>(controlsLayout)
659 		.AddTextControl(fFileName, 0, 0)
660 		.AddMenuField(fImageFormatSelector, 0, 1)
661 		.AddMenuField(fCaptureRateSelector, 0, 2)
662 		.Add(BSpaceLayoutItem::CreateGlue(), 0, 3, 2, 1);
663 
664 	// FTP setup box
665 	BGridLayout* ftpLayout = new BGridLayout(B_USE_DEFAULT_SPACING,
666 		B_USE_SMALL_SPACING);
667 	ftpLayout->SetInsets(B_USE_SMALL_SPACING);
668 
669 	BView* outputView = new BView("Output", B_SUPPORTS_LAYOUT, NULL);
670 	outputView->SetLayout(ftpLayout);
671 
672 	fFtpSetupBox = new BBox("FTP Setup", B_WILL_DRAW);
673 	fFtpSetupBox->SetLabel(B_TRANSLATE("Output"));
674 	fFtpSetupBox->AddChild(outputView);
675 	float minWidth = be_plain_font->StringWidth(
676 		"The server label plus ftp.reasonably.com");
677 	fFtpSetupBox->SetExplicitMinSize(BSize(minWidth, B_SIZE_UNSET));
678 	fFtpSetupBox->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
679 
680 	fUploadClientMenu = new BPopUpMenu(B_TRANSLATE("Send to" B_UTF8_ELLIPSIS));
681 	for (int i = 0; i < kUploadClientsCount; i++) {
682 		BMessage *m = new BMessage(msg_upl_client);
683 		m->AddInt32("client", i);
684 		fUploadClientMenu->AddItem(new BMenuItem(kUploadClients[i], m));
685 	}
686 
687 	fUploadClientMenu->SetTargetForItems(this);
688 	fUploadClientMenu->FindItem(fUploadClientSetting->Value())->SetMarked(true);
689 
690 	fUploadClientSelector = new BMenuField("UploadClient", NULL,
691 		fUploadClientMenu);
692 
693 	fUploadClientSelector->SetLabel(B_TRANSLATE("Type:"));
694 
695 	fServerName = new BTextControl("Server", B_TRANSLATE("Server:"),
696 		fServerSetting->Value(), new BMessage(msg_server));
697 	fServerName->SetTarget(this);
698 
699 	fLoginId = new BTextControl("Login", B_TRANSLATE("Login:"),
700 		fLoginSetting->Value(), new BMessage(msg_login));
701 	fLoginId->SetTarget(this);
702 
703 	fPassword = new BTextControl("Password", B_TRANSLATE("Password:"),
704 		fPasswordSetting->Value(), new BMessage(msg_password));
705 	fPassword->SetTarget(this);
706 	fPassword->TextView()->HideTyping(true);
707 	// BeOS HideTyping() seems broken, it empties the text
708 	fPassword->SetText(fPasswordSetting->Value());
709 
710 	fDirectory = new BTextControl("Directory", B_TRANSLATE("Directory:"),
711 		fDirectorySetting->Value(), new BMessage(msg_directory));
712 	fDirectory->SetTarget(this);
713 
714 	fPassiveFtp = new BCheckBox("Passive FTP", B_TRANSLATE("Passive FTP"),
715 		new BMessage(msg_passiveftp));
716 	fPassiveFtp->SetTarget(this);
717 	fPassiveFtp->SetValue(fPassiveFtpSetting->Value());
718 	fPassiveFtp->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
719 
720 	BLayoutBuilder::Grid<>(ftpLayout)
721 		.AddMenuField(fUploadClientSelector, 0, 0)
722 		.AddTextControl(fServerName, 0, 1)
723 		.AddTextControl(fLoginId, 0, 2)
724 		.AddTextControl(fPassword, 0, 3)
725 		.AddTextControl(fDirectory, 0, 4)
726 		.Add(fPassiveFtp, 0, 5, 2, 1);
727 
728 	fStatusLine = new BStringView("Status Line",
729 		B_TRANSLATE("Waiting" B_UTF8_ELLIPSIS));
730 	fStatusLine->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
731 }
732 
733 
734 void
735 VideoWindow::ApplyControls()
736 {
737 	if (!Lock())
738 		return;
739 
740 	// apply controls
741 	fFileName->Invoke();
742 	PostMessage(fImageFormatMenu->FindMarked()->Message());
743 	PostMessage(fCaptureRateMenu->FindMarked()->Message());
744 	PostMessage(fUploadClientMenu->FindMarked()->Message());
745 	fServerName->Invoke();
746 	fLoginId->Invoke();
747 	fPassword->Invoke();
748 	fDirectory->Invoke();
749 	fPassiveFtp->Invoke();
750 
751 	Unlock();
752 }
753 
754 
755 void
756 VideoWindow::ErrorAlert(const char* message, status_t err)
757 {
758 	Lock();
759 	fErrorView->SetText(message);
760 	fErrorView->MakeEditable(false);
761 	fErrorView->MakeSelectable(false);
762 	fErrorView->SetWordWrap(true);
763 	fErrorView->SetAlignment(B_ALIGN_CENTER);
764 	fErrorView->SetExplicitMinSize(BSize(VIDEO_SIZE_X, VIDEO_SIZE_Y));
765 	fErrorView->SetExplicitMaxSize(BSize(VIDEO_SIZE_X, VIDEO_SIZE_Y));
766 	fErrorView->Show();
767 	fVideoView->Hide();
768 	Unlock();
769 
770 	printf("%s\n%s [%" B_PRIx32 "]", message, strerror(err), err);
771 }
772 
773 
774 void
775 VideoWindow::_SetUpSettings(const char* filename, const char* dirname)
776 {
777 	fSettings = new Settings(filename, dirname);
778 
779 	fServerSetting = new StringValueSetting("Server", "ftp.my.server",
780 		B_TRANSLATE("server address expected"));
781 
782 	fLoginSetting = new StringValueSetting("Login", "loginID",
783 		B_TRANSLATE("login ID expected"));
784 
785 	fPasswordSetting = new StringValueSetting("Password",
786 		B_TRANSLATE("password"), B_TRANSLATE("password expected"));
787 
788 	fDirectorySetting = new StringValueSetting("Directory", "web/images",
789 		B_TRANSLATE("destination directory expected"));
790 
791 	fPassiveFtpSetting = new BooleanValueSetting("PassiveFtp", 1);
792 
793 	fFilenameSetting = new StringValueSetting("StillImageFilename",
794 		"codycam.jpg", B_TRANSLATE("still image filename expected"));
795 
796 	fImageFormatSettings = new StringValueSetting("ImageFileFormat",
797 		B_TRANSLATE("JPEG image"), B_TRANSLATE("image file format expected"));
798 
799 	fCaptureRateSetting = new EnumeratedStringValueSetting("CaptureRate",
800 		kCaptureRates[3].name, &CaptureRateAt,
801 		B_TRANSLATE("capture rate expected"),
802 		"unrecognized capture rate specified");
803 
804 	fUploadClientSetting = new EnumeratedStringValueSetting("UploadClient",
805 		B_TRANSLATE("FTP"), &UploadClientAt,
806 		B_TRANSLATE("upload client name expected"),
807 		B_TRANSLATE("unrecognized upload client specified"));
808 
809 	fSettings->Add(fServerSetting);
810 	fSettings->Add(fLoginSetting);
811 	fSettings->Add(fPasswordSetting);
812 	fSettings->Add(fDirectorySetting);
813 	fSettings->Add(fPassiveFtpSetting);
814 	fSettings->Add(fFilenameSetting);
815 	fSettings->Add(fImageFormatSettings);
816 	fSettings->Add(fCaptureRateSetting);
817 	fSettings->Add(fUploadClientSetting);
818 
819 	fSettings->TryReadingSettings();
820 }
821 
822 
823 void
824 VideoWindow::_UploadClientChanged()
825 {
826 	bool enableServerControls = fFtpInfo.uploadClient < 2;
827 	fServerName->SetEnabled(enableServerControls);
828 	fLoginId->SetEnabled(enableServerControls);
829 	fPassword->SetEnabled(enableServerControls);
830 	fDirectory->SetEnabled(enableServerControls);
831 	fPassiveFtp->SetEnabled(enableServerControls);
832 }
833 
834 
835 void
836 VideoWindow::_QuitSettings()
837 {
838 	fServerSetting->ValueChanged(fServerName->Text());
839 	fLoginSetting->ValueChanged(fLoginId->Text());
840 	fPasswordSetting->ValueChanged(fFtpInfo.passwordText);
841 	fDirectorySetting->ValueChanged(fDirectory->Text());
842 	fPassiveFtpSetting->ValueChanged(fPassiveFtp->Value());
843 	fFilenameSetting->ValueChanged(fFileName->Text());
844 	fImageFormatSettings->ValueChanged(fImageFormatMenu->FindMarked()->Label());
845 	fCaptureRateSetting->ValueChanged(fCaptureRateMenu->FindMarked()->Label());
846 	fUploadClientSetting->ValueChanged(fUploadClientMenu->FindMarked()->Label());
847 
848 	fSettings->SaveSettings();
849 	delete fSettings;
850 }
851 
852 
853 void
854 VideoWindow::ToggleMenuOnOff()
855 {
856 	BMenuItem* item = fMenu->FindItem(msg_video);
857 	item->SetEnabled(!item->IsEnabled());
858 
859 	item = fMenu->FindItem(msg_start);
860 	item->SetEnabled(!item->IsEnabled());
861 
862 	item = fMenu->FindItem(msg_stop);
863 	item->SetEnabled(!item->IsEnabled());
864 }
865 
866 
867 //	#pragma mark -
868 
869 
870 ControlWindow::ControlWindow(BView* controls,
871 	media_node node)
872 	:
873 	BWindow(BRect(), B_TRANSLATE("Video settings"), B_TITLED_WINDOW,
874 		B_ASYNCHRONOUS_CONTROLS)
875 {
876 	fView = controls;
877 	fNode = node;
878 
879 	AddChild(fView);
880 }
881 
882 
883 void
884 ControlWindow::MessageReceived(BMessage* message)
885 {
886 	BParameterWeb* web = NULL;
887 	status_t err;
888 
889 	switch (message->what) {
890 		case B_MEDIA_WEB_CHANGED:
891 		{
892 			// If this is a tab view, find out which tab
893 			// is selected
894 			BTabView* tabView = dynamic_cast<BTabView*>(fView);
895 			int32 tabNum = -1;
896 			if (tabView)
897 				tabNum = tabView->Selection();
898 
899 			RemoveChild(fView);
900 			delete fView;
901 
902 			err = BMediaRoster::Roster()->GetParameterWebFor(fNode, &web);
903 
904 			if (err >= B_OK && web != NULL) {
905 				fView = BMediaTheme::ViewFor(web);
906 				AddChild(fView);
907 
908 				// Another tab view?  Restore previous selection
909 				if (tabNum > 0) {
910 					BTabView* newTabView = dynamic_cast<BTabView*>(fView);
911 					if (newTabView)
912 						newTabView->Select(tabNum);
913 				}
914 			}
915 			break;
916 		}
917 
918 		default:
919 			BWindow::MessageReceived(message);
920 			break;
921 	}
922 }
923 
924 
925 bool
926 ControlWindow::QuitRequested()
927 {
928 	be_app->PostMessage(msg_control_win);
929 	return true;
930 }
931 
932 
933 //	#pragma mark -
934 
935 
936 int main() {
937 	CodyCam app;
938 	app.Run();
939 	return 0;
940 }
941 
942