136e44e03e6ae0b0eec156427ae9f8ec8a73b93b
[vlc.git] / modules / gui / macosx / simple_prefs.m
1 /*****************************************************************************
2 * simple_prefs.m: Simple Preferences for Mac OS X
3 *****************************************************************************
4 * Copyright (C) 2008 the VideoLAN team
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 "simple_prefs.h"
25 #import "prefs.h"
26 #import <vlc_keys.h>
27
28 static NSString* VLCSPrefsToolbarIdentifier = @"Our Simple Preferences Toolbar Identifier";
29 static NSString* VLCIntfSettingToolbarIdentifier = @"Intf Settings Item Identifier";
30 static NSString* VLCAudioSettingToolbarIdentifier = @"Audio Settings Item Identifier";
31 static NSString* VLCVideoSettingToolbarIdentifier = @"Video Settings Item Identifier";
32 static NSString* VLCOSDSettingToolbarIdentifier = @"Subtitles Settings Item Identifier";
33 static NSString* VLCInputSettingToolbarIdentifier = @"Input Settings Item Identifier";
34 static NSString* VLCHotkeysSettingToolbarIdentifier = @"Hotkeys Settings Item Identifier";
35
36 @implementation VLCSimplePrefs
37
38 static VLCSimplePrefs *_o_sharedInstance = nil;
39
40 + (VLCSimplePrefs *)sharedInstance
41 {
42     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
43 }
44
45 - (id)init
46 {
47     if (_o_sharedInstance) {
48         [self dealloc];
49     } else {
50         p_intf = VLCIntf;
51         _o_sharedInstance = [super init];
52     }
53     
54     return _o_sharedInstance;
55 }
56
57 - (void)dealloc
58 {
59     [o_currentlyShownCategoryView release];
60     [o_sprefs_toolbar release];
61
62     [o_hotkeySettings release];
63     [o_hotkeyDescriptions release];
64
65     [super dealloc];
66 }
67
68
69 - (NSString *)OSXKeyToString:(int)val
70 {
71     NSMutableString *o_temp_str = [[[NSMutableString alloc] init] autorelease];
72     if( val & KEY_MODIFIER_CTRL )
73         [o_temp_str appendString: @"Ctrl+"];
74     if( val & KEY_MODIFIER_ALT )
75         [o_temp_str appendString: @"Alt+"];
76     if( val & KEY_MODIFIER_SHIFT )
77         [o_temp_str appendString: @"Shift+"];
78     if( val & KEY_MODIFIER_COMMAND )
79         [o_temp_str appendString: @"Command+"];
80     
81     unsigned int i_keys = sizeof(vlc_keys)/sizeof(key_descriptor_t);
82     for( unsigned int i = 0; i< i_keys; i++ )
83     {
84         if( vlc_keys[i].i_key_code == (val& ~KEY_MODIFIER) )
85         {
86             [o_temp_str appendString: [NSString stringWithUTF8String: vlc_keys[i].psz_key_string]];
87         }
88     }
89     return o_temp_str;
90 }
91
92 - (void)awakeFromNib
93 {
94     [self initStrings];
95     
96     /* setup the toolbar */
97     o_sprefs_toolbar = [[[NSToolbar alloc] initWithIdentifier: VLCSPrefsToolbarIdentifier] autorelease];
98     [o_sprefs_toolbar setAllowsUserCustomization: NO];
99     [o_sprefs_toolbar setAutosavesConfiguration: NO];
100     [o_sprefs_toolbar setDisplayMode: NSToolbarDisplayModeIconAndLabel];
101     [o_sprefs_toolbar setSizeMode: NSToolbarSizeModeRegular];
102     [o_sprefs_toolbar setDelegate: self];
103     [o_sprefs_win setToolbar: o_sprefs_toolbar];
104 }
105
106 - (NSToolbarItem *) toolbar: (NSToolbar *)o_sprefs_toolbar 
107       itemForItemIdentifier: (NSString *)o_itemIdent 
108   willBeInsertedIntoToolbar: (BOOL)b_willBeInserted
109 {
110     NSToolbarItem *o_toolbarItem = nil;
111     
112     #define CreateToolbarItem( o_name, o_desc, o_img, sel ) \
113     o_toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: o_itemIdent] autorelease]; \
114     \
115     [o_toolbarItem setLabel: o_name]; \
116     [o_toolbarItem setPaletteLabel: o_desc]; \
117     \
118     [o_toolbarItem setToolTip: o_desc]; \
119     [o_toolbarItem setImage: [NSImage imageNamed: o_img]]; \
120     \
121     [o_toolbarItem setTarget: self]; \
122     [o_toolbarItem setAction: @selector( sel )]; \
123     \
124     [o_toolbarItem setEnabled: YES]; \
125     [o_toolbarItem setAutovalidates: YES]
126     
127     if( [o_itemIdent isEqual: VLCIntfSettingToolbarIdentifier] )
128     {
129         CreateToolbarItem( _NS("Interface"), _NS("Interface Settings"), @"spref_cone_Interface_64", showInterfaceSettings );
130     }
131     else if( [o_itemIdent isEqual: VLCAudioSettingToolbarIdentifier] )
132     {
133         CreateToolbarItem( _NS("Audio"), _NS("General Audio Settings"), @"spref_cone_Audio_64", showAudioSettings );
134     }
135     else if( [o_itemIdent isEqual: VLCVideoSettingToolbarIdentifier] )
136     {
137         CreateToolbarItem( _NS("Video"), _NS("General Video Settings"), @"spref_cone_Video_64", showVideoSettings );
138     }
139     else if( [o_itemIdent isEqual: VLCOSDSettingToolbarIdentifier] )
140     {
141         CreateToolbarItem( _NS("Subtitles & OSD"), _NS("Subtitles & OSD Settings"), @"spref_cone_Subtitles_64", showOSDSettings );
142     }
143     else if( [o_itemIdent isEqual: VLCInputSettingToolbarIdentifier] )
144     {
145         CreateToolbarItem( _NS("Input & Codecs"), _NS("Input & Codec settings"), @"spref_cone_Input_64", showInputSettings );
146     }
147     else if( [o_itemIdent isEqual: VLCHotkeysSettingToolbarIdentifier] )
148     {
149         CreateToolbarItem( _NS("Hotkeys"), _NS("Hotkeys settings"), @"spref_cone_Hotkeys_64", showHotkeySettings );
150     }
151
152     return o_toolbarItem;
153 }
154
155 - (NSArray *)toolbarDefaultItemIdentifiers: (NSToolbar *)toolbar
156 {
157     return [NSArray arrayWithObjects: VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier, 
158         VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier, NSToolbarFlexibleSpaceItemIdentifier, nil];
159 }
160
161 - (NSArray *)toolbarAllowedItemIdentifiers: (NSToolbar *)toolbar
162 {
163     return [NSArray arrayWithObjects: VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier, 
164         VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier, NSToolbarFlexibleSpaceItemIdentifier, nil];
165 }
166
167 - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
168 {
169     return [NSArray arrayWithObjects: VLCIntfSettingToolbarIdentifier, VLCAudioSettingToolbarIdentifier, VLCVideoSettingToolbarIdentifier, 
170         VLCOSDSettingToolbarIdentifier, VLCInputSettingToolbarIdentifier, VLCHotkeysSettingToolbarIdentifier, nil];
171 }
172
173 - (void)initStrings
174 {
175     msg_Warn( p_intf, "localisation of the simple preferences is not implemented!" );
176 }
177
178 - (void)resetControls
179 {
180     module_config_t *p_item;
181     int i, y = 0;
182     char *psz_tmp;
183
184     #define SetupIntList( object, name ) \
185     [object removeAllItems]; \
186     p_item = config_FindConfig( VLC_OBJECT(p_intf), name ); \
187     for( i = 0; i < p_item->i_list; i++ ) \
188     { \
189         if( p_item->ppsz_list_text[i] != NULL) \
190             [object addItemWithTitle: _NS( p_item->ppsz_list_text[i] )]; \
191         else \
192             [object addItemWithTitle: [NSString stringWithUTF8String: p_item->ppsz_list[i]]]; \
193     } \
194     if( p_item->value.i < [object numberOfItems] ) \
195         [object selectItemAtIndex: p_item->value.i]; \
196     else \
197         [object selectItemAtIndex: 0]
198
199     #define SetupStringList( object, name ) \
200         [object removeAllItems]; \
201         y = 0; \
202         p_item = config_FindConfig( VLC_OBJECT(p_intf), name ); \
203         for( i = 0; p_item->ppsz_list[i] != nil; i++ ) \
204         { \
205             [object addItemWithTitle: _NS( p_item->ppsz_list_text[i] )]; \
206             if( p_item->value.psz && !strcmp( p_item->value.psz, p_item->ppsz_list[i] ) ) \
207                 y = i; \
208         } \
209         [object selectItemAtIndex: y]
210
211     /**********************
212      * interface settings *
213      **********************/
214     SetupStringList( o_intf_lang_pop, "language" );
215     SetupIntList( o_intf_art_pop, "album-art" );
216
217     [o_intf_meta_ckb setState: config_GetInt( p_intf, "fetch-meta" )];
218     [o_intf_fspanel_ckb setState: config_GetInt( p_intf, "macosx-fspanel" )];
219     [o_intf_embedded_ckb setState: config_GetInt( p_intf, "embedded-video" )];
220
221     /******************
222      * audio settings *
223      ******************/
224     [o_audio_enable_ckb setState: config_GetInt( p_intf, "audio" )];
225     [o_audio_vol_fld setIntValue: config_GetInt( p_intf, "volume" )];
226     [o_audio_vol_sld setIntValue: config_GetInt( p_intf, "volume" )];
227
228     [o_audio_spdif_ckb setState: config_GetInt( p_intf, "spdif" )];
229
230     SetupIntList( o_audio_dolby_pop, "force-dolby-surround" );
231
232     [o_audio_lang_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "audio-language" )]];
233
234     [o_audio_headphone_ckb setState: config_GetInt( p_intf, "headphone-dolby" )];
235     
236     psz_tmp = config_GetPsz( p_intf, "audio-filter" );
237     if( psz_tmp )
238         [o_audio_norm_ckb setState: (int)strstr( psz_tmp, "normvol" )];
239     [o_audio_norm_fld setFloatValue: config_GetFloat( p_intf, "norm-max-level" )];
240     
241     // visualizer
242     msg_Warn( p_intf, "visualizer not implemented!" );
243     
244     /* Last.FM is optional */
245     if( module_Exists( p_intf, "audioscrobbler" ) )
246     {
247         [o_audio_lastuser_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "lastfm-username" )]];
248         [o_audio_lastpwd_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "lastfm-password" )]];
249
250         if( config_ExistIntf( VLC_OBJECT( p_intf ), "audioscrobbler" ) )
251             [o_audio_last_ckb setState: NSOnState];
252         else
253             [o_audio_last_ckb setState: NSOffState];
254     }
255
256     /******************
257      * video settings *
258      ******************/
259     [o_video_enable_ckb setState: config_GetInt( p_intf, "video" )];
260     [o_video_fullscreen_ckb setState: config_GetInt( p_intf, "fullscreen" )];
261     [o_video_onTop_ckb setState: config_GetInt( p_intf, "video-on-top" )];
262     [o_video_skipFrames_ckb setState: config_GetInt( p_intf, "skip-frames" )];
263     [o_video_black_ckb setState: config_GetInt( p_intf, "macosx-black" )];
264
265     msg_Warn( p_intf, "vout module and display device selectors not implemented!" );
266
267     if( config_GetPsz( p_intf, "snapshot-path" ) != NULL )
268         [o_video_snap_folder_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "snapshot-path" )]];
269     [o_video_snap_prefix_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "snapshot-prefix" )]];
270     [o_video_snap_seqnum_ckb setState: config_GetInt( p_intf, "snapshot-sequential" )];
271     
272     p_item = config_FindConfig( VLC_OBJECT(p_intf), "snapshot-format" );
273     for( i = 0; p_item->ppsz_list[i] != nil; i++ )
274     {
275         [o_video_snap_format_pop addItemWithTitle: [NSString stringWithUTF8String: p_item->ppsz_list[i]]];
276         if( p_item->value.psz && !strcmp( p_item->value.psz, p_item->ppsz_list[i] ) )
277             y = i;
278     }
279     [o_video_snap_format_pop selectItemAtIndex: y];
280
281     /***************************
282      * input & codecs settings *
283      ***************************/
284     [o_input_serverport_fld setIntValue: config_GetInt( p_intf, "server-port" )];
285     if( config_GetPsz( p_intf, "http-proxy" ) != NULL )
286         [o_input_httpproxy_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "http-proxy" )]];
287     [o_input_postproc_fld setIntValue: config_GetInt( p_intf, "ffmpeg-pp-q" )];
288
289     SetupIntList( o_input_avi_pop, "avi-index" );
290
291     [o_input_rtsp_ckb setState: config_GetInt( p_intf, "rtsp-tcp" )];
292
293     psz_tmp = config_GetPsz( p_intf, "access-filter" );
294     if( psz_tmp )
295     {
296         [o_input_record_ckb setState: (int)strstr( psz_tmp, "record" )];
297         [o_input_dump_ckb setState: (int)strstr( psz_tmp, "dump" )];
298         [o_input_bandwidth_ckb setState: (int)strstr( psz_tmp, "bandwidth" )];
299         [o_input_timeshift_ckb setState: (int)strstr( psz_tmp, "timeshift" )];
300     }
301
302     [o_input_cachelevel_pop removeAllItems];
303     [o_input_cachelevel_pop addItemsWithTitles: 
304         [NSArray arrayWithObjects: _NS("Custom"), _NS("Lowest latency"), _NS("Low latency"), _NS("Normal"),
305             _NS("High latency"), _NS("Higher latency"), nil]];
306     [[o_input_cachelevel_pop itemAtIndex: 0] setTag: 0];
307     [[o_input_cachelevel_pop itemAtIndex: 1] setTag: 100];
308     [[o_input_cachelevel_pop itemAtIndex: 2] setTag: 200];
309     [[o_input_cachelevel_pop itemAtIndex: 3] setTag: 300];
310     [[o_input_cachelevel_pop itemAtIndex: 4] setTag: 400];
311     [[o_input_cachelevel_pop itemAtIndex: 5] setTag: 500];
312     
313 #define TestCaC( name ) \
314     b_cache_equal =  b_cache_equal && \
315         ( i_cache == config_GetInt( p_intf, name ) )
316
317 #define TestCaCi( name, int ) \
318         b_cache_equal = b_cache_equal &&  \
319         ( ( i_cache * int ) == config_GetInt( p_intf, name ) )
320
321     /* Select the accurate value of the PopupButton */
322     bool b_cache_equal = true;
323     int i_cache = config_GetInt( p_intf, "file-caching");
324     
325     TestCaC( "udp-caching" );
326     if( module_Exists (p_intf, "dvdread") )
327         TestCaC( "dvdread-caching" );
328     if( module_Exists (p_intf, "dvdnav") )
329         TestCaC( "dvdnav-caching" );
330     TestCaC( "tcp-caching" );
331     TestCaC( "fake-caching" ); 
332     TestCaC( "cdda-caching" );
333     TestCaC( "screen-caching" ); 
334     TestCaC( "vcd-caching" );
335     TestCaCi( "rtsp-caching", 4 ); 
336     TestCaCi( "ftp-caching", 2 );
337     TestCaCi( "http-caching", 4 );
338     if(module_Exists (p_intf, "access_realrtsp"))
339         TestCaCi( "realrtsp-caching", 10 );
340     TestCaCi( "mms-caching", 19 );
341     if( b_cache_equal )
342         [o_input_cachelevel_pop selectItemWithTag: i_cache];
343     else
344         [o_input_cachelevel_pop selectItemWithTitle: _NS("Custom")];
345
346     /*********************
347      * subtitle settings *
348      *********************/
349     [o_osd_osd_ckb setState: config_GetInt( p_intf, "osd" )];
350     
351     [o_osd_encoding_pop removeAllItems];
352     y = 0;
353     p_item = config_FindConfig( VLC_OBJECT(p_intf), "subsdec-encoding" );
354     for( i = 0; p_item->ppsz_list[i] != nil; i++ )
355     {
356         if( p_item->ppsz_list[i] != "" )
357             [o_osd_encoding_pop addItemWithTitle: _NS( p_item->ppsz_list[i] )];
358         else
359             [o_osd_encoding_pop addItemWithTitle: @" "];
360
361         if( p_item->value.psz && !strcmp( p_item->value.psz, p_item->ppsz_list[i] ) )
362             y = i;
363     }
364     [o_osd_encoding_pop selectItemAtIndex: y];
365     
366     [o_osd_lang_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "sub-language" )]];
367     if( config_GetPsz( p_intf, "freetype-font" ) != NULL )
368         [o_osd_font_fld setStringValue: [NSString stringWithUTF8String: config_GetPsz( p_intf, "freetype-font" )]];
369
370     SetupIntList( o_osd_font_color_pop, "freetype-color" );
371     SetupIntList( o_osd_font_size_pop, "freetype-rel-fontsize" );
372     SetupIntList( o_osd_font_effect_pop, "freetype-effect" );
373
374     /********************
375      * hotkeys settings *
376      ********************/
377     struct hotkey *p_hotkeys = p_intf->p_libvlc->p_hotkeys;
378     o_hotkeySettings = [[NSMutableArray alloc] init];
379     NSMutableArray *o_tempArray_desc = [[NSMutableArray alloc] init];
380     i = 1;
381
382     while( i < 100 )
383     {
384         p_item = config_FindConfig( VLC_OBJECT(p_intf), p_hotkeys[i].psz_action );
385         if( !p_item )
386             break;
387
388         [o_tempArray_desc addObject: _NS( p_item->psz_text )];
389         [o_hotkeySettings addObject: [self OSXKeyToString: p_item->value.i]];
390
391         i++;
392     }
393     o_hotkeyDescriptions = [[NSArray alloc] initWithArray: o_tempArray_desc copyItems: YES];
394     [o_tempArray_desc release];
395     [o_hotkeys_listbox reloadData];
396 }
397
398 - (void)showSimplePrefs
399 {
400     /* we want to show the interface settings, if no category was chosen */
401     if( [o_sprefs_toolbar selectedItemIdentifier] == nil )
402     {
403         [o_sprefs_toolbar setSelectedItemIdentifier: VLCIntfSettingToolbarIdentifier];
404         [self showInterfaceSettings];
405     }
406     
407     [self resetControls];
408
409     [o_sprefs_win makeKeyAndOrderFront: self];
410 }
411
412 - (IBAction)buttonAction:(id)sender
413 {
414     if( sender == o_sprefs_cancel_btn )
415         [o_sprefs_win orderOut: sender];
416     else if( sender == o_sprefs_save_btn )
417     {
418         [self saveChangedSettings];
419         [o_sprefs_win orderOut: sender];
420     }
421     else if( sender == o_sprefs_reset_btn )
422         NSBeginInformationalAlertSheet( _NS("Reset Preferences"), _NS("Cancel"),
423                                         _NS("Continue"), nil, o_sprefs_win, self,
424                                         @selector(sheetDidEnd: returnCode: contextInfo:), NULL, nil,
425                                         _NS("Beware this will reset the VLC media player preferences.\n"
426                                             "Are you sure you want to continue?") );
427     else if( sender == o_sprefs_basicFull_matrix )
428     {
429         [o_sprefs_win orderOut: self];
430         [[[VLCMain sharedInstance] getPreferences] showPrefs];
431         [self resetControls];
432     }
433     else
434         msg_Err( p_intf, "unknown buttonAction sender" );
435 }
436
437 - (void)sheetDidEnd:(NSWindow *)o_sheet 
438          returnCode:(int)i_return
439         contextInfo:(void *)o_context
440 {
441     if( i_return == NSAlertAlternateReturn )
442     {
443         config_ResetAll( p_intf );
444         b_intfSettingChanged = b_videoSettingChanged = b_audioSettingChanged = YES;
445         [self resetControls];
446     }
447 }
448
449 - (void)saveChangedSettings
450 {
451     module_config_t *p_item;
452     char *psz_tmp;
453     int i;
454     
455 #define SaveIntList( object, name ) \
456     p_item = config_FindConfig( VLC_OBJECT(p_intf), name ); \
457     if( [object indexOfSelectedItem] >= 0 ) \
458         config_PutInt( p_intf, name, p_item->pi_list[[object indexOfSelectedItem]] ); \
459     else \
460         config_PutInt( p_intf, name, [object intValue] ) \
461                     
462 #define SaveStringList( object, name ) \
463     p_item = config_FindConfig( VLC_OBJECT(p_intf), name ); \
464     if( [object indexOfSelectedItem] >= 0 ) \
465         config_PutPsz( p_intf, name, strdup( p_item->ppsz_list[[object indexOfSelectedItem]] ) ); \
466     else \
467         config_PutPsz( p_intf, name, strdup( [[VLCMain sharedInstance] delocalizeString: [object stringValue]] ) )
468
469     /**********************
470      * interface settings *
471      **********************/
472     if( b_intfSettingChanged )
473     {
474         SaveStringList( o_intf_lang_pop, "language" );
475         SaveIntList( o_intf_art_pop, "album-art" );
476
477         config_PutInt( p_intf, "fetch-meta", [o_intf_meta_ckb state] );
478         config_PutInt( p_intf, "macosx-fspanel", [o_intf_fspanel_ckb state] );
479         config_PutInt( p_intf, "embedded-video", [o_intf_embedded_ckb state] );
480
481         /* okay, let's save our changes to vlcrc */
482         i = config_SaveConfigFile( p_intf, "main" );
483         i = config_SaveConfigFile( p_intf, "macosx" );
484
485         if( i != 0 )
486         {
487             msg_Err( p_intf, "An error occurred while saving the Interface settings using SimplePrefs" );
488             i = 0;
489         }
490
491         b_intfSettingChanged = NO;
492     }
493     
494     /******************
495      * audio settings *
496      ******************/
497     if( b_audioSettingChanged )
498     {
499         config_PutInt( p_intf, "audio", [o_audio_enable_ckb state] );
500         config_PutInt( p_intf, "volume", [o_audio_vol_sld intValue] );
501         config_PutInt( p_intf, "spdif", [o_audio_spdif_ckb state] );
502
503         SaveIntList( o_audio_dolby_pop, "force-dolby-surround" );
504
505         config_PutPsz( p_intf, "audio-language", [[o_audio_lang_fld stringValue] UTF8String] );
506         config_PutInt( p_intf, "headphone-dolby", [o_audio_headphone_ckb state] );
507
508         psz_tmp = config_GetPsz( p_intf, "audio-filter" );
509         if(! psz_tmp)
510             config_PutPsz( p_intf, "audio-filter", "volnorm" );
511         else if( (int)strstr( psz_tmp, "normvol" ) == NO )
512         {
513             /* work-around a GCC 4.0.1 bug */
514             psz_tmp = (char *)[[NSString stringWithFormat: @"%s:volnorm", psz_tmp] UTF8String];
515             config_PutPsz( p_intf, "audio-filter", psz_tmp );
516         }
517         else
518         {
519             psz_tmp = (char *)[[[NSString stringWithUTF8String: psz_tmp] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@":volnorm"]] UTF8String];
520             psz_tmp = (char *)[[[NSString stringWithUTF8String: psz_tmp] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"volnorm:"]] UTF8String];
521             psz_tmp = (char *)[[[NSString stringWithUTF8String: psz_tmp] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"volnorm"]] UTF8String];
522             config_PutPsz( p_intf, "audio-filter", psz_tmp );
523         }
524         config_PutFloat( p_intf, "norm-max-level", [o_audio_norm_fld floatValue] );
525
526         msg_Warn( p_intf, "visualizer not implemented!" );
527
528         /* Last.FM is optional */
529         if( module_Exists( p_intf, "audioscrobbler" ) )
530         {    
531             if( [o_audio_last_ckb state] == NSOnState )
532                 config_AddIntf( VLC_OBJECT( p_intf ), "audioscrobbler" );
533             else
534                 config_RemoveIntf( VLC_OBJECT( p_intf ), "audioscrobbler" );
535
536             config_PutPsz( p_intf, "lastfm-username", [[o_audio_lastuser_fld stringValue] UTF8String] );
537             config_PutPsz( p_intf, "lastfm-password", [[o_audio_lastuser_fld stringValue] UTF8String] );
538         }
539
540         /* okay, let's save our changes to vlcrc */
541         i = config_SaveConfigFile( p_intf, "main" );
542         i = i + config_SaveConfigFile( p_intf, "audioscrobbler" );
543         i = i + config_SaveConfigFile( p_intf, "volnorm" );
544
545         if( i != 0 )
546         {
547             msg_Err( p_intf, "An error occurred while saving the Audio settings using SimplePrefs" );
548             i = 0;
549         }
550         b_audioSettingChanged = NO;
551     }
552     
553     /******************
554      * video settings *
555      ******************/
556     if( b_videoSettingChanged )
557     {
558         config_PutInt( p_intf, "video", [o_video_enable_ckb state] );
559         config_PutInt( p_intf, "fullscreen", [o_video_fullscreen_ckb state] );
560         config_PutInt( p_intf, "video-on-top", [o_video_onTop_ckb state] );
561         config_PutInt( p_intf, "skip-frames", [o_video_skipFrames_ckb state] );
562         config_PutInt( p_intf, "macosx-black", [o_video_black_ckb state] );
563
564         msg_Warn( p_intf, "vout module and display device selectors not implemented!" );
565
566         config_PutPsz( p_intf, "snapshot-path", [[o_video_snap_folder_fld stringValue] UTF8String] );
567         config_PutPsz( p_intf, "snapshot-prefix", [[o_video_snap_prefix_fld stringValue] UTF8String] );
568         config_PutInt( p_intf, "snapshot-sequential", [o_video_snap_seqnum_ckb state] );
569
570         if( [o_video_snap_format_pop indexOfSelectedItem] >= 0 )
571             config_PutPsz( p_intf, "snapshot-format", [[[o_video_snap_format_pop selectedItem] title] UTF8String] );
572
573         i = config_SaveConfigFile( p_intf, "main" );
574         i = i + config_SaveConfigFile( p_intf, "macosx" );
575
576         if( i != 0 )
577         {
578             msg_Err( p_intf, "An error occurred while saving the Video settings using SimplePrefs" );
579             i = 0;
580         }
581         b_videoSettingChanged = NO;
582     }
583     
584     /***************************
585      * input & codecs settings *
586      ***************************/
587     if( b_inputSettingChanged )
588     {
589         config_PutInt( p_intf, "server-port", [o_input_serverport_fld intValue] );
590         config_PutPsz( p_intf, "http-proxy", [[o_input_httpproxy_fld stringValue] UTF8String] );
591         config_PutInt( p_intf, "ffmpeg-pp-q", [o_input_postproc_fld intValue] );
592
593         SaveIntList( o_input_avi_pop, "avi-index" );
594
595         config_PutInt( p_intf, "rtsp-tcp", [o_input_rtsp_ckb state] );
596
597         #define CaCi( name, int ) config_PutInt( p_intf, name, int * [[o_input_cachelevel_pop selectedItem] tag] )
598         #define CaC( name ) CaCi( name, 1 )
599         msg_Dbg( p_intf, "Adjusting all cache values at: %i", [[o_input_cachelevel_pop selectedItem] tag] );
600         CaC( "udp-caching" );
601         if( module_Exists (p_intf, "dvdread" ) )
602         {
603             CaC( "dvdread-caching" );
604             i = i + config_SaveConfigFile( p_intf, "dvdread" );
605         }
606         if( module_Exists (p_intf, "dvdnav" ) )
607         {
608             CaC( "dvdnav-caching" );
609             i = i + config_SaveConfigFile( p_intf, "dvdnav" );
610         }
611         CaC( "tcp-caching" ); CaC( "vcd-caching" );
612         CaC( "fake-caching" ); CaC( "cdda-caching" ); CaC( "file-caching" );
613         CaC( "screen-caching" );
614         CaCi( "rtsp-caching", 4 ); CaCi( "ftp-caching", 2 );
615         CaCi( "http-caching", 4 );
616         if( module_Exists (p_intf, "access_realrtsp" ) )
617         {
618             CaCi( "realrtsp-caching", 10 );
619             i = i + config_SaveConfigFile( p_intf, "access_realrtsp" );
620         }
621         CaCi( "mms-caching", 19 );
622
623         #define SaveAccessFilter( object, name ) \
624         if( [object state] == NSOnState ) \
625         { \
626             if( b_first ) \
627             { \
628                 [o_temp appendString: name]; \
629                 b_first = NO; \
630             } \
631             else \
632                 [o_temp appendFormat: @":%@", name]; \
633         }
634
635         BOOL b_first = YES;
636         NSMutableString *o_temp = [[NSMutableString alloc] init];
637         SaveAccessFilter( o_input_record_ckb, @"record" );
638         SaveAccessFilter( o_input_dump_ckb, @"dump" );
639         SaveAccessFilter( o_input_bandwidth_ckb, @"bandwidth" );
640         SaveAccessFilter( o_input_timeshift_ckb, @"timeshift" );
641         config_PutPsz( p_intf, "access-filter", [o_temp UTF8String] );
642         [o_temp release];
643
644         i = config_SaveConfigFile( p_intf, "main" );
645         i = i + config_SaveConfigFile( p_intf, "ffmpeg" );
646         i = i + config_SaveConfigFile( p_intf, "access_http" );
647         i = i + config_SaveConfigFile( p_intf, "access_file" );
648         i = i + config_SaveConfigFile( p_intf, "access_tcp" );
649         i = i + config_SaveConfigFile( p_intf, "access_fake" );
650         i = i + config_SaveConfigFile( p_intf, "cdda" );
651         i = i + config_SaveConfigFile( p_intf, "screen" );
652         i = i + config_SaveConfigFile( p_intf, "vcd" );
653         i = i + config_SaveConfigFile( p_intf, "access_ftp" );
654         i = i + config_SaveConfigFile( p_intf, "access_mms" );
655         i = i + config_SaveConfigFile( p_intf, "live555" );
656
657         if( i != 0 )
658         {
659             msg_Err( p_intf, "An error occurred while saving the Input settings using SimplePrefs" );
660             i = 0;
661         }
662         b_inputSettingChanged = NO;
663     }
664     
665     /**********************
666      * subtitles settings *
667      **********************/
668     if( b_osdSettingChanged )
669     {
670         config_PutInt( p_intf, "osd", [o_osd_osd_ckb state] );
671
672         if( [o_osd_encoding_pop indexOfSelectedItem] >= 0 )
673             config_PutPsz( p_intf, "subsdec-encoding", [[[o_osd_encoding_pop selectedItem] title] UTF8String] );
674
675         config_PutPsz( p_intf, "sub-language", [[o_osd_lang_fld stringValue] UTF8String] );
676         config_PutPsz( p_intf, "freetype-font", [[o_osd_font_fld stringValue] UTF8String] );
677
678         SaveIntList( o_osd_font_color_pop, "freetype-color" );
679         SaveIntList( o_osd_font_size_pop, "freetype-rel-fontsize" );
680         SaveIntList( o_osd_font_effect_pop, "freetype-effect" );
681
682         i = config_SaveConfigFile( p_intf, NULL );
683
684         if( i != 0 )
685         {
686             msg_Err( p_intf, "An error occurred while saving the OSD/Subtitle settings using SimplePrefs" );
687             i = 0;
688         }
689         b_osdSettingChanged = NO;
690     }
691     
692     /********************
693      * hotkeys settings *
694      ********************/
695     if( b_hotkeyChanged )
696     {
697         struct hotkey *p_hotkeys = p_intf->p_libvlc->p_hotkeys;
698         i = 1;
699         while( i < [o_hotkeySettings count] ) // FIXME: this is ugly!
700         {
701             /* FIXME: this does only work for single keys!!! */
702             config_PutInt( p_intf, p_hotkeys[i].psz_action, StringToKey( (char *)[[o_hotkeySettings objectAtIndex: i] UTF8String] ) );
703
704             i++;
705         }        
706         
707         i = config_SaveConfigFile( p_intf, "main" );
708         
709         if( i != 0 )
710         {
711             msg_Err( p_intf, "An error occurred while saving the Hotkey settings using SimplePrefs" );
712             i = 0;
713         }
714         b_hotkeyChanged = NO;
715     }
716 }
717
718 - (void)showSettingsForCategory: (id)o_new_category_view
719 {
720     NSRect o_win_rect, o_view_rect, o_old_view_rect;
721     o_win_rect = [o_sprefs_win frame];
722     o_view_rect = [o_new_category_view frame];
723     
724     if( o_currentlyShownCategoryView != nil )
725     {
726         /* restore our window's height, if we've shown another category previously */
727         o_old_view_rect = [o_currentlyShownCategoryView frame];
728         o_win_rect.size.height = o_win_rect.size.height - o_old_view_rect.size.height;
729
730         /* remove our previous category view */
731         [o_currentlyShownCategoryView removeFromSuperviewWithoutNeedingDisplay];
732     }
733     
734     o_win_rect.size.height = o_win_rect.size.height + o_view_rect.size.height;
735     
736     [o_sprefs_win displayIfNeeded];
737     [o_sprefs_win setFrame: o_win_rect display:YES animate: YES];
738     
739     [o_new_category_view setFrame: NSMakeRect( 0, 
740                                                [o_sprefs_controls_box frame].size.height, 
741                                                o_view_rect.size.width, 
742                                                o_view_rect.size.height )];
743     [o_new_category_view setNeedsDisplay: YES];
744     [o_new_category_view setAutoresizesSubviews: YES];
745     [[o_sprefs_win contentView] addSubview: o_new_category_view];
746     
747     /* keep our current category for further reference */
748     [o_currentlyShownCategoryView release];
749     o_currentlyShownCategoryView = o_new_category_view;
750     [o_currentlyShownCategoryView retain];
751 }
752
753 - (IBAction)interfaceSettingChanged:(id)sender
754 {
755     b_intfSettingChanged = YES;
756 }
757
758 - (void)showInterfaceSettings
759 {
760     msg_Dbg( p_intf, "showing interface settings" );
761     [self showSettingsForCategory: o_intf_view];
762 }
763
764 - (IBAction)audioSettingChanged:(id)sender
765 {
766     if( sender == o_audio_vol_sld )
767         [o_audio_vol_fld setIntValue: [o_audio_vol_sld intValue]];
768     
769     if( sender == o_audio_vol_fld )
770         [o_audio_vol_sld setIntValue: [o_audio_vol_fld intValue]];
771     
772     b_audioSettingChanged = YES;
773 }
774
775 - (void)showAudioSettings
776 {
777     msg_Dbg( p_intf, "showing audio settings" );
778     [self showSettingsForCategory: o_audio_view];
779 }
780
781 - (IBAction)videoSettingChanged:(id)sender
782 {
783     if( sender == o_video_snap_folder_btn )
784     {
785         o_selectFolderPanel = [[NSOpenPanel alloc] init];
786         [o_selectFolderPanel setCanChooseDirectories: YES];
787         [o_selectFolderPanel setCanChooseFiles: NO];
788         [o_selectFolderPanel setResolvesAliases: YES];
789         [o_selectFolderPanel setAllowsMultipleSelection: NO];
790         [o_selectFolderPanel setMessage: _NS("Choose the folder to save your video snapshots to.")];
791         [o_selectFolderPanel setCanCreateDirectories: YES];
792         [o_selectFolderPanel setPrompt: _NS("Choose")];
793         [o_selectFolderPanel beginSheetForDirectory: nil file: nil modalForWindow: o_sprefs_win 
794                                       modalDelegate: self 
795                                      didEndSelector: @selector(savePanelDidEnd:returnCode:contextInfo:)
796                                         contextInfo: o_video_snap_folder_btn];
797     }
798     else
799         b_videoSettingChanged = YES;
800 }
801
802 - (void)savePanelDidEnd:(NSOpenPanel * )panel returnCode: (int)returnCode contextInfo: (void *)contextInfo
803 {
804     if( returnCode == NSOKButton )
805     {
806         if( contextInfo == o_video_snap_folder_btn )
807         {
808             [o_video_snap_folder_fld setStringValue: [o_selectFolderPanel filename]];
809             b_videoSettingChanged = YES;
810         }
811         else if( contextInfo == o_osd_font_btn )
812         {
813             [o_osd_font_fld setStringValue: [o_selectFolderPanel filename]];
814             b_osdSettingChanged = YES;
815         }
816     }
817
818     [o_selectFolderPanel release];
819 }
820
821 - (void)showVideoSettings
822 {
823     msg_Dbg( p_intf, "showing video settings" );
824     [self showSettingsForCategory: o_video_view];
825 }
826
827 - (IBAction)osdSettingChanged:(id)sender
828 {
829     if( sender == o_osd_font_btn )
830     {
831         o_selectFolderPanel = [[NSOpenPanel alloc] init];
832         [o_selectFolderPanel setCanChooseDirectories: NO];
833         [o_selectFolderPanel setCanChooseFiles: YES];
834         [o_selectFolderPanel setResolvesAliases: YES];
835         [o_selectFolderPanel setAllowsMultipleSelection: NO];
836         [o_selectFolderPanel setMessage: _NS("Choose the font to display your Subtitles with.")];
837         [o_selectFolderPanel setCanCreateDirectories: NO];
838         [o_selectFolderPanel setPrompt: _NS("Choose")];
839         [o_selectFolderPanel setAllowedFileTypes: [NSArray arrayWithObjects: @"dfont", @"ttf", @"otf", @"FFIL", nil]];
840         [o_selectFolderPanel beginSheetForDirectory: @"/System/Library/Fonts/" file: nil modalForWindow: o_sprefs_win 
841                                       modalDelegate: self 
842                                      didEndSelector: @selector(savePanelDidEnd:returnCode:contextInfo:)
843                                         contextInfo: o_osd_font_btn];
844     }
845     else
846         b_osdSettingChanged = YES;
847 }
848
849 - (void)showOSDSettings
850 {
851     msg_Dbg( p_intf, "showing OSD settings" );
852     [self showSettingsForCategory: o_osd_view];
853 }
854
855 - (IBAction)inputSettingChanged:(id)sender
856 {
857     b_inputSettingChanged = YES;
858 }
859
860 - (void)showInputSettings
861 {
862     msg_Dbg( p_intf, "showing Input Settings" );
863     [self showSettingsForCategory: o_input_view];
864 }
865
866 - (IBAction)hotkeySettingChanged:(id)sender
867 {
868     if( sender == o_hotkeys_change_btn || sender == o_hotkeys_listbox )
869     {
870         [o_hotkeys_change_lbl setStringValue: [NSString stringWithFormat: _NS("Press new keys for\n\"%@\""), 
871                                                [o_hotkeyDescriptions objectAtIndex: [o_hotkeys_listbox selectedRow]]]];
872         [o_hotkeys_change_keys_lbl setStringValue: [o_hotkeySettings objectAtIndex: [o_hotkeys_listbox selectedRow]]];
873         [o_hotkeys_change_taken_lbl setStringValue: @""];
874         [o_hotkeys_change_win setInitialFirstResponder: [o_hotkeys_change_win contentView]];
875         [o_hotkeys_change_win makeFirstResponder: [o_hotkeys_change_win contentView]];
876         [NSApp runModalForWindow: o_hotkeys_change_win];
877     }
878     else if( sender == o_hotkeys_change_cancel_btn )
879     {
880         [NSApp stopModal];
881         [o_hotkeys_change_win close];
882     }
883     else if( sender == o_hotkeys_change_ok_btn )
884     {
885         int i_returnValue;
886         b_hotkeyChanged = YES;
887
888         i_returnValue = [o_hotkeySettings indexOfObject: [o_hotkeys_change_keys_lbl stringValue]];
889         if( i_returnValue != NSNotFound )
890             [o_hotkeySettings replaceObjectAtIndex: i_returnValue withObject: @"Unset"];        
891
892         [o_hotkeySettings replaceObjectAtIndex: [o_hotkeys_listbox selectedRow] withObject: [o_hotkeys_change_keys_lbl stringValue]];
893
894         [NSApp stopModal];
895         [o_hotkeys_change_win close];
896
897         [o_hotkeys_listbox reloadData];
898     }
899     else if( sender == o_hotkeys_clear_btn )
900     {
901         [o_hotkeySettings replaceObjectAtIndex: [o_hotkeys_listbox selectedRow] withObject: @"Unset"];
902         [o_hotkeys_listbox reloadData];
903         b_hotkeyChanged = YES;
904     }
905 }
906
907 - (void)showHotkeySettings
908 {
909     msg_Dbg( p_intf, "showing HotKey Settings" );
910     [self showSettingsForCategory: o_hotkeys_view];
911 }
912
913 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
914 {
915     return [o_hotkeySettings count];
916 }
917
918 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
919 {
920     if( [[aTableColumn identifier] isEqualToString: @"action"] )
921         return [o_hotkeyDescriptions objectAtIndex: rowIndex];
922     else if( [[aTableColumn identifier] isEqualToString: @"shortcut"] )
923         return [o_hotkeySettings objectAtIndex: rowIndex];
924     else
925     {
926         NSLog(@"unknown TableColumn identifier (%@)!", [aTableColumn identifier] );
927         return NULL;
928     }
929 }
930
931 - (void)changeHotkeyTo: (NSString *)o_theNewKey
932 {
933     int i_returnValue;
934     if( o_theNewKey == @"invalid" || o_theNewKey == @""  )
935     {
936         [o_hotkeys_change_keys_lbl setStringValue: _NS("Invalid combination")];
937         [o_hotkeys_change_taken_lbl setStringValue: _NS("Regrettably, these keys cannot be assigned as hotkey shortcuts.")];
938         [o_hotkeys_change_ok_btn setEnabled: NO];
939     }
940     else
941     {
942         [o_hotkeys_change_keys_lbl setStringValue: o_theNewKey];
943
944         i_returnValue = [o_hotkeySettings indexOfObject: o_theNewKey];
945         if( i_returnValue != NSNotFound )
946             [o_hotkeys_change_taken_lbl setStringValue: [NSString stringWithFormat:
947                                                          _NS("This combination is already taken by \"%@\"."),
948                                                          [o_hotkeyDescriptions objectAtIndex: i_returnValue]]];
949         else
950             [o_hotkeys_change_taken_lbl setStringValue: @""];
951
952         [o_hotkeys_change_ok_btn setEnabled: YES];
953     }
954 }
955     
956 @end
957
958 /********************
959  * hotkeys settings *
960  ********************/
961
962 @implementation VLCHotkeyChangeWindow
963
964 - (void)keyDown:(NSEvent *)o_theEvent
965 {
966     NSMutableString *o_temp = [[NSMutableString alloc] init];
967     
968     if( [o_theEvent modifierFlags] & NSShiftKeyMask )
969         [o_temp appendString: @"Shift+"];
970     
971     if( [o_theEvent modifierFlags] & NSControlKeyMask )
972         [o_temp appendString: @"Ctrl+"];
973     
974     if( [o_theEvent modifierFlags] & NSCommandKeyMask )
975         [o_temp appendString: @"Command+"];
976     
977     if( [o_theEvent modifierFlags] & NSAlternateKeyMask  )
978         [o_temp appendString: @"Alt+"];
979     
980     if( [o_theEvent modifierFlags] & NSFunctionKeyMask  )
981     {
982         unichar key = 0;
983         key = [[o_theEvent charactersIgnoringModifiers] characterAtIndex: 0];
984         
985         switch( key )
986         {
987             case 0x1b:
988                 [o_temp appendString: @"Esc"];
989                 break;
990             case NSF1FunctionKey:
991                 [o_temp appendString: @"F1"];
992                 break;
993             case NSF2FunctionKey:
994                 [o_temp appendString: @"F2"];
995                 break;
996             case NSF3FunctionKey:
997                 [o_temp appendString: @"F3"];
998                 break;
999             case NSF4FunctionKey:
1000                 [o_temp appendString: @"F4"];
1001                 break;
1002             case NSF5FunctionKey:
1003                 [o_temp appendString: @"F5"];
1004                 break;
1005             case NSF6FunctionKey:
1006                 [o_temp appendString: @"F6"];
1007                 break;
1008             case NSF7FunctionKey:
1009                 [o_temp appendString: @"F7"];
1010                 break;
1011             case NSF8FunctionKey:
1012                 [o_temp appendString: @"F8"];
1013                 break;
1014             case NSF9FunctionKey:
1015                 [o_temp appendString: @"F9"];
1016                 break;
1017             case NSF10FunctionKey:
1018                 [o_temp appendString: @"F10"];
1019                 break;
1020             case NSF11FunctionKey:
1021                 [o_temp appendString: @"F11"];
1022                 break;
1023             case NSF12FunctionKey:
1024                 [o_temp appendString: @"F12"];
1025                 break;
1026             case NSInsertFunctionKey:
1027                 [o_temp appendString: @"Insert"];
1028                 break;
1029             case NSHomeFunctionKey:
1030                 [o_temp appendString: @"Home"];
1031                 break;
1032             case NSEndFunctionKey:
1033                 [o_temp appendString: @"End"];
1034                 break;
1035             case NSPageUpFunctionKey:
1036                 [o_temp appendString: @"Page Up"];
1037                 break;
1038             case NSPageDownFunctionKey:
1039                 [o_temp appendString: @"Page Down"];
1040                 break;
1041             case NSMenuFunctionKey:
1042                 [o_temp appendString: @"Menu"];
1043                 break;
1044             case NSTabCharacter:
1045                 [o_temp appendString: @"Tab"];
1046                 break;
1047             case NSDeleteCharacter:
1048                 [o_temp appendString: @"Delete"];
1049                 break;
1050             case NSBackspaceCharacter:
1051                 [o_temp appendString: @"Backspace"];
1052                 break;
1053             case NSUpArrowFunctionKey:
1054                 [o_temp appendString: @"Up"];
1055                 break;
1056             case NSDownArrowFunctionKey:
1057                 [o_temp appendString: @"Down"];
1058                 break;
1059             case NSRightArrowFunctionKey:
1060                 [o_temp appendString: @"Right"];
1061                 break;
1062             case NSLeftArrowFunctionKey:
1063                 [o_temp appendString: @"Left"];
1064                 break;
1065             case NSEnterCharacter:
1066                 [o_temp appendString: @"Enter"];
1067                 break;
1068             default:
1069             {
1070                 msg_Warn( VLCIntf, "user pressed unknown function key" );
1071                 o_temp = @"invalid";
1072                 break;
1073             }
1074         }
1075     }
1076     else
1077     {
1078         if( [[o_theEvent charactersIgnoringModifiers] isEqualToString: @" "] )
1079             [o_temp appendString: @"Space"];
1080         else
1081             [o_temp appendString: [o_theEvent charactersIgnoringModifiers]];        
1082     }
1083
1084     /* FIXME: implement sanity checks here as we don't want the user to interfere with hard shortcuts in our main menu */
1085     [[[VLCMain sharedInstance] getSimplePreferences] changeHotkeyTo: o_temp];
1086
1087     NSLog( @"user pressed %@", o_temp );
1088
1089     [o_temp release];
1090 }
1091
1092 @end