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
39const int32 kBtnHeight = 20;
40const int32 kBtnWidth = 60;
41const int32 kBtnBuffer = 25;
42const int32 kXBuffer = 10;
43const int32 kYBuffer = 10;
44const int32 kMenuHeight = 15;
45const int32 kButtonHeight = 15;
46const 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
56namespace {
57
58void
59ErrorAlert(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 [%lx]", message, strerror(err), err);
69}
70
71
72// functions for EnumeratedStringValueSettings
73
74const char*
75CaptureRateAt(int32 i)
76{
77	return (i >= 0 && i < kCaptureRatesCount) ? kCaptureRates[i].name : NULL;
78}
79
80
81const char*
82UploadClientAt(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
95CodyCam::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
132CodyCam::~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
147void
148CodyCam::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
162bool
163CodyCam::QuitRequested()
164{
165	_TearDownNodes();
166	snooze(100000);
167
168	return true;
169}
170
171
172void
173CodyCam::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
229status_t
230CodyCam::_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
390void
391CodyCam::_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
421VideoWindow::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
494VideoWindow::~VideoWindow()
495{
496	_QuitSettings();
497}
498
499
500
501bool
502VideoWindow::QuitRequested()
503{
504	be_app->PostMessage(B_QUIT_REQUESTED);
505	return false;
506}
507
508
509void
510VideoWindow::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 = %ld\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
599BView*
600VideoWindow::VideoView()
601{
602	return fVideoView;
603}
604
605
606BStringView*
607VideoWindow::StatusLine()
608{
609	return fStatusLine;
610}
611
612
613void
614VideoWindow::_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
729void
730VideoWindow::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
750void
751VideoWindow::_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
799void
800VideoWindow::_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
811void
812VideoWindow::_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
829void
830VideoWindow::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
846ControlWindow::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
859void
860ControlWindow::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
900bool
901ControlWindow::QuitRequested()
902{
903	be_app->PostMessage(msg_control_win);
904	return true;
905}
906
907
908//	#pragma mark -
909
910
911int main() {
912	CodyCam app;
913	app.Run();
914	return 0;
915}
916
917