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* 60 CaptureRateAt(int32 i) 61 { 62 return (i >= 0 && i < kCaptureRatesCount) ? kCaptureRates[i].name : NULL; 63 } 64 65 66 const char* 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 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 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 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 148 CodyCam::QuitRequested() 149 { 150 _TearDownNodes(); 151 snooze(100000); 152 153 return true; 154 } 155 156 157 void 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 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 211 status_t 212 CodyCam::_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 376 void 377 CodyCam::_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 407 VideoWindow::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 490 VideoWindow::~VideoWindow() 491 { 492 _QuitSettings(); 493 } 494 495 496 bool 497 VideoWindow::QuitRequested() 498 { 499 be_app->PostMessage(B_QUIT_REQUESTED); 500 return false; 501 } 502 503 504 void 505 VideoWindow::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 594 BView* 595 VideoWindow::VideoView() 596 { 597 return fVideoView; 598 } 599 600 601 BStringView* 602 VideoWindow::StatusLine() 603 { 604 return fStatusLine; 605 } 606 607 608 void 609 VideoWindow::_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 743 void 744 VideoWindow::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 764 void 765 VideoWindow::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 783 void 784 VideoWindow::_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 832 void 833 VideoWindow::_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 844 void 845 VideoWindow::_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 863 void 864 VideoWindow::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 880 ControlWindow::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 893 void 894 ControlWindow::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 935 bool 936 ControlWindow::QuitRequested() 937 { 938 be_app->PostMessage(msg_control_win); 939 return true; 940 } 941 942 943 // #pragma mark - 944 945 946 int main() 947 { 948 CodyCam app; 949 app.Run(); 950 return 0; 951 } 952