summaryrefslogtreecommitdiff
path: root/src/macos.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos.m')
-rw-r--r--src/macos.m502
1 files changed, 502 insertions, 0 deletions
diff --git a/src/macos.m b/src/macos.m
new file mode 100644
index 00000000..edbb6df0
--- /dev/null
+++ b/src/macos.m
@@ -0,0 +1,502 @@
1/* Copyright 2020 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "macos.h"
24#include "app.h"
25#include "ui/command.h"
26#include "ui/widget.h"
27#include "ui/color.h"
28
29#import <AppKit/AppKit.h>
30
31#if 0
32static NSTouchBarItemIdentifier play_TouchId_ = @"fi.skyjake.BitwiseHarmony.play";
33static NSTouchBarItemIdentifier restart_TouchId_ = @"fi.skyjake.BitwiseHarmony.restart";
34
35static NSTouchBarItemIdentifier seqMoveUp_TouchId_ = @"fi.skyjake.BitwiseHarmony.sequence.move.up";
36static NSTouchBarItemIdentifier seqMoveDown_TouchId_ = @"fi.skyjake.BitwiseHarmony.sequence.move.down";
37
38static NSTouchBarItemIdentifier goto_TouchId_ = @"fi.skyjake.BitwiseHarmony.goto";
39static NSTouchBarItemIdentifier mute_TouchId_ = @"fi.skyjake.BitwiseHarmony.mute";
40static NSTouchBarItemIdentifier solo_TouchId_ = @"fi.skyjake.BitwiseHarmony.solo";
41static NSTouchBarItemIdentifier color_TouchId_ = @"fi.skyjake.BitwiseHarmony.color";
42static NSTouchBarItemIdentifier event_TouchId_ = @"fi.skyjake.BitwiseHarmony.event";
43
44static NSTouchBarItemIdentifier eventList_TouchId_ = @"fi.skyjake.BitwiseHarmony.eventlist";
45static NSTouchBarItemIdentifier masterGainEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.mastergain";
46static NSTouchBarItemIdentifier resetEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.reset";
47static NSTouchBarItemIdentifier voiceEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.voice";
48static NSTouchBarItemIdentifier panEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.pan";
49static NSTouchBarItemIdentifier gainEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.gain";
50static NSTouchBarItemIdentifier fadeEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.fade";
51static NSTouchBarItemIdentifier pitchSpeedEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.pitchspeed";
52static NSTouchBarItemIdentifier pitchBendUpEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.pitchbendup";
53static NSTouchBarItemIdentifier pitchBendDownEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.pitchbenddown";
54static NSTouchBarItemIdentifier tremoloEvent_TouchId_ = @"fi.skyjake.BitwiseHarmony.event.tremolo";
55#endif
56
57enum iTouchBarVariant {
58 default_TouchBarVariant,
59};
60
61@interface CommandButton : NSButtonTouchBarItem {
62 NSString *command;
63 iWidget *widget;
64}
65- (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
66 title:(NSString *)title
67 command:(NSString *)cmd;
68- (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
69 title:(NSString *)title
70 widget:(iWidget *)widget
71 command:(NSString *)cmd;
72- (void)dealloc;
73@end
74
75@implementation CommandButton
76
77- (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
78 title:(NSString *)title
79 command:(NSString *)cmd {
80 [super initWithIdentifier:identifier];
81 self.title = title;
82 self.target = self;
83 self.action = @selector(buttonPressed);
84 command = cmd;
85 return self;
86}
87
88- (id)initWithIdentifier:(NSTouchBarItemIdentifier)identifier
89 title:(NSString *)title
90 widget:(iWidget *)aWidget
91 command:(NSString *)cmd {
92 [self initWithIdentifier:identifier title:title command:[cmd retain]];
93 widget = aWidget;
94 return self;
95}
96
97- (void)dealloc {
98 [command release];
99 [super dealloc];
100}
101
102- (void)buttonPressed {
103 const char *cmd = [command cStringUsingEncoding:NSUTF8StringEncoding];
104 if (widget) {
105 postCommand_Widget(widget, "%s", cmd);
106 }
107 else {
108 postCommand_App(cmd);
109 }
110}
111
112@end
113
114@interface MyDelegate : NSResponder<NSApplicationDelegate, NSTouchBarDelegate> {
115 enum iTouchBarVariant touchBarVariant;
116 NSString *currentAppearanceName;
117 NSObject<NSApplicationDelegate> *sdlDelegate;
118 NSMutableDictionary<NSString *, NSString*> *menuCommands;
119}
120- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl;
121//- (NSTouchBar *)makeTouchBar;
122/* SDL needs to do its own thing. */
123- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename;
124- (void)applicationDidFinishLaunching:(NSNotification *)notifications;
125@end
126
127@implementation MyDelegate
128
129- (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl {
130 [super init];
131 currentAppearanceName = nil;
132 menuCommands = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
133 touchBarVariant = default_TouchBarVariant;
134 sdlDelegate = sdl;
135 return self;
136}
137
138- (void)dealloc {
139 [menuCommands release];
140 [currentAppearanceName release];
141 [super dealloc];
142}
143
144- (void)setTouchBarVariant:(enum iTouchBarVariant)variant {
145 touchBarVariant = variant;
146 self.touchBar = nil;
147}
148
149static void appearanceChanged_MacOS_(NSString *name) {
150 const iBool isDark = [name containsString:@"Dark"];
151 const iBool isHighContrast = [name containsString:@"HighContrast"];
152 postCommandf_App("os.theme.changed dark:%d contrast:%d", isDark ? 1 : 0, isHighContrast ? 1 : 0);
153// printf("Effective appearance changed: %s\n", [name cStringUsingEncoding:NSUTF8StringEncoding]);
154// fflush(stdout);
155}
156
157- (void)setAppearance:(NSString *)name {
158 if (!currentAppearanceName || ![name isEqualToString:currentAppearanceName]) {
159 if (currentAppearanceName) {
160 [currentAppearanceName release];
161 }
162 currentAppearanceName = [name retain];
163 appearanceChanged_MacOS_(currentAppearanceName);
164 }
165}
166
167- (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem {
168 [menuCommands setObject:command forKey:[menuItem title]];
169}
170
171- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
172 return [sdlDelegate application:theApplication openFile:filename];
173}
174
175- (void)applicationDidFinishLaunching:(NSNotification *)notification {
176 [sdlDelegate applicationDidFinishLaunching:notification];
177}
178
179- (void)observeValueForKeyPath:(NSString *)keyPath
180 ofObject:(id)object
181 change:(NSDictionary *)change
182 context:(void *)context {
183 iUnused(object, change);
184 if ([keyPath isEqualToString:@"effectiveAppearance"] && context == self) {
185 [self setAppearance:[[NSApp effectiveAppearance] name]];
186 }
187}
188
189#if 0
190- (NSTouchBar *)makeTouchBar {
191 NSTouchBar *bar = [[NSTouchBar alloc] init];
192 bar.delegate = self;
193 switch (touchBarVariant) {
194 case default_TouchBarVariant:
195 bar.defaultItemIdentifiers = @[ play_TouchId_, restart_TouchId_,
196 NSTouchBarItemIdentifierFixedSpaceSmall,
197 NSTouchBarItemIdentifierOtherItemsProxy ];
198 break;
199 case sequence_TouchBarVariant:
200 bar.defaultItemIdentifiers = @[ play_TouchId_, restart_TouchId_,
201 NSTouchBarItemIdentifierFlexibleSpace,
202 seqMoveUp_TouchId_, seqMoveDown_TouchId_,
203 NSTouchBarItemIdentifierFlexibleSpace,
204 NSTouchBarItemIdentifierOtherItemsProxy];
205 break;
206 case tracker_TouchBarVariant:
207 bar.defaultItemIdentifiers = @[ play_TouchId_, restart_TouchId_,
208 NSTouchBarItemIdentifierFlexibleSpace,
209 goto_TouchId_,
210 event_TouchId_,
211 NSTouchBarItemIdentifierFlexibleSpace,
212 solo_TouchId_, mute_TouchId_, color_TouchId_,
213 NSTouchBarItemIdentifierFlexibleSpace,
214 NSTouchBarItemIdentifierOtherItemsProxy ];
215 break;
216 case wide_TouchBarVariant:
217 bar.defaultItemIdentifiers = @[ play_TouchId_, restart_TouchId_,
218 NSTouchBarItemIdentifierFlexibleSpace,
219 event_TouchId_,
220 NSTouchBarItemIdentifierFlexibleSpace,
221 solo_TouchId_, mute_TouchId_, color_TouchId_,
222 NSTouchBarItemIdentifierFlexibleSpace,
223 seqMoveUp_TouchId_, seqMoveDown_TouchId_,
224 NSTouchBarItemIdentifierFlexibleSpace,
225 NSTouchBarItemIdentifierOtherItemsProxy ];
226 break;
227 }
228 return bar;
229}
230#endif
231
232- (void)showPreferences {
233 postCommand_App("preferences");
234}
235
236- (void)closeTab {
237 postCommand_App("tabs.close");
238}
239
240- (void)postMenuItemCommand:(id)sender {
241 NSString *command = [menuCommands objectForKey:[(NSMenuItem *)sender title]];
242 if (command) {
243 postCommand_App([command cStringUsingEncoding:NSUTF8StringEncoding]);
244 }
245}
246
247- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
248 makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
249 iUnused(touchBar);
250#if 0
251 if ([identifier isEqualToString:play_TouchId_]) {
252 return [NSButtonTouchBarItem
253 buttonTouchBarItemWithIdentifier:identifier
254 image:[NSImage imageNamed:NSImageNameTouchBarPlayPauseTemplate]
255 target:self
256 action:@selector(playPressed)];
257 }
258 else if ([identifier isEqualToString:restart_TouchId_]) {
259 return [NSButtonTouchBarItem
260 buttonTouchBarItemWithIdentifier:identifier
261 image:[NSImage imageNamed:NSImageNameTouchBarSkipToStartTemplate]
262 target:self
263 action:@selector(restartPressed)];
264 }
265 else if ([identifier isEqualToString:seqMoveUp_TouchId_]) {
266 return [[CommandButton alloc] initWithIdentifier:identifier
267 title:@"Seq\u2b06"
268 widget:findWidget_App("sequence")
269 command:@"sequence.swap arg:-1"];
270 }
271 else if ([identifier isEqualToString:seqMoveDown_TouchId_]) {
272 return [[CommandButton alloc] initWithIdentifier:identifier
273 title:@"Seq\u2b07"
274 widget:findWidget_App("sequence")
275 command:@"sequence.swap arg:1"];
276 }
277 else if ([identifier isEqualToString:goto_TouchId_]) {
278 return [[CommandButton alloc] initWithIdentifier:identifier
279 title:@"Go to…"
280 command:@"pattern.goto arg:-1"];
281 }
282 else if ([identifier isEqualToString:event_TouchId_]) {
283 NSTouchBar *events = [[NSTouchBar alloc] init];
284 events.delegate = self;
285 events.defaultItemIdentifiers = @[ eventList_TouchId_ ];
286 NSPopoverTouchBarItem *pop = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier];
287 pop.collapsedRepresentationLabel = @"Event";
288 pop.popoverTouchBar = events;
289 [events release];
290 return pop;
291 }
292 else if ([identifier isEqualToString:eventList_TouchId_]) {
293 const struct {
294 NSTouchBarItemIdentifier id;
295 const char *title;
296 const char *command;
297 } buttonDefs_[] = {
298 { voiceEvent_TouchId_, "Voice", "tracker.setevent type:2" },
299 { panEvent_TouchId_, "Pan", "tracker.setevent type:3 arg:128" },
300 { gainEvent_TouchId_, "Gain", "tracker.setevent type:4 arg:128" },
301 { fadeEvent_TouchId_, "Fade", "tracker.setevent type:5" },
302 { tremoloEvent_TouchId_, "Trem", "tracker.setevent type:9" },
303 { pitchSpeedEvent_TouchId_, "P.Spd", "tracker.setevent type:6" },
304 { pitchBendUpEvent_TouchId_, "BnUp", "tracker.setevent type:7" },
305 { pitchBendDownEvent_TouchId_, "BnDn", "tracker.setevent type:8" },
306 { masterGainEvent_TouchId_, "M.Gain", "tracker.setevent type:10 arg:64" },
307 { resetEvent_TouchId_, "Reset", "tracker.setevent type:1" },
308 };
309 NSMutableArray *items = [[NSMutableArray alloc] init];
310 iForIndices(i, buttonDefs_) {
311 CommandButton *button = [[CommandButton alloc]
312 initWithIdentifier:buttonDefs_[i].id
313 title:[NSString stringWithUTF8String:buttonDefs_[i].title]
314 widget:findWidget_App("tracker")
315 command:[NSString stringWithUTF8String:buttonDefs_[i].command]
316 ];
317 [items addObject:button];
318 }
319 NSGroupTouchBarItem *group = [NSGroupTouchBarItem groupItemWithIdentifier:identifier
320 items:items];
321 [items release];
322 return group;
323 }
324 else if ([identifier isEqualToString:mute_TouchId_]) {
325 return [[CommandButton alloc] initWithIdentifier:identifier
326 title:@"Mute"
327 widget:findWidget_App("tracker")
328 command:@"tracker.mute"];
329 }
330 else if ([identifier isEqualToString:solo_TouchId_]) {
331 return [[CommandButton alloc] initWithIdentifier:identifier
332 title:@"Solo"
333 widget:findWidget_App("tracker")
334 command:@"tracker.solo"];
335 }
336 else if ([identifier isEqualToString:color_TouchId_]) {
337 NSTouchBar *colors = [[NSTouchBar alloc] init];
338 colors.delegate = self;
339 colors.defaultItemIdentifiers = @[ NSTouchBarItemIdentifierFlexibleSpace,
340 whiteColor_TouchId_,
341 yellowColor_TouchId_,
342 orangeColor_TouchId_,
343 redColor_TouchId_,
344 magentaColor_TouchId_,
345 blueColor_TouchId_,
346 cyanColor_TouchId_,
347 greenColor_TouchId_,
348 NSTouchBarItemIdentifierFlexibleSpace ];
349 NSPopoverTouchBarItem *pop = [[NSPopoverTouchBarItem alloc] initWithIdentifier:identifier];
350 pop.collapsedRepresentationImage = [NSImage imageNamed:NSImageNameTouchBarColorPickerFill];
351 pop.popoverTouchBar = colors;
352 [colors release];
353 return pop;
354 }
355 else if ([identifier isEqualToString:whiteColor_TouchId_]) {
356 return [[ColorButton alloc] initWithIdentifier:identifier
357 trackColor:white_TrackColor];
358 }
359 else if ([identifier isEqualToString:yellowColor_TouchId_]) {
360 return [[ColorButton alloc] initWithIdentifier:identifier
361 trackColor:yellow_TrackColor];
362 }
363 else if ([identifier isEqualToString:orangeColor_TouchId_]) {
364 return [[ColorButton alloc] initWithIdentifier:identifier
365 trackColor:orange_TrackColor];
366 }
367 else if ([identifier isEqualToString:redColor_TouchId_]) {
368 return [[ColorButton alloc] initWithIdentifier:identifier
369 trackColor:red_TrackColor];
370 }
371 else if ([identifier isEqualToString:magentaColor_TouchId_]) {
372 return [[ColorButton alloc] initWithIdentifier:identifier
373 trackColor:magenta_TrackColor];
374 }
375 else if ([identifier isEqualToString:blueColor_TouchId_]) {
376 return [[ColorButton alloc] initWithIdentifier:identifier
377 trackColor:blue_TrackColor];
378 }
379 else if ([identifier isEqualToString:cyanColor_TouchId_]) {
380 return [[ColorButton alloc] initWithIdentifier:identifier
381 trackColor:cyan_TrackColor];
382 }
383 else if ([identifier isEqualToString:greenColor_TouchId_]) {
384 return [[ColorButton alloc] initWithIdentifier:identifier
385 trackColor:green_TrackColor];
386 }
387#endif
388 return nil;
389}
390
391@end
392
393void enableMomentumScroll_MacOS(void) {
394 [[NSUserDefaults standardUserDefaults] setBool: YES
395 forKey: @"AppleMomentumScrollSupported"];
396}
397
398void setupApplication_MacOS(void) {
399 NSApplication *app = [NSApplication sharedApplication];
400 //appearanceChanged_MacOS_([[app effectiveAppearance] name]);
401 /* Our delegate will override SDL's delegate. */
402 MyDelegate *myDel = [[MyDelegate alloc] initWithSDLDelegate:app.delegate];
403 [myDel setAppearance:[[app effectiveAppearance] name]];
404 app.delegate = myDel;
405 NSMenu *appMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu];
406 NSMenuItem *prefsItem = [appMenu itemWithTitle:@"Preferences…"];
407 prefsItem.target = myDel;
408 prefsItem.action = @selector(showPreferences);
409 /* Get rid of the default window close item */
410 NSMenu *windowMenu = [[[NSApp mainMenu] itemWithTitle:@"Window"] submenu];
411 NSMenuItem *windowCloseItem = [windowMenu itemWithTitle:@"Close"];
412 windowCloseItem.target = myDel;
413 windowCloseItem.action = @selector(closeTab);
414}
415
416void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *items, size_t count) {
417 NSApplication *app = [NSApplication sharedApplication];
418 MyDelegate *myDel = (MyDelegate *) app.delegate;
419 [app addObserver:myDel
420 forKeyPath:@"effectiveAppearance"
421 options:0
422 context:myDel];
423 NSMenu *appMenu = [app mainMenu];
424 NSMenuItem *mainItem = [appMenu insertItemWithTitle:[NSString stringWithUTF8String:menuLabel]
425 action:nil
426 keyEquivalent:@""
427 atIndex:atIndex];
428 NSMenu *menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menuLabel]];
429 for (size_t i = 0; i < count; ++i) {
430 const char *label = items[i].label;
431 if (label[0] == '\r') {
432 /* Skip the formatting escape. */
433 label += 2;
434 }
435 if (equal_CStr(label, "---")) {
436 [menu addItem:[NSMenuItem separatorItem]];
437 }
438 else {
439 const iBool hasCommand = (items[i].command && items[i].command[0]);
440 iString key;
441 init_String(&key);
442 if (items[i].key == SDLK_LEFT) {
443 appendChar_String(&key, 0x2190);
444 }
445 else if (items[i].key == SDLK_RIGHT) {
446 appendChar_String(&key, 0x2192);
447 }
448 else if (items[i].key) {
449 appendChar_String(&key, items[i].key);
450 }
451 NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:label]
452 action:(hasCommand ? @selector(postMenuItemCommand:) : nil)
453 keyEquivalent:[NSString stringWithUTF8String:cstr_String(&key)]];
454 NSEventModifierFlags modMask = 0;
455 if (items[i].kmods & KMOD_GUI) {
456 modMask |= NSEventModifierFlagCommand;
457 }
458 if (items[i].kmods & KMOD_ALT) {
459 modMask |= NSEventModifierFlagOption;
460 }
461 if (items[i].kmods & KMOD_CTRL) {
462 modMask |= NSEventModifierFlagControl;
463 }
464 if (items[i].kmods & KMOD_SHIFT) {
465 modMask |= NSEventModifierFlagShift;
466 }
467 [item setKeyEquivalentModifierMask:modMask];
468 if (hasCommand) {
469 [myDel setCommand:[NSString stringWithUTF8String:items[i].command] forMenuItem:item];
470 }
471 deinit_String(&key);
472 }
473 }
474 [mainItem setSubmenu:menu];
475 [menu release];
476}
477
478void handleCommand_MacOS(const char *cmd) {
479 if (equal_Command(cmd, "prefs.ostheme.changed")) {
480 if (arg_Command(cmd)) {
481 appearanceChanged_MacOS_([[NSApp effectiveAppearance] name]);
482 }
483 }
484#if 0
485 if (equal_Command(cmd, "tabs.changed")) {
486 MyDelegate *myDel = (MyDelegate *) [[NSApplication sharedApplication] delegate];
487 const char *tabId = suffixPtr_Command(cmd, "id");
488 if (equal_CStr(tabId, "tracker")) {
489 [myDel setTouchBarVariant:tracker_TouchBarVariant];
490 }
491 else if (equal_CStr(tabId, "sequence")) {
492 [myDel setTouchBarVariant:sequence_TouchBarVariant];
493 }
494 else if (equal_CStr(tabId, "trackertab")) {
495 [myDel setTouchBarVariant:wide_TouchBarVariant];
496 }
497 else {
498 [myDel setTouchBarVariant:default_TouchBarVariant];
499 }
500 }
501#endif
502}