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