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				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
212status_t
213CodyCam::_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
377void
378CodyCam::_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
408VideoWindow::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
491VideoWindow::~VideoWindow()
492{
493	_QuitSettings();
494}
495
496
497bool
498VideoWindow::QuitRequested()
499{
500	be_app->PostMessage(B_QUIT_REQUESTED);
501	return false;
502}
503
504
505void
506VideoWindow::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
595BView*
596VideoWindow::VideoView()
597{
598	return fVideoView;
599}
600
601
602BStringView*
603VideoWindow::StatusLine()
604{
605	return fStatusLine;
606}
607
608
609void
610VideoWindow::_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
744void
745VideoWindow::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
765void
766VideoWindow::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
784void
785VideoWindow::_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
833void
834VideoWindow::_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
845void
846VideoWindow::_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
864void
865VideoWindow::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
881ControlWindow::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
895void
896ControlWindow::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
937bool
938ControlWindow::QuitRequested()
939{
940	be_app->PostMessage(msg_control_win);
941	return true;
942}
943
944
945//	#pragma mark -
946
947
948int main()
949{
950	CodyCam app;
951	app.Run();
952	return 0;
953}
954