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