1 /*****************************************************************************
2 * ExtensionsDialogProvider.m: Mac OS X Extensions Dialogs
3 *****************************************************************************
4 * Copyright (C) 2005-2012 VLC authors and VideoLAN
7 * Authors: Brendon Justin <brendonjustin@gmail.com>,
8 * Derk-Jan Hartman <hartman@videolan dot org>,
9 * Felix Paul Kühne <fkuehne@videolan dot org>
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.
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.
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 *****************************************************************************/
26 #import "ExtensionsDialogProvider.h"
29 #import "ExtensionsManager.h"
31 #import "VLCUIWidgets.h"
33 #import <WebKit/WebKit.h>
36 /*****************************************************************************
37 * VLCExtensionsDialogProvider implementation
38 *****************************************************************************/
40 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
41 vlc_value_t old_val, vlc_value_t new_val,
44 static NSView *createControlFromWidget(extension_widget_t *widget, id self)
46 assert(!widget->p_sys_intf);
47 switch (widget->type) {
48 case EXTENSION_WIDGET_HTML:
50 WebView *webView = [[WebView alloc] initWithFrame:NSMakeRect (0,0,1,1)];
51 [webView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
52 [webView setDrawsBackground:NO];
55 case EXTENSION_WIDGET_LABEL:
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];
66 case EXTENSION_WIDGET_TEXT_FIELD:
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];
76 case EXTENSION_WIDGET_BUTTON:
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];
87 case EXTENSION_WIDGET_DROPDOWN:
89 VLCDialogPopUpButton *popup = [[VLCDialogPopUpButton alloc] init];
90 [popup setAction:@selector(popUpSelectionChanged:)];
91 [popup setTarget:self];
92 [popup setWidget:widget];
95 case EXTENSION_WIDGET_LIST:
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];
105 NSTableColumn *column = [[NSTableColumn alloc] init];
106 [list addTableColumn:column];
108 [list setDataSource:list];
109 [list setDelegate:self];
110 [list setWidget:widget];
114 case EXTENSION_WIDGET_IMAGE:
116 NSImageView *imageView = [[NSImageView alloc] init];
117 [imageView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
118 [imageView setImageFrameStyle:NSImageFramePhoto];
119 [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
122 case EXTENSION_WIDGET_SPIN_ICON:
124 NSProgressIndicator *spinner = [[NSProgressIndicator alloc] init];
125 [spinner setUsesThreadedAnimation:YES];
126 [spinner setStyle:NSProgressIndicatorSpinningStyle];
127 [spinner setDisplayedWhenStopped:YES];
128 [spinner startAnimation:self];
137 static void updateControlFromWidget(NSView *control, extension_widget_t *widget, id self)
139 switch (widget->type) {
140 case EXTENSION_WIDGET_HTML:
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];
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];
163 case EXTENSION_WIDGET_LABEL:
164 case EXTENSION_WIDGET_PASSWORD:
165 case EXTENSION_WIDGET_TEXT_FIELD:
167 if (!widget->psz_text)
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];
177 case EXTENSION_WIDGET_CHECK_BOX:
178 case EXTENSION_WIDGET_BUTTON:
180 assert([control isKindOfClass:[NSButton class]]);
181 NSButton *button = (NSButton *)control;
182 if (!widget->psz_text)
184 [button setTitle:[NSString stringWithUTF8String:widget->psz_text]];
187 case EXTENSION_WIDGET_DROPDOWN:
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)
195 [popup addItemWithTitle:[NSString stringWithUTF8String:value->psz_text]];
197 [popup synchronizeTitleAndSelectedItem];
198 [self popUpSelectionChanged:popup];
201 case EXTENSION_WIDGET_LIST:
203 assert([control isKindOfClass:[NSScrollView class]]);
204 NSScrollView *scrollView = (NSScrollView *)control;
205 assert([[scrollView documentView] isKindOfClass:[VLCDialogList class]]);
206 VLCDialogList *list = (VLCDialogList *)[scrollView documentView];
208 NSMutableArray *contentArray = [NSMutableArray array];
209 struct extension_widget_value_t *value;
210 for (value = widget->p_values; value != NULL; value = value->p_next)
212 NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
213 [NSNumber numberWithInt:value->i_id], @"id",
214 [NSString stringWithUTF8String:value->psz_text], @"text",
216 [contentArray addObject:entry];
218 list.contentArray = contentArray;
222 case EXTENSION_WIDGET_IMAGE:
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;
229 image = [[NSImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:string]];
230 [imageView setImage:image];
234 case EXTENSION_WIDGET_SPIN_ICON:
236 assert([control isKindOfClass:[NSProgressIndicator class]]);
237 NSProgressIndicator *progressIndicator = (NSProgressIndicator *)control;
238 if (widget->i_spin_loops != 0)
239 [progressIndicator startAnimation:self];
241 [progressIndicator stopAnimation:self];
249 * Ask the dialogs provider to create a new dialog
251 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
252 vlc_value_t old_val, vlc_value_t new_val,
260 ExtensionsDialogProvider *p_edp = [ExtensionsDialogProvider sharedInstance:(intf_thread_t *)p_this];
263 if (!new_val.p_address)
266 extension_dialog_t *p_dialog = (extension_dialog_t*) new_val.p_address;
267 [p_edp manageDialog:p_dialog];
271 @implementation ExtensionsDialogProvider
273 static ExtensionsDialogProvider *_o_sharedInstance = nil;
275 + (ExtensionsDialogProvider *)sharedInstance:(intf_thread_t *)_p_intf
277 return _o_sharedInstance ? _o_sharedInstance : [[self alloc] initWithIntf:_p_intf];
282 if (_o_sharedInstance) {
283 [_o_sharedInstance release];
287 - (id)initWithIntf:(intf_thread_t *)_p_intf
289 if (_o_sharedInstance)
292 if ((self = [super init])) {
293 _o_sharedInstance = self;
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);
301 return _o_sharedInstance;
306 msg_Dbg(p_intf, "ExtensionsDialogProvider is quitting...");
307 var_DelCallback(p_intf, "dialog-extension", dialogCallback, NULL);
312 - (void)performEventWithObject: (NSValue *)o_value ofType: (const char*)type
314 NSString *o_type = [NSString stringWithUTF8String:type];
316 if ([o_type isEqualToString: @"dialog-extension"]) {
317 [self performSelectorOnMainThread:@selector(updateExtensionDialog:)
323 msg_Err(VLCIntf, "unhandled dialog type: '%s'", type);
326 - (void)triggerClick:(id)sender
328 assert([sender isKindOfClass:[VLCDialogButton class]]);
329 VLCDialogButton *button = sender;
330 extension_widget_t *widget = [button widget];
332 vlc_mutex_lock(&widget->p_dialog->lock);
333 extension_WidgetClicked(widget->p_dialog, widget);
334 vlc_mutex_unlock(&widget->p_dialog->lock);
337 - (void)syncTextField:(NSNotification *)notifcation
339 id sender = [notifcation object];
340 assert([sender isKindOfClass:[VLCDialogTextField class]]);
341 VLCDialogTextField *field = sender;
342 extension_widget_t *widget = [field widget];
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);
350 - (void)tableViewSelectionDidChange:(NSNotification *)notifcation
352 id sender = [notifcation object];
353 assert(sender && [sender isKindOfClass:[VLCDialogList class]]);
354 VLCDialogList *list = sender;
356 struct extension_widget_value_t *value;
358 for (value = [list widget]->p_values; value != NULL; value = value->p_next, i++)
359 value->b_selected = (i == [list selectedRow]);
362 - (void)popUpSelectionChanged:(id)sender
364 assert([sender isKindOfClass:[VLCDialogPopUpButton class]]);
365 VLCDialogPopUpButton *popup = sender;
366 struct extension_widget_value_t *value;
368 for (value = [popup widget]->p_values; value != NULL; value = value->p_next, i++)
369 value->b_selected = (i == [popup indexOfSelectedItem]);
373 - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
375 NSView *contentView = [sender contentView];
376 assert([contentView isKindOfClass:[VLCDialogGridView class]]);
377 VLCDialogGridView *gridView = (VLCDialogGridView *)contentView;
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];
387 - (BOOL)windowShouldClose:(id)sender
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;
397 - (void)updateWidgets:(extension_dialog_t *)dialog
399 extension_widget_t *widget;
400 VLCDialogWindow *dialogWindow = dialog->p_sys_intf;
402 FOREACH_ARRAY(widget, dialog->widgets) {
404 continue; /* Some widgets may be NULL@this point */
406 BOOL shouldDestroy = widget->b_kill;
407 NSView *control = widget->p_sys_intf;
408 BOOL update = widget->b_update;
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];
418 if (update && !shouldDestroy) {
419 updateControlFromWidget(control, widget, self);
420 [control setHidden:widget->b_hide];
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);
431 VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
432 [gridView addSubview:control atRow:row column:col rowSpan:vsp colSpan:hsp];
434 widget->b_update = false;
438 VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
439 [gridView removeSubview:control];
441 widget->p_sys_intf = NULL;
448 * Note: Lock on p_dialog->lock must be held. */
449 - (VLCDialogWindow *)createExtensionDialog:(extension_dialog_t *)p_dialog
451 VLCDialogWindow *dialogWindow = nil;
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
460 [dialogWindow setDelegate:self];
461 [dialogWindow setDialog:p_dialog];
462 [dialogWindow setTitle:[NSString stringWithUTF8String:p_dialog->psz_title]];
464 VLCDialogGridView *gridView = [[VLCDialogGridView alloc] init];
465 [gridView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
466 [dialogWindow setContentView:gridView];
469 p_dialog->p_sys_intf = (void *)dialogWindow;
472 [self updateWidgets:p_dialog];
475 [dialogWindow setDelegate:nil];
476 [dialogWindow close];
477 p_dialog->p_sys_intf = NULL;
485 * Note: Lock on p_dialog->lock must be held. */
486 - (int)destroyExtensionDialog:(extension_dialog_t *)p_dialog
490 VLCDialogWindow *dialogWindow = (VLCDialogWindow*) p_dialog->p_sys_intf;
494 [VLCDialogWindow release];
496 p_dialog->p_sys_intf = NULL;
497 vlc_cond_signal(&p_dialog->cond);
502 * Update/Create/Destroy a dialog
504 - (VLCDialogWindow *)updateExtensionDialog:(NSValue *)o_value
506 extension_dialog_t *p_dialog = [o_value pointerValue];
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. */
515 vlc_mutex_lock(&p_dialog->lock);
516 if (!p_dialog->b_kill && !dialogWindow) {
517 dialogWindow = [self createExtensionDialog:p_dialog];
519 BOOL visible = !p_dialog->b_hide;
521 [dialogWindow center];
522 [dialogWindow makeKeyAndOrderFront:self];
524 [dialogWindow orderOut:nil];
526 [dialogWindow setHas_lock:NO];
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];
536 [dialogWindow setTitle:titleString];
539 [dialogWindow setHas_lock:NO];
541 BOOL visible = !p_dialog->b_hide;
543 [dialogWindow center];
544 [dialogWindow makeKeyAndOrderFront:self];
547 [dialogWindow orderOut:nil];
549 else if (p_dialog->b_kill) {
550 [self destroyExtensionDialog:p_dialog];
552 vlc_cond_signal(&p_dialog->cond);
553 vlc_mutex_unlock(&p_dialog->lock);
558 * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
560 - (void)manageDialog:(extension_dialog_t *)p_dialog
563 ExtensionsManager *extMgr = [ExtensionsManager getInstance:p_intf];
564 assert(extMgr != NULL);
566 NSValue *o_value = [NSValue valueWithPointer:p_dialog];
567 [self performSelectorOnMainThread:@selector(updateExtensionDialog:)