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