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