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