]> git.sesse.net Git - vlc/blob - modules/gui/macosx/ConvertAndSave.m
macosx/CAS: implemented full profile management, which is retained across sessions...
[vlc] / modules / gui / macosx / ConvertAndSave.m
1 /*****************************************************************************
2  * ConvertAndSave.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2012 Felix Paul Kühne
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #import "ConvertAndSave.h"
25 #import "intf.h"
26 #import "playlist.h"
27 #import <vlc_common.h>
28 #import <vlc_url.h>
29
30 /* mini doc:
31  * the used NSMatrix includes a bunch of cells referenced most easily by tags. There you go: */
32 #define MPEGTS 0
33 #define WEBM 1
34 #define OGG 2
35 #define MP4 3
36 #define MPEGPS 4
37 #define MJPEG 5
38 #define WAV 6
39 #define FLV 7
40 #define MPEG1 8
41 #define MKV 9
42 #define RAW 10
43 #define AVI 11
44 #define ASF 12
45 /* 13-15 are present, but not set */
46
47 @interface VLCConvertAndSave ()
48 - (void)updateDropView;
49 - (void)updateOKButton;
50 - (void)resetCustomizationSheetBasedOnProfile:(NSString *)profileString;
51 - (void)selectCellByEncapsulationFormat:(NSString *)format;
52 - (NSString *)currentEncapsulationFormatAsFileExtension:(BOOL)b_extension;
53 - (NSString *)composedOptions;
54 - (void)updateCurrentProfile;
55 - (void)storeProfilesOnDisk;
56 @end
57
58 @implementation VLCConvertAndSave
59
60 @synthesize MRL=_MRL, outputDestination=_outputDestination, profileNames=_profileNames, profileValueList=_profileValueList, currentProfile=_currentProfile;
61
62 static VLCConvertAndSave *_o_sharedInstance = nil;
63
64 #pragma mark -
65 #pragma mark Initialization
66
67 + (void)initialize{
68     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
69
70     /* We are using the same format as the Qt4 intf here:
71      * Container(string), transcode video(bool), transcode audio(bool),
72      * use subtitles(bool), video codec(string), video bitrate(integer),
73      * scale(float), fps(float), width(integer, height(integer),
74      * audio codec(string), audio bitrate(integer), channels(integer),
75      * samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
76     NSArray * defaultProfiles = [[NSArray alloc] initWithObjects:
77                                  @"mp4;1;1;0;h264;0;0;0;0;0;mpga;128;2;44100;0;1",
78                                  @"webm;1;1;0;VP80;2000;0;0;0;0;vorb;128;2;44100;0;1",
79                                  @"ts;1;1;0;h264;800;1;0;0;0;mpga;128;2;44100;0;0",
80                                  @"ts;1;1;0;drac;800;1;0;0;0;mpga;128;2;44100;0;0",
81                                  @"ogg;1;1;0;theo;800;1;0;0;0;vorb;128;2;44100;0;0",
82                                  @"ogg;1;1;0;theo;800;1;0;0;0;flac;128;2;44100;0;0",
83                                  @"ts;1;1;0;mp2v;800;1;0;0;0;mpga;128;2;44100;0;0",
84                                  @"asf;1;1;0;WMV2;800;1;0;0;0;wma2;128;2;44100;0;0",
85                                  @"asf;1;1;0;DIV3;800;1;0;0;0;mp3;128;2;44100;0;0",
86                                  @"ogg;1;1;0;none;800;1;0;0;0;vorb;128;2;44100;none;0",
87                                  @"raw;1;1;0;none;800;1;0;0;0;mp3;128;2;44100;none;0",
88                                  @"mp4;1;1;0;none;800;1;0;0;0;mpga;128;2;44100;none;0",
89                                  @"raw;1;1;0;none;800;1;0;0;0;flac;128;2;44100;none;0",
90                                  @"wav;1;1;0;none;800;1;0;0;0;s16l;128;2;44100;none;0", nil];
91
92     NSArray * defaultProfileNames = [[NSArray alloc] initWithObjects:
93                                      @"Video - H.264 + MP3 (MP4)",
94                                      @"Video - VP80 + Vorbis (Webm)",
95                                      @"Video - H.264 + MP3 (TS)",
96                                      @"Video - Dirac + MP3 (TS)",
97                                      @"Video - Theora + Vorbis (OGG)",
98                                      @"Video - Theora + Flac (OGG)",
99                                      @"Video - MPEG-2 + MPGA (TS)",
100                                      @"Video - WMV + WMA (ASF)",
101                                      @"Video - DIV3 + MP3 (ASF)",
102                                      @"Audio - Vorbis (OGG)",
103                                      @"Audio - MP3",
104                                      @"Audio - MP3 (MP4)",
105                                      @"Audio - FLAC",
106                                      @"Audio - CD",
107                                      nil];
108
109     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:defaultProfiles, @"CASProfiles", defaultProfileNames, @"CASProfileNames", nil];
110
111     [defaults registerDefaults:appDefaults];
112     [defaultProfiles release];
113 }
114
115 + (VLCConvertAndSave *)sharedInstance
116 {
117     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
118 }
119
120 - (id)init
121 {
122     if (_o_sharedInstance) {
123         [self dealloc];
124     } else {
125         _o_sharedInstance = [super init];
126     }
127
128     return _o_sharedInstance;
129 }
130
131 - (void)dealloc
132 {
133     if (_currentProfile)
134         [_currentProfile release];
135
136     [_profileNames release];
137     [_profileValueList release];
138     [_videoCodecs release];
139     [_audioCodecs release];
140     [_subsCodecs release];
141
142     [super dealloc];
143 }
144
145 - (void)awakeFromNib
146 {
147     [_window setTitle: _NS("Convert & Save")];
148     [_ok_btn setTitle: _NS("Save")];
149     [_drop_lbl setStringValue: _NS("Drop media here")];
150     [_drop_btn setTitle: _NS("Open media...")];
151     [_profile_lbl setStringValue: _NS("Choose Profile")];
152     [_profile_btn setTitle: _NS("Customize...")];
153     [_destination_lbl setStringValue: _NS("Choose Destination")];
154     [_destination_filename_stub_lbl setStringValue: _NS("Choose an output location")];
155     [_destination_filename_lbl setHidden: YES];
156     [_destination_browse_btn setTitle:_NS("Browse...")];
157     [_destination_stream_btn setTitle:_NS("Setup Streaming...")];
158     [_destination_stream_lbl setStringValue:@"Select Streaming Method"];
159     [_destination_itwantafile_btn setTitle:_NS("Save as File")];
160     [_destination_itwantastream_btn setTitle:_NS("Stream")];
161     [_destination_cancel_btn setHidden:YES];
162     [_customize_ok_btn setTitle: _NS("Apply")];
163     [_customize_cancel_btn setTitle: _NS("Cancel")];
164     [_customize_newProfile_btn setTitle: _NS("Save as new Profile...")];
165     [[_customize_tabview tabViewItemAtIndex:0] setLabel: _NS("Encapsulation")];
166     [[_customize_tabview tabViewItemAtIndex:1] setLabel: _NS("Video codec")];
167     [[_customize_tabview tabViewItemAtIndex:2] setLabel: _NS("Audio codec")];
168     [[_customize_tabview tabViewItemAtIndex:3] setLabel: _NS("Subtitles")];
169     [_customize_tabview selectTabViewItemAtIndex: 0];
170     [_customize_vid_ckb setTitle: _NS("Video")];
171     [_customize_vid_keep_ckb setTitle: _NS("Keep original video track")];
172     [_customize_vid_codec_lbl setStringValue: _NS("Codec")];
173     [_customize_vid_bitrate_lbl setStringValue: _NS("Bitrate")];
174     [_customize_vid_framerate_lbl setStringValue: _NS("Frame Rate")];
175     [_customize_vid_res_box setTitle: _NS("Resolution")];
176     [_customize_vid_res_lbl setStringValue: _NS("You just need to fill one of the three following parameters, VLC will autodetect the other using the original aspect ratio")];
177     [_customize_vid_width_lbl setStringValue: _NS("Width")];
178     [_customize_vid_height_lbl setStringValue: _NS("Height")];
179     [_customize_vid_scale_lbl setStringValue: _NS("Scale")];
180     [_customize_aud_ckb setTitle: _NS("Audio")];
181     [_customize_aud_keep_ckb setTitle: _NS("Keep original audio track")];
182     [_customize_aud_codec_lbl setStringValue: _NS("Codec")];
183     [_customize_aud_bitrate_lbl setStringValue: _NS("Bitrate")];
184     [_customize_aud_channels_lbl setStringValue: _NS("Channels")];
185     [_customize_aud_samplerate_lbl setStringValue: _NS("Sample Rate")];
186     [_customize_subs_ckb setTitle: _NS("Subtitles")];
187     [_customize_subs_overlay_ckb setTitle: _NS("Overlay subtitles on the video")];
188     [_addProfile_title_lbl setStringValue:_NS("Save as new profile")];
189     [_addProfile_subtitle_lbl setStringValue:_NS("Enter a name for the new profile:")];
190     [_addProfile_cancel_btn setTitle:_NS("Cancel")];
191     [_addProfile_ok_btn setTitle:_NS("Save")];
192     [_deleteProfile_title_lbl setStringValue:_NS("Remove a profile")];
193     [_deleteProfile_subtitle_lbl setStringValue:_NS("Select the profile you would like to remove:")];
194     [_deleteProfile_cancel_btn setTitle:_NS("Cancel")];
195     [_deleteProfile_ok_btn setTitle:_NS("Remove")];
196     [_stream_ok_btn setTitle:_NS("Close")];
197     [_stream_destination_lbl setStringValue:_NS("Stream Destination")];
198     [_stream_announcement_lbl setStringValue:_NS("Stream Announcement")];
199     [_stream_type_lbl setStringValue:_NS("Type")];
200     [_stream_address_lbl setStringValue:_NS("Address")];
201     [_stream_ttl_lbl setStringValue:_NS("TTL")];
202     [_stream_ttl_fld setEnabled:NO];
203     [_stream_ttl_stepper setEnabled:NO];
204     [_stream_port_lbl setStringValue:_NS("Port")];
205     [_stream_sap_ckb setStringValue:_NS("SAP Announcement")];
206     [_stream_http_ckb setStringValue:_NS("HTTP Announcement")];
207     [_stream_rtsp_ckb setStringValue:_NS("RTSP Announcement")];
208     [_stream_sdp_ckb setStringValue:_NS("Export SDP as file")];
209     [_stream_sap_ckb setState:NSOffState];
210     [_stream_http_ckb setState:NSOffState];
211     [_stream_rtsp_ckb setState:NSOffState];
212     [_stream_sdp_ckb setState:NSOffState];
213
214     /* there is no way to hide single cells, so replace the existing ones with empty cells.. */
215     id blankCell = [[[NSCell alloc] init] autorelease];
216     [blankCell setEnabled:NO];
217     [_customize_encap_matrix putCell:blankCell atRow:3 column:1];
218     [_customize_encap_matrix putCell:blankCell atRow:3 column:2];
219     [_customize_encap_matrix putCell:blankCell atRow:3 column:3];
220
221     /* fetch profiles from defaults */
222     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
223     [self setProfileValueList: [defaults arrayForKey:@"CASProfiles"]];
224     [self setProfileNames: [defaults arrayForKey:@"CASProfileNames"]];
225     [self recreateProfilePopup];
226
227     _videoCodecs = [[NSArray alloc] initWithObjects:
228                     [NSArray arrayWithObjects:@"MPEG-1", @"MPEG-2", @"MPEG-4", @"DIVX 1", @"DIVX 2", @"DIVX 3", @"H.263", @"H.264", @"VP8", @"WMV1", @"WMV2", @"M-JPEG", @"Theora", @"Dirac", nil],
229                     [NSArray arrayWithObjects:@"mpgv", @"mp2v", @"mp4v", @"DIV1", @"DIV2", @"DIV3", @"H263", @"h264", @"VP80", @"WMV1", @"WMV2", @"MJPG", @"theo", @"drac", nil],
230                     nil];
231     _audioCodecs = [[NSArray alloc] initWithObjects:
232                     [NSArray arrayWithObjects:@"MPEG Audio", @"MP3", @"MPEG 4 Audio (AAC)", @"A52/AC-3", @"Vorbis", @"Flac", @"Speex", @"WAV", @"WMA2", nil],
233                     [NSArray arrayWithObjects:@"mpga", @"mp3", @"mp4a", @"a52", @"vorb", @"flac", @"spx", @"s16l", @"wma2", nil],
234                     nil];
235     _subsCodecs = [[NSArray alloc] initWithObjects:
236                    [NSArray arrayWithObjects:@"DVB subtitle", @"T.140", nil],
237                    [NSArray arrayWithObjects:@"dvbs", @"t140", nil],
238                    nil];
239
240     [_customize_vid_codec_pop removeAllItems];
241     [_customize_vid_scale_pop removeAllItems];
242     [_customize_aud_codec_pop removeAllItems];
243     [_customize_aud_samplerate_pop removeAllItems];
244     [_customize_subs_pop removeAllItems];
245
246     [_customize_vid_codec_pop addItemsWithTitles:[_videoCodecs objectAtIndex:0]];
247     [_customize_aud_codec_pop addItemsWithTitles:[_audioCodecs objectAtIndex:0]];
248     [_customize_subs_pop addItemsWithTitles:[_subsCodecs objectAtIndex:0]];
249
250     [_customize_aud_samplerate_pop addItemWithTitle:@"8000"];
251     [_customize_aud_samplerate_pop addItemWithTitle:@"11025"];
252     [_customize_aud_samplerate_pop addItemWithTitle:@"22050"];
253     [_customize_aud_samplerate_pop addItemWithTitle:@"44100"];
254     [_customize_aud_samplerate_pop addItemWithTitle:@"48000"];
255
256     [_customize_vid_scale_pop addItemWithTitle:@"1"];
257     [_customize_vid_scale_pop addItemWithTitle:@"0.25"];
258     [_customize_vid_scale_pop addItemWithTitle:@"0.5"];
259     [_customize_vid_scale_pop addItemWithTitle:@"0.75"];
260     [_customize_vid_scale_pop addItemWithTitle:@"1.25"];
261     [_customize_vid_scale_pop addItemWithTitle:@"1.5"];
262     [_customize_vid_scale_pop addItemWithTitle:@"1.75"];
263     [_customize_vid_scale_pop addItemWithTitle:@"2"];
264
265     [_ok_btn setEnabled: NO];
266
267     [self resetCustomizationSheetBasedOnProfile:[self.profileValueList objectAtIndex:0]];
268 }
269
270 # pragma mark -
271 # pragma mark Code to Communicate with other objects
272
273 - (void)toggleWindow
274 {
275     [_window makeKeyAndOrderFront: nil];
276 }
277
278 # pragma mark -
279 # pragma mark User Interaction
280
281 - (IBAction)finalizePanel:(id)sender
282 {
283     playlist_t * p_playlist = pl_Get(VLCIntf);
284
285     input_item_t *p_input = input_item_New([_MRL UTF8String], [[_dropin_media_lbl stringValue] UTF8String]);
286     if (!p_input)
287         return;
288
289     input_item_AddOption(p_input, [[self composedOptions] UTF8String], VLC_INPUT_OPTION_TRUSTED);
290
291     int returnValue;
292     returnValue = playlist_AddInput( p_playlist, p_input, PLAYLIST_STOP, PLAYLIST_END, true, pl_Unlocked );
293
294     if (returnValue == VLC_SUCCESS) {
295         /* let's "play" */
296         PL_LOCK;
297         playlist_item_t *p_item = playlist_ItemGetByInput( p_playlist, p_input );
298         playlist_Control( p_playlist, PLAYLIST_VIEWPLAY, pl_Locked, NULL,
299                          p_item );
300         PL_UNLOCK;
301     }
302     else
303         msg_Err( VLCIntf, "CAS: playlist add input failed :(");
304
305     /* we're done with this input */
306     vlc_gc_decref( p_input );
307
308     [_window performClose:sender];
309 }
310
311 - (IBAction)openMedia:(id)sender
312 {
313     /* preliminary implementation until the open panel is cleaned up */
314     NSOpenPanel * openPanel = [NSOpenPanel openPanel];
315     [openPanel setCanChooseDirectories:NO];
316     [openPanel setResolvesAliases:YES];
317     [openPanel setAllowsMultipleSelection:NO];
318     [openPanel beginSheetModalForWindow:_window completionHandler:^(NSInteger returnCode) {
319         if (returnCode == NSOKButton)
320         {
321             [self setMRL: [NSString stringWithUTF8String:vlc_path2uri([[[openPanel URL] path] UTF8String], NULL)]];
322             [self updateOKButton];
323             [self updateDropView];
324         }
325     }];
326 }
327
328 - (IBAction)switchProfile:(id)sender
329 {
330     NSUInteger index = [_profile_pop indexOfSelectedItem];
331     if (index < ([self.profileValueList count] - 1))
332         [self resetCustomizationSheetBasedOnProfile:[self.profileValueList objectAtIndex:index]];
333 }
334
335 - (IBAction)customizeProfile:(id)sender
336 {
337     [NSApp beginSheet:_customize_panel modalForWindow:_window modalDelegate:self didEndSelector:NULL contextInfo:nil];
338 }
339
340 - (IBAction)closeCustomizationSheet:(id)sender
341 {
342     [_customize_panel orderOut:sender];
343     [NSApp endSheet: _customize_panel];
344
345     if (sender == _customize_ok_btn)
346         [self updateCurrentProfile];
347 }
348
349 - (IBAction)newProfileAction:(id)sender
350 {
351     if (sender == _customize_newProfile_btn) {
352         [_addProfile_name_fld setStringValue:@""];
353         [NSApp beginSheet:_addProfile_panel modalForWindow:_customize_panel modalDelegate:self didEndSelector:NULL contextInfo:nil];
354     } else {
355         [_addProfile_panel orderOut:sender];
356         [NSApp endSheet: _addProfile_panel];
357
358         if (sender == _addProfile_ok_btn && [[_addProfile_name_fld stringValue] length] > 0) {
359             /* prepare current data */
360             [self updateCurrentProfile];
361
362             /* add profile to arrays */
363             NSMutableArray * workArray = [[NSMutableArray alloc] initWithArray:self.profileNames];
364             [workArray addObject:[_addProfile_name_fld stringValue]];
365             [self setProfileNames:[[[NSArray alloc] initWithArray:workArray] autorelease]];
366             [workArray release];
367             workArray = [[NSMutableArray alloc] initWithArray:self.profileValueList];
368             [workArray addObject:[[[NSArray alloc] initWithArray:self.currentProfile] autorelease]];
369             [self setProfileValueList:[[[NSArray alloc] initWithArray:workArray] autorelease]];
370             [workArray release];
371
372             /* update UI */
373             [self recreateProfilePopup];
374             [_profile_pop selectItemWithTitle:[_addProfile_name_fld stringValue]];
375
376             /* update internals */
377             [self switchProfile:sender];
378             [self storeProfilesOnDisk];
379         }
380     }
381 }
382
383 - (IBAction)deleteProfileAction:(id)sender
384 {
385     if (sender == _deleteProfile_cancel_btn) {
386         /* close panel */
387         [_deleteProfile_panel orderOut:sender];
388         [NSApp endSheet: _deleteProfile_panel];
389     } else if (sender == _deleteProfile_ok_btn) {
390         /* close panel */
391         [_deleteProfile_panel orderOut:sender];
392         [NSApp endSheet: _deleteProfile_panel];
393
394         /* remove requested profile from the arrays */
395         NSMutableArray * workArray = [[NSMutableArray alloc] initWithArray:self.profileNames];
396         [workArray removeObjectAtIndex:[_deleteProfile_pop indexOfSelectedItem]];
397         [self setProfileNames:[[[NSArray alloc] initWithArray:workArray] autorelease]];
398         [workArray release];
399         workArray = [[NSMutableArray alloc] initWithArray:self.profileValueList];
400         [workArray removeObjectAtIndex:[_deleteProfile_pop indexOfSelectedItem]];
401         [self setProfileValueList:[[[NSArray alloc] initWithArray:workArray] autorelease]];
402         [workArray release];
403
404         /* update UI */
405         [_profile_pop removeAllItems];
406         [_profile_pop addItemsWithTitles:self.profileNames];
407         [_profile_pop addItemWithTitle:_NS("Custom")];
408         [[_profile_pop menu] addItem:[NSMenuItem separatorItem]];
409         [_profile_pop addItemWithTitle:_NS("Organize Profiles")];
410         [[_profile_pop lastItem] setTarget: self];
411         [[_profile_pop lastItem] setAction: @selector(deleteProfileAction:)];
412
413         /* update internals */
414         [self switchProfile:sender];
415         [self storeProfilesOnDisk];
416     } else {
417         /* show panel */
418         [_deleteProfile_pop removeAllItems];
419         [_deleteProfile_pop addItemsWithTitles:self.profileNames];
420         [NSApp beginSheet:_deleteProfile_panel modalForWindow:_window modalDelegate:self didEndSelector:NULL contextInfo:nil];
421     }
422 }
423
424 - (IBAction)iWantAFile:(id)sender
425 {
426     NSRect boxFrame = [_destination_box frame];
427     NSRect subViewFrame = [_destination_itwantafile_view frame];
428     subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
429     subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
430     [_destination_itwantafile_view setFrame: subViewFrame];
431     [[_destination_itwantafile_btn animator] setHidden: YES];
432     [[_destination_itwantastream_btn animator] setHidden: YES];
433     [_destination_box performSelector:@selector(addSubview:) withObject:_destination_itwantafile_view afterDelay:0.2];
434     [[_destination_cancel_btn animator] setHidden:NO];
435     b_streaming = NO;
436     [_ok_btn setTitle:_NS("Save")];
437 }
438
439 - (IBAction)iWantAStream:(id)sender
440 {
441     NSRect boxFrame = [_destination_box frame];
442     NSRect subViewFrame = [_destination_itwantastream_view frame];
443     subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
444     subViewFrame.origin.y = ((boxFrame.size.height - subViewFrame.size.height) / 2) - 15.;
445     [_destination_itwantastream_view setFrame: subViewFrame];
446     [[_destination_itwantafile_btn animator] setHidden: YES];
447     [[_destination_itwantastream_btn animator] setHidden: YES];
448     [_destination_box performSelector:@selector(addSubview:) withObject:_destination_itwantastream_view afterDelay:0.2];
449     [[_destination_cancel_btn animator] setHidden:NO];
450     b_streaming = YES;
451     [_ok_btn setTitle:_NS("Stream")];
452 }
453
454 - (IBAction)cancelDestination:(id)sender
455 {
456     if ([_destination_itwantastream_view superview] != nil)
457         [_destination_itwantastream_view removeFromSuperview];
458     if ([_destination_itwantafile_view superview] != nil)
459         [_destination_itwantafile_view removeFromSuperview];
460
461     [_destination_cancel_btn setHidden:YES];
462     [[_destination_itwantafile_btn animator] setHidden: NO];
463     [[_destination_itwantastream_btn animator] setHidden: NO];
464     b_streaming = NO;
465 }
466
467 - (IBAction)browseFileDestination:(id)sender
468 {
469     NSSavePanel * saveFilePanel = [NSSavePanel savePanel];
470     [saveFilePanel setCanSelectHiddenExtension: YES];
471     [saveFilePanel setCanCreateDirectories: YES];
472     if ([[_customize_encap_matrix selectedCell] tag] != RAW) // there is no clever guess for this
473         [saveFilePanel setAllowedFileTypes:[NSArray arrayWithObject:[self currentEncapsulationFormatAsFileExtension:YES]]];
474     [saveFilePanel beginSheetModalForWindow:_window completionHandler:^(NSInteger returnCode) {
475         if (returnCode == NSOKButton) {
476             [self setOutputDestination:[[saveFilePanel URL] path]];
477             [_destination_filename_lbl setStringValue: [[NSFileManager defaultManager] displayNameAtPath:_outputDestination]];
478             [[_destination_filename_stub_lbl animator] setHidden: YES];
479             [[_destination_filename_lbl animator] setHidden: NO];
480         } else {
481             [self setOutputDestination:@""];
482             [[_destination_filename_lbl animator] setHidden: YES];
483             [[_destination_filename_stub_lbl animator] setHidden: NO];
484         }
485         [self updateOKButton];
486     }];
487 }
488
489 - (IBAction)showStreamPanel:(id)sender
490 {
491     [NSApp beginSheet:_stream_panel modalForWindow:_window modalDelegate:self didEndSelector:NULL contextInfo:nil];
492 }
493
494 - (IBAction)closeStreamPanel:(id)sender
495 {
496     NSMutableString * labelContent = [[NSMutableString alloc] initWithFormat:@"%@ @ %@:%@", [_stream_type_pop titleOfSelectedItem], [_stream_address_fld stringValue], [_stream_port_fld stringValue]];
497
498     if ([_stream_type_pop indexOfSelectedItem] > 1) {
499         [labelContent appendFormat:@" TTL:%@", [_stream_ttl_fld stringValue]];
500         if ([_stream_sap_ckb state] || [_stream_rtsp_ckb state] || [_stream_http_ckb state] || [_stream_sdp_ckb state])
501             [labelContent appendFormat:@" — %@:\"%@\" (", _NS("Channel"), [_stream_channel_fld stringValue]];
502
503         BOOL b_gotSomething;
504         if (_stream_sap_ckb) {
505             [labelContent appendString:@"SAP"];
506             b_gotSomething = YES;
507         }
508         if (_stream_rtsp_ckb) {
509             if (b_gotSomething)
510                 [labelContent appendString:@", "];
511             [labelContent appendString:@"RTSP"];
512             b_gotSomething = YES;
513         }
514         if (_stream_http_ckb) {
515             if (b_gotSomething)
516                 [labelContent appendString:@", "];
517             [labelContent appendString:@"HTTP"];
518             b_gotSomething = YES;
519         }
520         if ([_stream_sdp_ckb state]) {
521             if (b_gotSomething)
522                 [labelContent appendString:@", "];
523             [labelContent appendString:@"SDP"];
524         }
525
526         if ([_stream_sap_ckb state] || [_stream_rtsp_ckb state] || [_stream_http_ckb state] || [_stream_sdp_ckb state])
527             [labelContent appendString:@")"];
528     }
529     [_destination_stream_lbl setStringValue:labelContent];
530     [labelContent release];
531
532     [_stream_panel orderOut:sender];
533     [NSApp endSheet: _stream_panel];
534 }
535
536 - (IBAction)streamTypeToggle:(id)sender
537 {
538     NSUInteger index = [_stream_type_pop indexOfSelectedItem];
539     if (index <= 1) { // HTTP, MMSH
540         [_stream_ttl_fld setEnabled:NO];
541         [_stream_ttl_stepper setEnabled:NO];
542         [_stream_sap_ckb setEnabled:NO];
543         [_stream_rtsp_ckb setEnabled:NO];
544         [_stream_http_ckb setEnabled:NO];
545         [_stream_sdp_ckb setEnabled:NO];
546     } else if (index == 2) { // RTP
547         [_stream_ttl_fld setEnabled:YES];
548         [_stream_ttl_stepper setEnabled:YES];
549         [_stream_sap_ckb setEnabled:YES];
550         [_stream_rtsp_ckb setEnabled:YES];
551         [_stream_http_ckb setEnabled:YES];
552         [_stream_sdp_ckb setEnabled:YES];
553         [_stream_channel_fld setEnabled:YES];
554         [_stream_sdp_fld setEnabled:[_stream_sdp_ckb state]];
555     } else { // UDP
556         [_stream_ttl_fld setEnabled:YES];
557         [_stream_ttl_stepper setEnabled:YES];
558         [_stream_sap_ckb setEnabled:YES];
559         [_stream_rtsp_ckb setEnabled:NO];
560         [_stream_http_ckb setEnabled:NO];
561         [_stream_sdp_ckb setEnabled:NO];
562         [_stream_channel_fld setEnabled:YES];
563     }
564 }
565
566 - (IBAction)streamAnnouncementToggle:(id)sender
567 {
568     [_stream_sdp_fld setEnabled:[_stream_sdp_ckb state]];
569 }
570
571 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
572 {
573     NSPasteboard *paste = [sender draggingPasteboard];
574     NSArray *types = [NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil];
575     NSString *desired_type = [paste availableTypeFromArray: types];
576     NSData *carried_data = [paste dataForType: desired_type];
577
578     if( carried_data ) {
579         if( [desired_type isEqualToString:NSFilenamesPboardType] ) {
580             NSArray *values = [[paste propertyListForType: NSFilenamesPboardType] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
581
582             if ([values count] > 0) {
583                 [self setMRL: [NSString stringWithUTF8String:vlc_path2uri([[values objectAtIndex:0] UTF8String], NULL)]];
584                 [self updateOKButton];
585                 [self updateDropView];
586                 return YES;
587             }
588         } else if( [desired_type isEqualToString:@"VLCPlaylistItemPboardType"] ) {
589             NSArray * array = [[[VLCMain sharedInstance] playlist] draggedItems];
590             NSUInteger count = [array count];
591             if (count > 0) {
592                 playlist_t * p_playlist = pl_Get( VLCIntf );
593                 playlist_item_t * p_item = NULL;
594
595                 PL_LOCK;
596                 /* let's look for the first proper input item */
597                 for (NSUInteger x = 0; x < count; x++) {
598                     p_item = [[array objectAtIndex:x] pointerValue];
599                     if (p_item) {
600                         if (p_item->p_input) {
601                             if (p_item->p_input->psz_uri != nil) {
602                                 [self setMRL: [NSString stringWithFormat:@"%s", p_item->p_input->psz_uri]];
603                                 [self updateDropView];
604                                 [self updateOKButton];
605
606                                 PL_UNLOCK;
607
608                                 return YES;
609                             }
610                         }
611                     }
612                 }
613                 PL_UNLOCK;
614             }
615         }
616     }
617     return NO;
618 }
619
620 # pragma mark -
621 # pragma mark Private Functionality
622 - (void)updateDropView
623 {
624     if ([_MRL length] > 0) {
625         NSString * path = [[NSURL URLWithString:_MRL] path];
626         [_dropin_media_lbl setStringValue: [[NSFileManager defaultManager] displayNameAtPath: path]];
627         NSImage * image = [[NSWorkspace sharedWorkspace] iconForFile: path];
628         [image setSize:NSMakeSize(128,128)];
629         [_dropin_icon_view setImage: image];
630
631         if (![_dropin_view superview]) {
632             NSRect boxFrame = [_drop_box frame];
633             NSRect subViewFrame = [_dropin_view frame];
634             subViewFrame.origin.x = (boxFrame.size.width - subViewFrame.size.width) / 2;
635             subViewFrame.origin.y = (boxFrame.size.height - subViewFrame.size.height) / 2;
636             [_dropin_view setFrame: subViewFrame];
637             [[_drop_image_view animator] setHidden: YES];
638             [_drop_box performSelector:@selector(addSubview:) withObject:_dropin_view afterDelay:0.6];
639         }
640     } else {
641         [_dropin_view removeFromSuperview];
642         [[_drop_image_view animator] setHidden: NO];
643     }
644 }
645
646 - (void)updateOKButton
647 {
648     if ([_outputDestination length] > 0 && [_MRL length] > 0)
649         [_ok_btn setEnabled: YES];
650     else
651         [_ok_btn setEnabled: NO];
652 }
653
654 - (void)resetCustomizationSheetBasedOnProfile:(NSString *)profileString
655 {
656     /* Container(string), transcode video(bool), transcode audio(bool),
657     * use subtitles(bool), video codec(string), video bitrate(integer),
658     * scale(float), fps(float), width(integer, height(integer),
659     * audio codec(string), audio bitrate(integer), channels(integer),
660     * samplerate(integer), subtitle codec(string), subtitle overlay(bool) */
661
662     NSArray * components = [profileString componentsSeparatedByString:@";"];
663     if ([components count] != 16) {
664         msg_Err(VLCIntf, "CAS: the requested profile '%s' is invalid", [profileString UTF8String]);
665         return;
666     }
667
668     [self selectCellByEncapsulationFormat:[components objectAtIndex:0]];
669     [_customize_vid_ckb setState:[[components objectAtIndex:1] intValue]];
670     [_customize_aud_ckb setState:[[components objectAtIndex:2] intValue]];
671     [_customize_subs_ckb setState:[[components objectAtIndex:3] intValue]];
672     [_customize_vid_bitrate_fld setStringValue:[components objectAtIndex:5]];
673     [_customize_vid_scale_pop selectItemWithTitle:[components objectAtIndex:6]];
674     [_customize_vid_framerate_fld setStringValue:[components objectAtIndex:7]];
675     [_customize_vid_width_fld setStringValue:[components objectAtIndex:8]];
676     [_customize_vid_height_fld setStringValue:[components objectAtIndex:9]];
677     [_customize_aud_bitrate_fld setStringValue:[components objectAtIndex:11]];
678     [_customize_aud_channels_fld setStringValue:[components objectAtIndex:12]];
679     [_customize_aud_samplerate_pop selectItemWithTitle:[components objectAtIndex:13]];
680     [_customize_subs_overlay_ckb setState:[[components objectAtIndex:15] intValue]];
681
682     /* since there is no proper lookup mechanism in arrays, we need to implement a string specific one ourselves */
683     NSArray * tempArray = [_videoCodecs objectAtIndex:1];
684     NSUInteger count = [tempArray count];
685     NSString * searchString = [components objectAtIndex:4];
686     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
687         [_customize_vid_codec_pop selectItemAtIndex:-1];
688     } else {
689         for (NSUInteger x = 0; x < count; x++) {
690             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
691                 [_customize_vid_codec_pop selectItemAtIndex:x];
692                 break;
693             }
694         }
695     }
696
697     tempArray = [_audioCodecs objectAtIndex:1];
698     count = [tempArray count];
699     searchString = [components objectAtIndex:10];
700     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
701         [_customize_aud_codec_pop selectItemAtIndex:-1];
702     } else {
703         for (NSUInteger x = 0; x < count; x++) {
704             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
705                 [_customize_aud_codec_pop selectItemAtIndex:x];
706                 break;
707             }
708         }
709     }
710
711     tempArray = [_subsCodecs objectAtIndex:1];
712     count = [tempArray count];
713     searchString = [components objectAtIndex:14];
714     if ([searchString isEqualToString:@"none"] || [searchString isEqualToString:@"0"]) {
715         [_customize_subs_pop selectItemAtIndex:-1];
716     } else {
717         for (NSUInteger x = 0; x < count; x++) {
718             if ([[tempArray objectAtIndex:x] isEqualToString: searchString]) {
719                 [_customize_subs_pop selectItemAtIndex:x];
720                 break;
721             }
722         }
723     }
724
725     [self setCurrentProfile: [[[NSMutableArray alloc] initWithArray: [profileString componentsSeparatedByString:@";"]] autorelease]];
726 }
727
728 - (void)selectCellByEncapsulationFormat:(NSString *)format
729 {
730     if ([format isEqualToString:@"ts"])
731         [_customize_encap_matrix selectCellWithTag:MPEGTS];
732     else if([format isEqualToString:@"webm"])
733         [_customize_encap_matrix selectCellWithTag:WEBM];
734     else if([format isEqualToString:@"ogg"])
735         [_customize_encap_matrix selectCellWithTag:OGG];
736     else if([format isEqualToString:@"ogm"])
737         [_customize_encap_matrix selectCellWithTag:OGG];
738     else if([format isEqualToString:@"mp4"])
739         [_customize_encap_matrix selectCellWithTag:MP4];
740     else if([format isEqualToString:@"mov"])
741         [_customize_encap_matrix selectCellWithTag:MP4];
742     else if([format isEqualToString:@"ps"])
743         [_customize_encap_matrix selectCellWithTag:MPEGPS];
744     else if([format isEqualToString:@"mpjpeg"])
745         [_customize_encap_matrix selectCellWithTag:MJPEG];
746     else if([format isEqualToString:@"wav"])
747         [_customize_encap_matrix selectCellWithTag:WAV];
748     else if([format isEqualToString:@"flv"])
749         [_customize_encap_matrix selectCellWithTag:FLV];
750     else if([format isEqualToString:@"mpeg1"])
751         [_customize_encap_matrix selectCellWithTag:MPEG1];
752     else if([format isEqualToString:@"mkv"])
753         [_customize_encap_matrix selectCellWithTag:MKV];
754     else if([format isEqualToString:@"raw"])
755         [_customize_encap_matrix selectCellWithTag:RAW];
756     else if([format isEqualToString:@"avi"])
757         [_customize_encap_matrix selectCellWithTag:AVI];
758     else if([format isEqualToString:@"asf"])
759         [_customize_encap_matrix selectCellWithTag:ASF];
760     else if([format isEqualToString:@"wmv"])
761         [_customize_encap_matrix selectCellWithTag:ASF];
762     else
763         msg_Err(VLCIntf, "CAS: unknown encap format requested for customization");
764 }
765
766 - (NSString *)currentEncapsulationFormatAsFileExtension:(BOOL)b_extension
767 {
768     NSUInteger cellTag = [[_customize_encap_matrix selectedCell] tag];
769     NSString * returnValue;
770     switch (cellTag) {
771         case MPEGTS:
772             returnValue = @"ts";
773             break;
774         case WEBM:
775             returnValue = @"webm";
776             break;
777         case OGG:
778             returnValue = @"ogg";
779             break;
780         case MP4:
781         {
782             if (b_extension)
783                 returnValue = @"m4v";
784             else
785                 returnValue = @"mp4";
786             break;
787         }
788         case MPEGPS:
789         {
790             if (b_extension)
791                 returnValue = @"mpg";
792             else
793                 returnValue = @"ps";
794             break;
795         }
796         case MJPEG:
797             returnValue = @"mjpeg";
798             break;
799         case WAV:
800             returnValue = @"wav";
801             break;
802         case FLV:
803             returnValue = @"flv";
804             break;
805         case MPEG1:
806         {
807             if (b_extension)
808                 returnValue = @"mpg";
809             else
810                 returnValue = @"mpeg1";
811             break;
812         }
813         case MKV:
814             returnValue = @"mkv";
815             break;
816         case RAW:
817             returnValue = @"raw";
818             break;
819         case AVI:
820             returnValue = @"avi";
821             break;
822         case ASF:
823             returnValue = @"asf";
824             break;
825
826         default:
827             returnValue = @"none";
828             break;
829     }
830
831     return returnValue;
832 }
833
834 - (NSString *)composedOptions
835 {
836     NSMutableString *composedOptions = [[NSMutableString alloc] initWithString:@":sout=#transcode{"];
837     if ([[self.currentProfile objectAtIndex:1] intValue]) {
838         // video is enabled
839         [composedOptions appendFormat:@"vcodec=%@", [self.currentProfile objectAtIndex:4]];
840         if (![[self.currentProfile objectAtIndex:4] isEqualToString:@"none"]) {
841             if ([[self.currentProfile objectAtIndex:5] intValue] > 0) // bitrate
842                 [composedOptions appendFormat:@",vb=%@", [self.currentProfile objectAtIndex:5]];
843             if ([[self.currentProfile objectAtIndex:6] floatValue] > 0.) // scale
844                 [composedOptions appendFormat:@",scale=%@", [self.currentProfile objectAtIndex:6]];
845             if ([[self.currentProfile objectAtIndex:7] floatValue] > 0.) // fps
846                 [composedOptions appendFormat:@",fps=%@", [self.currentProfile objectAtIndex:7]];
847             if ([[self.currentProfile objectAtIndex:8] intValue] > 0) // width
848                 [composedOptions appendFormat:@",width=%@", [self.currentProfile objectAtIndex:8]];
849             if ([[self.currentProfile objectAtIndex:9] intValue] > 0) // height
850                 [composedOptions appendFormat:@",height=%@", [self.currentProfile objectAtIndex:9]];
851         }
852     }
853     if ([[self.currentProfile objectAtIndex:2] intValue]) {
854         // audio is enabled
855
856         // add another comma in case video is enabled
857         if ([[self.currentProfile objectAtIndex:1] intValue])
858             [composedOptions appendString:@","];
859
860         [composedOptions appendFormat:@"acodec=%@", [self.currentProfile objectAtIndex:10]];
861         if (![[self.currentProfile objectAtIndex:10] isEqualToString:@"none"]) {
862             [composedOptions appendFormat:@",ab=%@", [self.currentProfile objectAtIndex:11]]; // bitrate
863             [composedOptions appendFormat:@",channels=%@", [self.currentProfile objectAtIndex:12]]; // channel number
864             [composedOptions appendFormat:@",samplerate=%@", [self.currentProfile objectAtIndex:13]]; // sample rate
865         }
866     }
867     if ([self.currentProfile objectAtIndex:3]) {
868         // subtitles enabled
869         [composedOptions appendFormat:@",scodec=%@", [self.currentProfile objectAtIndex:14]];
870         if ([[self.currentProfile objectAtIndex:15] intValue])
871             [composedOptions appendFormat:@",soverlay"];
872     }
873
874     // add muxer
875     [composedOptions appendFormat:@"}:standard{mux=%@", [self.currentProfile objectAtIndex:0]];
876
877     // add output destination (file only at this point)
878     [composedOptions appendFormat:@",dst=%@,access=file}", _outputDestination];
879
880     NSString * returnString = [NSString stringWithString:composedOptions];
881     [composedOptions release];
882
883     return returnString;
884 }
885
886 - (void)updateCurrentProfile
887 {
888     [self.currentProfile removeAllObjects];
889
890     NSInteger i;
891     [self.currentProfile addObject: [self currentEncapsulationFormatAsFileExtension:NO]];
892     [self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customize_vid_ckb state]]];
893     [self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customize_aud_ckb state]]];
894     [self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customize_subs_ckb state]]];
895     i = [_customize_vid_codec_pop indexOfSelectedItem];
896     if (i >= 0)
897         [self.currentProfile addObject: [[_videoCodecs objectAtIndex:1] objectAtIndex:i]];
898     else
899         [self.currentProfile addObject: @"none"];
900     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_vid_bitrate_fld intValue]]];
901     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [[[_customize_vid_scale_pop selectedItem] title] intValue]]];
902     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_vid_framerate_fld intValue]]];
903     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_vid_width_fld intValue]]];
904     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_vid_height_fld intValue]]];
905     i = [_customize_aud_codec_pop indexOfSelectedItem];
906     if (i >= 0)
907         [self.currentProfile addObject: [[_audioCodecs objectAtIndex:1] objectAtIndex:i]];
908     else
909         [self.currentProfile addObject: @"none"];
910     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_aud_bitrate_fld intValue]]];
911     [self.currentProfile addObject: [NSString stringWithFormat:@"%i", [_customize_aud_channels_fld intValue]]];
912     [self.currentProfile addObject: [[_customize_aud_samplerate_pop selectedItem] title]];
913     i = [_customize_subs_pop indexOfSelectedItem];
914     if (i >= 0)
915         [self.currentProfile addObject: [[_subsCodecs objectAtIndex:1] objectAtIndex:i]];
916     else
917         [self.currentProfile addObject: @"none"];
918     [self.currentProfile addObject: [NSString stringWithFormat:@"%li", [_customize_subs_overlay_ckb state]]];
919 }
920
921 - (void)storeProfilesOnDisk
922 {
923     NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
924     [defaults setObject:_profileNames forKey:@"CASProfileNames"];
925     [defaults setObject:_profileValueList forKey:@"CASProfiles"];
926     [defaults synchronize];
927 }
928
929 - (void)recreateProfilePopup
930 {
931     [_profile_pop removeAllItems];
932     [_profile_pop addItemsWithTitles:self.profileNames];
933     [_profile_pop addItemWithTitle:_NS("Custom")];
934     [[_profile_pop menu] addItem:[NSMenuItem separatorItem]];
935     [_profile_pop addItemWithTitle:_NS("Organize Profiles...")];
936     [[_profile_pop lastItem] setTarget: self];
937     [[_profile_pop lastItem] setAction: @selector(deleteProfileAction:)];
938 }
939
940 @end
941
942 # pragma mark -
943 # pragma mark Drag and drop handling
944
945 @implementation VLCDropEnabledBox
946
947 - (void)awakeFromNib
948 {
949     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
950 }
951
952 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
953 {
954     b_activeDragAndDrop = YES;
955     [self setNeedsDisplay:YES];
956
957     if (OSX_SNOW_LEOPARD || OSX_LION)
958         [[NSCursor dragCopyCursor] set];
959
960     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
961         return NSDragOperationGeneric;
962
963     return NSDragOperationNone;
964 }
965
966 - (void)draggingEnded:(id < NSDraggingInfo >)sender
967 {
968     [[NSCursor arrowCursor] set];
969     b_activeDragAndDrop = NO;
970     [self setNeedsDisplay:YES];
971 }
972
973 - (void)draggingExited:(id < NSDraggingInfo >)sender
974 {
975     [[NSCursor arrowCursor] set];
976     b_activeDragAndDrop = NO;
977     [self setNeedsDisplay:YES];
978 }
979
980 - (void)drawRect:(NSRect)dirtyRect
981 {
982     if (b_activeDragAndDrop) {
983         [[NSColor colorWithCalibratedRed:(.154/.255) green:(.154/.255) blue:(.154/.255) alpha:1.] setFill];
984         NSRect frameRect = [[self contentView] bounds];
985         frameRect.origin.x += 10;
986         frameRect.origin.y += 10;
987         frameRect.size.width -= 17;
988         frameRect.size.height -= 17;
989         NSFrameRectWithWidthUsingOperation(frameRect, 4., NSCompositeHighlight);
990     }
991
992     [super drawRect:dirtyRect];
993 }
994
995 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
996 {
997     return YES;
998 }
999
1000 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1001 {
1002     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
1003 }
1004
1005 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
1006 {
1007     [self setNeedsDisplay:YES];
1008 }
1009
1010 @end
1011
1012 @implementation VLCDropEnabledImageView
1013
1014 - (void)awakeFromNib
1015 {
1016     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
1017 }
1018
1019 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1020 {
1021     return [[[self superview] superview] draggingEntered:sender];
1022 }
1023
1024 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
1025 {
1026     return YES;
1027 }
1028
1029 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1030 {
1031     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
1032 }
1033
1034 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
1035 {
1036     [self setNeedsDisplay:YES];
1037 }
1038
1039 @end
1040
1041 @implementation VLCDropEnabledButton
1042
1043 - (void)awakeFromNib
1044 {
1045     [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
1046 }
1047
1048 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1049 {
1050     return [[[self superview] superview] draggingEntered:sender];
1051 }
1052
1053 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
1054 {
1055     return YES;
1056 }
1057
1058 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1059 {
1060     return [[VLCConvertAndSave sharedInstance] performDragOperation: sender];
1061 }
1062
1063 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
1064 {
1065     [self setNeedsDisplay:YES];
1066 }
1067
1068 @end