Vuo  2.4.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
18#include <AppKit/AppKit.h>
19#include <objc/runtime.h>
20
23static NSString *currentFontName;
24static double currentPointSize;
25static bool currentUnderline;
26static NSColor *currentColor;
29static 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
61static 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;
140@end
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
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
193json_object * VuoInputEditorFont::show(QPoint portLeftCenter, json_object *originalValue, json_object *details, map<QString, json_object *> portNamesAndValues)
195 // QFontDialog is unusably buggy (see https://b33p.net/kosada/node/6966#comment-24042), so we're directly invoking Cocoa's NSFontPanel.
196
197 // 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.
198 // Create a modal dialog to catch events for us.
199 QDialog *dialog = new QDialog;
200 dialog->resize(1,1);
201 dialog->setWindowFlags(Qt::FramelessWindowHint|Qt::NoDropShadowWindowHint);
202
203 // NSFontPanel applies its changes to the first object on the responder chain.
204 // So create a text editor widget with some methods to catch NSFontPanel's changes.
205 VuoInputEditorFontTextEdit *textEditView;
206 {
207 NSView *d = (NSView *)dialog->winId();
208 textEditView = [[[VuoInputEditorFontTextEdit alloc] initWithFrame:NSMakeRect(0,0,1,1)] autorelease];
209
210 [d addSubview:textEditView];
211 [[d window] makeFirstResponder:textEditView];
212
213 currentEditor = this;
214 }
215
216 NSFontManager *fm = [NSFontManager sharedFontManager];
217 NSFontPanel *fp = [fm fontPanel:YES];
218
219 [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
220
221 // QColorDialog (used in VuoInputEditorColor) reparents the sharedColorPanel's contentView when NoButtons=false;
222 // when the dialog is shown by the Font editor, the buttons cause a crash.
223 // By setting NoButtons=true, then discreetly opening the dialog, we force Qt to switch the sharedColorPanel back to normal.
224 // https://b33p.net/kosada/node/7892
225 {
226 [[NSColorPanel sharedColorPanel] setAlphaValue:0];
227 QColorDialog dialog;
228 dialog.setOption(QColorDialog::NoButtons, true);
229 dialog.show();
230 dialog.hide();
231 [[NSColorPanel sharedColorPanel] setAlphaValue:1];
232 }
233
234 // Create a delegate to catch if the user closes the font panel.
235 VuoInputEditorFontPanelDelegate *delegate = [[VuoInputEditorFontPanelDelegate alloc] initWithQDialog:dialog];
236 [fp setDelegate:delegate];
237
238 // Preset the font panel with the port's original font.
239 {
240 VuoFont originalVuoFont = VuoFont_makeFromJson(originalValue);
241 [currentFontName release];
242 if (originalVuoFont.fontName)
243 {
244 currentFontName = [[NSString stringWithUTF8String:originalVuoFont.fontName] retain];
245 currentPointSize = originalVuoFont.pointSize;
246 }
247 else
248 {
249 NSFont *originalFont = [NSFont userFontOfSize:12];
250 currentFontName = [[originalFont fontName] retain];
251 currentPointSize = [originalFont pointSize];
252 }
253
254 currentColor = [[NSColor colorWithCalibratedRed:originalVuoFont.color.r green:originalVuoFont.color.g blue:originalVuoFont.color.b alpha:originalVuoFont.color.a] retain];
255
256 currentUnderline = originalVuoFont.underline;
257
258 currentAlignment = originalVuoFont.alignment;
259
261
262 currentLineSpacing = originalVuoFont.lineSpacing;
263 }
264
265 // Position the font panel left of the port from which it was invoked.
266 {
267 NSSize panelSize = [fp frame].size;
268 NSSize screenSize = [[fp screen] frame].size;
269 [fp setFrameTopLeftPoint:NSMakePoint(portLeftCenter.x() - panelSize.width, screenSize.height - portLeftCenter.y() + panelSize.height/2)];
270 }
271
272
273 const unsigned int buttonWidth = 80;
274 const unsigned int buttonHeight = 24;
275 const unsigned int buttonSep = 12;
276 NSView *accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 4*buttonWidth+3*buttonSep, 2*buttonHeight+3*buttonSep)];
277 [fp setAccessoryView:accessoryView];
278 [accessoryView autorelease];
279 [fp setMinSize:NSMakeSize([accessoryView frame].size.width, [fp minSize].height)];
280
282 [accessoryDelegate autorelease];
283
284 // Add a horizontal alignment widget.
285 {
286 NSSegmentedControl *alignmentControl = [[NSSegmentedControl alloc] initWithFrame:NSMakeRect(0, 0, buttonWidth+buttonSep+buttonWidth, buttonHeight)];
287 [alignmentControl setSegmentCount:3];
288 [alignmentControl setSelectedSegment:currentAlignment];
289
290 [alignmentControl setImage:[NSImage imageNamed:@"align-left"] forSegment:0];
291 [alignmentControl setImage:[NSImage imageNamed:@"align-center"] forSegment:1];
292 [alignmentControl setImage:[NSImage imageNamed:@"align-right"] forSegment:2];
293
294 [alignmentControl setTarget:accessoryDelegate];
295 [alignmentControl setAction:@selector(alignmentChanged:)];
296
297 [accessoryView addSubview:alignmentControl];
298 [alignmentControl autorelease];
299 }
300
301 int secondBaseline = buttonHeight+buttonSep;
302 int sliderWidth = buttonWidth+buttonSep+buttonWidth;
303
304 // Add character spacing widget.
305 {
306 NSTextField *charSpacingLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(0, secondBaseline+buttonSep, sliderWidth, buttonHeight)];
307 [charSpacingLabel setEditable:NO];
308 [charSpacingLabel setBordered:NO];
309 [charSpacingLabel setDrawsBackground:NO];
310 [charSpacingLabel setStringValue:@"Character Spacing"];
311 [charSpacingLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
312 [accessoryView addSubview:charSpacingLabel];
313 [charSpacingLabel autorelease];
314
315 NSSlider *charSpacingControl = [[NSSlider alloc] initWithFrame:NSMakeRect(0, secondBaseline, sliderWidth, buttonHeight)];
316 [charSpacingControl setNumberOfTickMarks:1]; // A single tick at the center.
317 [charSpacingControl setMinValue:0];
318 [charSpacingControl setMaxValue:2];
319 [charSpacingControl setDoubleValue:currentCharacterSpacing];
320 [[charSpacingControl cell] setControlSize:NSControlSizeSmall];
321
322 [charSpacingControl setTarget:accessoryDelegate];
323 [charSpacingControl setAction:@selector(charSpacingChanged:)];
324
325 [accessoryView addSubview:charSpacingControl];
326 [charSpacingControl autorelease];
327 }
328
329 // Add line spacing widget.
330 {
331 NSTextField *lineSpacingLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(2*buttonWidth+2*buttonSep, secondBaseline+buttonSep, sliderWidth, buttonHeight)];
332 [lineSpacingLabel setEditable:NO];
333 [lineSpacingLabel setBordered:NO];
334 [lineSpacingLabel setDrawsBackground:NO];
335 [lineSpacingLabel setStringValue:@"Line Spacing"];
336 [lineSpacingLabel setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
337 [accessoryView addSubview:lineSpacingLabel];
338 [lineSpacingLabel autorelease];
339
340 NSSlider *lineSpacingControl = [[NSSlider alloc] initWithFrame:NSMakeRect(2*buttonWidth+2*buttonSep, secondBaseline, sliderWidth, buttonHeight)];
341 [lineSpacingControl setNumberOfTickMarks:1]; // A single tick at the center.
342 [lineSpacingControl setMinValue:0];
343 [lineSpacingControl setMaxValue:2];
344 [lineSpacingControl setDoubleValue:currentLineSpacing];
345 [[lineSpacingControl cell] setControlSize:NSControlSizeSmall];
346
347 [lineSpacingControl setTarget:accessoryDelegate];
348 [lineSpacingControl setAction:@selector(lineSpacingChanged:)];
349
350 [accessoryView addSubview:lineSpacingControl];
351 [lineSpacingControl autorelease];
352 }
353
354
355 // Provide another way to dismiss the font panel (more visible than just the panel's close button).
356 {
357 {
358 NSButton *okButton = [[NSButton alloc] initWithFrame:NSMakeRect(3*buttonWidth+3*buttonSep,0,buttonWidth,buttonHeight)];
359 [okButton setKeyEquivalent:@"\r"]; // Return
360 [okButton setTitle:@"OK"];
361 [okButton setButtonType:NSButtonTypeMomentaryLight];
362 [okButton setBezelStyle:NSBezelStyleRounded];
363 [okButton setTarget:delegate];
364 [okButton setAction:@selector(okButtonPressed)];
365 [accessoryView addSubview:okButton];
366 [okButton autorelease];
367 }
368 {
369 NSButton *cancelButton = [[NSButton alloc] initWithFrame:NSMakeRect(2*buttonWidth+3*buttonSep,0,buttonWidth,buttonHeight)];
370 [cancelButton setKeyEquivalent:@"\E"]; // Escape
371 [cancelButton setTitle:@"Cancel"];
372 [cancelButton setButtonType:NSButtonTypeMomentaryLight];
373 [cancelButton setBezelStyle:NSBezelStyleRounded];
374 [cancelButton setTarget:delegate];
375 [cancelButton setAction:@selector(cancelButtonPressed)];
376 [accessoryView addSubview:cancelButton];
377 [cancelButton autorelease];
378 }
379 }
380
381 // Wait for the user to dismiss the font panel.
382 int result = dialog->exec();
383
384 // Hide the color picker.
385 [[NSColorPanel sharedColorPanel] orderOut:nil];
386
387 // Hide the font panel.
388 [fp orderOut:nil];
389
390 [delegate release];
391
392 delete dialog;
393
394 currentEditor = NULL;
395
396 if (result == QDialog::Accepted)
398 else
399 return json_object_get(originalValue);
400}
401
407 json_object *valueAsJson = VuoFont_getJson(font);
408 emit valueChanged(valueAsJson);
409 json_object_put(valueAsJson);
410}