1 // Copyright 1999, Be Incorporated. All Rights Reserved. 2 // Copyright 2000-2004, Jun Suzuki. All Rights Reserved. 3 // Copyright 2007, 2010 Stephan Aßmus. All Rights Reserved. 4 // Copyright 2010-2013, Haiku, Inc. All Rights Reserved. 5 // This file may be used under the terms of the Be Sample Code License. 6 7 8 #include "MediaConverterWindow.h" 9 10 #include <stdio.h> 11 #include <string.h> 12 #include <unistd.h> 13 14 #include <Alert.h> 15 #include <Application.h> 16 #include <Box.h> 17 #include <Button.h> 18 #include <Catalog.h> 19 #include <ControlLook.h> 20 #include <FilePanel.h> 21 #include <FindDirectory.h> 22 #include <LayoutBuilder.h> 23 #include <Locale.h> 24 #include <Menu.h> 25 #include <MenuBar.h> 26 #include <MenuField.h> 27 #include <MenuItem.h> 28 #include <Path.h> 29 #include <PopUpMenu.h> 30 #include <Roster.h> 31 #include <ScrollBar.h> 32 #include <ScrollView.h> 33 #include <Slider.h> 34 #include <StringView.h> 35 #include <TextControl.h> 36 37 #include "MediaFileInfoView.h" 38 #include "MediaFileListView.h" 39 #include "MessageConstants.h" 40 41 42 #undef B_TRANSLATION_CONTEXT 43 #define B_TRANSLATION_CONTEXT "MediaConverter" 44 #define VERSION "1.3.0" 45 46 47 static const unsigned int kMinSourceWidth = 12; 48 static const unsigned int kQualitySliderWidth = 28; 49 static const unsigned int kDurationWidth = 10; 50 51 52 // #pragma mark - DirectoryFilter 53 54 55 class DirectoryFilter : public BRefFilter { 56 public: 57 DirectoryFilter() {}; 58 virtual bool Filter(const entry_ref* ref, 59 BNode* node, struct stat_beos* st, const char* filetype) 60 { 61 return node->IsDirectory(); 62 } 63 }; 64 65 66 // #pragma mark - FileFormatMenuItem 67 68 69 class FileFormatMenuItem : public BMenuItem { 70 public: 71 FileFormatMenuItem(media_file_format* format); 72 virtual ~FileFormatMenuItem(); 73 74 media_file_format fFileFormat; 75 }; 76 77 78 FileFormatMenuItem::FileFormatMenuItem(media_file_format* format) 79 : 80 BMenuItem(format->pretty_name, new BMessage(FORMAT_SELECT_MESSAGE)) 81 { 82 memcpy(&fFileFormat, format, sizeof(fFileFormat)); 83 } 84 85 86 FileFormatMenuItem::~FileFormatMenuItem() 87 { 88 } 89 90 91 // #pragma mark - CodecMenuItem 92 93 94 class CodecMenuItem : public BMenuItem { 95 public: 96 CodecMenuItem(media_codec_info* ci, uint32 message_type); 97 virtual ~CodecMenuItem(); 98 99 media_codec_info fCodecInfo; 100 }; 101 102 103 CodecMenuItem::CodecMenuItem(media_codec_info* ci, uint32 message_type) 104 : 105 BMenuItem(ci->pretty_name, new BMessage(message_type)) 106 { 107 memcpy(&fCodecInfo, ci, sizeof(fCodecInfo)); 108 } 109 110 111 CodecMenuItem::~CodecMenuItem() 112 { 113 } 114 115 116 // #pragma mark - OutputBox 117 118 119 class OutputBox : public BBox { 120 public: 121 OutputBox(border_style border, BView* child); 122 virtual void FrameResized(float width, float height) 123 { 124 MediaConverterWindow* window 125 = dynamic_cast<MediaConverterWindow*>(Window()); 126 if (window != NULL) 127 window->TruncateOutputFolderPath(); 128 BBox::FrameResized(width, height); 129 } 130 }; 131 132 133 OutputBox::OutputBox(border_style border, BView* child) 134 : 135 BBox(border, child) 136 { 137 } 138 139 140 // #pragma mark - MediaConverterWindow 141 142 143 MediaConverterWindow::MediaConverterWindow(BRect frame) 144 : 145 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("MediaConverter"), 146 B_TITLED_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, B_NOT_ZOOMABLE 147 | B_NOT_V_RESIZABLE | B_ASYNCHRONOUS_CONTROLS 148 | B_AUTO_UPDATE_SIZE_LIMITS), 149 fVideoQuality(75), 150 fAudioQuality(75), 151 fSaveFilePanel(NULL), 152 fOpenFilePanel(NULL), 153 fOutputDirSpecified(false), 154 fEnabled(true), 155 fConverting(false), 156 fCancelling(false) 157 { 158 BPath outputDir; 159 if (find_directory(B_USER_DIRECTORY, &outputDir) != B_OK) 160 outputDir.SetTo("/boot/home"); 161 fOutputDir.SetTo(outputDir.Path()); 162 163 fMenuBar = new BMenuBar("menubar"); 164 _CreateMenu(); 165 166 float padding = be_control_look->DefaultItemSpacing(); 167 168 fListView = new MediaFileListView(); 169 fListView->SetExplicitMinSize(BSize(padding * kMinSourceWidth, B_SIZE_UNSET)); 170 BScrollView* scroller = new BScrollView(NULL, fListView, 0, false, true); 171 172 // file list view box 173 fSourcesBox = new BBox(B_FANCY_BORDER, scroller); 174 fSourcesBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0)); 175 // fSourcesBox's layout adjusted in _UpdateLabels 176 177 // info box 178 fInfoView = new MediaFileInfoView(); 179 fInfoView->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 180 B_ALIGN_VERTICAL_UNSET)); 181 fInfoBox = new BBox(B_FANCY_BORDER, fInfoView); 182 183 // output menu fields 184 fFormatMenu = new BMenuField(NULL, B_TRANSLATE("File format:"), 185 new BPopUpMenu("")); 186 fAudioMenu = new BMenuField(NULL, B_TRANSLATE("Audio encoding:"), 187 new BPopUpMenu("")); 188 fVideoMenu = new BMenuField(NULL, B_TRANSLATE("Video encoding:"), 189 new BPopUpMenu("")); 190 191 // output folder 192 fDestButton = new BButton(B_TRANSLATE("Output folder"), 193 new BMessage(OUTPUT_FOLDER_MESSAGE)); 194 BAlignment labelAlignment(be_control_look->DefaultLabelAlignment()); 195 fOutputFolder = new BStringView(NULL, outputDir.Path()); 196 fOutputFolder->SetExplicitAlignment(labelAlignment); 197 198 // start/end duration 199 fStartDurationTC = new BTextControl(NULL, "0", NULL); 200 BLayoutItem* startDuration = fStartDurationTC->CreateTextViewLayoutItem(); 201 startDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET)); 202 startDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 203 B_ALIGN_VERTICAL_CENTER)); 204 fEndDurationTC = new BTextControl(NULL, "0", NULL); 205 BLayoutItem* endDuration = fEndDurationTC->CreateTextViewLayoutItem(); 206 endDuration->SetExplicitSize(BSize(padding * kDurationWidth, B_SIZE_UNSET)); 207 endDuration->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 208 B_ALIGN_VERTICAL_CENTER)); 209 210 // video quality 211 fVideoQualitySlider = new BSlider("VSlider", "" , 212 new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL); 213 fVideoQualitySlider->SetModificationMessage( 214 new BMessage(VIDEO_QUALITY_CHANGED_MESSAGE)); 215 fVideoQualitySlider->SetValue(fVideoQuality); 216 fVideoQualitySlider->SetEnabled(false); 217 fVideoQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth, 218 B_SIZE_UNSET)); 219 220 // audio quality 221 fAudioQualitySlider = new BSlider("ASlider", "" , 222 new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE), 1, 100, B_HORIZONTAL); 223 fAudioQualitySlider->SetModificationMessage( 224 new BMessage(AUDIO_QUALITY_CHANGED_MESSAGE)); 225 fAudioQualitySlider->SetValue(fAudioQuality); 226 fAudioQualitySlider->SetEnabled(false); 227 fAudioQualitySlider->SetExplicitSize(BSize(padding * kQualitySliderWidth, 228 B_SIZE_UNSET)); 229 230 // output format box 231 BView* outputGrid = BLayoutBuilder::Grid<>() 232 .Add(fFormatMenu->CreateLabelLayoutItem(), 0, 0) 233 .Add(fFormatMenu->CreateMenuBarLayoutItem(), 1, 0) 234 .Add(fAudioMenu->CreateLabelLayoutItem(), 0, 1) 235 .Add(fAudioMenu->CreateMenuBarLayoutItem(), 1, 1) 236 .Add(fVideoMenu->CreateLabelLayoutItem(), 0, 2) 237 .Add(fVideoMenu->CreateMenuBarLayoutItem(), 1, 2) 238 .Add(fDestButton, 0, 3) 239 .Add(fOutputFolder, 1, 3) 240 .Add(fStartDurationTC->CreateLabelLayoutItem(), 0, 4) 241 .Add(startDuration, 1, 4) 242 .Add(fEndDurationTC->CreateLabelLayoutItem(), 0, 5) 243 .Add(endDuration, 1, 5) 244 .Add(fVideoQualitySlider, 0, 6, 2, 1) 245 .Add(fAudioQualitySlider, 0, 7, 2, 1) 246 .View(); 247 outputGrid->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 248 B_ALIGN_USE_FULL_HEIGHT)); 249 fOutputBox = new OutputBox(B_FANCY_BORDER, outputGrid); 250 fOutputBox->SetLayout(new BGroupLayout(B_HORIZONTAL, 0)); 251 // fOutputBox's layout adjusted in _UpdateLabels 252 253 // buttons 254 fPreviewButton = new BButton(B_TRANSLATE("Preview"), 255 new BMessage(PREVIEW_MESSAGE)); 256 fPreviewButton->SetEnabled(false); 257 258 fConvertButton = new BButton(B_TRANSLATE("Convert"), 259 new BMessage(CONVERT_BUTTON_MESSAGE)); 260 261 // Status views 262 fStatus = new BStringView(NULL, NULL); 263 fStatus->SetExplicitAlignment(labelAlignment); 264 fFileStatus = new BStringView(NULL, NULL); 265 fFileStatus->SetExplicitAlignment(labelAlignment); 266 267 SetStatusMessage(""); 268 _UpdateLabels(); 269 270 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 271 .SetInsets(0, 0, 0, 0) 272 .Add(fMenuBar) 273 .AddSplit(B_HORIZONTAL, B_USE_DEFAULT_SPACING) 274 .SetInsets(B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING, 275 B_USE_WINDOW_SPACING, 0) 276 .Add(fSourcesBox) 277 .AddGroup(B_VERTICAL, B_USE_ITEM_SPACING) 278 .Add(fInfoBox) 279 .Add(fOutputBox) 280 .End() 281 .End() 282 .AddGrid(B_USE_ITEM_SPACING) 283 .SetInsets(B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING, 284 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING) 285 .Add(fStatus, 0, 0) 286 .Add(fFileStatus, 0, 1) 287 .Add(BSpaceLayoutItem::CreateGlue(), 1, 0) 288 .Add(fPreviewButton, 2, 0) 289 .Add(fConvertButton, 3, 0) 290 .End(); 291 } 292 293 294 MediaConverterWindow::~MediaConverterWindow() 295 { 296 delete fSaveFilePanel; 297 delete fOpenFilePanel; 298 } 299 300 301 // #pragma mark - 302 303 304 void 305 MediaConverterWindow::MessageReceived(BMessage* message) 306 { 307 entry_ref inRef; 308 309 char buffer[40]; 310 BEntry inEntry; 311 312 switch (message->what) { 313 #if B_BEOS_VERSION <= B_BEOS_VERSION_6 314 case B_LANGUAGE_CHANGED: 315 LanguageChanged(); 316 break; 317 #endif 318 319 case INIT_FORMAT_MENUS: 320 BuildFormatMenu(); 321 if (CountSourceFiles() == 0) 322 SetEnabled(false, false); 323 break; 324 325 case B_SIMPLE_DATA: 326 if (message->WasDropped()) { 327 DetachCurrentMessage(); 328 message->what = B_REFS_RECEIVED; 329 BMessenger(be_app).SendMessage(message); 330 delete message; 331 } 332 break; 333 334 case FORMAT_SELECT_MESSAGE: 335 BuildAudioVideoMenus(); 336 break; 337 case AUDIO_CODEC_SELECT_MESSAGE: 338 break; 339 case VIDEO_CODEC_SELECT_MESSAGE: 340 break; 341 342 case CONVERT_BUTTON_MESSAGE: 343 if (!fConverting) { 344 fConvertButton->SetLabel(B_TRANSLATE("Cancel")); 345 fConverting = true; 346 SetStatusMessage(B_TRANSLATE("Convert")); 347 SetEnabled(false, true); 348 BMessenger(be_app).SendMessage(START_CONVERSION_MESSAGE); 349 } else if (!fCancelling) { 350 fCancelling = true; 351 SetStatusMessage(B_TRANSLATE("Cancelling" B_UTF8_ELLIPSIS)); 352 BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE); 353 } 354 break; 355 356 case CONVERSION_DONE_MESSAGE: 357 { 358 SetStatusMessage(fCancelling ? B_TRANSLATE("Conversion cancelled") 359 : B_TRANSLATE("Conversion completed")); 360 fConverting = false; 361 fCancelling = false; 362 bool enable = CountSourceFiles() > 0; 363 SetEnabled(enable, enable); 364 fConvertButton->SetLabel(B_TRANSLATE("Convert")); 365 break; 366 } 367 368 case OUTPUT_FOLDER_MESSAGE: 369 // Execute Save Panel 370 if (fSaveFilePanel == NULL) { 371 BButton* selectThisDir; 372 373 BMessage folderSelect(FOLDER_SELECT_MESSAGE); 374 fSaveFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, 375 B_DIRECTORY_NODE, true, &folderSelect, NULL, false, true); 376 fSaveFilePanel->SetButtonLabel(B_DEFAULT_BUTTON, 377 B_TRANSLATE("Select")); 378 fSaveFilePanel->SetTarget(this); 379 380 fSaveFilePanel->Window()->Lock(); 381 fSaveFilePanel->Window()->SetTitle( 382 B_TRANSLATE("MediaConverter+:SaveDirectory")); 383 BRect buttonRect 384 = fSaveFilePanel->Window()->ChildAt(0)->FindView( 385 "cancel button")->Frame(); 386 buttonRect.right = buttonRect.left - 20; 387 buttonRect.left = buttonRect.right - 130; 388 selectThisDir = new BButton(buttonRect, NULL, 389 B_TRANSLATE("Select this folder"), 390 new BMessage(SELECT_THIS_DIR_MESSAGE), 391 B_FOLLOW_BOTTOM | B_FOLLOW_RIGHT); 392 selectThisDir->SetTarget(this); 393 fSaveFilePanel->Window()->ChildAt(0)->AddChild(selectThisDir); 394 fSaveFilePanel->Window()->Unlock(); 395 396 fSaveFilePanel->SetRefFilter(new DirectoryFilter); 397 } 398 fSaveFilePanel->Show(); 399 break; 400 401 case FOLDER_SELECT_MESSAGE: 402 // "SELECT" Button at Save Panel Pushed 403 fSaveFilePanel->GetNextSelectedRef(&inRef); 404 inEntry.SetTo(&inRef, true); 405 _SetOutputFolder(inEntry); 406 fOutputDirSpecified = true; 407 fSaveFilePanel->Rewind(); 408 break; 409 410 case SELECT_THIS_DIR_MESSAGE: 411 // "THIS DIR" Button at Save Panel Pushed 412 fSaveFilePanel->GetPanelDirectory(&inRef); 413 fSaveFilePanel->Hide(); 414 inEntry.SetTo(&inRef, true); 415 _SetOutputFolder(inEntry); 416 fOutputDirSpecified = true; 417 break; 418 419 case OPEN_FILE_MESSAGE: 420 // Execute Open Panel 421 if (!fOpenFilePanel) { 422 fOpenFilePanel = new BFilePanel(B_OPEN_PANEL, NULL, NULL, 423 B_FILE_NODE, true, NULL, NULL, false, true); 424 fOpenFilePanel->SetTarget(this); 425 } 426 fOpenFilePanel->Show(); 427 break; 428 429 case B_REFS_RECEIVED: 430 // Media Files Seleced by Open Panel 431 DetachCurrentMessage(); 432 message->what = B_REFS_RECEIVED; 433 BMessenger(be_app).SendMessage(message); 434 // fall through 435 436 case B_CANCEL: 437 break; 438 439 case QUIT_MESSAGE: 440 MediaConverterWindow::QuitRequested(); 441 break; 442 443 case PREVIEW_MESSAGE: 444 { 445 // Build the command line to launch the preview application. 446 // TODO: Launch the default app instead of hardcoded MediaPlayer! 447 int32 srcIndex = fListView->CurrentSelection(); 448 BMediaFile* inFile = NULL; 449 status_t status = GetSourceFileAt(srcIndex, &inFile, &inRef); 450 451 const char* argv[3]; 452 BString startPosString; 453 BPath path; 454 455 if (status == B_OK) { 456 argv[0] = "-pos"; 457 // NOTE: -pos argument is currently not supported by Haiku 458 // MediaPlayer. 459 startPosString << fStartDurationTC->Text(); 460 startPosString << "000"; 461 argv[1] = startPosString.String(); 462 463 status = inEntry.SetTo(&inRef); 464 } 465 466 if (status == B_OK) { 467 status = inEntry.GetPath(&path); 468 if (status == B_OK) 469 argv[2] = path.Path(); 470 } 471 472 if (status == B_OK) { 473 status = be_roster->Launch( 474 "application/x-vnd.Haiku-MediaPlayer", 475 3, (char**)argv, NULL); 476 } 477 478 if (status != B_OK && status != B_ALREADY_RUNNING) { 479 BString errorString(B_TRANSLATE("Error launching: %strError%")); 480 errorString.ReplaceFirst("%strError%", strerror(status)); 481 BAlert* alert = new BAlert(B_TRANSLATE("Error"), 482 errorString.String(), B_TRANSLATE("OK")); 483 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 484 alert->Go(); 485 } 486 break; 487 } 488 489 case VIDEO_QUALITY_CHANGED_MESSAGE: 490 { 491 int32 value; 492 message->FindInt32("be:value", &value); 493 snprintf(buffer, sizeof(buffer), 494 B_TRANSLATE("Video quality: %3d%%"), (int8)value); 495 fVideoQualitySlider->SetLabel(buffer); 496 fVideoQuality = value; 497 break; 498 } 499 500 case AUDIO_QUALITY_CHANGED_MESSAGE: 501 { 502 int32 value; 503 message->FindInt32("be:value", &value); 504 snprintf(buffer, sizeof(buffer), 505 B_TRANSLATE("Audio quality: %3d%%"), (int8)value); 506 fAudioQualitySlider->SetLabel(buffer); 507 fAudioQuality = value; 508 break; 509 } 510 511 default: 512 BWindow::MessageReceived(message); 513 break; 514 } 515 } 516 517 518 bool 519 MediaConverterWindow::QuitRequested() 520 { 521 if (!fConverting) { 522 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 523 return true; 524 } else if (!fCancelling) { 525 fCancelling = true; 526 SetStatusMessage(B_TRANSLATE("Cancelling")); 527 BMessenger(be_app).SendMessage(CANCEL_CONVERSION_MESSAGE); 528 } 529 530 return false; 531 } 532 533 534 // #pragma mark - 535 536 537 void 538 MediaConverterWindow::LanguageChanged() 539 { 540 _DestroyMenu(); 541 _CreateMenu(); 542 _UpdateLabels(); 543 BuildAudioVideoMenus(); 544 Lock(); 545 fInfoView->Invalidate(); 546 Unlock(); 547 } 548 549 550 void 551 MediaConverterWindow::BuildAudioVideoMenus() 552 { 553 BMenu* menu = fAudioMenu->Menu(); 554 BMenuItem* item; 555 556 // clear out old audio codec menu items 557 while ((item = menu->RemoveItem((int32)0)) != NULL) 558 delete item; 559 560 bool separator = true; 561 562 // get selected file format 563 FileFormatMenuItem* ffmi 564 = (FileFormatMenuItem*)fFormatMenu->Menu()->FindMarked(); 565 media_file_format* mf_format = &(ffmi->fFileFormat); 566 567 media_format format, outfmt; 568 format.Clear(); 569 media_codec_info codec_info; 570 int32 cookie = 0; 571 CodecMenuItem* cmi; 572 573 // add available audio encoders to menu 574 format.type = B_MEDIA_RAW_AUDIO; 575 format.u.raw_audio = media_raw_audio_format::wildcard; 576 while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info) 577 == B_OK) { 578 if (separator) { 579 menu->AddItem(new BMenuItem( 580 B_TRANSLATE_CONTEXT("No audio", "Audio codecs list"), 581 new BMessage(AUDIO_CODEC_SELECT_MESSAGE))); 582 menu->AddSeparatorItem(); 583 separator = false; 584 } 585 586 cmi = new CodecMenuItem(&codec_info, AUDIO_CODEC_SELECT_MESSAGE); 587 menu->AddItem(cmi); 588 // reset media format struct 589 /* 590 format.type = B_MEDIA_RAW_AUDIO; 591 format.u.raw_audio = media_raw_audio_format::wildcard; 592 */ 593 } 594 595 // mark first audio encoder 596 item = menu->ItemAt(0); 597 if (item != NULL) { 598 fAudioMenu->SetEnabled(fEnabled); 599 fAudioQualitySlider->SetEnabled(fEnabled); 600 item->SetMarked(true); 601 ((BInvoker*)item)->Invoke(); 602 } else { 603 item = new BMenuItem( 604 B_TRANSLATE_CONTEXT("None available", "Audio codecs"), NULL); 605 menu->AddItem(item); 606 item->SetMarked(true); 607 fAudioMenu->SetEnabled(false); 608 fAudioQualitySlider->SetEnabled(false); 609 } 610 611 // clear out old video codec menu items 612 menu = fVideoMenu->Menu(); 613 while ((item = menu->RemoveItem((int32)0)) != NULL) 614 delete item; 615 616 separator = true; 617 618 // construct a generic video format. Some of these parameters 619 // seem silly, but are needed for R4.5.x, which is more picky 620 // than subsequent BeOS releases will be. 621 format.Clear(); 622 format.type = B_MEDIA_RAW_VIDEO; 623 format.u.raw_video.last_active = (uint32)(240 - 1); 624 format.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; 625 format.u.raw_video.display.format = B_RGB32; 626 format.u.raw_video.display.line_width = (int32)320; 627 format.u.raw_video.display.line_count = (int32)240; 628 format.u.raw_video.display.bytes_per_row = 4 * 320; 629 630 // add available video encoders to menu 631 cookie = 0; 632 while (get_next_encoder(&cookie, mf_format, &format, &outfmt, &codec_info) 633 == B_OK) { 634 if (separator) { 635 menu->AddItem(new BMenuItem( 636 B_TRANSLATE_CONTEXT("No video", "Video codecs list"), 637 new BMessage(VIDEO_CODEC_SELECT_MESSAGE))); 638 menu->AddSeparatorItem(); 639 separator = false; 640 } 641 642 cmi = new CodecMenuItem(&codec_info, VIDEO_CODEC_SELECT_MESSAGE); 643 menu->AddItem(cmi); 644 } 645 646 // mark first video encoder 647 item = menu->ItemAt(0); 648 if (item != NULL) { 649 fVideoMenu->SetEnabled(fEnabled); 650 fVideoQualitySlider->SetEnabled(fEnabled); 651 item->SetMarked(true); 652 ((BInvoker*)item)->Invoke(); 653 } else { 654 item = new BMenuItem( 655 B_TRANSLATE_CONTEXT("None available", "Video codecs"), NULL); 656 menu->AddItem(item); 657 item->SetMarked(true); 658 fVideoMenu->SetEnabled(false); 659 fVideoQualitySlider->SetEnabled(false); 660 } 661 } 662 663 void 664 MediaConverterWindow::GetSelectedFormatInfo(media_file_format** format, 665 media_codec_info** audio, media_codec_info** video) 666 { 667 *audio = NULL; 668 *video = NULL; 669 *format = NULL; 670 671 FileFormatMenuItem* formatItem = 672 dynamic_cast<FileFormatMenuItem*>(fFormatMenu->Menu()->FindMarked()); 673 if (formatItem != NULL) 674 *format = &(formatItem->fFileFormat); 675 676 *audio = *video = NULL; 677 CodecMenuItem* codecItem = 678 dynamic_cast<CodecMenuItem*>(fAudioMenu->Menu()->FindMarked()); 679 if (codecItem != NULL) 680 *audio = &(codecItem->fCodecInfo); 681 682 codecItem = dynamic_cast<CodecMenuItem*>(fVideoMenu->Menu()->FindMarked()); 683 if (codecItem != NULL) 684 *video = &(codecItem->fCodecInfo); 685 } 686 687 688 void 689 MediaConverterWindow::BuildFormatMenu() 690 { 691 BMenu* menu = fFormatMenu->Menu(); 692 BMenuItem* item; 693 694 // clear out old format menu items 695 while ((item = menu->RemoveItem((int32)0)) != NULL) 696 delete item; 697 698 // add menu items for each file format 699 media_file_format mfi; 700 int32 cookie = 0; 701 FileFormatMenuItem* ff_item; 702 while (get_next_file_format(&cookie, &mfi) == B_OK) { 703 if ((mfi.capabilities & media_file_format::B_WRITABLE) == 0) 704 continue; 705 ff_item = new FileFormatMenuItem(&mfi); 706 menu->AddItem(ff_item); 707 } 708 709 // mark first item 710 item = menu->ItemAt(0); 711 if (item != NULL) { 712 item->SetMarked(true); 713 ((BInvoker*)item)->Invoke(); 714 } 715 } 716 717 718 void 719 MediaConverterWindow::SetFileMessage(const char* message) 720 { 721 fFileStatus->SetText(message); 722 } 723 724 725 void 726 MediaConverterWindow::SetStatusMessage(const char* message) 727 { 728 fStatus->SetText(message); 729 } 730 731 732 // #pragma mark - 733 734 735 bool 736 MediaConverterWindow::AddSourceFile(BMediaFile* file, const entry_ref& ref) 737 { 738 if (!fListView->AddMediaItem(file, ref)) 739 return false; 740 741 if (!fOutputDirSpecified) { 742 BEntry entry(&ref); 743 entry.GetParent(&entry); 744 _SetOutputFolder(entry); 745 } 746 747 return true; 748 } 749 750 751 void 752 MediaConverterWindow::RemoveSourceFile(int32 index) 753 { 754 delete fListView->RemoveItem(index); 755 fStartDurationTC->SetText("0"); 756 fEndDurationTC->SetText("0"); 757 } 758 759 760 int32 761 MediaConverterWindow::CountSourceFiles() 762 { 763 return fListView->CountItems(); 764 } 765 766 767 status_t 768 MediaConverterWindow::GetSourceFileAt(int32 index, BMediaFile** _file, 769 entry_ref* ref) 770 { 771 MediaFileListItem* item = dynamic_cast<MediaFileListItem*>( 772 fListView->ItemAt(index)); 773 if (item != NULL) { 774 *_file = item->fMediaFile; 775 *ref = item->fRef; 776 return B_OK; 777 } else 778 return B_ERROR; 779 } 780 781 782 void 783 MediaConverterWindow::SourceFileSelectionChanged() 784 { 785 int32 selected = fListView->CurrentSelection(); 786 BMediaFile* file = NULL; 787 entry_ref ref; 788 bool enabled = GetSourceFileAt(selected, &file, &ref) == B_OK; 789 790 fPreviewButton->SetEnabled(enabled); 791 fVideoQualitySlider->SetEnabled(enabled); 792 fAudioQualitySlider->SetEnabled(enabled); 793 fStartDurationTC->SetEnabled(enabled); 794 fEndDurationTC->SetEnabled(enabled); 795 796 BString duration; 797 if (enabled) { 798 fInfoView->Update(file, &ref); 799 // HACK: get the fInfoView to update the duration "synchronously" 800 UpdateIfNeeded(); 801 duration << fInfoView->Duration() / 1000; 802 } else 803 duration = "0"; 804 805 // update duration text controls 806 fStartDurationTC->SetText("0"); 807 fEndDurationTC->SetText(duration.String()); 808 } 809 810 811 // #pragma mark - 812 813 814 void 815 MediaConverterWindow::SetEnabled(bool enabled, bool convertEnabled) 816 { 817 fConvertButton->SetEnabled(convertEnabled); 818 if (enabled == fEnabled) 819 return; 820 821 fFormatMenu->SetEnabled(enabled); 822 fAudioMenu->SetEnabled(enabled); 823 fVideoMenu->SetEnabled(enabled); 824 fListView->SetEnabled(enabled); 825 fStartDurationTC->SetEnabled(enabled); 826 fEndDurationTC->SetEnabled(enabled); 827 828 fEnabled = enabled; 829 } 830 831 832 bool 833 MediaConverterWindow::IsEnabled() 834 { 835 return fEnabled; 836 } 837 838 839 const char* 840 MediaConverterWindow::StartDuration() const 841 { 842 return fStartDurationTC->Text(); 843 } 844 845 846 const char* 847 MediaConverterWindow::EndDuration() const 848 { 849 return fEndDurationTC->Text(); 850 } 851 852 853 BDirectory 854 MediaConverterWindow::OutputDirectory() const 855 { 856 return fOutputDir; 857 } 858 859 860 void 861 MediaConverterWindow::SetAudioQualityLabel(const char* label) 862 { 863 fAudioQualitySlider->SetLabel(label); 864 } 865 866 867 void 868 MediaConverterWindow::SetVideoQualityLabel(const char* label) 869 { 870 fVideoQualitySlider->SetLabel(label); 871 } 872 873 874 void 875 MediaConverterWindow::TruncateOutputFolderPath() 876 { 877 BEntry entry; 878 fOutputDir.GetEntry(&entry); 879 BPath path; 880 entry.GetPath(&path); 881 BString pathString(path.Path()); 882 float maxWidth = fVideoMenu->MenuBar()->Frame().Width(); 883 884 fOutputFolder->TruncateString(&pathString, B_TRUNCATE_MIDDLE, maxWidth); 885 fOutputFolder->SetText(pathString.String()); 886 if (fOutputFolder->StringWidth(path.Path()) > maxWidth) 887 fOutputFolder->SetToolTip(path.Path()); 888 else 889 fOutputFolder->SetToolTip((const char*)NULL); 890 } 891 892 893 // #pragma mark - 894 895 896 void 897 MediaConverterWindow::_UpdateLabels() 898 { 899 if (fSourcesBox != NULL) { 900 fSourcesBox->SetLabel(B_TRANSLATE("Source files")); 901 _UpdateBBoxLayoutInsets(fSourcesBox); 902 } 903 904 if (fInfoBox != NULL) 905 fInfoBox->SetLabel(B_TRANSLATE("File details")); 906 907 if (fOutputBox != NULL) { 908 fOutputBox->SetLabel(B_TRANSLATE("Output format")); 909 _UpdateBBoxLayoutInsets(fOutputBox); 910 } 911 912 if (fConvertButton != NULL) 913 fConvertButton->SetLabel(B_TRANSLATE("Convert")); 914 915 if (fPreviewButton != NULL) 916 fPreviewButton->SetLabel(B_TRANSLATE("Preview")); 917 918 if (fDestButton != NULL) 919 fDestButton->SetLabel(B_TRANSLATE("Output folder")); 920 921 if (fVideoQualitySlider != NULL) { 922 char buffer[40]; 923 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Video quality: %3d%%"), 924 (int8)fVideoQuality); 925 fVideoQualitySlider->SetLabel(buffer); 926 fVideoQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), 927 B_TRANSLATE("High")); 928 } 929 930 if (fAudioQualitySlider != NULL) { 931 char buffer[40]; 932 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Audio quality: %3d%%"), 933 (int8)fAudioQuality); 934 fAudioQualitySlider->SetLabel(buffer); 935 fAudioQualitySlider->SetLimitLabels(B_TRANSLATE("Low"), 936 B_TRANSLATE("High")); 937 } 938 939 if (fStartDurationTC != NULL) 940 fStartDurationTC->SetLabel(B_TRANSLATE("Start [ms]: ")); 941 942 if (fEndDurationTC != NULL) 943 fEndDurationTC->SetLabel(B_TRANSLATE("End [ms]: ")); 944 945 if (fFormatMenu != NULL) 946 fFormatMenu->SetLabel(B_TRANSLATE("File format:")); 947 948 if (fAudioMenu != NULL) 949 fAudioMenu->SetLabel(B_TRANSLATE("Audio encoding:")); 950 951 if (fVideoMenu != NULL) 952 fVideoMenu->SetLabel(B_TRANSLATE("Video encoding:")); 953 954 SetFileMessage(B_TRANSLATE("Drop media files onto this window")); 955 } 956 957 958 void 959 MediaConverterWindow::_UpdateBBoxLayoutInsets(BBox* box) 960 { 961 BTwoDimensionalLayout* layout 962 = dynamic_cast<BTwoDimensionalLayout*>(box->GetLayout()); 963 if (layout != NULL) { 964 float padding = be_control_look->DefaultItemSpacing(); 965 layout->SetInsets(padding, box->TopBorderOffset() + padding, padding, 966 padding); 967 } 968 } 969 970 971 void 972 MediaConverterWindow::_DestroyMenu() 973 { 974 BMenu* menu; 975 976 while ((menu = fMenuBar->SubmenuAt(0)) != NULL) { 977 fMenuBar->RemoveItem(menu); 978 delete menu; 979 } 980 } 981 982 983 void 984 MediaConverterWindow::_CreateMenu() 985 { 986 BMenu* menu; 987 BMenuItem* item; 988 989 menu = new BMenu(B_TRANSLATE_CONTEXT("File", "Menu")); 990 item = new BMenuItem(B_TRANSLATE_CONTEXT("Open" B_UTF8_ELLIPSIS, "Menu"), 991 new BMessage(OPEN_FILE_MESSAGE), 'O'); 992 menu->AddItem(item); 993 menu->AddSeparatorItem(); 994 item = new BMenuItem(B_TRANSLATE_CONTEXT("Quit", "Menu"), 995 new BMessage(QUIT_MESSAGE), 'Q'); 996 menu->AddItem(item); 997 998 fMenuBar->AddItem(menu); 999 } 1000 1001 1002 void 1003 MediaConverterWindow::_SetOutputFolder(BEntry entry) 1004 { 1005 BPath path; 1006 entry.GetPath(&path); 1007 if (access(path.Path(), W_OK) != -1) { 1008 fOutputDir.SetTo(&entry); 1009 } else { 1010 BString errorString(B_TRANSLATE("Error writing to location: %strPath%." 1011 " Defaulting to location: /boot/home")); 1012 errorString.ReplaceFirst("%strPath%", path.Path()); 1013 BAlert* alert = new BAlert(B_TRANSLATE("Error"), 1014 errorString.String(), B_TRANSLATE("OK")); 1015 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1016 alert->Go(); 1017 fOutputDir.SetTo("/boot/home"); 1018 } 1019 TruncateOutputFolderPath(); 1020 } 1021