Vuo  2.0.1
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 #ifndef NS_RETURNS_INNER_POINTER
18 #define NS_RETURNS_INNER_POINTER
19 #endif
20 #include <AppKit/AppKit.h>
21 #include <objc/runtime.h>
22 
25 static NSString *currentFontName;
26 static double currentPointSize;
27 static bool currentUnderline;
28 static NSColor *currentColor;
30 static double currentCharacterSpacing;
31 static double currentLineSpacing;
32 
37 {
38  return new VuoInputEditorFont();
39 }
40 
45 {
46  // Convert to RGB colorspace (since the color picker might return a grey or CMYK color).
47  currentColor = [[currentColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace] retain];
48 
49  return VuoFont_make(
50  VuoText_make([currentFontName UTF8String]),
53  VuoColor_makeWithRGBA([currentColor redComponent], [currentColor greenComponent], [currentColor blueComponent], [currentColor alphaComponent]),
57  );
58 }
59 
63 static NSDictionary *getCurrentAttributes(void)
64 {
65  return [NSDictionary dictionaryWithObjectsAndKeys:
66  [NSNumber numberWithInteger:currentUnderline?kCTUnderlineStyleSingle:kCTUnderlineStyleNone], kCTUnderlineStyleAttributeName,
67  [currentColor retain], NSForegroundColorAttributeName,
68  nil];
69 }
70 
74 @interface VuoInputEditorFontTextEdit : NSTextView
75 @end
76 @implementation VuoInputEditorFontTextEdit
80 - (void)drawRect:(NSRect)dirtyRect
81 {
82  NSFontManager *fm = [NSFontManager sharedFontManager];
83  NSFontPanel *fp = [fm fontPanel:YES];
84 
85  // Show the font panel and give it focus.
86  [fp makeKeyAndOrderFront:nil];
87 
88  // Need to set the font and attributes _after_ showing the panel since showing it resets everything.
89  [fm setSelectedFont:[NSFont fontWithName:currentFontName size:currentPointSize] isMultiple:NO];
90  [fm setSelectedAttributes:getCurrentAttributes() isMultiple:NO];
91 }
92 
96 - (NSUInteger)validModesForFontPanel:(NSFontPanel *)fontPanel
97 {
98  return NSFontPanelUnderlineEffectModeMask | NSFontPanelTextColorEffectModeMask | NSFontPanelCollectionModeMask | NSFontPanelFaceModeMask | NSFontPanelSizeModeMask;
99 }
100 
104 - (void)changeFont:(NSFontManager *)fm
105 {
106  NSFont *oldFont = [NSFont userFontOfSize:12];
107  NSFont *newFont = [fm convertFont:oldFont];
108 
109  currentFontName = [newFont fontName];
110  currentPointSize = [newFont pointSize];
111 
113 }
114 
118 - (void)changeAttributes:(NSFontManager *)fm
119 {
120  NSDictionary *newAttributes = [fm convertAttributes:getCurrentAttributes()];
121 
122  NSNumber *underlineNumber = [newAttributes objectForKey:(NSString *)kCTUnderlineStyleAttributeName];
123  if (underlineNumber && [underlineNumber integerValue] != kCTUnderlineStyleNone)
124  currentUnderline = true;
125  else
126  currentUnderline = false;
127 
128  currentColor = [[newAttributes objectForKey:@"NSColor"] retain];
129 
131 }
132 @end
133 
137 @interface VuoInputEditorFontPanelDelegate : NSObject <NSWindowDelegate>
138 {
139  QDialog *dialog;
140 }
141 @end
142 @implementation VuoInputEditorFontPanelDelegate
143 - (id)initWithQDialog:(QDialog *)_dialog
144 {
145  dialog = _dialog;
146  return [super init];
147 }
148 - (void)windowWillClose:(NSNotification *)notification
149 {
150  // If the user clicks the font panel's close button, also dismiss its parent modal dialog.
151  dialog->accept();
152 }
153 - (void)okButtonPressed
154 {
155  dialog->accept();
156 }
157 - (void)cancelButtonPressed
158 {
159  dialog->reject();
160 }
161 @end
162 
166 @interface VuoInputEditorFontAccessoryDelegate : NSObject
167 {
168 }
169 @end
171 - (void)alignmentChanged:(NSSegmentedControl *)control
172 {
173  currentAlignment = (VuoHorizontalAlignment)[control selectedSegment];
175 }
176 
177 - (void)charSpacingChanged:(NSSlider *)control
178 {
179  currentCharacterSpacing = [control doubleValue];
181 }
182 - (void)lineSpacingChanged:(NSSlider *)control
183 {
184  currentLineSpacing = [control doubleValue];
186 }
187 @end
188 
192 json_object * VuoInputEditorFont::show(QPoint portLeftCenter, json_object *originalValue, json_object *details, map<QString, json_object *> portNamesAndValues)
193 {
194  // QFontDialog is unusably buggy (see https://b33p.net/kosada/node/6966#comment-24042), so we're directly invoking Cocoa's NSFontPanel.
195 
196  // 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.
197  // Create a modal dialog to catch events for us.
198  QDialog *dialog = new QDialog;
199  dialog->resize(1,1);
200  dialog->setWindowFlags(Qt::FramelessWindowHint|Qt::NoDropShadowWindowHint);
201 
202  // NSFontPanel applies its changes to the first object on the responder chain.
203  // So create a text editor widget with some methods to catch NSFontPanel's changes.
204  VuoInputEditorFontTextEdit *textEditView;
205  {
206  NSView *d = (NSView *)dialog->winId();
207  textEditView = [[[VuoInputEditorFontTextEdit alloc] initWithFrame:NSMakeRect(0,0,1,1)] autorelease];
208 
209  [d addSubview:textEditView];
210  [[d window] makeFirstResponder:textEditView];
211 
212  currentEditor = this;
213  }
214 
215  NSFontManager *fm = [NSFontManager sharedFontManager];
216  NSFontPanel *fp = [fm fontPanel:YES];
217 
218  [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
219 
220  // QColorDialog (used in VuoInputEditorColor) reparents the sharedColorPanel's contentView when NoButtons=false;
221  // when the dialog is shown by the Font editor, the buttons cause a crash.
222  // By setting NoButtons=true, then discreetly opening the dialog, we force Qt to switch the sharedColorPanel back to normal.
223  // https://b33p.net/kosada/node/7892
224  {
225  [[NSColorPanel sharedColorPanel] setAlphaValue:0];
226  QColorDialog dialog;
227  dialog.setOption(QColorDialog::NoButtons, true);
228  dialog.show();
229  dialog.hide();
230  [[NSColorPanel sharedColorPanel] setAlphaValue:1];
231  }
232 
233  // Create a delegate to catch if the user closes the font panel.
234  VuoInputEditorFontPanelDelegate *delegate = [[VuoInputEditorFontPanelDelegate alloc] initWithQDialog:dialog];
235  [fp setDelegate:delegate];
236 
237  // Preset the font panel with the port's original font.
238  {
239  VuoFont originalVuoFont = VuoFont_makeFromJson(originalValue);
240  if (originalVuoFont.fontName)
241  {
242  currentFontName = [NSString stringWithUTF8String:originalVuoFont.fontName];
243  currentPointSize = originalVuoFont.pointSize;
244  }
245  else
246  {
247  NSFont *originalFont = [NSFont userFontOfSize:12];
248  currentFontName = [originalFont fontName];
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:NSSmallControlSize];
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:NSSmallControlSize];
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:NSMomentaryLightButton];
360  [okButton setBezelStyle:NSRoundedBezelStyle];
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:NSMomentaryLightButton];
371  [cancelButton setBezelStyle:NSRoundedBezelStyle];
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 }