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
48static const unsigned int kMinSourceWidth = 12;
49static const unsigned int kQualitySliderWidth = 28;
50static const unsigned int kDurationWidth = 10;
51
52
53// #pragma mark - DirectoryFilter
54
55
56class DirectoryFilter : public BRefFilter {
57public:
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
70class FileFormatMenuItem : public BMenuItem {
71public:
72	FileFormatMenuItem(media_file_format* format);
73	virtual ~FileFormatMenuItem();
74
75	media_file_format fFileFormat;
76};
77
78
79FileFormatMenuItem::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
87FileFormatMenuItem::~FileFormatMenuItem()
88{
89}
90
91
92// #pragma mark - CodecMenuItem
93
94
95class CodecMenuItem : public BMenuItem {
96public:
97	CodecMenuItem(media_codec_info* ci, uint32 message_type);
98	virtual ~CodecMenuItem();
99
100	media_codec_info fCodecInfo;
101};
102
103
104CodecMenuItem::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
112CodecMenuItem::~CodecMenuItem()
113{
114}
115
116
117// #pragma mark - OutputBox
118
119
120class OutputBox : public BBox {
121public:
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
134OutputBox::OutputBox(border_style border, BView* child)
135	:
136	BBox(border, child)
137{
138}
139
140
141// #pragma mark - MediaConverterWindow
142
143
144MediaConverterWindow::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
295MediaConverterWindow::~MediaConverterWindow()
296{
297	delete fSaveFilePanel;
298	delete fOpenFilePanel;
299}
300
301
302// #pragma mark -
303
304
305void
306MediaConverterWindow::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
532bool
533MediaConverterWindow::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
551void
552MediaConverterWindow::LanguageChanged()
553{
554	_DestroyMenu();
555	_CreateMenu();
556	_UpdateLabels();
557	BuildAudioVideoMenus();
558	Lock();
559	fInfoView->Invalidate();
560	Unlock();
561}
562
563
564void
565MediaConverterWindow::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
677void
678MediaConverterWindow::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
702void
703MediaConverterWindow::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
732void
733MediaConverterWindow::SetFileMessage(const char* message)
734{
735	fFileStatus->SetText(message);
736}
737
738
739void
740MediaConverterWindow::SetStatusMessage(const char* message)
741{
742	fStatus->SetText(message);
743}
744
745
746// #pragma mark -
747
748
749bool
750MediaConverterWindow::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
765void
766MediaConverterWindow::RemoveSourceFile(int32 index)
767{
768	delete fListView->RemoveItem(index);
769	fStartDurationTC->SetText("0");
770	fEndDurationTC->SetText("0");
771}
772
773
774int32
775MediaConverterWindow::CountSourceFiles()
776{
777	return fListView->CountItems();
778}
779
780
781status_t
782MediaConverterWindow::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
796void
797MediaConverterWindow::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
828void
829MediaConverterWindow::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
846bool
847MediaConverterWindow::IsEnabled()
848{
849	return fEnabled;
850}
851
852
853const char*
854MediaConverterWindow::StartDuration() const
855{
856	return fStartDurationTC->Text();
857}
858
859
860const char*
861MediaConverterWindow::EndDuration() const
862{
863	return fEndDurationTC->Text();
864}
865
866
867BDirectory
868MediaConverterWindow::OutputDirectory() const
869{
870	return fOutputDir;
871}
872
873
874void
875MediaConverterWindow::SetAudioQualityLabel(const char* label)
876{
877	fAudioQualitySlider->SetLabel(label);
878}
879
880
881void
882MediaConverterWindow::SetVideoQualityLabel(const char* label)
883{
884	fVideoQualitySlider->SetLabel(label);
885}
886
887
888void
889MediaConverterWindow::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
910void
911MediaConverterWindow::_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
985void
986MediaConverterWindow::_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
998void
999MediaConverterWindow::_DestroyMenu()
1000{
1001	BMenu* menu;
1002
1003	while ((menu = fMenuBar->SubmenuAt(0)) != NULL) {
1004		fMenuBar->RemoveItem(menu);
1005		delete menu;
1006	}
1007}
1008
1009
1010void
1011MediaConverterWindow::_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
1029void
1030MediaConverterWindow::_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