Vuo  2.0.0
VuoAppAboutBox.m
Go to the documentation of this file.
1 
10 #import "VuoAppAboutBox.h"
11 
12 #import "VuoApp.h"
13 
14 #import <libgen.h>
15 #import <dirent.h>
16 
17 #import "VuoAppSplashView.h"
18 #import "VuoConfig.h"
19 
24 @end
25 
26 @implementation NSColor (VuoAppAboutBox)
27 - (NSString *)cssString
28 {
29  NSColor *srgbColor = [self colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
30  return [NSString stringWithFormat:@"rgba(%g,%g,%g,%g)",
31  255 * srgbColor.redComponent,
32  255 * srgbColor.greenComponent,
33  255 * srgbColor.blueComponent,
34  srgbColor.alphaComponent];
35 }
36 @end
37 
41 @interface VuoAppAboutTextView : NSTextView
42 @property (retain, nonatomic) NSString *mostRecentText;
43 @end
44 
45 @implementation VuoAppAboutTextView
46 
47 - (instancetype)init
48 {
49  if (self = [super init])
50  {
51  self.linkTextAttributes = @{ NSForegroundColorAttributeName:NSColor.secondaryLabelColor };
52  self.editable = NO;
53  }
54  return self;
55 }
56 
57 - (NSSize)intrinsicContentSize
58 {
59  [self.layoutManager ensureLayoutForTextContainer:self.textContainer];
60  return [self.layoutManager usedRectForTextContainer:self.textContainer].size;
61 }
62 
63 - (void)didChangeText
64 {
65  [super didChangeText];
66  [self invalidateIntrinsicContentSize];
67 }
68 
69 - (void)viewDidChangeEffectiveAppearance
70 {
71  [self setText:self.mostRecentText];
72 }
73 
74 - (void)setText:(NSString *)text
75 {
76  self.mostRecentText = text;
77  NSMutableString *s = [NSMutableString new];
78  [s appendString:@"<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>"];
79 
80  [NSAppearance setCurrentAppearance:self.effectiveAppearance];
81  NSString *headingCSS = NSColor.labelColor.cssString;
82  NSString *subheadingCSS = NSColor.tertiaryLabelColor.cssString;
83  NSString *bodyCSS = NSColor.secondaryLabelColor.cssString;
84 
85  [s appendFormat:@"<style> \
86  body { font: 10pt 'Helvetica Neue'; color: %@; } \
87  p { padding-top: 30px; } \
88  h1 { padding-top: 30px; } \
89  .name { font-size: 28pt; font-weight: 300; } \
90  .version { color: %@; font-size: 22pt; font-weight: 200; } \
91  .copyright { color: %@; } \
92  .description { font-weight: bold; } \
93  .licenses { color: %@; font-size: 8pt; } \
94  pre { font: 'Monaco'; } \
95  </style>",
96  headingCSS,
97  subheadingCSS,
98  subheadingCSS,
99  bodyCSS];
100  [s appendString:text];
101  NSAttributedString *as = [[NSAttributedString alloc] initWithHTML:[s dataUsingEncoding:NSUTF8StringEncoding] documentAttributes:nil];
102  [s release];
103  self.textStorage.mutableString.string = @"";
104  [self.textStorage appendAttributedString:as];
105  [as release];
106 }
107 
108 @end
109 
110 
114 @interface VuoAppAboutContentView : NSView
115 @end
116 
117 @implementation VuoAppAboutContentView
118 - (void)cancelOperation:(id)sender
119 {
120  [self.window close];
121 }
122 @end
123 
124 
128 @interface VuoAppAboutWindow : NSWindow
129 @end
130 
131 @implementation VuoAppAboutWindow
132 
133 - (instancetype)init
134 {
135  if (self = [super initWithContentRect:NSMakeRect(0, 0, 820, 500)
136  styleMask:NSTitledWindowMask|NSClosableWindowMask|NSFullSizeContentViewWindowMask
137  backing:NSBackingStoreBuffered
138  defer:NO])
139  {
140  self.releasedWhenClosed = NO;
141  self.titlebarAppearsTransparent = YES;
142 
143 
144  NSBox *leftColumn = [NSBox new];
145  leftColumn.translatesAutoresizingMaskIntoConstraints = NO;
146  leftColumn.boxType = NSBoxCustom;
147  leftColumn.borderType = NSNoBorder;
148  leftColumn.fillColor = NSColor.controlBackgroundColor;
149 
150  // Contents of left column.
151  {
152  NSImageView *icon = [NSImageView new];
153  icon.translatesAutoresizingMaskIntoConstraints = NO;
154  icon.image = NSApplication.sharedApplication.applicationIconImage;
155  [leftColumn addSubview:icon];
156 
157  VuoAppSplashView *poweredByVuo = [VuoAppSplashView new];
158  poweredByVuo.translatesAutoresizingMaskIntoConstraints = NO;
159  poweredByVuo.borderVisible = NO;
160  [leftColumn addSubview:poweredByVuo];
161 
162  NSDictionary *views = NSDictionaryOfVariableBindings(icon, poweredByVuo);
163  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[icon(160)]-|" options:0 metrics:nil views:views]];
164  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[poweredByVuo]-|" options:0 metrics:nil views:views]];
165  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[icon(160)]-(>=0)-[poweredByVuo(120)]-|" options:0 metrics:nil views:views]];
166 
167  [poweredByVuo release];
168  [icon release];
169  }
170 
171 
172  NSBox *rightColumn = [NSBox new];
173  rightColumn.translatesAutoresizingMaskIntoConstraints = NO;
174  rightColumn.boxType = NSBoxCustom;
175  rightColumn.borderType = NSNoBorder;
176  rightColumn.fillColor = NSColor.windowBackgroundColor;
177 
178  // Contents of right column.
179  {
180  // Description.
181  VuoAppAboutTextView *description = [VuoAppAboutTextView new];
182  description.frame = NSMakeRect(0, 0, 560, 0);
183  description.translatesAutoresizingMaskIntoConstraints = NO;
184  description.drawsBackground = NO;
185  {
186  NSMutableString *s = [NSMutableString new];
187 
188  NSString *name = NSBundle.mainBundle.infoDictionary[@"CFBundleName"];
189  if (!name)
190  name = NSProcessInfo.processInfo.processName;
191  [s appendFormat:@"<span class='name'>%@</span>", name];
192 
193  NSString *version = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"];
194  if (version.length)
195  [s appendFormat:@" <span class='version'>%@</span>", version];
196 
197  NSString *copyright = NSBundle.mainBundle.infoDictionary[@"NSHumanReadableCopyright"];
198  if (copyright)
199  [s appendFormat:@"<p class='copyright'>%@</p>", copyright];
200 
201  NSString *desc = NSBundle.mainBundle.infoDictionary[@"description"];
202  if (desc)
203  [s appendFormat:@"<div class='description'>%@</div><br>", desc];
204 
205  NSString *homepage = NSBundle.mainBundle.infoDictionary[@"homepageURL"];
206  NSString *documentation = NSBundle.mainBundle.infoDictionary[@"documentationURL"];
207  if (homepage)
208  [s appendFormat:@"<p><a href='%@'>%@</a></p>", homepage, homepage];
209  if (documentation)
210  [s appendFormat:@"<p><a href='%@'>%@</a></p>", documentation, documentation];
211 
212  [description setText:s];
213  [s release];
214  }
215  [rightColumn addSubview:description];
216 
217 
218  // Licenses.
219  VuoAppAboutTextView *licenses = [VuoAppAboutTextView new];
220  {
221  NSMutableString *s = [NSMutableString new];
222  [s appendString:@"<div class='licenses'>"];
223  [s appendString:[self licenseString]];
224  [s appendString:@"</div>"];
225  [licenses setText:s];
226  [s release];
227  }
228  NSScrollView *licensesScroller = [[NSScrollView alloc] initWithFrame:NSMakeRect(20, 20, 560, 280)];
229  licensesScroller.hasVerticalScroller = YES;
230  licensesScroller.documentView = licenses;
231  licenses.frame = NSMakeRect(0, 0, licensesScroller.contentSize.width, licensesScroller.contentSize.height);
232  [rightColumn addSubview:licensesScroller];
233 
234 
235  NSDictionary *views = NSDictionaryOfVariableBindings(description, licensesScroller);
236  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[description]-|" options:0 metrics:nil views:views]];
237  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[licensesScroller]-|" options:0 metrics:nil views:views]];
238  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[description][licensesScroller]-|" options:0 metrics:nil views:views]];
239 
240  [licenses release];
241  [licensesScroller release];
242  [description release];
243  }
244 
245 
247  self.contentView = c;
248  [c addSubview:leftColumn];
249  [c addSubview:rightColumn];
250 
251  NSDictionary *views = NSDictionaryOfVariableBindings(leftColumn,rightColumn);
252  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[leftColumn][rightColumn]|" options:0 metrics:nil views:views]];
253  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[leftColumn]|" options:0 metrics:nil views:views]];
254  [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[rightColumn]|" options:0 metrics:nil views:views]];
255 
256  [c release];
257  [leftColumn release];
258  [rightColumn release];
259  }
260 
261  return self;
262 }
263 
267 - (NSString *)licenseString
268 {
269  NSMutableString *s = [NSMutableString new];
270 
271 
272  [s appendString:@"<p>This app may include software and resources licensed under the following terms:</p>"];
273 
274 
275  // Derive the path of "Licenses" directory.
276  char licensesPath[PATH_MAX+1];
277  licensesPath[0] = 0;
278  if (strlen(VuoApp_getVuoFrameworkPath()))
279  {
280  strncpy(licensesPath, VuoApp_getVuoFrameworkPath(), PATH_MAX);
281  strncat(licensesPath, "/Vuo.framework/Versions/" VUO_FRAMEWORK_VERSION_STRING "/Documentation/Licenses", PATH_MAX);
282  }
283  else if (strlen(VuoApp_getVuoRunnerFrameworkPath()))
284  {
285  strncpy(licensesPath, VuoApp_getVuoRunnerFrameworkPath(), PATH_MAX);
286  strncat(licensesPath, "/VuoRunner.framework/Versions/" VUO_FRAMEWORK_VERSION_STRING "/Documentation/Licenses", PATH_MAX);
287  }
288 
289 
290  bool foundLicenses = false;
291  DIR *dirp = licensesPath[0] ? opendir(licensesPath) : NULL;
292  if (dirp)
293  {
294  struct dirent *dp;
295  while ((dp = readdir(dirp)) != NULL)
296  {
297  if (dp->d_name[0] == '.')
298  continue;
299 
300  char *nameWithoutExtension = strdup(dp->d_name);
301  nameWithoutExtension[strlen(nameWithoutExtension) - 4] = 0;
302  [s appendString:[NSString stringWithFormat:@"<h2>%s</h2>", nameWithoutExtension]];
303  free(nameWithoutExtension);
304 
305  size_t pathSize = strlen(licensesPath) + dp->d_namlen + 2;
306  char licensePath[pathSize];
307  strlcpy(licensePath, licensesPath, pathSize);
308  strlcat(licensePath, "/", pathSize);
309  strlcat(licensePath, dp->d_name, pathSize);
310 
311  [s appendString:[NSString stringWithFormat:@"<pre>%@</pre><br>",
312  [NSString stringWithContentsOfFile:[NSString stringWithUTF8String:licensePath] usedEncoding:nil error:nil]]];
313 
314  foundLicenses = true;
315  }
316  closedir(dirp);
317  }
318 
319  if (!foundLicenses)
320  [s appendString:@"<p>(No license information found.)</p>"];
321 
322  return [s autorelease];
323 }
324 
325 @end
326 
330 @interface VuoAppAboutBox ()
331 @property(nonatomic, strong) NSWindow *window;
332 @end
333 
334 @implementation VuoAppAboutBox
335 
339 - (void)displayAboutPanel:(id)sender
340 {
341  if (!_window)
342  _window = [VuoAppAboutWindow new];
343  [_window center];
344  [_window makeKeyAndOrderFront:sender];
345 }
346 
347 @end