]> git.sesse.net Git - vlc/blob - modules/gui/macosx/prefs.m
f765765de4b695130766bdd3f2ab066731afb55b
[vlc] / modules / gui / macosx / prefs.m
1 /*****************************************************************************
2  * prefs.m: MacOS X module for vlc
3  *****************************************************************************
4  * Copyright (C) 2002-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Derk-Jan Hartman <hartman at videolan dot org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /* VLCPrefs manages the main preferences dialog
26    the class is related to wxwindows intf, PrefsPanel */
27 /* VLCTreeItem should contain:
28    - the children of the treeitem
29    - the associated prefs widgets
30    - the documentview with all the prefs widgets in it
31    - a saveChanges action
32    - a revertChanges action
33    - a redraw view action
34    - the children action should generate a list of the treeitems children (to be used by VLCPrefs datasource)
35
36    The class is sort of a mix of wxwindows intfs, PrefsTreeCtrl and ConfigTreeData
37 */
38 /* VLCConfigControl are subclassed NSView's containing and managing individual config items
39    the classes are VERY closely related to wxwindows ConfigControls */
40
41 /*****************************************************************************
42  * Preamble
43  *****************************************************************************/
44 #include <stdlib.h>                                      /* malloc(), free() */
45 #include <sys/param.h>                                    /* for MAXPATHLEN */
46 #include <string.h>
47
48 #ifdef HAVE_CONFIG_H
49 # include "config.h"
50 #endif
51
52 #include <vlc_common.h>
53 #include <vlc_config_cat.h>
54
55 #import "intf.h"
56 #import "prefs.h"
57 #import "simple_prefs.h"
58 #import "prefs_widgets.h"
59 #import "vlc_keys.h"
60
61 /* /!\ Warning: Unreadable code :/ */
62
63 @interface VLCTreeItem : NSObject
64 {
65     NSString *o_name;
66     NSString *o_title;
67     NSString *o_help;
68     vlc_object_t * _vlc_object;
69     VLCTreeItem *o_parent;
70     NSMutableArray *o_children;
71     int i_object_category;
72     NSMutableArray *o_subviews;
73 }
74
75 - (id)initWithName: (NSString *)o_item_name
76     withTitle: (NSString *)o_item_title
77     withHelp: (NSString *)o_item_help
78     withObject: (vlc_object_t *)object
79     parent:(VLCTreeItem *)o_parent_item
80     children:(NSMutableArray *)o_children_array
81     whithCategory: (int) i_category;
82
83 + (VLCTreeItem *)rootItem;
84 - (int)numberOfChildren;
85 - (VLCTreeItem *)childAtIndex:(int)i_index;
86 - (vlc_object_t*)vlcObject;
87 - (NSString *)name;
88 - (NSString *)title;
89 - (NSString *)help;
90 - (BOOL)hasPrefs:(NSString *)o_module_name;
91 - (NSView *)showView:(NSScrollView *)o_prefs_view;
92 - (void)applyChanges;
93 - (void)resetView;
94
95 @end
96
97 #pragma mark -
98
99 /*****************************************************************************
100  * VLCPrefs implementation
101  *****************************************************************************/
102 @implementation VLCPrefs
103
104 static VLCPrefs *_o_sharedMainInstance = nil;
105
106 + (VLCPrefs *)sharedInstance
107 {
108     return _o_sharedMainInstance ? _o_sharedMainInstance : [[self alloc] init];
109 }
110
111 - (id)init
112 {
113     if( _o_sharedMainInstance ) {
114         [self dealloc];
115     }
116     else
117     {
118         _o_sharedMainInstance = [super init];
119         p_intf = VLCIntf;
120         o_empty_view = [[NSView alloc] init];
121     }
122
123     return _o_sharedMainInstance;
124 }
125
126 - (void)dealloc
127 {
128     [o_empty_view release];
129     [super dealloc];
130 }
131
132 - (void)awakeFromNib
133 {
134     p_intf = VLCIntf;
135
136     [self initStrings];
137     [o_prefs_view setBorderType: NSGrooveBorder];
138     [o_prefs_view setHasVerticalScroller: YES];
139     [o_prefs_view setDrawsBackground: NO];
140     [o_prefs_view setDocumentView: o_empty_view];
141     [o_tree selectRow:0 byExtendingSelection:NO];
142 }
143
144 - (void)setTitle: (NSString *) o_title_name
145 {
146     [o_title setStringValue: o_title_name];
147 }
148
149 - (void)showPrefs
150 {
151     [[o_basicFull_matrix cellAtRow:0 column:0] setState: NSOffState];
152     [[o_basicFull_matrix cellAtRow:0 column:1] setState: NSOnState];
153     
154     [o_prefs_window center];
155     [o_prefs_window makeKeyAndOrderFront:self];
156 }
157
158 - (void)initStrings
159 {
160     [o_prefs_window setTitle: _NS("Preferences")];
161     [o_save_btn setTitle: _NS("Save")];
162     [o_cancel_btn setTitle: _NS("Cancel")];
163     [o_reset_btn setTitle: _NS("Reset All")];
164     [[o_basicFull_matrix cellAtRow: 0 column: 0] setStringValue: _NS("Basic")];
165     [[o_basicFull_matrix cellAtRow: 0 column: 1] setStringValue: _NS("All")];
166 }
167
168 - (IBAction)savePrefs: (id)sender
169 {
170     /* TODO: call savePrefs on Root item */
171     [[VLCTreeItem rootItem] applyChanges];
172     config_SaveConfigFile( p_intf, NULL );
173     [o_prefs_window orderOut:self];
174 }
175
176 - (IBAction)closePrefs: (id)sender
177 {
178     [o_prefs_window orderOut:self];
179 }
180
181 - (IBAction)resetAll: (id)sender
182 {
183     NSBeginInformationalAlertSheet(_NS("Reset Preferences"), _NS("Cancel"),
184         _NS("Continue"), nil, o_prefs_window, self,
185         @selector(sheetDidEnd: returnCode: contextInfo:), NULL, nil,
186         _NS("Beware this will reset the VLC media player preferences.\n"
187             "Are you sure you want to continue?") );
188 }
189
190 - (void)sheetDidEnd:(NSWindow *)o_sheet returnCode:(int)i_return
191     contextInfo:(void *)o_context
192 {
193     if( i_return == NSAlertAlternateReturn )
194     {
195         [o_prefs_view setDocumentView: o_empty_view];
196         config_ResetAll( p_intf );
197         [[VLCTreeItem rootItem] resetView];
198         [[o_tree itemAtRow:[o_tree selectedRow]]
199             showView:o_prefs_view];
200     }
201 }
202
203 - (IBAction)buttonAction: (id)sender
204 {
205     [o_prefs_window orderOut: self];
206     [[o_basicFull_matrix cellAtRow:0 column:0] setState: NSOnState];
207     [[o_basicFull_matrix cellAtRow:0 column:1] setState: NSOffState];
208     [[[VLCMain sharedInstance] getSimplePreferences] showSimplePrefs];
209 }
210
211 - (void)loadConfigTree
212 {
213 }
214
215 - (void)outlineViewSelectionIsChanging:(NSNotification *)o_notification
216 {
217 }
218
219 /* update the document view to the view of the selected tree item */
220 - (void)outlineViewSelectionDidChange:(NSNotification *)o_notification
221 {
222     [[o_tree itemAtRow:[o_tree selectedRow]] showView: o_prefs_view];
223 }
224
225 @end
226
227 @implementation VLCPrefs (NSTableDataSource)
228
229 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
230     return (item == nil) ? [[VLCTreeItem rootItem] numberOfChildren] :
231                             [item numberOfChildren];
232 }
233
234 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
235 {
236     return (item == nil) ? YES : ( ([item numberOfChildren] != -1) &&
237                                    ([item numberOfChildren] != 0));
238 }
239
240 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
241     return (item == nil) ? [[VLCTreeItem rootItem] childAtIndex:index] :
242                             (id)[item childAtIndex:index];
243 }
244
245 - (id)outlineView:(NSOutlineView *)outlineView
246     objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
247 {
248     return (item == nil) ? @"" : (id)[item name];
249 }
250
251 @end
252
253 @implementation VLCTreeItem
254
255 static VLCTreeItem *o_root_item = nil;
256
257 #define IsALeafNode ((id)-1)
258
259 - (id)initWithName: (NSString *)o_item_name
260     withTitle: (NSString *)o_item_title
261     withHelp: (NSString *)o_item_help
262     withObject: (vlc_object_t *)object
263     parent:(VLCTreeItem *)o_parent_item
264     children:(NSMutableArray *)o_children_array
265     whithCategory: (int) i_category
266 {
267     self = [super init];
268
269     if( self != nil )
270     {
271         o_name = [o_item_name copy];
272         o_title= [o_item_title copy];
273         o_help= [o_item_help copy];
274         _vlc_object = object ? vlc_object_hold( object ) : NULL;
275         o_parent = o_parent_item;
276         o_children = o_children_array;
277         i_object_category = i_category;
278         o_subviews = nil;
279     }
280     return( self );
281 }
282
283 + (VLCTreeItem *)rootItem
284 {
285    if (o_root_item == nil)
286         o_root_item = [[VLCTreeItem alloc] initWithName:@"main" withTitle:@"main" withHelp:@"" withObject:NULL
287             parent:nil children:[[NSMutableArray alloc] initWithCapacity:10]
288             whithCategory: -1];
289    return o_root_item;
290 }
291
292 - (void)dealloc
293 {
294     if(_vlc_object) vlc_object_release( _vlc_object );
295     if (o_children != IsALeafNode) [o_children release];
296     [o_name release];
297     [o_title release];
298     [o_help release];
299     [super dealloc];
300 }
301
302 /* Creates and returns the array of children
303  * Loads children incrementally */
304 - (NSArray *)children
305 {
306     if( o_children == IsALeafNode )
307         return o_children;
308     if( [ o_children count] == 0 )
309     {
310         intf_thread_t   *p_intf = VLCIntf;
311         module_t       **p_list;
312         module_t        *p_module = NULL;
313         module_t        *p_main_module;
314         module_config_t *p_items;
315         if( [[self name] isEqualToString: @"main"] )
316         {
317             p_main_module = module_get_main( p_intf );
318             assert( p_main_module );
319
320             /* We found the main module */
321             /* Enumerate config categories and store a reference so we can
322              * generate their config panel them when it is asked by the user. */
323             VLCTreeItem *p_last_category = NULL;
324             unsigned int i_confsize;
325             p_items = module_config_get( p_main_module, &i_confsize );
326             o_children = [[NSMutableArray alloc] initWithCapacity:10];
327             for( int i = 0; i < i_confsize; i++ )
328             {
329                 NSString *o_child_name;
330                 NSString *o_child_title;
331                 NSString *o_child_help;
332
333                 switch( p_items[i].i_type )
334                 {
335                     case CONFIG_CATEGORY:
336                         if( p_items[i].value.i == -1 ) break;
337
338                         o_child_name = [[VLCMain sharedInstance]
339                             localizedString: config_CategoryNameGet( p_items[i].value.i )];
340                         o_child_title = o_child_name;
341                         o_child_help = [[VLCMain sharedInstance]
342                             localizedString: config_CategoryHelpGet( p_items[i].value.i )];
343                         p_last_category = [VLCTreeItem alloc];
344                         [o_children addObject:[p_last_category
345                             initWithName: o_child_name
346                             withTitle: o_child_title
347                             withHelp: o_child_help
348                             withObject: (vlc_object_t*)p_main_module
349                             parent:self
350                             children:[[NSMutableArray alloc]
351                                 initWithCapacity:10]
352                             whithCategory: p_items[i].value.i]];
353                         break;
354                     case CONFIG_SUBCATEGORY:
355                         if( p_items[i].value.i == -1 ) break;
356
357                         if( p_items[i].value.i != SUBCAT_PLAYLIST_GENERAL &&
358                             p_items[i].value.i != SUBCAT_VIDEO_GENERAL &&
359                             p_items[i].value.i != SUBCAT_INPUT_GENERAL &&
360                             p_items[i].value.i != SUBCAT_INTERFACE_GENERAL &&
361                             p_items[i].value.i != SUBCAT_SOUT_GENERAL &&
362                             p_items[i].value.i != SUBCAT_ADVANCED_MISC &&
363                             p_items[i].value.i != SUBCAT_AUDIO_GENERAL )
364                         {
365                             o_child_name = [[VLCMain sharedInstance]
366                                 localizedString: config_CategoryNameGet( p_items[i].value.i ) ];
367                             o_child_title = o_child_name;
368                             o_child_help = [[VLCMain sharedInstance]
369                                 localizedString: config_CategoryHelpGet( p_items[i].value.i ) ];
370
371                             [p_last_category->o_children
372                                 addObject:[[VLCTreeItem alloc]
373                                 initWithName: o_child_name
374                                 withTitle: o_child_title
375                                 withHelp: o_child_help
376                                 withObject: (vlc_object_t*)p_main_module
377                                 parent:p_last_category
378                                 children:[[NSMutableArray alloc]
379                                     initWithCapacity:10]
380                                 whithCategory: p_items[i].value.i]];
381                         }
382
383                         break;
384                     default:
385                         break;
386                 }
387             }
388
389             vlc_object_release( (vlc_object_t *)p_main_module );
390
391             /* List the modules */
392             p_list = module_list_get( NULL );
393             if( !p_list ) return nil;
394
395             /* Build a tree of the plugins */
396             /* Add the capabilities */
397             for( size_t i = 0; p_list[i]; i++ )
398             {
399                 unsigned int confsize;
400                 p_module = p_list[i];
401
402                 /* Exclude the main module */
403                 if( module_is_main( p_module ) )
404                     continue;
405
406                 /* Exclude empty plugins (submodules don't have config */
407                 /* options, they are stored in the parent module) */
408                 p_items = module_config_get( p_module, &confsize );
409
410                 unsigned int j;
411
412                 int i_category = -1;
413                 int i_subcategory = -1;
414                 bool b_item = false;
415
416                 for( j = 0; j < confsize; j++ )
417                 {
418                     if( p_items[j].i_type == CONFIG_CATEGORY )
419                         i_category = p_items[j].value.i;
420                     else if( p_items[j].i_type == CONFIG_SUBCATEGORY )
421                         i_subcategory = p_items[j].value.i;
422
423                     if( p_items[j].i_type & CONFIG_ITEM )
424                         b_item = true;
425             
426                     if( b_item && i_category >= 0 && i_subcategory >= 0 )
427                         break;
428                 }
429     
430                 if( !b_item ) continue;
431
432                 /* Find the right category item */
433
434                 long cookie;
435                 bool b_found = false;
436
437                 VLCTreeItem* p_category_item, * p_subcategory_item;
438                 for (j = 0 ; j < [o_children count] ; j++)
439                 {
440                     p_category_item = [o_children objectAtIndex: j];
441                     if( p_category_item->i_object_category == i_category )
442                     {
443                         b_found = true;
444                         break;
445                     }
446                 }
447                 if( !b_found ) continue;
448
449                 /* Find subcategory item */
450                 b_found = false;
451                 cookie = -1;
452                 for (j = 0 ; j < [p_category_item->o_children count] ; j++)
453                 {
454                     p_subcategory_item = [p_category_item->o_children
455                                             objectAtIndex: j];
456                     if( p_subcategory_item->i_object_category == i_subcategory )
457                     {
458                         b_found = true;
459                         break;
460                     }
461                 }
462                 if( !b_found )
463                     p_subcategory_item = p_category_item;
464
465                 [p_subcategory_item->o_children addObject:[[VLCTreeItem alloc]
466                     initWithName:[[VLCMain sharedInstance]
467                         localizedString: module_get_name( p_module, false ) ]
468                     withTitle:[[VLCMain sharedInstance]
469                         localizedString:  module_GetLongName( p_module ) ]
470                     withHelp: @""
471                     withObject: (vlc_object_t*)p_main_module
472                     parent:p_subcategory_item
473                     children:IsALeafNode
474                     whithCategory: -1]];
475                 }
476             module_list_free( p_list );
477         }
478     }
479     return o_children;
480 }
481
482 - (vlc_object_t *)vlcObject
483 {
484     return vlc_object_hold(_vlc_object);
485 }
486
487 - (NSString *)name
488 {
489     return [[o_name retain] autorelease];
490 }
491
492 - (NSString *)title
493 {
494     return [[o_title retain] autorelease];
495 }
496
497 - (NSString *)help
498 {
499     return [[o_help retain] autorelease];
500 }
501
502 - (VLCTreeItem *)childAtIndex:(int)i_index
503 {
504     return [[self children] objectAtIndex:i_index];
505 }
506
507 - (int)numberOfChildren {
508     id i_tmp = [self children];
509     return (i_tmp == IsALeafNode) ? (-1) : (int)[i_tmp count];
510 }
511
512 - (BOOL)hasPrefs:(NSString *)o_module_name
513 {
514     unsigned int confsize;
515
516     intf_thread_t *p_intf = VLCIntf;
517     module_t *p_parser;
518
519     const char *psz_module_name = (char *)[o_module_name UTF8String];
520
521     /* look for module */
522     p_parser = module_find( p_intf, psz_module_name );
523     if( !p_parser )
524         return( NO );
525
526     module_config_get( p_parser, &confsize );
527     BOOL b_has_prefs = confsize != 0;
528     module_release( p_parser );
529     return( b_has_prefs );
530 }
531
532 - (NSView *)showView:(NSScrollView *)o_prefs_view
533 {
534     NSRect          s_vrc;
535     NSView          *o_view;
536
537     [[VLCPrefs sharedInstance] setTitle: [self title]];
538     /* NSLog( [self getHelp] ); */
539     s_vrc = [[o_prefs_view contentView] bounds]; s_vrc.size.height -= 4;
540     o_view = [[VLCFlippedView alloc] initWithFrame: s_vrc];
541     [o_view setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin |
542                                     NSViewMaxYMargin];
543
544 /* Create all subviews if it isn't already done because we cannot use */
545 /* setHiden for MacOS < 10.3*/
546     if( o_subviews == nil )
547     {
548         intf_thread_t   *p_intf = VLCIntf;
549         vlc_list_t      *p_list;
550         module_t        *p_module = NULL;
551         module_t        *p_main_module;
552         module_config_t *p_items;
553         unsigned int confsize;
554
555         o_subviews = [[NSMutableArray alloc] initWithCapacity:10];
556         /* Get a pointer to the module */
557         if( i_object_category == -1 )
558         {
559             p_module = (module_t *) [self vlcObject];
560             assert( p_module );
561
562             p_items = module_config_get( p_module, &confsize );
563
564             for( unsigned int i = 0; i < confsize; i++ )
565             {
566                 switch( p_items[i].i_type )
567                 {
568                     case CONFIG_SUBCATEGORY:
569                     case CONFIG_CATEGORY:
570                     case CONFIG_SECTION:
571                     case CONFIG_HINT_USAGE:
572                         break;
573                     default:
574                     {
575                         VLCConfigControl *o_control = nil;
576                         o_control = [VLCConfigControl newControl:&p_items[i]
577                                                       withView:o_view];
578                         if( o_control )
579                         {
580                             [o_control setAutoresizingMask: NSViewMaxYMargin |
581                                 NSViewWidthSizable];
582                             [o_subviews addObject: o_control];
583                         }
584                     }
585                     break;
586                 }
587             }
588             vlc_object_release( (vlc_object_t*)p_module );
589         }
590         else
591         {
592             p_main_module = module_get_main( p_intf );
593             assert( p_main_module );
594             module_config_t *p_items;
595
596             unsigned int i, confsize;
597             p_items = module_config_get( p_main_module, &confsize );
598
599             /* We need to first, find the right (sub)category,
600              * and then abort when we find a new (sub)category. Part of the Ugliness. */
601             bool in_right_category = false;
602             bool in_subcategory = false;
603             bool done = false;
604             for( i = 0; i < confsize; i++ )
605             {
606                 if( !p_items[i].i_type )
607                 {
608                     msg_Err( p_intf, "invalid preference item found" );
609                     break;
610                 }
611
612                 switch( p_items[i].i_type )
613                 {
614                     case CONFIG_CATEGORY:
615                         if(!in_right_category && p_items[i].value.i == i_object_category)
616                             in_right_category = true;
617                         else if(in_right_category)
618                             done = true;
619                         break;
620                     case CONFIG_SUBCATEGORY:
621                         if(!in_right_category && p_items[i].value.i == i_object_category)
622                         {
623                             in_right_category = true;
624                             in_subcategory = true;
625                         }
626                         else if(in_right_category && in_subcategory)
627                             done = true;
628                         break;
629                     case CONFIG_SECTION:
630                     case CONFIG_HINT_USAGE:
631                         break;
632                     default:
633                     {
634                         if(!in_right_category) break;
635
636                         VLCConfigControl *o_control = nil;
637                         o_control = [VLCConfigControl newControl:&p_items[i]
638                                                       withView:o_view];
639                         if( o_control != nil )
640                         {
641                             [o_control setAutoresizingMask: NSViewMaxYMargin |
642                                                             NSViewWidthSizable];
643                             [o_subviews addObject: o_control];
644                         }
645                         break;
646                     }
647                 }
648                 if( done ) break;
649             }
650             vlc_object_release( (vlc_object_t*)p_main_module );
651         }
652     }
653
654     if( o_view != nil )
655     {
656         int i_lastItem = 0;
657         int i_yPos = -2;
658         int i_max_label = 0;
659
660         NSEnumerator *enumerator = [o_subviews objectEnumerator];
661         VLCConfigControl *o_widget;
662         NSRect o_frame;
663  
664         while( ( o_widget = [enumerator nextObject] ) )
665             if( i_max_label < [o_widget getLabelSize] )
666                 i_max_label = [o_widget getLabelSize];
667
668         enumerator = [o_subviews objectEnumerator];
669         while( ( o_widget = [enumerator nextObject] ) )
670         {
671             int i_widget;
672
673             i_widget = [o_widget getViewType];
674             i_yPos += [VLCConfigControl calcVerticalMargin:i_widget
675                 lastItem:i_lastItem];
676             [o_widget setYPos:i_yPos];
677             o_frame = [o_widget frame];
678             o_frame.size.width = [o_view frame].size.width -
679                                     LEFTMARGIN - RIGHTMARGIN;
680             [o_widget setFrame:o_frame];
681             [o_widget alignWithXPosition: i_max_label];
682             i_yPos += [o_widget frame].size.height;
683             i_lastItem = i_widget;
684             [o_view addSubview:o_widget];
685          }
686
687         o_frame = [o_view frame];
688         o_frame.size.height = i_yPos;
689         [o_view setFrame:o_frame];
690         [o_prefs_view setDocumentView:o_view];
691
692     }
693     return o_view;
694 }
695
696 - (void)applyChanges
697 {
698     unsigned int i;
699     if( o_subviews != nil )
700         //Item has been shown
701         for( i = 0 ; i < [o_subviews count] ; i++ )
702             [[o_subviews objectAtIndex:i] applyChanges];
703
704     if( o_children != IsALeafNode )
705         for( i = 0 ; i < [o_children count] ; i++ )
706             [[o_children objectAtIndex:i] applyChanges];
707 }
708
709 - (void)resetView
710 {
711     unsigned int i;
712     if( o_subviews != nil )
713     {
714         //Item has been shown
715         [o_subviews release];
716         o_subviews = nil;
717     }
718
719     if( o_children != IsALeafNode )
720         for( i = 0 ; i < [o_children count] ; i++ )
721             [[o_children objectAtIndex:i] resetView];
722 }
723
724 @end
725
726
727 @implementation VLCFlippedView
728
729 - (BOOL)isFlipped
730 {
731     return( YES );
732 }
733
734 @end