]> git.sesse.net Git - vlc/blob - modules/gui/macosx/ExtensionsDialogProvider.m
macosx: Fix drawing issues with podcast controls and dropzone
[vlc] / modules / gui / macosx / ExtensionsDialogProvider.m
1 /*****************************************************************************
2  * ExtensionsDialogProvider.m: Mac OS X Extensions Dialogs
3  *****************************************************************************
4  * Copyright (C) 2010-2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Pierre d'Herbemont <pdherbemont # videolan org>
8  *          Brendon Justin <brendonjustin@gmail.com>,
9  *          Derk-Jan Hartman <hartman@videolan dot org>,
10  *          Felix Paul Kühne <fkuehne@videolan dot org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #import "ExtensionsDialogProvider.h"
28
29 #import "intf.h"
30 #import "ExtensionsManager.h"
31 #import "misc.h"
32 #import "VLCUIWidgets.h"
33
34 #import <WebKit/WebKit.h>
35 #import <stdlib.h>
36
37 /*****************************************************************************
38  * VLCExtensionsDialogProvider implementation
39  *****************************************************************************/
40
41 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
42                            vlc_value_t old_val, vlc_value_t new_val,
43                            void *param);
44
45 static NSView *createControlFromWidget(extension_widget_t *widget, id self)
46 {
47     assert(!widget->p_sys_intf);
48     switch (widget->type) {
49         case EXTENSION_WIDGET_HTML:
50         {
51             WebView *webView = [[WebView alloc] initWithFrame:NSMakeRect (0,0,1,1)];
52             [webView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
53             [webView setDrawsBackground:NO];
54             return webView;
55         }
56         case EXTENSION_WIDGET_LABEL:
57         {
58             NSTextField *field = [[NSTextField alloc] init];
59             [field setEditable:NO];
60             [field setBordered:NO];
61             [field setDrawsBackground:NO];
62             [field setFont:[NSFont systemFontOfSize:0]];
63             [[field cell] setControlSize:NSRegularControlSize];
64             [field setAutoresizingMask:NSViewNotSizable];
65             return field;
66         }
67         case EXTENSION_WIDGET_TEXT_FIELD:
68         {
69             VLCDialogTextField *field = [[VLCDialogTextField alloc] init];
70             [field setWidget:widget];
71             [field setAutoresizingMask:NSViewWidthSizable];
72             [field setFont:[NSFont systemFontOfSize:0]];
73             [[field cell] setControlSize:NSRegularControlSize];
74             [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(syncTextField:)  name:NSControlTextDidChangeNotification object:field];
75             return field;
76         }
77         case EXTENSION_WIDGET_CHECK_BOX:
78         {
79             VLCDialogButton *button = [[VLCDialogButton alloc] init];
80             [button setButtonType:NSSwitchButton];
81             [button setWidget:widget];
82             [button setAction:@selector(triggerClick:)];
83             [button setTarget:self];
84             [[button cell] setControlSize:NSRegularControlSize];
85             [button setAutoresizingMask:NSViewWidthSizable];
86             return button;
87         }
88         case EXTENSION_WIDGET_BUTTON:
89         {
90             VLCDialogButton *button = [[VLCDialogButton alloc] init];
91             [button setBezelStyle:NSRoundedBezelStyle];
92             [button setWidget:widget];
93             [button setAction:@selector(triggerClick:)];
94             [button setTarget:self];
95             [[button cell] setControlSize:NSRegularControlSize];
96             [button setAutoresizingMask:NSViewNotSizable];
97             return button;
98         }
99         case EXTENSION_WIDGET_DROPDOWN:
100         {
101             VLCDialogPopUpButton *popup = [[VLCDialogPopUpButton alloc] init];
102             [popup setAction:@selector(popUpSelectionChanged:)];
103             [popup setTarget:self];
104             [popup setWidget:widget];
105             return popup;
106         }
107         case EXTENSION_WIDGET_LIST:
108         {
109             NSScrollView *scrollView = [[NSScrollView alloc] init];
110             [scrollView setHasVerticalScroller:YES];
111             VLCDialogList *list = [[VLCDialogList alloc] init];
112             [list setUsesAlternatingRowBackgroundColors:YES];
113             [list setHeaderView:nil];
114             [scrollView setDocumentView:list];
115             [scrollView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
116
117             NSTableColumn *column = [[NSTableColumn alloc] init];
118             [list addTableColumn:column];
119             [column release];
120             [list setDataSource:list];
121             [list setDelegate:self];
122             [list setWidget:widget];
123             [list release];
124             return scrollView;
125         }
126         case EXTENSION_WIDGET_IMAGE:
127         {
128             NSImageView *imageView = [[NSImageView alloc] init];
129             [imageView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
130             [imageView setImageFrameStyle:NSImageFramePhoto];
131             [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
132             return imageView;
133         }
134         case EXTENSION_WIDGET_SPIN_ICON:
135         {
136             NSProgressIndicator *spinner = [[NSProgressIndicator alloc] init];
137             [spinner setUsesThreadedAnimation:YES];
138             [spinner setStyle:NSProgressIndicatorSpinningStyle];
139             [spinner setDisplayedWhenStopped:YES];
140             [spinner startAnimation:self];
141             return spinner;
142         }
143         default:
144             return nil;
145     }
146
147 }
148
149 static void updateControlFromWidget(NSView *control, extension_widget_t *widget, id self)
150 {
151     switch (widget->type) {
152         case EXTENSION_WIDGET_HTML:
153         {
154             // Get the web view
155             assert([control isKindOfClass:[WebView class]]);
156             WebView *webView = (WebView *)control;
157             NSString *string = [NSString stringWithUTF8String:widget->psz_text];
158             [[webView mainFrame] loadHTMLString:string baseURL:[NSURL URLWithString:@""]];
159             [webView setNeedsDisplay:YES];
160             break;
161
162         }
163         {
164             assert([control isKindOfClass:[NSTextView class]]);
165             NSTextView *textView = (NSTextView *)control;
166             NSString *string = [NSString stringWithUTF8String:widget->psz_text];
167             NSAttributedString *attrString = [[NSAttributedString alloc] initWithHTML:[string dataUsingEncoding: NSISOLatin1StringEncoding] documentAttributes:NULL];
168             [[textView textStorage] setAttributedString:attrString];
169             [textView setNeedsDisplay:YES];
170             [textView scrollRangeToVisible:NSMakeRange(0, 0)];
171             [attrString release];
172             break;
173
174         }
175         case EXTENSION_WIDGET_LABEL:
176         case EXTENSION_WIDGET_PASSWORD:
177         case EXTENSION_WIDGET_TEXT_FIELD:
178         {
179             if (!widget->psz_text)
180                 break;
181             assert([control isKindOfClass:[NSControl class]]);
182             NSControl *field = (NSControl *)control;
183             NSString *string = [NSString stringWithCString:widget->psz_text encoding:NSUTF8StringEncoding];
184             NSAttributedString *attrString = [[NSAttributedString alloc] initWithHTML:[string dataUsingEncoding: NSISOLatin1StringEncoding] documentAttributes:NULL];
185             [field setAttributedStringValue:attrString];
186             [attrString release];
187             break;
188         }
189         case EXTENSION_WIDGET_CHECK_BOX:
190         case EXTENSION_WIDGET_BUTTON:
191         {
192             assert([control isKindOfClass:[NSButton class]]);
193             NSButton *button = (NSButton *)control;
194             if (!widget->psz_text)
195                 break;
196             [button setTitle:[NSString stringWithUTF8String:widget->psz_text]];
197             break;
198         }
199         case EXTENSION_WIDGET_DROPDOWN:
200         {
201             assert([control isKindOfClass:[NSPopUpButton class]]);
202             NSPopUpButton *popup = (NSPopUpButton *)control;
203             [popup removeAllItems];
204             struct extension_widget_value_t *value;
205             for (value = widget->p_values; value != NULL; value = value->p_next)
206                 [popup addItemWithTitle:[NSString stringWithUTF8String:value->psz_text]];
207             [popup synchronizeTitleAndSelectedItem];
208             [self popUpSelectionChanged:popup];
209             break;
210         }
211         case EXTENSION_WIDGET_LIST:
212         {
213             assert([control isKindOfClass:[NSScrollView class]]);
214             NSScrollView *scrollView = (NSScrollView *)control;
215             assert([[scrollView documentView] isKindOfClass:[VLCDialogList class]]);
216             VLCDialogList *list = (VLCDialogList *)[scrollView documentView];
217
218             NSMutableArray *contentArray = [NSMutableArray array];
219             struct extension_widget_value_t *value;
220             for (value = widget->p_values; value != NULL; value = value->p_next)
221             {
222                 NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
223                                        [NSNumber numberWithInt:value->i_id], @"id",
224                                        [NSString stringWithUTF8String:value->psz_text], @"text",
225                                        nil];
226                 [contentArray addObject:entry];
227             }
228             list.contentArray = contentArray;
229             [list reloadData];
230             break;
231         }
232         case EXTENSION_WIDGET_IMAGE:
233         {
234             assert([control isKindOfClass:[NSImageView class]]);
235             NSImageView *imageView = (NSImageView *)control;
236             NSString *string = widget->psz_text ? [NSString stringWithUTF8String:widget->psz_text] : nil;
237             NSImage *image = nil;
238             if (string)
239                 image = [[NSImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:string]];
240             [imageView setImage:image];
241             [image release];
242             break;
243         }
244         case EXTENSION_WIDGET_SPIN_ICON:
245         {
246             assert([control isKindOfClass:[NSProgressIndicator class]]);
247             NSProgressIndicator *progressIndicator = (NSProgressIndicator *)control;
248             if (widget->i_spin_loops != 0)
249                 [progressIndicator startAnimation:self];
250             else
251                 [progressIndicator stopAnimation:self];
252             break;
253         }
254     }
255
256 }
257
258 /**
259  * Ask the dialogs provider to create a new dialog
260  **/
261 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
262                            vlc_value_t old_val, vlc_value_t new_val,
263                            void *param)
264 {
265     (void) p_this;
266     (void) psz_variable;
267     (void) old_val;
268     (void) param;
269
270     ExtensionsDialogProvider *p_edp = [ExtensionsDialogProvider sharedInstance:(intf_thread_t *)p_this];
271     if (!p_edp)
272         return VLC_EGENERIC;
273     if (!new_val.p_address)
274         return VLC_EGENERIC;
275
276     extension_dialog_t *p_dialog = (extension_dialog_t*) new_val.p_address;
277     [p_edp manageDialog:p_dialog];
278     return VLC_SUCCESS;
279 }
280
281 @implementation ExtensionsDialogProvider
282
283 static ExtensionsDialogProvider *_o_sharedInstance = nil;
284
285 + (ExtensionsDialogProvider *)sharedInstance:(intf_thread_t *)_p_intf
286 {
287     return _o_sharedInstance ? _o_sharedInstance : [[self alloc] initWithIntf:_p_intf];
288 }
289
290 + (void)killInstance
291 {
292     if (_o_sharedInstance) {
293         [_o_sharedInstance release];
294     }
295 }
296
297 - (id)initWithIntf:(intf_thread_t *)_p_intf
298 {
299     if (_o_sharedInstance)
300         [self dealloc];
301
302     if ((self = [super init])) {
303         _o_sharedInstance = self;
304         p_intf = _p_intf;
305
306         // The Cocoa interface already called dialog_Register()
307         var_Create(p_intf, "dialog-extension", VLC_VAR_ADDRESS);
308         var_AddCallback(p_intf, "dialog-extension", dialogCallback, NULL);
309     }
310
311     return _o_sharedInstance;
312 }
313
314 - (void)dealloc
315 {
316     msg_Dbg(p_intf, "ExtensionsDialogProvider is quitting...");
317     var_DelCallback(p_intf, "dialog-extension", dialogCallback, NULL);
318
319     [super dealloc];
320 }
321
322 - (void)performEventWithObject: (NSValue *)o_value ofType: (const char*)type
323 {
324     NSString *o_type = [NSString stringWithUTF8String:type];
325
326     if ([o_type isEqualToString: @"dialog-extension"]) {
327         [self performSelectorOnMainThread:@selector(updateExtensionDialog:)
328                                withObject:o_value
329                             waitUntilDone:YES];
330
331     }
332     else
333         msg_Err(VLCIntf, "unhandled dialog type: '%s'", type);
334 }
335
336 - (void)triggerClick:(id)sender
337 {
338     assert([sender isKindOfClass:[VLCDialogButton class]]);
339     VLCDialogButton *button = sender;
340     extension_widget_t *widget = [button widget];
341
342     vlc_mutex_lock(&widget->p_dialog->lock);
343     extension_WidgetClicked(widget->p_dialog, widget);
344     vlc_mutex_unlock(&widget->p_dialog->lock);
345 }
346
347 - (void)syncTextField:(NSNotification *)notifcation
348 {
349     id sender = [notifcation object];
350     assert([sender isKindOfClass:[VLCDialogTextField class]]);
351     VLCDialogTextField *field = sender;
352     extension_widget_t *widget = [field widget];
353
354     vlc_mutex_lock(&widget->p_dialog->lock);
355     free(widget->psz_text);
356     widget->psz_text = strdup([[field stringValue] UTF8String]);
357     vlc_mutex_unlock(&widget->p_dialog->lock);
358 }
359
360 - (void)tableViewSelectionDidChange:(NSNotification *)notifcation
361 {
362     id sender = [notifcation object];
363     assert(sender && [sender isKindOfClass:[VLCDialogList class]]);
364     VLCDialogList *list = sender;
365
366     struct extension_widget_value_t *value;
367     unsigned i = 0;
368     for (value = [list widget]->p_values; value != NULL; value = value->p_next, i++)
369         value->b_selected = (i == [list selectedRow]);
370 }
371
372 - (void)popUpSelectionChanged:(id)sender
373 {
374     assert([sender isKindOfClass:[VLCDialogPopUpButton class]]);
375     VLCDialogPopUpButton *popup = sender;
376     struct extension_widget_value_t *value;
377     unsigned i = 0;
378     for (value = [popup widget]->p_values; value != NULL; value = value->p_next, i++)
379         value->b_selected = (i == [popup indexOfSelectedItem]);
380
381 }
382
383 - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
384 {
385     NSView *contentView = [sender contentView];
386     assert([contentView isKindOfClass:[VLCDialogGridView class]]);
387     VLCDialogGridView *gridView = (VLCDialogGridView *)contentView;
388
389     NSRect rect = NSMakeRect(0, 0, 0, 0);
390     rect.size = frameSize;
391     rect = [sender contentRectForFrameRect:rect];
392     rect.size = [gridView flexSize:rect.size];
393     rect = [sender frameRectForContentRect:rect];
394     return rect.size;
395 }
396
397 - (BOOL)windowShouldClose:(id)sender
398 {
399     assert([sender isKindOfClass:[VLCDialogWindow class]]);
400     VLCDialogWindow *window = sender;
401     extension_dialog_t *dialog = [window dialog];
402     extension_DialogClosed(dialog);
403     dialog->p_sys_intf = NULL;
404     return YES;
405 }
406
407 - (void)updateWidgets:(extension_dialog_t *)dialog
408 {
409     extension_widget_t *widget;
410     VLCDialogWindow *dialogWindow = dialog->p_sys_intf;
411
412     FOREACH_ARRAY(widget, dialog->widgets) {
413         if (!widget)
414             continue; /* Some widgets may be NULL@this point */
415
416         BOOL shouldDestroy = widget->b_kill;
417         NSView *control = widget->p_sys_intf;
418         BOOL update = widget->b_update;
419
420         if (!control && !shouldDestroy) {
421             control = createControlFromWidget(widget, self);
422             updateControlFromWidget(control, widget, self);
423             widget->p_sys_intf = control;
424             update = YES; // Force update and repositionning
425             [control setHidden:widget->b_hide];
426         }
427
428         if (update && !shouldDestroy) {
429             updateControlFromWidget(control, widget, self);
430             [control setHidden:widget->b_hide];
431
432             int row = widget->i_row - 1;
433             int col = widget->i_column - 1;
434             int hsp = __MAX(1, widget->i_horiz_span);
435             int vsp = __MAX(1, widget->i_vert_span);
436             if (row < 0) {
437                 row = 4;
438                 col = 0;
439             }
440
441             VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
442             [gridView addSubview:control atRow:row column:col rowSpan:vsp colSpan:hsp];
443
444             widget->b_update = false;
445         }
446
447         if (shouldDestroy) {
448             VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
449             [gridView removeSubview:control];
450             [control release];
451             widget->p_sys_intf = NULL;
452         }
453     }
454     FOREACH_END()
455 }
456
457 /** Create a dialog
458  * Note: Lock on p_dialog->lock must be held. */
459 - (VLCDialogWindow *)createExtensionDialog:(extension_dialog_t *)p_dialog
460 {
461     VLCDialogWindow *dialogWindow = nil;
462
463     BOOL shouldDestroy = p_dialog->b_kill;
464     if (!shouldDestroy) {
465         NSRect content = NSMakeRect(0, 0, 1, 1);
466         dialogWindow = [[VLCDialogWindow alloc] initWithContentRect:content
467                                                           styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask
468                                                             backing:NSBackingStoreBuffered
469                                                               defer:NO];
470         [dialogWindow setDelegate:self];
471         [dialogWindow setDialog:p_dialog];
472         [dialogWindow setTitle:[NSString stringWithUTF8String:p_dialog->psz_title]];
473
474         VLCDialogGridView *gridView = [[VLCDialogGridView alloc] init];
475         [gridView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
476         [dialogWindow setContentView:gridView];
477         [gridView release];
478
479         p_dialog->p_sys_intf = (void *)dialogWindow;
480     }
481
482     [self updateWidgets:p_dialog];
483
484     if (shouldDestroy) {
485         [dialogWindow setDelegate:nil];
486         [dialogWindow close];
487         p_dialog->p_sys_intf = NULL;
488         dialogWindow = nil;
489     }
490
491     return dialogWindow;
492 }
493
494 /** Destroy a dialog
495  * Note: Lock on p_dialog->lock must be held. */
496 - (int)destroyExtensionDialog:(extension_dialog_t *)p_dialog
497 {
498     assert(p_dialog);
499
500     VLCDialogWindow *dialogWindow = (VLCDialogWindow*) p_dialog->p_sys_intf;
501     if (!dialogWindow) {
502         msg_Warn(VLCIntf, "dialog window not found");
503         return VLC_EGENERIC;
504     }
505
506     [dialogWindow setDelegate:nil];
507     [dialogWindow close];
508     dialogWindow = nil;
509
510     p_dialog->p_sys_intf = NULL;
511     vlc_cond_signal(&p_dialog->cond);
512     return VLC_SUCCESS;
513 }
514
515 /**
516  * Update/Create/Destroy a dialog
517  **/
518 - (VLCDialogWindow *)updateExtensionDialog:(NSValue *)o_value
519 {
520     extension_dialog_t *p_dialog = [o_value pointerValue];
521
522     VLCDialogWindow *dialogWindow = (VLCDialogWindow*) p_dialog->p_sys_intf;
523     if (p_dialog->b_kill && !dialogWindow) {
524         /* This extension could not be activated properly but tried
525            to create a dialog. We must ignore it. */
526         return NULL;
527     }
528
529     vlc_mutex_lock(&p_dialog->lock);
530     if (!p_dialog->b_kill && !dialogWindow) {
531         dialogWindow = [self createExtensionDialog:p_dialog];
532
533         BOOL visible = !p_dialog->b_hide;
534         if (visible) {
535             [dialogWindow center];
536             [dialogWindow makeKeyAndOrderFront:self];
537         } else
538             [dialogWindow orderOut:nil];
539
540         [dialogWindow setHas_lock:NO];
541     }
542     else if (!p_dialog->b_kill && dialogWindow) {
543         [dialogWindow setHas_lock:YES];
544         [self updateWidgets:p_dialog];
545         if (strcmp([[dialogWindow title] UTF8String],
546                     p_dialog->psz_title) != 0) {
547             NSString *titleString = [NSString stringWithCString:p_dialog->psz_title
548                                                        encoding:NSUTF8StringEncoding];
549
550             [dialogWindow setTitle:titleString];
551         }
552
553         [dialogWindow setHas_lock:NO];
554
555         BOOL visible = !p_dialog->b_hide;
556         if (visible)
557             [dialogWindow makeKeyAndOrderFront:self];
558         else
559             [dialogWindow orderOut:nil];
560     }
561     else if (p_dialog->b_kill) {
562         [self destroyExtensionDialog:p_dialog];
563     }
564     vlc_cond_signal(&p_dialog->cond);
565     vlc_mutex_unlock(&p_dialog->lock);
566     return dialogWindow;
567 }
568
569 /**
570  * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
571  **/
572 - (void)manageDialog:(extension_dialog_t *)p_dialog
573 {
574     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
575     assert(p_dialog);
576     ExtensionsManager *extMgr = [ExtensionsManager getInstance:p_intf];
577     assert(extMgr != NULL);
578
579     NSValue *o_value = [NSValue valueWithPointer:p_dialog];
580     [self performSelectorOnMainThread:@selector(updateExtensionDialog:)
581                            withObject:o_value
582                         waitUntilDone:YES];
583     [pool release];
584 }
585
586 @end