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