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