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