xref: /haiku/src/apps/mediaconverter/MediaConverterWindow.cpp (revision fed8255311c711fdb1508d849002c5ed217e8f45)
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