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 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 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 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 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 491 VideoWindow::~VideoWindow() 492 { 493 _QuitSettings(); 494 } 495 496 497 bool 498 VideoWindow::QuitRequested() 499 { 500 be_app->PostMessage(B_QUIT_REQUESTED); 501 return false; 502 } 503 504 505 void 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* 596 VideoWindow::VideoView() 597 { 598 return fVideoView; 599 } 600 601 602 BStringView* 603 VideoWindow::StatusLine() 604 { 605 return fStatusLine; 606 } 607 608 609 void 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 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 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 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 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 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 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 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 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 938 ControlWindow::QuitRequested() 939 { 940 be_app->PostMessage(msg_control_win); 941 return true; 942 } 943 944 945 // #pragma mark - 946 947 948 int main() 949 { 950 CodyCam app; 951 app.Run(); 952 return 0; 953 } 954