Vuo  2.3.0
VuoInputEditorFont.mm
Go to the documentation of this file.
1 
10 #include "VuoInputEditorFont.hh"
11 
12 // https://b33p.net/kosada/node/7282#comment-25535
13 #if defined(slots)
14 #undef slots
15 #endif
16 
17 #include "VuoMacOSSDKWorkaround.h"
18 #include <AppKit/AppKit.h>
19 #include <objc/runtime.h>
20 
23 static NSString *currentFontName;
24 static double currentPointSize;
25 static bool currentUnderline;
26 static NSColor *currentColor;
28 static double currentCharacterSpacing;
29 static double currentLineSpacing;
30 
35 {
36  return new VuoInputEditorFont();
37 }
38 
43 {
44  // Convert to RGB colorspace (since the color picker might return a grey or CMYK color).
45  currentColor = [[currentColor colorUsingColorSpace:NSColorSpace.sRGBColorSpace] retain];
46 
47  return VuoFont_make(
48  VuoText_make([currentFontName UTF8String]),
51  VuoColor_makeWithRGBA([currentColor redComponent], [currentColor greenComponent], [currentColor blueComponent], [currentColor alphaComponent]),
55  );
56 }
57 
61 static NSDictionary *getCurrentAttributes(void)
62 {
63  return [NSDictionary dictionaryWithObjectsAndKeys:
64  [NSNumber numberWithInteger:currentUnderline?kCTUnderlineStyleSingle:kCTUnderlineStyleNone], kCTUnderlineStyleAttributeName,
65  [currentColor retain], NSForegroundColorAttributeName,
66  nil];
67 }
68 
72 @interface VuoInputEditorFontTextEdit : NSTextView
73 @end
74 @implementation VuoInputEditorFontTextEdit
78 - (void)drawRect:(NSRect)dirtyRect
79 {
80  NSFontManager *fm = [NSFontManager sharedFontManager];
81  NSFontPanel *fp = [fm fontPanel:YES];
82 
83  // Show the font panel and give it focus.
84  [fp makeKeyAndOrderFront:nil];
85 
86  // Need to set the font and attributes _after_ showing the panel since showing it resets everything.
87  [fm setSelectedFont:[NSFont fontWithName:currentFontName size:currentPointSize] isMultiple:NO];
88  [fm setSelectedAttributes:getCurrentAttributes() isMultiple:NO];
89 }
90 
94 - (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel
95 {
96  return NSFontPanelUnderlineEffectModeMask | NSFontPanelTextColorEffectModeMask | NSFontPanelCollectionModeMask | NSFontPanelFaceModeMask | NSFontPanelSizeModeMask;
97 }
98 
102 - (void)changeFont:(NSFontManager *)fm
103 {
104  NSFont *oldFont = [NSFont userFontOfSize:12];
105  NSFont *newFont = [fm convertFont:oldFont];
106 
107  [currentFontName release];
108  currentFontName = [[newFont fontName] retain];
109  currentPointSize = [newFont pointSize];
110 
112 }
113 
117 - (void)changeAttributes:(NSFontManager *)fm
118 {
119  NSDictionary *newAttributes = [fm convertAttributes:getCurrentAttributes()];
120 
121  NSNumber *underlineNumber = [newAttributes objectForKey:(NSString *)kCTUnderlineStyleAttributeName];
122  if (underlineNumber && [underlineNumber integerValue] != kCTUnderlineStyleNone)
123  currentUnderline = true;
124  else
125  currentUnderline = false;
126 
127  currentColor = [[newAttributes objectForKey:@"NSColor"] retain];
128 
130 }
131 @end
132 
136 @interface VuoInputEditorFontPanelDelegate : NSObject <NSWindowDelegate>
137 {
138  QDialog *dialog;
139 }
140 @end
141 @implementation VuoInputEditorFontPanelDelegate
142 - (id)initWithQDialog:(QDialog *)_dialog
143 {
144  dialog = _dialog;
145  return [super init];
146 }
147 - (void)windowWillClose:(NSNotification *)notification
148 {
149  // If the user clicks the font panel's close button, also dismiss its parent modal dialog.
150  dialog->accept();
151 }
152 - (void)okButtonPressed
153 {
154  dialog->accept();
155 }
156 - (void)cancelButtonPressed
157 {
158  dialog->reject();
159 }
160 @end
161 
165 @interface VuoInputEditorFontAccessoryDelegate : NSObject
166 {
167 }
168 @end
170 - (void)alignmentChanged:(NSSegmentedControl *)control
171 {
172  currentAlignment = (VuoHorizontalAlignment)[control selectedSegment];
174 }
175 
176 - (void)charSpacingChanged:(NSSlider *)control
177 {
178  currentCharacterSpacing = [control doubleValue];
180 }
181 - (void)lineSpacingChanged:(NSSlider *)control
182 {
183  currentLineSpacing = [control doubleValue];
185 }
186 @end
187 
191 json_object * VuoInputEditorFont::show(QPoint portLeftCenter, json_object *originalValue, json_object *details, map<QString, json_object *> portNamesAndValues)
192 {
193  // QFontDialog is unusably buggy (see https://b33p.net/kosada/node/6966#comment-24042), so we're directly invoking Cocoa's NSFontPanel.
194 
195  // NSFontPanel is a panel (a nonmodal auxiliary window), but Vuo's input editors are designed to be modal, so we have to trick it into being modal.
196  // Create a modal dialog to catch events for us.
197  QDialog *dialog = new QDialog;
198  dialog->resize(1,1);
199  dialog->setWindowFlags(Qt::FramelessWindowHint|Qt::NoDropShadowWindowHint);
200 
201  // NSFontPanel applies its changes to the first object on the responder chain.
202  // So create a text editor widget with some methods to catch NSFontPanel's changes.
203  VuoInputEditorFontTextEdit *textEditView;
204  {
205  NSView *d = (NSView *)dialog->winId();
206  textEditView = [[[VuoInputEditorFontTextEdit alloc] initWithFrame:NSMakeRect(0,0,1,1)] autorelease];
207 
208  [d addSubview:textEditView];
209  [[d window] makeFirstResponder:textEditView];
210 
211  currentEditor = this;
212  }
213 
214  NSFontManager *fm = [NSFontManager sharedFontManager];
215  NSFontPanel *fp = [fm fontPanel:YES];
216 
217  [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
218 
219  // QColorDialog (used in VuoInputEditorColor) reparents the sharedColorPanel's contentView when NoButtons=false;
220  // when the dialog is shown by the Font editor, the buttons cause a crash.
221  // By setting NoButtons=true, then discreetly opening the dialog, we force Qt to switch the sharedColorPanel back to normal.
222  // https://b33p.net/kosada/node/7892
223  {
224  [[NSColorPanel sharedColorPanel] setAlphaValue:0];
225  QColorDialog dialog;
226  dialog.setOption(QColorDialog::NoButtons, true);
227  dialog.show();
228  dialog.hide();
229  [[NSColorPanel sharedColorPanel] setAlphaValue:1];
230  }
231 
232  // Create a delegate to catch if the user closes the font panel.
233  VuoInputEditorFontPanelDelegate *delegate = [[VuoInputEditorFontPanelDelegate alloc] initWithQDialog:dialog];
234  [fp setDelegate:delegate];
235 
236  // Preset the font panel with the port's original font.
237  {
238  VuoFont originalVuoFont = VuoFont_makeFromJson(originalValue);
239  [currentFontName release];
240  if (originalVuoFont.fontName)
241  {
242  currentFontName = [[NSString stringWithUTF8String:originalVuoFont.fontName] retain];
243  currentPointSize = originalVuoFont.pointSize;
244  }
245  else
246  {
247  NSFont *originalFont = [NSFont userFontOfSize:12];
248  currentFontName = [[originalFont fontName] retain];
249  currentPointSize = [originalFont pointSize];
250  }
251 
252  currentColor = [[NSColor colorWithCalibratedRed:originalVuoFont.color.r green:originalVuoFont.color.g blue:originalVuoFont.color.b alpha:originalVuoFont.color.a] retain];
253 
254  currentUnderline = originalVuoFont.underline;
255 
256  currentAlignment = originalVuoFont.alignment;
257 
258  currentCharacterSpacing = originalVuoFont.characterSpacing;
259 
260  currentLineSpacing = originalVuoFont.lineSpacing;
261  }
262 
263  // Position the font panel left of the port from which it was invoked.
264  {
265  NSSize panelSize = [fp frame].size;
266  NSSize screenSize = [[fp screen] frame].size;
267  [fp setFrameTopLeftPoint:NSMakePoint(portLeftCenter.x() - panelSize.width, screenSize.height - portLeftCenter.y() + panelSize.height/2)];
268  }
269 
270 
271  const unsigned int buttonWidth = 80;
272  const unsigned int buttonHeight = 24;
273  const unsigned int buttonSep = 12;
274  NSView *accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 4*buttonWidth+3*buttonSep, 2*buttonHeight+3*buttonSep)];
275  [fp setAccessoryView:accessoryView];
276  [accessoryView autorelease];
277  [fp setMinSize:NSMakeSize([accessoryView frame].size.width, [fp minSize].height)];
278 
280  [accessoryDelegate autorelease];
281 
282  // Add a horizontal alignment widget.
283  {
284  NSSegmentedControl *alignmentControl = [[NSSegmentedControl alloc] initWithFrame:NSMakeRect(0, 0, buttonWidth+buttonSep+buttonWidth, buttonHeight)];
285  [alignmentControl setSegmentCount:3];
286  [alignmentControl setSelectedSegment:currentAlignment];
287 
288  [alignmentControl setImage:[NSImage imageNamed:@"align-left"] forSegment:0];
289  [alignmentControl setImage:[NSImage imageNamed:@"align-center"] forSegment:1];
290  [alignmentControl setImage:[NSImage imageNamed:@"align-right"] forSegment:2];
291 
292  [alignmentControl setTarget:accessoryDelegate];
293  [alignmentControl setAction:@selector(alignmentChanged:)];
294 
295  [accessoryView addSubview:alignmentControl];
296  [alignmentControl autorelease];
297  }
298 
299  int secondBaseline = buttonHeight+buttonSep;
300  int sliderWidth = buttonWidth+buttonSep+buttonWidth;
301 
302  // Add character spacing widget.
303  {
304  NSTextField *charSpacingLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, secondBaseline+buttonSep, sliderWidth, buttonHeight)];
305  [charSpacingLabel setEditable:NO];
306  [charSpacingLabel setBordered:NO];
307  [charSpacingLabel setDrawsBackground:NO];
308  [charSpacingLabel setStringValue:@"Character Spacing"];
309  [charSpacingLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
310  [accessoryView addSubview:charSpacingLabel];
311  [charSpacingLabel autorelease];
312 
313  NSSlider *charSpacingControl = [[NSSlider alloc] initWithFrame:NSMakeRect(0, secondBaseline, sliderWidth, buttonHeight)];
314  [charSpacingControl setNumberOfTickMarks:1]; // A single tick at the center.
315  [charSpacingControl setMinValue:0];
316  [charSpacingControl setMaxValue:2];
317  [charSpacingControl setDoubleValue:currentCharacterSpacing];
318  [[charSpacingControl cell] setControlSize:NSControlSizeSmall];
319 
320  [charSpacingControl setTarget:accessoryDelegate];
321  [charSpacingControl setAction:@selector(charSpacingChanged:)];
322 
323  [accessoryView addSubview:charSpacingControl];
324  [charSpacingControl autorelease];
325  }
326 
327  // Add line spacing widget.
328  {
329  NSTextField *lineSpacingLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(2*buttonWidth+2*buttonSep, secondBaseline+buttonSep, sliderWidth, buttonHeight)];
330  [lineSpacingLabel setEditable:NO];
331  [lineSpacingLabel setBordered:NO];
332  [lineSpacingLabel setDrawsBackground:NO];
333  [lineSpacingLabel setStringValue:@"Line Spacing"];
334  [lineSpacingLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
335  [accessoryView addSubview:lineSpacingLabel];
336  [lineSpacingLabel autorelease];
337 
338  NSSlider *lineSpacingControl = [[NSSlider alloc] initWithFrame:NSMakeRect(2*buttonWidth+2*buttonSep, secondBaseline, sliderWidth, buttonHeight)];
339  [lineSpacingControl setNumberOfTickMarks:1]; // A single tick at the center.
340  [lineSpacingControl setMinValue:0];
341  [lineSpacingControl setMaxValue:2];
342  [lineSpacingControl setDoubleValue:currentLineSpacing];
343  [[lineSpacingControl cell] setControlSize:NSControlSizeSmall];
344 
345  [lineSpacingControl setTarget:accessoryDelegate];
346  [lineSpacingControl setAction:@selector(lineSpacingChanged:)];
347 
348  [accessoryView addSubview:lineSpacingControl];
349  [lineSpacingControl autorelease];
350  }
351 
352 
353  // Provide another way to dismiss the font panel (more visible than just the panel's close button).
354  {
355  {
356  NSButton *okButton = [[NSButton alloc] initWithFrame:NSMakeRect(3*buttonWidth+3*buttonSep,0,buttonWidth,buttonHeight)];
357  [okButton setKeyEquivalent:@"\r"]; // Return
358  [okButton setTitle:@"OK"];
359  [okButton setButtonType:NSButtonTypeMomentaryLight];
360  [okButton setBezelStyle:NSBezelStyleRounded];
361  [okButton setTarget:delegate];
362  [okButton setAction:@selector(okButtonPressed)];
363  [accessoryView addSubview:okButton];
364  [okButton autorelease];
365  }
366  {
367  NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(2*buttonWidth+3*buttonSep,0,buttonWidth,buttonHeight)];
368  [cancelButton setKeyEquivalent:@"\E"]; // Escape
369  [cancelButton setTitle:@"Cancel"];
370  [cancelButton setButtonType:NSButtonTypeMomentaryLight];
371  [cancelButton setBezelStyle:NSBezelStyleRounded];
372  [cancelButton setTarget:delegate];
373  [cancelButton setAction:@selector(cancelButtonPressed)];
374  [accessoryView addSubview:cancelButton];
375  [cancelButton autorelease];
376  }
377  }
378 
379  // Wait for the user to dismiss the font panel.
380  int result = dialog->exec();
381 
382  // Hide the color picker.
383  [[NSColorPanel sharedColorPanel] orderOut:nil];
384 
385  // Hide the font panel.
386  [fp orderOut:nil];
387 
388  [delegate release];
389 
390  delete dialog;
391 
392  currentEditor = NULL;
393 
394  if (result == QDialog::Accepted)
396  else
397  return originalValue;
398 }
399 
404 {
405  json_object *valueAsJson = VuoFont_getJson(font);
406  emit valueChanged(valueAsJson);
407  json_object_put(valueAsJson);
408 }