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