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