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