]> git.sesse.net Git - vlc/blob - modules/gui/macosx_dialog_provider/dialogProvider.m
macosx_dialog_provider: Fix a few ignored selector, and assert on bad selector.
[vlc] / modules / gui / macosx_dialog_provider / dialogProvider.m
1 /*****************************************************************************
2  * dialogProvider.m: Minimal Dialog Provider for Mac OS X
3  *****************************************************************************
4  * Copyright (C) 2009-2010 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 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #import <stdlib.h>                                      /* malloc(), free() */
28 #import <string.h>
29
30 #ifdef HAVE_CONFIG_H
31 # import "config.h"
32 #endif
33
34 #import <vlc_common.h>
35 #import <vlc_plugin.h>
36 #import <vlc_dialog.h>
37 #import <vlc_interface.h>
38
39 #import <Cocoa/Cocoa.h>
40 #import "VLCLoginPanel.h"
41 #import "VLCProgressPanel.h"
42
43 /*****************************************************************************
44  * Prototypes
45  *****************************************************************************/
46 static int  OpenIntf(vlc_object_t *);
47 static void CloseIntf(vlc_object_t *);
48 static void Run(intf_thread_t * );
49
50 static int DisplayError(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * );
51 static int DisplayCritical(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * );
52 static int DisplayQuestion(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * );
53 static int DisplayLogin(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * );
54 static int DisplayProgressPanelAction(vlc_object_t *,const char *,vlc_value_t,vlc_value_t,void * );
55
56 static void updateProgressPanel (void *, const char *, float);
57 static bool checkProgressPanel (void *);
58 static void destroyProgressPanel (void *);
59
60 @interface VLCDialogDisplayer : NSObject
61 {
62     VLCProgressPanel *_currentProgressBarPanel;
63 }
64
65 + (NSDictionary *)dictionaryForDialog:(const char *)title :(const char *)message :(const char *)yes :(const char *)no :(const char *)cancel;
66
67 - (void)displayError:(NSDictionary *)dialog;
68 - (void)displayCritical:(NSDictionary *)dialog;
69 - (NSNumber *)displayQuestion:(NSDictionary *)dialog;
70 - (NSDictionary *)displayLogin:(NSDictionary *)dialog;
71
72 - (void)displayProgressBar:(NSDictionary *)dict;
73 - (void)updateProgressPanel:(NSDictionary *)dict;
74 - (void)destroyProgressPanel;
75 - (NSNumber *)checkProgressPanel;
76
77 - (id)resultFromSelectorOnMainThread:(SEL)sel withObject:(id)object;
78 @end
79
80 static inline NSDictionary *DictFromDialogFatal(dialog_fatal_t *dialog) {
81     return [VLCDialogDisplayer dictionaryForDialog:dialog->title :dialog->message :NULL :NULL :NULL];
82 }
83 static inline NSDictionary *DictFromDialogLogin(dialog_login_t *dialog) {
84     return [VLCDialogDisplayer dictionaryForDialog:dialog->title :dialog->message :NULL :NULL :NULL];
85 }
86 static inline NSDictionary *DictFromDialogQuestion(dialog_question_t *dialog) {
87     return [VLCDialogDisplayer dictionaryForDialog:dialog->title :dialog->message :dialog->yes :dialog->no :dialog->cancel];
88 }
89 static inline NSDictionary *DictFromDialogProgressBar(dialog_progress_bar_t *dialog) {
90     return [VLCDialogDisplayer dictionaryForDialog:dialog->title :dialog->message :NULL :NULL :dialog->cancel];
91 }
92
93 struct intf_sys_t
94 {
95     VLCDialogDisplayer *displayer;
96
97     vlc_mutex_t lock;
98     vlc_cond_t wait;
99     bool is_hidding_noaction_dialogs;
100 };
101
102
103 #define T_HIDE_NOACTION N_("Hide no user action dialogs")
104 #define LT_HIDE_NOACTION N_("Don't display dialogs that don't require user action (Critical and error panel).")
105
106 #define prefix "macosx-dialog-provider"
107 /*****************************************************************************
108  * Module descriptor
109  *****************************************************************************/
110
111 vlc_module_begin()
112     /* Minimal interface. see intf.m */
113     set_shortname("Mac OS X Dialogs")
114     add_shortcut("macosx_dialog_provider")
115     add_shortcut("miosx")
116     set_description("Minimal Mac OS X Dialog Provider")
117     set_capability("interface", 0)
118
119     /* This setting is interesting, because when used with a libvlc app
120      * it's almost certain that the client program will display error by
121      * itself. Moreover certain action might end up in an error, but 
122      * the client wants to ignored them completely. */
123     add_bool(prefix "hide-no-user-action-dialogs", true, NULL, T_HIDE_NOACTION, LT_HIDE_NOACTION, false)
124
125     set_callbacks(OpenIntf, CloseIntf)
126     set_category(CAT_INTERFACE)
127     set_subcategory(SUBCAT_INTERFACE_MAIN)
128 vlc_module_end()
129
130 /*****************************************************************************
131  * OpenIntf: initialize interface
132  *****************************************************************************/
133 int OpenIntf(vlc_object_t *p_this)
134 {
135     intf_thread_t *p_intf = (intf_thread_t*) p_this;
136
137     p_intf->p_sys = malloc(sizeof(intf_sys_t));
138     if(!p_intf->p_sys)
139         return VLC_ENOMEM;
140
141     memset(p_intf->p_sys,0,sizeof(*p_intf->p_sys));
142
143     p_intf->p_sys->displayer = [[VLCDialogDisplayer alloc] init];
144
145     bool hide = var_CreateGetBool(p_intf, prefix "hide-no-user-action-dialogs");
146     p_intf->p_sys->is_hidding_noaction_dialogs = hide;
147
148     /* subscribe to various interactive dialogues */
149     
150     if (!hide)
151     {
152         var_Create(p_intf,"dialog-error",VLC_VAR_ADDRESS);
153         var_AddCallback(p_intf,"dialog-error",DisplayError,p_intf);
154         var_Create(p_intf,"dialog-critical",VLC_VAR_ADDRESS);
155         var_AddCallback(p_intf,"dialog-critical",DisplayCritical,p_intf);        
156     }
157     var_Create(p_intf,"dialog-login",VLC_VAR_ADDRESS);
158     var_AddCallback(p_intf,"dialog-login",DisplayLogin,p_intf);
159     var_Create(p_intf,"dialog-question",VLC_VAR_ADDRESS);
160     var_AddCallback(p_intf,"dialog-question",DisplayQuestion,p_intf);
161     var_Create(p_intf,"dialog-progress-bar",VLC_VAR_ADDRESS);
162     var_AddCallback(p_intf,"dialog-progress-bar",DisplayProgressPanelAction,p_intf);
163     dialog_Register(p_intf);
164
165     msg_Dbg(p_intf,"Mac OS X dialog provider initialised");
166     
167     return VLC_SUCCESS;
168 }
169
170 /*****************************************************************************
171  * CloseIntf: destroy interface
172  *****************************************************************************/
173 void CloseIntf(vlc_object_t *p_this)
174 {
175     intf_thread_t *p_intf = (intf_thread_t*) p_this;
176
177     /* unsubscribe from the interactive dialogues */
178     dialog_Unregister(p_intf);
179     
180     if (!p_intf->p_sys->is_hidding_noaction_dialogs)
181     {
182         var_DelCallback(p_intf,"dialog-error",DisplayError,p_intf);
183         var_DelCallback(p_intf,"dialog-critical",DisplayCritical,p_intf);
184     }
185     var_DelCallback(p_intf,"dialog-login",DisplayLogin,p_intf);
186     var_DelCallback(p_intf,"dialog-question",DisplayQuestion,p_intf);
187     var_DelCallback(p_intf,"dialog-progress-bar",DisplayProgressPanelAction,p_intf);
188     
189     [p_intf->p_sys->displayer release];
190
191     msg_Dbg(p_intf,"Mac OS X dialog provider closed");
192     free(p_intf->p_sys);
193 }
194
195
196 /*****************************************************************************
197  * Callbacks triggered by the "dialog-*" variables
198  *****************************************************************************/
199 static int DisplayError(vlc_object_t *p_this, const char *type, vlc_value_t previous, vlc_value_t value, void *data)
200 {
201     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
202     dialog_fatal_t *dialog = value.p_address;
203     intf_thread_t *p_intf = (intf_thread_t*) p_this;
204     intf_sys_t *sys = p_intf->p_sys;
205     [sys->displayer performSelectorOnMainThread:@selector(displayError:) withObject:DictFromDialogFatal(dialog) waitUntilDone:NO];
206     [pool release];
207     return VLC_SUCCESS;
208 }
209
210 static int DisplayCritical(vlc_object_t *p_this, const char *type, vlc_value_t previous, vlc_value_t value, void *data)
211 {
212     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
213     dialog_fatal_t *dialog = value.p_address;
214     intf_thread_t *p_intf = (intf_thread_t*) p_this;
215     intf_sys_t *sys = p_intf->p_sys;
216     [sys->displayer performSelectorOnMainThread:@selector(displayCritical:) withObject:DictFromDialogFatal(dialog) waitUntilDone:NO];
217     [pool release];
218     return VLC_SUCCESS;
219 }
220
221 static int DisplayQuestion(vlc_object_t *p_this, const char *type, vlc_value_t previous, vlc_value_t value, void *data)
222 {
223     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
224     dialog_question_t *dialog = value.p_address;
225     intf_thread_t *p_intf = (intf_thread_t*) p_this;
226     intf_sys_t *sys = p_intf->p_sys;
227     dialog->answer = [[sys->displayer resultFromSelectorOnMainThread:@selector(displayQuestion:) withObject:DictFromDialogQuestion(dialog)] intValue];
228     [pool release];
229     return VLC_SUCCESS;
230 }
231
232 static int DisplayLogin(vlc_object_t *p_this, const char *type, vlc_value_t previous, vlc_value_t value, void *data)
233 {
234     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
235     dialog_login_t *dialog = value.p_address;
236     intf_thread_t *p_intf = (intf_thread_t*) p_this;
237     intf_sys_t *sys = p_intf->p_sys;
238     NSDictionary *dict = [sys->displayer resultFromSelectorOnMainThread:@selector(displayLogin:) withObject:DictFromDialogLogin(dialog)];
239     if (dict) {
240         *dialog->username = strdup([[dict objectForKey:@"username"] UTF8String]);
241         *dialog->password = strdup([[dict objectForKey:@"password"] UTF8String]);
242     }
243     [pool release];
244     return VLC_SUCCESS;
245 }
246
247 static int DisplayProgressPanelAction(vlc_object_t *p_this, const char *type, vlc_value_t previous, vlc_value_t value, void *data)
248 {
249     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
250     dialog_progress_bar_t *dialog = value.p_address;
251     intf_thread_t *p_intf = (intf_thread_t*) p_this;
252     intf_sys_t *sys = p_intf->p_sys;
253
254     [sys->displayer performSelectorOnMainThread:@selector(displayProgressBar:) withObject:DictFromDialogProgressBar(dialog) waitUntilDone:YES];
255
256     dialog->pf_update = updateProgressPanel;
257     dialog->pf_check = checkProgressPanel;
258     dialog->pf_destroy = destroyProgressPanel;
259     dialog->p_sys = p_intf->p_sys;
260
261     [pool release];
262     return VLC_SUCCESS;
263 }
264
265 void updateProgressPanel (void *priv, const char *text, float value)
266 {
267     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
268     intf_sys_t *sys = (intf_sys_t *)priv;
269
270     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
271                           [NSNumber numberWithFloat:value], @"value",
272                           text ? [NSString stringWithUTF8String:text] : nil, @"text",
273                           nil];
274                           
275     [sys->displayer performSelectorOnMainThread:@selector(updateProgressPanel:) withObject:dict waitUntilDone:YES];
276
277     [pool release];
278 }
279
280 void destroyProgressPanel (void *priv)
281 {
282     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
283     intf_sys_t *sys = (intf_sys_t *)priv;
284
285     [sys->displayer performSelectorOnMainThread:@selector(destroyProgressPanel) withObject:nil waitUntilDone:YES];
286     
287     [pool release];
288 }
289
290 bool checkProgressPanel (void *priv)
291 {
292     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
293     intf_sys_t *sys = (intf_sys_t *)priv;
294     BOOL ret;
295
296     ret = [[sys->displayer resultFromSelectorOnMainThread:@selector(checkProgressPanel) withObject:nil] boolValue];
297
298     [pool release];
299     return ret;
300 }
301
302
303 @implementation VLCDialogDisplayer
304 - (void)dealloc
305 {
306     assert(!_currentProgressBarPanel); // This has to be closed on main thread.
307     [super dealloc];
308 }
309
310 + (NSDictionary *)dictionaryForDialog:(const char *)title :(const char *)message :(const char *)yes :(const char *)no :(const char *)cancel
311 {
312     NSMutableDictionary *dict = [NSMutableDictionary dictionary];
313     if (title)
314         [dict setObject:[NSString stringWithUTF8String:title] forKey:@"title"];
315     if (message)
316         [dict setObject:[NSString stringWithUTF8String:message] forKey:@"message"];
317     if (yes)
318         [dict setObject:[NSString stringWithUTF8String:yes] forKey:@"yes"];
319     if (no)
320         [dict setObject:[NSString stringWithUTF8String:no] forKey:@"no"];
321     if (cancel)
322         [dict setObject:[NSString stringWithUTF8String:cancel] forKey:@"cancel"];
323     
324     return dict;
325 }
326 #define VLCAssertIsMainThread() assert([NSThread isMainThread])
327
328 - (void)displayError:(NSDictionary *)dialog
329 {
330     VLCAssertIsMainThread();
331     
332     NSRunInformationalAlertPanel([dialog objectForKey:@"title"],
333                                  [dialog objectForKey:@"message"],
334                                  @"OK", nil, nil);
335 }
336
337 - (void)displayCritical:(NSDictionary *)dialog
338 {
339     VLCAssertIsMainThread();
340     
341     NSRunCriticalAlertPanel([dialog objectForKey:@"title"],
342                                  [dialog objectForKey:@"message"],
343                                  @"OK", nil, nil);
344 }
345
346 - (NSNumber *)displayQuestion:(NSDictionary *)dialog
347 {
348     VLCAssertIsMainThread();
349     
350     NSInteger alertRet = 0;
351     
352     NSAlert *alert = [NSAlert alertWithMessageText:[dialog objectForKey:@"title"]
353                               defaultButton:[dialog objectForKey:@"yes"]
354                             alternateButton:[dialog objectForKey:@"no"] 
355                                 otherButton:[dialog objectForKey:@"cancel"]
356                   informativeTextWithFormat:[dialog objectForKey:@"message"]];
357     [alert setAlertStyle:NSInformationalAlertStyle];
358     alertRet = [alert runModal];
359
360     int ret;
361     switch (alertRet) {
362         case NSAlertDefaultReturn:
363             ret = 1;
364             break;
365         case NSAlertAlternateReturn:
366             ret = 2;
367             break;
368         case NSAlertOtherReturn:
369             ret = 3;
370             break;
371         default:
372             assert(0);
373             ret = 0;
374             break;
375     }
376
377     return [NSNumber numberWithInt:ret];
378 }
379
380 - (NSDictionary *)displayLogin:(NSDictionary *)dialog
381 {
382     VLCAssertIsMainThread();
383
384     VLCLoginPanel *panel = [[VLCLoginPanel alloc] init];
385     [panel createContentView];
386     [panel setDialogTitle:[dialog objectForKey:@"title"]];
387     [panel setDialogMessage:[dialog objectForKey:@"message"]];
388     [panel center];
389     NSInteger ret = [NSApp runModalForWindow:panel];
390     [panel close];
391     
392     if (!ret)
393         return nil;
394     
395     return [NSDictionary dictionaryWithObjectsAndKeys:
396             [panel userName], @"username",
397             [panel password], @"password",
398             nil];
399 }
400
401 - (void)displayProgressBar:(NSDictionary *)dialog
402 {
403     VLCAssertIsMainThread();
404
405     if(_currentProgressBarPanel)
406         [self destroyProgressPanel];
407
408     assert(!_currentProgressBarPanel);
409     _currentProgressBarPanel = [[VLCProgressPanel alloc] init];
410     [_currentProgressBarPanel createContentView];
411     [_currentProgressBarPanel setDialogTitle:[dialog objectForKey:@"title"]];
412     [_currentProgressBarPanel setDialogMessage:[dialog objectForKey:@"message"] ?: @""];
413     [_currentProgressBarPanel setCancelButtonLabel:[dialog objectForKey:@"cancel"]];
414
415     [_currentProgressBarPanel center];
416     [_currentProgressBarPanel makeKeyAndOrderFront:nil];
417 }
418
419 - (void)updateProgressPanel:(NSDictionary *)dict
420 {
421     VLCAssertIsMainThread();
422
423     assert(_currentProgressBarPanel);
424     [_currentProgressBarPanel setDialogMessage:[dict objectForKey:@"text"] ?: @""];
425     [_currentProgressBarPanel setProgressAsDouble:[[dict objectForKey:@"value"] doubleValue] * 1000.];
426 }
427
428 - (void)destroyProgressPanel
429 {
430     VLCAssertIsMainThread();
431
432     [_currentProgressBarPanel close];
433     [_currentProgressBarPanel release];
434     _currentProgressBarPanel = nil;
435 }
436
437 - (NSNumber *)checkProgressPanel
438 {
439     VLCAssertIsMainThread();
440     
441     return [NSNumber numberWithBool:[_currentProgressBarPanel isCancelled]];
442 }
443
444
445 /**
446  * Helper to execute a function on main thread and get its return value.
447  */
448 - (void)execute:(NSDictionary *)dict
449 {
450     SEL sel = [[dict objectForKey:@"sel"] pointerValue];
451     id *result = [[dict objectForKey:@"result"] pointerValue];
452     id object = [dict objectForKey:@"object"];
453
454     NSAssert(sel, @"Try to execute a NULL selector");
455     NSAssert(object, @"Try to execute from a nil object");
456
457     *result = [self performSelector:sel withObject:object];
458     [*result retain]; // Balanced in -resultFromSelectorOnMainThread
459 }
460
461 - (id)resultFromSelectorOnMainThread:(SEL)sel withObject:(id)object
462 {
463     id result = nil;
464     NSAssert(sel, @"Try to execute a NULL selector");
465     NSAssert(sel, @"Try to execute from a nil object");
466     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
467      [NSValue valueWithPointer:sel], @"sel",
468      [NSValue valueWithPointer:&result], @"result",
469      object, @"object", nil];
470     [self performSelectorOnMainThread:@selector(execute:) withObject:dict waitUntilDone:YES];
471     return [result autorelease];
472 }
473 @end
474                                   
475                                   
476