1 /*****************************************************************************
2 * ExtensionsDialogProvider.m: Mac OS X Extensions Dialogs
3 *****************************************************************************
4 * Copyright (C) 2010-2013 VLC authors and VideoLAN
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>
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.
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.
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 *****************************************************************************/
27 #import "ExtensionsDialogProvider.h"
30 #import "ExtensionsManager.h"
32 #import "VLCUIWidgets.h"
34 #import <WebKit/WebKit.h>
37 /*****************************************************************************
38 * VLCExtensionsDialogProvider implementation
39 *****************************************************************************/
41 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
42 vlc_value_t old_val, vlc_value_t new_val,
45 static NSView *createControlFromWidget(extension_widget_t *widget, id self)
47 assert(!widget->p_sys_intf);
48 switch (widget->type) {
49 case EXTENSION_WIDGET_HTML:
51 WebView *webView = [[WebView alloc] initWithFrame:NSMakeRect (0,0,1,1)];
52 [webView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
53 [webView setDrawsBackground:NO];
56 case EXTENSION_WIDGET_LABEL:
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];
67 case EXTENSION_WIDGET_TEXT_FIELD:
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];
77 case EXTENSION_WIDGET_CHECK_BOX:
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];
88 case EXTENSION_WIDGET_BUTTON:
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];
99 case EXTENSION_WIDGET_DROPDOWN:
101 VLCDialogPopUpButton *popup = [[VLCDialogPopUpButton alloc] init];
102 [popup setAction:@selector(popUpSelectionChanged:)];
103 [popup setTarget:self];
104 [popup setWidget:widget];
107 case EXTENSION_WIDGET_LIST:
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];
117 NSTableColumn *column = [[NSTableColumn alloc] init];
118 [list addTableColumn:column];
120 [list setDataSource:list];
121 [list setDelegate:self];
122 [list setWidget:widget];
126 case EXTENSION_WIDGET_IMAGE:
128 NSImageView *imageView = [[NSImageView alloc] init];
129 [imageView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
130 [imageView setImageFrameStyle:NSImageFramePhoto];
131 [imageView setImageScaling:NSImageScaleProportionallyUpOrDown];
134 case EXTENSION_WIDGET_SPIN_ICON:
136 NSProgressIndicator *spinner = [[NSProgressIndicator alloc] init];
137 [spinner setUsesThreadedAnimation:YES];
138 [spinner setStyle:NSProgressIndicatorSpinningStyle];
139 [spinner setDisplayedWhenStopped:YES];
140 [spinner startAnimation:self];
149 static void updateControlFromWidget(NSView *control, extension_widget_t *widget, id self)
151 switch (widget->type) {
152 case EXTENSION_WIDGET_HTML:
155 assert([control isKindOfClass:[WebView class]]);
156 WebView *webView = (WebView *)control;
157 NSString *string = @(widget->psz_text);
158 [[webView mainFrame] loadHTMLString:string baseURL:[NSURL URLWithString:@""]];
159 [webView setNeedsDisplay:YES];
164 assert([control isKindOfClass:[NSTextView class]]);
165 NSTextView *textView = (NSTextView *)control;
166 NSString *string = @(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];
175 case EXTENSION_WIDGET_LABEL:
176 case EXTENSION_WIDGET_PASSWORD:
177 case EXTENSION_WIDGET_TEXT_FIELD:
179 if (!widget->psz_text)
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];
189 case EXTENSION_WIDGET_CHECK_BOX:
190 case EXTENSION_WIDGET_BUTTON:
192 assert([control isKindOfClass:[NSButton class]]);
193 NSButton *button = (NSButton *)control;
194 if (!widget->psz_text)
196 [button setTitle:@(widget->psz_text)];
199 case EXTENSION_WIDGET_DROPDOWN:
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:@(value->psz_text)];
207 [popup synchronizeTitleAndSelectedItem];
208 [self popUpSelectionChanged:popup];
211 case EXTENSION_WIDGET_LIST:
213 assert([control isKindOfClass:[NSScrollView class]]);
214 NSScrollView *scrollView = (NSScrollView *)control;
215 assert([[scrollView documentView] isKindOfClass:[VLCDialogList class]]);
216 VLCDialogList *list = (VLCDialogList *)[scrollView documentView];
218 NSMutableArray *contentArray = [NSMutableArray array];
219 struct extension_widget_value_t *value;
220 for (value = widget->p_values; value != NULL; value = value->p_next)
222 NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
223 @(value->i_id), @"id",
224 @(value->psz_text), @"text",
226 [contentArray addObject:entry];
228 list.contentArray = contentArray;
232 case EXTENSION_WIDGET_IMAGE:
234 assert([control isKindOfClass:[NSImageView class]]);
235 NSImageView *imageView = (NSImageView *)control;
236 NSString *string = widget->psz_text ? @(widget->psz_text) : nil;
237 NSImage *image = nil;
239 image = [[NSImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:string]];
240 [imageView setImage:image];
244 case EXTENSION_WIDGET_SPIN_ICON:
246 assert([control isKindOfClass:[NSProgressIndicator class]]);
247 NSProgressIndicator *progressIndicator = (NSProgressIndicator *)control;
248 if (widget->i_spin_loops != 0)
249 [progressIndicator startAnimation:self];
251 [progressIndicator stopAnimation:self];
259 * Ask the dialogs provider to create a new dialog
261 static int dialogCallback(vlc_object_t *p_this, const char *psz_variable,
262 vlc_value_t old_val, vlc_value_t new_val,
270 ExtensionsDialogProvider *p_edp = [ExtensionsDialogProvider sharedInstance:(intf_thread_t *)p_this];
273 if (!new_val.p_address)
276 extension_dialog_t *p_dialog = (extension_dialog_t*) new_val.p_address;
277 [p_edp manageDialog:p_dialog];
281 @implementation ExtensionsDialogProvider
283 static ExtensionsDialogProvider *_o_sharedInstance = nil;
285 + (ExtensionsDialogProvider *)sharedInstance:(intf_thread_t *)_p_intf
287 return _o_sharedInstance ? _o_sharedInstance : [[self alloc] initWithIntf:_p_intf];
292 if (_o_sharedInstance) {
293 [_o_sharedInstance release];
297 - (id)initWithIntf:(intf_thread_t *)_p_intf
299 if (_o_sharedInstance)
302 if ((self = [super init])) {
303 _o_sharedInstance = self;
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);
311 return _o_sharedInstance;
316 msg_Dbg(p_intf, "ExtensionsDialogProvider is quitting...");
317 var_DelCallback(p_intf, "dialog-extension", dialogCallback, NULL);
322 - (void)performEventWithObject: (NSValue *)o_value ofType: (const char*)type
324 NSString *o_type = @(type);
326 if ([o_type isEqualToString: @"dialog-extension"]) {
327 [self performSelectorOnMainThread:@selector(updateExtensionDialog:)
333 msg_Err(VLCIntf, "unhandled dialog type: '%s'", type);
336 - (void)triggerClick:(id)sender
338 assert([sender isKindOfClass:[VLCDialogButton class]]);
339 VLCDialogButton *button = sender;
340 extension_widget_t *widget = [button widget];
342 vlc_mutex_lock(&widget->p_dialog->lock);
343 extension_WidgetClicked(widget->p_dialog, widget);
344 vlc_mutex_unlock(&widget->p_dialog->lock);
347 - (void)syncTextField:(NSNotification *)notifcation
349 id sender = [notifcation object];
350 assert([sender isKindOfClass:[VLCDialogTextField class]]);
351 VLCDialogTextField *field = sender;
352 extension_widget_t *widget = [field widget];
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);
360 - (void)tableViewSelectionDidChange:(NSNotification *)notifcation
362 id sender = [notifcation object];
363 assert(sender && [sender isKindOfClass:[VLCDialogList class]]);
364 VLCDialogList *list = sender;
366 struct extension_widget_value_t *value;
368 for (value = [list widget]->p_values; value != NULL; value = value->p_next, i++)
369 value->b_selected = (i == [list selectedRow]);
372 - (void)popUpSelectionChanged:(id)sender
374 assert([sender isKindOfClass:[VLCDialogPopUpButton class]]);
375 VLCDialogPopUpButton *popup = sender;
376 struct extension_widget_value_t *value;
378 for (value = [popup widget]->p_values; value != NULL; value = value->p_next, i++)
379 value->b_selected = (i == [popup indexOfSelectedItem]);
383 - (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
385 NSView *contentView = [sender contentView];
386 assert([contentView isKindOfClass:[VLCDialogGridView class]]);
387 VLCDialogGridView *gridView = (VLCDialogGridView *)contentView;
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];
397 - (BOOL)windowShouldClose:(id)sender
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;
407 - (void)updateWidgets:(extension_dialog_t *)dialog
409 extension_widget_t *widget;
410 VLCDialogWindow *dialogWindow = dialog->p_sys_intf;
412 FOREACH_ARRAY(widget, dialog->widgets) {
414 continue; /* Some widgets may be NULL@this point */
416 BOOL shouldDestroy = widget->b_kill;
417 NSView *control = widget->p_sys_intf;
418 BOOL update = widget->b_update;
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];
428 if (update && !shouldDestroy) {
429 updateControlFromWidget(control, widget, self);
430 [control setHidden:widget->b_hide];
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);
441 VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
442 [gridView addSubview:control atRow:row column:col rowSpan:vsp colSpan:hsp];
444 widget->b_update = false;
448 VLCDialogGridView *gridView = (VLCDialogGridView *)[dialogWindow contentView];
449 [gridView removeSubview:control];
451 widget->p_sys_intf = NULL;
458 * Note: Lock on p_dialog->lock must be held. */
459 - (VLCDialogWindow *)createExtensionDialog:(extension_dialog_t *)p_dialog
461 VLCDialogWindow *dialogWindow = nil;
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
470 [dialogWindow setDelegate:self];
471 [dialogWindow setDialog:p_dialog];
472 [dialogWindow setTitle:@(p_dialog->psz_title)];
474 VLCDialogGridView *gridView = [[VLCDialogGridView alloc] init];
475 [gridView setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
476 [dialogWindow setContentView:gridView];
479 p_dialog->p_sys_intf = (void *)dialogWindow;
482 [self updateWidgets:p_dialog];
485 [dialogWindow setDelegate:nil];
486 [dialogWindow close];
487 p_dialog->p_sys_intf = NULL;
495 * Note: Lock on p_dialog->lock must be held. */
496 - (int)destroyExtensionDialog:(extension_dialog_t *)p_dialog
500 VLCDialogWindow *dialogWindow = (VLCDialogWindow*) p_dialog->p_sys_intf;
502 msg_Warn(VLCIntf, "dialog window not found");
506 [dialogWindow setDelegate:nil];
507 [dialogWindow close];
510 p_dialog->p_sys_intf = NULL;
511 vlc_cond_signal(&p_dialog->cond);
516 * Update/Create/Destroy a dialog
518 - (VLCDialogWindow *)updateExtensionDialog:(NSValue *)o_value
520 extension_dialog_t *p_dialog = [o_value pointerValue];
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. */
529 vlc_mutex_lock(&p_dialog->lock);
530 if (!p_dialog->b_kill && !dialogWindow) {
531 dialogWindow = [self createExtensionDialog:p_dialog];
533 BOOL visible = !p_dialog->b_hide;
535 [dialogWindow center];
536 [dialogWindow makeKeyAndOrderFront:self];
538 [dialogWindow orderOut:nil];
540 [dialogWindow setHas_lock:NO];
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];
550 [dialogWindow setTitle:titleString];
553 [dialogWindow setHas_lock:NO];
555 BOOL visible = !p_dialog->b_hide;
557 [dialogWindow makeKeyAndOrderFront:self];
559 [dialogWindow orderOut:nil];
561 else if (p_dialog->b_kill) {
562 [self destroyExtensionDialog:p_dialog];
564 vlc_cond_signal(&p_dialog->cond);
565 vlc_mutex_unlock(&p_dialog->lock);
570 * Ask the dialog manager to create/update/kill the dialog. Thread-safe.
572 - (void)manageDialog:(extension_dialog_t *)p_dialog
574 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
576 ExtensionsManager *extMgr = [ExtensionsManager getInstance:p_intf];
577 assert(extMgr != NULL);
579 NSValue *o_value = [NSValue valueWithPointer:p_dialog];
580 [self performSelectorOnMainThread:@selector(updateExtensionDialog:)