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