qt/macos: add missing addons intf and meta categories
[vlc.git] / modules / gui / macosx / AddonManager.m
1 /*****************************************************************************
2  * AddonManager.m: Addons manager for the Mac
3  ****************************************************************************
4  * Copyright (C) 2014 VideoLAN and authors
5  * Author:       Felix Paul Kühne <fkuehne # videolan.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #import <vlc_common.h>
23 #import <vlc_events.h>
24 #import <vlc_addons.h>
25
26 #import "AddonManager.h"
27 #import "intf.h"
28 #import "MainWindow.h"
29 #import "AddonListDataSource.h"
30
31 @interface VLCAddonManager ()
32 {
33     addons_manager_t *_manager;
34     NSMutableArray *_addons;
35     NSArray *_displayedAddons;
36     BOOL _shouldRefreshSideBarOnAddonChange;
37 }
38
39 - (void)addAddon:(NSValue *)o_value;
40 - (void)discoveryEnded;
41 - (void)addonChanged:(NSValue *)o_value;
42 @end
43
44 static void addonsEventsCallback( const vlc_event_t *event, void *data )
45 {
46     @autoreleasepool {
47         if (event->type == vlc_AddonFound)
48             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(addAddon:) withObject:[NSValue valueWithPointer:event->u.addon_generic_event.p_entry] waitUntilDone:NO];
49         else if (event->type == vlc_AddonsDiscoveryEnded)
50             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(discoveryEnded) withObject:nil waitUntilDone:NO];
51         else if (event->type == vlc_AddonChanged)
52             [[VLCAddonManager sharedInstance] performSelectorOnMainThread:@selector(addonChanged:) withObject:[NSValue valueWithPointer:event->u.addon_generic_event.p_entry] waitUntilDone:NO];
53     }
54 }
55
56 @implementation VLCAddonManager
57
58 static VLCAddonManager *_o_sharedInstance = nil;
59
60 + (VLCAddonManager *)sharedInstance
61 {
62     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] init];
63 }
64
65 #pragma mark - object handling
66
67 - (id)init
68 {
69     if (_o_sharedInstance)
70         [self dealloc];
71     else {
72         _o_sharedInstance = [super init];
73         _addons = [[NSMutableArray alloc] init];
74     }
75
76     return _o_sharedInstance;
77 }
78
79 - (void)dealloc
80 {
81     [_addons release];
82     [_displayedAddons release];
83     if ( _manager )
84         addons_manager_Delete(_manager);
85     [super dealloc];
86 }
87
88 #pragma mark - UI handling
89
90 - (void)awakeFromNib
91 {
92     [_typeSwitcher removeAllItems];
93     [_typeSwitcher addItemWithTitle:_NS("All")];
94     [[_typeSwitcher lastItem] setTag: -1];
95     /* no skins on OS X so far
96     [_typeSwitcher addItemWithTitle:_NS("Skins")];
97     [[_typeSwitcher lastItem] setTag:ADDON_SKIN2]; */
98     [_typeSwitcher addItemWithTitle:_NS("Playlist parsers")];
99     [[_typeSwitcher lastItem] setTag:ADDON_PLAYLIST_PARSER];
100     [_typeSwitcher addItemWithTitle:_NS("Service Discovery")];
101     [[_typeSwitcher lastItem] setTag:ADDON_SERVICE_DISCOVERY];
102     [_typeSwitcher addItemWithTitle:_NS("Interfaces")];
103     [[_typeSwitcher lastItem] setTag:ADDON_INTERFACE];
104     [_typeSwitcher addItemWithTitle:_NS("Art and meta fetchers")];
105     [[_typeSwitcher lastItem] setTag:ADDON_META];
106     [_typeSwitcher addItemWithTitle:_NS("Extensions")];
107     [[_typeSwitcher lastItem] setTag:ADDON_EXTENSION];
108
109     [_localAddonsOnlyCheckbox setTitle:_NS("Show Installed Only")];
110     [_localAddonsOnlyCheckbox setState:NSOffState];
111     [_downloadCatalogButton setTitle:_NS("Find more addons online")];
112     [_spinner setUsesThreadedAnimation:YES];
113
114     [self updateInstallButton:NO];
115     [_installButton setHidden:YES];
116
117     [_name setStringValue:@""];
118     [_author setStringValue:@""];
119     [_version setStringValue:@""];
120     [_description setString:@""];
121     [_window setTitle:_NS("Addons Manager")];
122     [_window setReleasedWhenClosed:NO];
123
124     [[[_addonsTable tableColumnWithIdentifier:@"installed"] headerCell] setStringValue:_NS("Installed")];
125     [[[_addonsTable tableColumnWithIdentifier:@"name"] headerCell] setStringValue:_NS("Name")];
126     [[[_addonsTable tableColumnWithIdentifier:@"author"] headerCell] setStringValue:_NS("Author")];
127     [[[_addonsTable tableColumnWithIdentifier:@"type"] headerCell] setStringValue:_NS("Type")];
128
129     _manager = addons_manager_New((vlc_object_t *)VLCIntf);
130     if (!_manager)
131         return;
132
133     vlc_event_manager_t *p_em = _manager->p_event_manager;
134     vlc_event_attach(p_em, vlc_AddonFound, addonsEventsCallback, self);
135     vlc_event_attach(p_em, vlc_AddonsDiscoveryEnded, addonsEventsCallback, self);
136     vlc_event_attach(p_em, vlc_AddonChanged, addonsEventsCallback, self);
137
138     [self _findInstalled];
139 }
140
141 - (void)showWindow
142 {
143     [_window makeKeyAndOrderFront:nil];
144 }
145
146 - (IBAction)switchType:(id)sender
147 {
148     [self _refactorDataModel];
149 }
150
151 - (IBAction)toggleLocalCheckbox:(id)sender
152 {
153     [self _refactorDataModel];
154 }
155
156 - (IBAction)downloadCatalog:(id)sender
157 {
158     [self _findNewAddons];
159     [_downloadCatalogButton setHidden:YES];
160     [_localAddonsOnlyCheckbox setHidden:NO];
161 }
162
163 - (IBAction)installSelection:(id)sender
164 {
165     NSInteger selectedRow = [_addonsTable selectedRow];
166     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0)
167         return;
168
169     VLCAddon *currentAddon = [_displayedAddons objectAtIndex:selectedRow];
170     [self _installAddonWithID:[currentAddon uuid] type:[currentAddon type]];
171
172     [_installButton setEnabled:NO];
173 }
174
175 - (IBAction)uninstallSelection:(id)sender
176 {
177     NSInteger selectedRow = [_addonsTable selectedRow];
178     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0)
179         return;
180
181     VLCAddon *currentAddon = [_displayedAddons objectAtIndex:selectedRow];
182     [self _removeAddonWithID:[currentAddon uuid] type:[currentAddon type]];
183
184     [_installButton setEnabled:NO];
185 }
186
187 - (void)updateInstallButton:(BOOL)b_is_installed
188 {
189     [_installButton setHidden:NO];
190     [_installButton setEnabled:YES];
191
192     if (b_is_installed) {
193         [_installButton setTitle:_NS("Uninstall")];
194         [_installButton setAction:@selector(uninstallSelection:)];
195     } else {
196         [_installButton setTitle:_NS("Install")];
197         [_installButton setAction:@selector(installSelection:)];
198     }
199 }
200
201 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
202 {
203     return [_displayedAddons count];
204 }
205
206 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
207 {
208     NSInteger selectedRow = [_addonsTable selectedRow];
209     if (selectedRow > _displayedAddons.count - 1 || selectedRow < 0) {
210         [_name setStringValue:@""];
211         [_author setStringValue:@""];
212         [_version setStringValue:@""];
213         [_description setString:@""];
214         [_installButton setHidden:YES];
215         return;
216     }
217
218     VLCAddon *currentItem = [_displayedAddons objectAtIndex:selectedRow];
219     [_name setStringValue:[currentItem name]];
220     [_author setStringValue:[currentItem author]];
221     [_version setStringValue:[currentItem version]];
222     [_description setString:[currentItem description]];
223
224     [self updateInstallButton:[currentItem isInstalled]];
225 }
226
227 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
228 {
229     NSString *identifier = [aTableColumn identifier];
230     if ([identifier isEqualToString:@"installed"]) {
231         return [[_displayedAddons objectAtIndex:rowIndex] isInstalled] ? @"✔" : @"✘";
232     } else if([identifier isEqualToString:@"name"])
233         return [[_displayedAddons objectAtIndex:rowIndex] name];
234
235     return @"";
236 }
237
238 #pragma mark - data handling
239
240 - (void)addAddon:(NSValue *)o_value
241 {
242     addon_entry_t *p_entry = [o_value pointerValue];
243     /* no skin support on OS X so far */
244     if (p_entry->e_type != ADDON_SKIN2)
245         [_addons addObject:[[[VLCAddon alloc] initWithAddon:p_entry] autorelease]];
246 }
247
248 - (void)discoveryEnded
249 {
250     [self _refactorDataModel];
251     [_spinner stopAnimation:nil];
252 }
253
254 - (void)addonChanged:(NSValue *)o_value
255 {
256     [self _refactorDataModel];
257     if (_shouldRefreshSideBarOnAddonChange) {
258         [[VLCMainWindow sharedInstance] performSelector:@selector(reloadSidebar) withObject:nil afterDelay:0.5];
259         _shouldRefreshSideBarOnAddonChange = NO;
260     }
261 }
262
263 #pragma mark - helpers
264
265 - (void)_refactorDataModel
266 {
267     BOOL installedOnly = _localAddonsOnlyCheckbox.state == NSOnState;
268     int type = [[_typeSwitcher selectedItem] tag];
269
270     NSUInteger count = _addons.count;
271     NSMutableArray *filteredItems = [[NSMutableArray alloc] initWithCapacity:count];
272     for (NSUInteger x = 0; x < count; x++) {
273         VLCAddon *currentItem = [_addons objectAtIndex:x];
274         if (type != -1) {
275             if ([currentItem type] == type) {
276                 if (installedOnly) {
277                     if ([currentItem isInstalled])
278                         [filteredItems addObject:currentItem];
279                 } else
280                     [filteredItems addObject:currentItem];
281             }
282         } else {
283             if (installedOnly) {
284                 if ([currentItem isInstalled])
285                     [filteredItems addObject:currentItem];
286             } else
287                 [filteredItems addObject:currentItem];
288         }
289     }
290
291     if (_displayedAddons)
292         [_displayedAddons release];
293     _displayedAddons = [NSArray arrayWithArray:filteredItems];
294     [_displayedAddons retain];
295     [filteredItems release];
296
297     // update ui
298     [_addonsTable reloadData];
299     [self tableViewSelectionDidChange:nil];
300 }
301
302 - (void)_findNewAddons
303 {
304     [_spinner startAnimation:nil];
305     addons_manager_Gather(_manager, "repo://");
306 }
307
308 /* FIXME: un-used */
309 - (void)_findDesignatedAddon:(NSString *)uri
310 {
311     addons_manager_Gather(_manager, [uri UTF8String]);
312 }
313
314 - (void)_findInstalled
315 {
316     addons_manager_LoadCatalog(_manager);
317
318     // enqueue, to process the addons first
319     [self performSelectorOnMainThread:@selector(_refactorDataModel) withObject:nil waitUntilDone:NO];
320 }
321
322 - (void)_installAddonWithID:(NSData *)o_data type:(addon_type_t)type
323 {
324     addon_uuid_t uuid;
325     [o_data getBytes:uuid length:sizeof(uuid)];
326
327     if (type == ADDON_SERVICE_DISCOVERY)
328         _shouldRefreshSideBarOnAddonChange = YES;
329
330     addons_manager_Install(_manager, uuid);
331 }
332
333 - (void)_removeAddonWithID:(NSData *)o_data type:(addon_type_t)type
334 {
335     addon_uuid_t uuid;
336     [o_data getBytes:uuid length:sizeof(uuid)];
337
338     if (type == ADDON_SERVICE_DISCOVERY)
339         _shouldRefreshSideBarOnAddonChange = YES;
340
341     addons_manager_Remove(_manager, uuid);
342 }
343
344 - (NSString *)_getAddonType:(int)i_type
345 {
346     switch (i_type)
347     {
348         case ADDON_SKIN2:
349             return _NS("Skins");
350         case ADDON_PLAYLIST_PARSER:
351             return _NS("Playlist parsers");
352         case ADDON_SERVICE_DISCOVERY:
353             return _NS("Service Discovery");
354         case ADDON_EXTENSION:
355             return _NS("Extensions");
356         default:
357             return _NS("Unknown");
358     }
359 }
360
361 @end