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