chromium/components/remote_cocoa/app_shim/color_panel_bridge.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/remote_cocoa/app_shim/color_panel_bridge.h"

#import <Cocoa/Cocoa.h>

#include "skia/ext/skia_utils_mac.h"

namespace {
// The currently active bridge, to which the ColorPanelListener will forward
// its observations.
remote_cocoa::ColorPanelBridge* g_current_panel_bridge = nullptr;
}  // namespace

// A singleton listener class to act as a event target for NSColorPanel and
// send the results to the C++ class, ColorPanelBridge.
@interface ColorPanelListener : NSObject {
 @protected
  // We don't call DidChooseColor if the change wasn't caused by the user
  // interacting with the panel.
  BOOL _nonUserChange;
}
// Called from NSNotificationCenter.
- (void)windowWillClose:(NSNotification*)notification;

// Called from NSColorPanel.
- (void)didChooseColor:(NSColorPanel*)panel;

// The singleton instance.
+ (ColorPanelListener*)instance;

// Show the NSColorPanel.
- (void)showColorPanel;

// Sets color to the NSColorPanel as a non user change.
- (void)setColor:(NSColor*)color;
@end

@implementation ColorPanelListener
- (instancetype)init {
  if ((self = [super init])) {
    NSColorPanel* panel = [NSColorPanel sharedColorPanel];
    NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self
           selector:@selector(windowWillClose:)
               name:NSWindowWillCloseNotification
             object:panel];
    [nc addObserver:self
           selector:@selector(windowDidResignKey:)
               name:NSWindowDidResignKeyNotification
             object:panel];
  }
  return self;
}

- (void)dealloc {
  // This object is never freed.
  NOTREACHED_IN_MIGRATION();
}

- (void)windowWillClose:(NSNotification*)notification {
  if (g_current_panel_bridge)
    g_current_panel_bridge->host()->DidCloseColorPanel();
  _nonUserChange = NO;
}

- (void)windowDidResignKey:(NSNotification*)notification {
  // Close the color panel when the user clicks away.
  [self windowWillClose:notification];
  [[NSColorPanel sharedColorPanel] close];
}

- (void)didChooseColor:(NSColorPanel*)panel {
  if (_nonUserChange) {
    _nonUserChange = NO;
    return;
  }
  _nonUserChange = NO;
  NSColor* color = panel.color;
  if (color.type == NSColorTypeCatalog) {
    color = [color colorUsingColorSpace:NSColorSpace.genericRGBColorSpace];
    // Some colors in "Developer" palette in "Color Palettes" tab can't be
    // converted to RGB. We just ignore such colors.
    // TODO(tkent): We should notice the rejection to users.
    if (!color)
      return;
  }
  SkColor skColor = 0;
  if (color.colorSpace == NSColorSpace.genericRGBColorSpace) {
    // genericRGB -> deviceRGB conversion isn't ignorable.  We'd like to use RGB
    // values shown in NSColorPanel UI.
    CGFloat red, green, blue, alpha;
    [color getRed:&red green:&green blue:&blue alpha:&alpha];
    skColor = SkColorSetARGB(
        SkScalarRoundToInt(255.0 * alpha), SkScalarRoundToInt(255.0 * red),
        SkScalarRoundToInt(255.0 * green), SkScalarRoundToInt(255.0 * blue));
  } else {
    skColor = skia::NSDeviceColorToSkColor(
        [color colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace]);
  }
  if (g_current_panel_bridge)
    g_current_panel_bridge->host()->DidChooseColorInColorPanel(skColor);
}

+ (ColorPanelListener*)instance {
  static ColorPanelListener* listener = [[ColorPanelListener alloc] init];
  return listener;
}

- (void)showColorPanel {
  NSColorPanel* panel = [NSColorPanel sharedColorPanel];
  [panel setShowsAlpha:NO];
  [panel setTarget:self];
  [panel setAction:@selector(didChooseColor:)];
  [panel makeKeyAndOrderFront:nil];
}

- (void)setColor:(NSColor*)color {
  _nonUserChange = YES;
  [[NSColorPanel sharedColorPanel] setColor:color];
}
@end

namespace remote_cocoa {

ColorPanelBridge::ColorPanelBridge(
    mojo::PendingRemote<mojom::ColorPanelHost> host)
    : host_(std::move(host)) {
  g_current_panel_bridge = this;
}

ColorPanelBridge::~ColorPanelBridge() {
  if (g_current_panel_bridge == this)
    g_current_panel_bridge = nullptr;
}

void ColorPanelBridge::Show(uint32_t initial_color, ShowCallback callback) {
  ColorPanelListener* listener = [ColorPanelListener instance];
  [listener setColor:skia::SkColorToDeviceNSColor(initial_color)];
  [listener showColorPanel];
  std::move(callback).Run();
}

void ColorPanelBridge::SetSelectedColor(uint32_t color,
                                        SetSelectedColorCallback callback) {
  ColorPanelListener* listener = [ColorPanelListener instance];
  [listener setColor:skia::SkColorToDeviceNSColor(color)];
  std::move(callback).Run();
}

}  // namespace remote_cocoa