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