macOS: Use dark appearance for panel modals
[vlc.git] / modules / gui / macosx / VLCStringUtility.m
blob16b80ebb107fd396c3b32e6078ec850b13c2f8de
1 /*****************************************************************************
2  * VLCStringUtility.m: MacOS X interface module
3  *****************************************************************************
4  * Copyright (C) 2002-2014 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Christophe Massiot <massiot@via.ecp.fr>
9  *          Derk-Jan Hartman <hartman at videolan dot org>
10  *          Felix Paul Kühne <fkuehne at 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  *****************************************************************************/
27 #import "VLCStringUtility.h"
29 #import "VLCMain.h"
30 #import "CompatibilityFixes.h"
32 #import <IOKit/storage/IOMedia.h>
33 #import <IOKit/storage/IOCDMedia.h>
34 #import <IOKit/storage/IODVDMedia.h>
35 #import <IOKit/storage/IOBDMedia.h>
37 NSString *const kVLCMediaAudioCD = @"AudioCD";
38 NSString *const kVLCMediaDVD = @"DVD";
39 NSString *const kVLCMediaVCD = @"VCD";
40 NSString *const kVLCMediaSVCD = @"SVCD";
41 NSString *const kVLCMediaBD = @"Blu-ray";
42 NSString *const kVLCMediaVideoTSFolder = @"VIDEO_TS";
43 NSString *const kVLCMediaBDMVFolder = @"BDMV";
44 NSString *const kVLCMediaUnknown = @"Unknown";
46 #import <vlc_keys.h>
47 #import <vlc_strings.h>
49 @implementation VLCStringUtility
52 + (VLCStringUtility *)sharedInstance
54     static VLCStringUtility *sharedInstance = nil;
55     static dispatch_once_t pred;
57     dispatch_once(&pred, ^{
58         sharedInstance = [VLCStringUtility new];
59     });
61     return sharedInstance;
64 #pragma mark -
65 #pragma mark String utility
67 - (NSString *)localizedString:(const char *)psz
69     NSString * stringObject = nil;
71     if (psz != NULL) {
72         stringObject = toNSStr(_(psz));
74         if (stringObject == NULL) {
75             msg_Err(getIntf(), "could not translate: %s", psz);
76             return @"";
77         }
78     } else
79         return @"";
81     return stringObject;
84 /* i_width is in pixels */
85 - (NSString *)wrapString:(NSString *)o_in_string toWidth:(int)i_width
87     NSMutableString *o_wrapped;
88     NSString *o_out_string;
89     NSRange glyphRange, effectiveRange, charRange;
90     NSRect lineFragmentRect;
91     unsigned glyphIndex, breaksInserted = 0;
93     NSTextStorage *o_storage = [[NSTextStorage alloc] initWithString: o_in_string
94                                                           attributes: [NSDictionary dictionaryWithObjectsAndKeys:
95                                                                        [NSFont labelFontOfSize: 0.0], NSFontAttributeName, nil]];
96     NSLayoutManager *o_layout_manager = [[NSLayoutManager alloc] init];
97     NSTextContainer *o_container = [[NSTextContainer alloc]
98                                     initWithContainerSize: NSMakeSize(i_width, 2000)];
100     [o_layout_manager addTextContainer: o_container];
101     [o_storage addLayoutManager: o_layout_manager];
103     o_wrapped = [o_in_string mutableCopy];
104     glyphRange = [o_layout_manager glyphRangeForTextContainer: o_container];
106     for (glyphIndex = glyphRange.location ; glyphIndex < NSMaxRange(glyphRange) ;
107         glyphIndex += effectiveRange.length) {
108         lineFragmentRect = [o_layout_manager lineFragmentRectForGlyphAtIndex: glyphIndex
109                                                               effectiveRange: &effectiveRange];
110         charRange = [o_layout_manager characterRangeForGlyphRange: effectiveRange
111                                                  actualGlyphRange: &effectiveRange];
112         if ([o_wrapped lineRangeForRange:
113             NSMakeRange(charRange.location + breaksInserted, charRange.length)].length > charRange.length) {
114             [o_wrapped insertString: @"\n" atIndex: NSMaxRange(charRange) + breaksInserted];
115             breaksInserted++;
116         }
117     }
118     o_out_string = [NSString stringWithString: o_wrapped];
120     return o_out_string;
123 - (NSString *)getCurrentTimeAsString:(input_thread_t *)p_input negative:(BOOL)b_negative
125     assert(p_input != nil);
127     char psz_time[MSTRTIME_MAX_SIZE];
128     int64_t t = var_GetInteger(p_input, "time");
130     mtime_t dur = input_item_GetDuration(input_GetItem(p_input));
131     if (b_negative && dur > 0) {
132         mtime_t remaining = 0;
133         if (dur > t)
134             remaining = dur - t;
135         return [NSString stringWithFormat: @"-%s", secstotimestr(psz_time, (remaining / 1000000))];
136     } else
137         return toNSStr(secstotimestr(psz_time, t / CLOCK_FREQ ));
140 - (NSString *)stringForTime:(long long int)time
142     if (time > 0) {
143         long long positiveDuration = llabs(time);
144         if (positiveDuration > 3600)
145             return [NSString stringWithFormat:@"%s%01ld:%02ld:%02ld",
146                     time < 0 ? "-" : "",
147                     (long) (positiveDuration / 3600),
148                     (long)((positiveDuration / 60) % 60),
149                     (long) (positiveDuration % 60)];
150         else
151             return [NSString stringWithFormat:@"%s%02ld:%02ld",
152                     time < 0 ? "-" : "",
153                     (long)((positiveDuration / 60) % 60),
154                     (long) (positiveDuration % 60)];
155     } else {
156         // Return a string that represents an undefined time.
157         return @"--:--";
158     }
161 #pragma mark -
162 #pragma mark Key Shortcuts
164 static struct
166     unichar i_nskey;
167     unsigned int i_vlckey;
168 } nskeys_to_vlckeys[] =
170     { NSUpArrowFunctionKey, KEY_UP },
171     { NSDownArrowFunctionKey, KEY_DOWN },
172     { NSLeftArrowFunctionKey, KEY_LEFT },
173     { NSRightArrowFunctionKey, KEY_RIGHT },
174     { NSF1FunctionKey, KEY_F1 },
175     { NSF2FunctionKey, KEY_F2 },
176     { NSF3FunctionKey, KEY_F3 },
177     { NSF4FunctionKey, KEY_F4 },
178     { NSF5FunctionKey, KEY_F5 },
179     { NSF6FunctionKey, KEY_F6 },
180     { NSF7FunctionKey, KEY_F7 },
181     { NSF8FunctionKey, KEY_F8 },
182     { NSF9FunctionKey, KEY_F9 },
183     { NSF10FunctionKey, KEY_F10 },
184     { NSF11FunctionKey, KEY_F11 },
185     { NSF12FunctionKey, KEY_F12 },
186     { NSInsertFunctionKey, KEY_INSERT },
187     { NSHomeFunctionKey, KEY_HOME },
188     { NSEndFunctionKey, KEY_END },
189     { NSPageUpFunctionKey, KEY_PAGEUP },
190     { NSPageDownFunctionKey, KEY_PAGEDOWN },
191     { NSMenuFunctionKey, KEY_MENU },
192     { NSTabCharacter, KEY_TAB },
193     { NSCarriageReturnCharacter, KEY_ENTER },
194     { NSEnterCharacter, KEY_ENTER },
195     { NSBackspaceCharacter, KEY_BACKSPACE },
196     { NSDeleteCharacter, KEY_DELETE },
197     {0,0}
201  * Takes the first value of an cocoa key string, and converts it to VLCs int representation.
202  */
203 unsigned int CocoaKeyToVLC(unichar i_key)
205     unsigned int i;
207     for (i = 0; nskeys_to_vlckeys[i].i_nskey != 0; i++) {
208         if (nskeys_to_vlckeys[i].i_nskey == i_key) {
209             return nskeys_to_vlckeys[i].i_vlckey;
210         }
211     }
212     return (unsigned int)i_key;
215 /* takes a good old const c string and converts it to NSString without UTF8 loss */
217 NSString *toNSStr(const char *str) {
218     return str != NULL ? [NSString stringWithUTF8String:str] : @"";
222  * Converts VLC key string to a prettified version, for hotkey settings.
223  * The returned string adapts similar how its done within the cocoa framework when setting this
224  * key to menu items.
225  */
226 - (NSString *)OSXStringKeyToString:(NSString *)theString
228     if (![theString isEqualToString:@""]) {
229         /* remove cruft */
230         if ([theString characterAtIndex:([theString length] - 1)] != 0x2b)
231             theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
232         else {
233             theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
234             theString = [NSString stringWithFormat:@"%@+", theString];
235         }
236         if ([theString characterAtIndex:([theString length] - 1)] != 0x2d)
237             theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
238         else {
239             theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
240             theString = [NSString stringWithFormat:@"%@-", theString];
241         }
242         /* modifiers */
243         theString = [theString stringByReplacingOccurrencesOfString:@"Command" withString: [NSString stringWithUTF8String:"\xE2\x8C\x98"]];
244         theString = [theString stringByReplacingOccurrencesOfString:@"Alt" withString: [NSString stringWithUTF8String:"\xE2\x8C\xA5"]];
245         theString = [theString stringByReplacingOccurrencesOfString:@"Shift" withString: [NSString stringWithUTF8String:"\xE2\x87\xA7"]];
246         theString = [theString stringByReplacingOccurrencesOfString:@"Ctrl" withString: [NSString stringWithUTF8String:"\xE2\x8C\x83"]];
247         /* show non-character keys correctly */
248         theString = [theString stringByReplacingOccurrencesOfString:@"Right" withString:[NSString stringWithUTF8String:"\xE2\x86\x92"]];
249         theString = [theString stringByReplacingOccurrencesOfString:@"Left" withString:[NSString stringWithUTF8String:"\xE2\x86\x90"]];
250         theString = [theString stringByReplacingOccurrencesOfString:@"Page Up" withString:[NSString stringWithUTF8String:"\xE2\x87\x9E"]];
251         theString = [theString stringByReplacingOccurrencesOfString:@"Page Down" withString:[NSString stringWithUTF8String:"\xE2\x87\x9F"]];
252         theString = [theString stringByReplacingOccurrencesOfString:@"Up" withString:[NSString stringWithUTF8String:"\xE2\x86\x91"]];
253         theString = [theString stringByReplacingOccurrencesOfString:@"Down" withString:[NSString stringWithUTF8String:"\xE2\x86\x93"]];
254         theString = [theString stringByReplacingOccurrencesOfString:@"Enter" withString:[NSString stringWithUTF8String:"\xe2\x86\xb5"]];
255         theString = [theString stringByReplacingOccurrencesOfString:@"Tab" withString:[NSString stringWithUTF8String:"\xe2\x87\xa5"]];
256         theString = [theString stringByReplacingOccurrencesOfString:@"Delete" withString:[NSString stringWithUTF8String:"\xe2\x8c\xab"]];        /* capitalize plain characters to suit the menubar's look */
257         theString = [theString capitalizedString];
258     }
259     else
260         theString = [NSString stringWithString:_NS("Not Set")];
261     return theString;
265  * Converts VLC key string to cocoa modifiers which can be used as setKeyEquivalent for menu items
266  */
267 - (unsigned int)VLCModifiersToCocoa:(NSString *)theString
269     unsigned int new = 0;
271     if ([theString rangeOfString:@"Command"].location != NSNotFound)
272         new |= NSCommandKeyMask;
273     if ([theString rangeOfString:@"Alt"].location != NSNotFound)
274         new |= NSAlternateKeyMask;
275     if ([theString rangeOfString:@"Shift"].location != NSNotFound)
276         new |= NSShiftKeyMask;
277     if ([theString rangeOfString:@"Ctrl"].location != NSNotFound)
278         new |= NSControlKeyMask;
279     return new;
283  * Converts VLC key to cocoa string which can be used as setKeyEquivalentModifierMask for menu items
284  */
285 - (NSString *)VLCKeyToString:(NSString *)theString
287     if (![theString isEqualToString:@""]) {
288         if ([theString characterAtIndex:([theString length] - 1)] != 0x2b)
289             theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
290         else {
291             theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
292             theString = [NSString stringWithFormat:@"%@+", theString];
293         }
294         if ([theString characterAtIndex:([theString length] - 1)] != 0x2d)
295             theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
296         else {
297             theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
298             theString = [NSString stringWithFormat:@"%@-", theString];
299         }
300         theString = [theString stringByReplacingOccurrencesOfString:@"Command" withString:@""];
301         theString = [theString stringByReplacingOccurrencesOfString:@"Alt" withString:@""];
302         theString = [theString stringByReplacingOccurrencesOfString:@"Shift" withString:@""];
303         theString = [theString stringByReplacingOccurrencesOfString:@"Ctrl" withString:@""];
304     }
306 #ifdef __clang__
307 #pragma GCC diagnostic ignored "-Wformat"
308 #endif
309     if ([theString length] > 1) {
310         if ([theString rangeOfString:@"Page Up"].location != NSNotFound)
311             return [NSString stringWithFormat:@"%C", NSPageUpFunctionKey];
312         else if ([theString rangeOfString:@"Page Down"].location != NSNotFound)
313             return [NSString stringWithFormat:@"%C", NSPageDownFunctionKey];
314         else if ([theString rangeOfString:@"Up"].location != NSNotFound)
315             return [NSString stringWithFormat:@"%C", NSUpArrowFunctionKey];
316         else if ([theString rangeOfString:@"Down"].location != NSNotFound)
317             return [NSString stringWithFormat:@"%C", NSDownArrowFunctionKey];
318         else if ([theString rangeOfString:@"Right"].location != NSNotFound)
319             return [NSString stringWithFormat:@"%C", NSRightArrowFunctionKey];
320         else if ([theString rangeOfString:@"Left"].location != NSNotFound)
321             return [NSString stringWithFormat:@"%C", NSLeftArrowFunctionKey];
322         else if ([theString rangeOfString:@"Enter"].location != NSNotFound)
323             return [NSString stringWithFormat:@"%C", NSEnterCharacter]; // we treat NSCarriageReturnCharacter as aquivalent
324         else if ([theString rangeOfString:@"Insert"].location != NSNotFound)
325             return [NSString stringWithFormat:@"%C", NSInsertFunctionKey];
326         else if ([theString rangeOfString:@"Home"].location != NSNotFound)
327             return [NSString stringWithFormat:@"%C", NSHomeFunctionKey];
328         else if ([theString rangeOfString:@"End"].location != NSNotFound)
329             return [NSString stringWithFormat:@"%C", NSEndFunctionKey];
330         else if ([theString rangeOfString:@"Menu"].location != NSNotFound)
331             return [NSString stringWithFormat:@"%C", NSMenuFunctionKey];
332         else if ([theString rangeOfString:@"Tab"].location != NSNotFound)
333             return [NSString stringWithFormat:@"%C", NSTabCharacter];
334         else if ([theString rangeOfString:@"Backspace"].location != NSNotFound)
335             return [NSString stringWithFormat:@"%C", NSBackspaceCharacter];
336         else if ([theString rangeOfString:@"Delete"].location != NSNotFound)
337             return [NSString stringWithFormat:@"%C", NSDeleteCharacter];
338         else if ([theString rangeOfString:@"F12"].location != NSNotFound)
339             return [NSString stringWithFormat:@"%C", NSF12FunctionKey];
340         else if ([theString rangeOfString:@"F11"].location != NSNotFound)
341             return [NSString stringWithFormat:@"%C", NSF11FunctionKey];
342         else if ([theString rangeOfString:@"F10"].location != NSNotFound)
343             return [NSString stringWithFormat:@"%C", NSF10FunctionKey];
344         else if ([theString rangeOfString:@"F9"].location != NSNotFound)
345             return [NSString stringWithFormat:@"%C", NSF9FunctionKey];
346         else if ([theString rangeOfString:@"F8"].location != NSNotFound)
347             return [NSString stringWithFormat:@"%C", NSF8FunctionKey];
348         else if ([theString rangeOfString:@"F7"].location != NSNotFound)
349             return [NSString stringWithFormat:@"%C", NSF7FunctionKey];
350         else if ([theString rangeOfString:@"F6"].location != NSNotFound)
351             return [NSString stringWithFormat:@"%C", NSF6FunctionKey];
352         else if ([theString rangeOfString:@"F5"].location != NSNotFound)
353             return [NSString stringWithFormat:@"%C", NSF5FunctionKey];
354         else if ([theString rangeOfString:@"F4"].location != NSNotFound)
355             return [NSString stringWithFormat:@"%C", NSF4FunctionKey];
356         else if ([theString rangeOfString:@"F3"].location != NSNotFound)
357             return [NSString stringWithFormat:@"%C", NSF3FunctionKey];
358         else if ([theString rangeOfString:@"F2"].location != NSNotFound)
359             return [NSString stringWithFormat:@"%C", NSF2FunctionKey];
360         else if ([theString rangeOfString:@"F1"].location != NSNotFound)
361             return [NSString stringWithFormat:@"%C", NSF1FunctionKey];
362         else if ([theString rangeOfString:@"Space"].location != NSNotFound)
363             return @" ";
364         /* note that we don't support esc here, since it is reserved for leaving fullscreen */
365     }
366 #ifdef __clang__
367 #pragma GCC diagnostic warning "-Wformat"
368 #endif
370     return theString;
373 #pragma mark -
374 #pragma mark base64 helpers
376 - (NSString *)b64Decode:(NSString *)string
378     char *psz_decoded_string = vlc_b64_decode([string UTF8String]);
379     if(!psz_decoded_string)
380         return @"";
382     NSString *returnStr = [NSString stringWithFormat:@"%s", psz_decoded_string];
383     free(psz_decoded_string);
385     return returnStr;
388 - (NSString *)b64EncodeAndFree:(char *)psz_string
390     char *psz_encoded_string = vlc_b64_encode(psz_string);
391     free(psz_string);
392     if(!psz_encoded_string)
393         return @"";
395     NSString *returnStr = [NSString stringWithFormat:@"%s", psz_encoded_string];
396     free(psz_encoded_string);
398     return returnStr;
401 - (NSString *) getBSDNodeFromMountPath:(NSString *)mountPath
403     OSStatus err;
404     FSRef ref;
405     FSVolumeRefNum actualVolume;
406     err = FSPathMakeRef ((const UInt8 *) [mountPath fileSystemRepresentation], &ref, NULL);
408     // get a FSVolumeRefNum from mountPath
409     if (noErr == err) {
410         FSCatalogInfo   catalogInfo;
411         err = FSGetCatalogInfo (&ref,
412                                 kFSCatInfoVolume,
413                                 &catalogInfo,
414                                 NULL,
415                                 NULL,
416                                 NULL
417                                 );
418         if (noErr == err)
419             actualVolume = catalogInfo.volume;
420         else
421             return @"";
422     }
423     else
424         return @"";
426     GetVolParmsInfoBuffer volumeParms;
427     err = FSGetVolumeParms(actualVolume, &volumeParms, sizeof(volumeParms));
428     if (noErr == err) {
429         NSString *bsdName = [NSString stringWithUTF8String:(char *)volumeParms.vMDeviceID];
430         return [NSString stringWithFormat:@"/dev/r%@", bsdName];
431     }
433     return @"";
436 - (NSString *)getVolumeTypeFromMountPath:(NSString *)mountPath
438     OSStatus err;
439     FSRef ref;
440     FSVolumeRefNum actualVolume;
441     NSString *returnValue;
442     err = FSPathMakeRef ((const UInt8 *) [mountPath fileSystemRepresentation], &ref, NULL);
444     // get a FSVolumeRefNum from mountPath
445     if (noErr == err) {
446         FSCatalogInfo   catalogInfo;
447         err = FSGetCatalogInfo (&ref,
448                                 kFSCatInfoVolume,
449                                 &catalogInfo,
450                                 NULL,
451                                 NULL,
452                                 NULL
453                                 );
454         if (noErr == err)
455             actualVolume = catalogInfo.volume;
456         else
457             goto out;
458     }
459     else
460         goto out;
462     GetVolParmsInfoBuffer volumeParms;
463     err = FSGetVolumeParms(actualVolume, &volumeParms, sizeof(volumeParms));
465     CFMutableDictionaryRef matchingDict;
466     io_service_t service;
468     if (!volumeParms.vMDeviceID) {
469         goto out;
470     }
472     matchingDict = IOBSDNameMatching(kIOMasterPortDefault, 0, volumeParms.vMDeviceID);
473     service = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict);
475     if (IO_OBJECT_NULL != service) {
476         if (IOObjectConformsTo(service, kIOCDMediaClass))
477             returnValue = kVLCMediaAudioCD;
478         else if (IOObjectConformsTo(service, kIODVDMediaClass))
479             returnValue = kVLCMediaDVD;
480         else if (IOObjectConformsTo(service, kIOBDMediaClass))
481             returnValue = kVLCMediaBD;
482         IOObjectRelease(service);
484         if (returnValue)
485             return returnValue;
486     }
488     out:
489     if ([mountPath rangeOfString:@"VIDEO_TS" options:NSCaseInsensitiveSearch | NSBackwardsSearch].location != NSNotFound)
490         returnValue = kVLCMediaVideoTSFolder;
491     else if ([mountPath rangeOfString:@"BDMV" options:NSCaseInsensitiveSearch | NSBackwardsSearch].location != NSNotFound)
492         returnValue = kVLCMediaBDMVFolder;
493     else {
494         // NSFileManager is not thread-safe, don't use defaultManager outside of the main thread
495         NSFileManager * fm = [[NSFileManager alloc] init];
497         NSArray *dirContents = [fm contentsOfDirectoryAtPath:mountPath error:nil];
498         for (int i = 0; i < [dirContents count]; i++) {
499             NSString *currentFile = [dirContents objectAtIndex:i];
500             NSString *fullPath = [mountPath stringByAppendingPathComponent:currentFile];
502             BOOL isDir;
503             if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && isDir)
504             {
505                 if ([currentFile caseInsensitiveCompare:@"SVCD"] == NSOrderedSame) {
506                     returnValue = kVLCMediaSVCD;
507                     break;
508                 }
509                 if ([currentFile caseInsensitiveCompare:@"VCD"] == NSOrderedSame) {
510                     returnValue = kVLCMediaVCD;
511                     break;
512                 }
513                 if ([currentFile caseInsensitiveCompare:@"BDMV"] == NSOrderedSame) {
514                     returnValue = kVLCMediaBDMVFolder;
515                     break;
516                 }
517                 if ([currentFile caseInsensitiveCompare:@"VIDEO_TS"] == NSOrderedSame) {
518                     returnValue = kVLCMediaVideoTSFolder;
519                     break;
520                 }
521             }
522         }
524         if (!returnValue)
525             returnValue = kVLCMediaVideoTSFolder;
526     }
528     return returnValue;
531 @end
533 NSImage *imageFromRes(NSString *o_id)
535     NSString *result = @"";
536     if (OSX_YOSEMITE_AND_HIGHER) {
537         result = [result stringByAppendingString:@"ys-"];
538     }
540     result = [result stringByAppendingString:o_id];
542     return [NSImage imageNamed:result];