Skip to content

Instantly share code, notes, and snippets.

@snarisi snarisi/nsterm.m
Created Jun 12, 2019

Embed
What would you like to do?
/* NeXT/Open/GNUstep / macOS communication module. -*- coding: utf-8 -*-
Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2019 Free Software
Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
/*
Originally by Carl Edman
Updated by Christian Limpach (chris@nice.ch)
OpenStep/Rhapsody port by Scott Bender (sbender@harmony-ds.com)
macOS/Aqua port by Christophe de Dinechin (descubes@earthlink.net)
GNUstep port and post-20 update by Adrian Robert (arobert@cogsci.ucsd.edu)
*/
/* This should be the first include, as it may set up #defines affecting
interpretation of even the system includes. */
#include <config.h>
#include <fcntl.h>
#include <math.h>
#include <pthread.h>
#include <sys/types.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <stdbool.h>
#include <c-ctype.h>
#include <c-strcase.h>
#include <ftoastr.h>
#include "lisp.h"
#include "blockinput.h"
#include "sysselect.h"
#include "nsterm.h"
#include "systime.h"
#include "character.h"
#include "fontset.h"
#include "composite.h"
#include "ccl.h"
#include "termhooks.h"
#include "termchar.h"
#include "menu.h"
#include "window.h"
#include "keyboard.h"
#include "buffer.h"
#include "font.h"
#include "pdumper.h"
#ifdef NS_IMPL_GNUSTEP
#include "process.h"
#endif
#ifdef NS_IMPL_COCOA
#include "macfont.h"
#include <Carbon/Carbon.h>
#endif
static EmacsMenu *dockMenu;
#ifdef NS_IMPL_COCOA
static EmacsMenu *mainMenu;
#endif
/* ==========================================================================
NSTRACE, Trace support.
========================================================================== */
#if NSTRACE_ENABLED
/* The following use "volatile" since they can be accessed from
parallel threads. */
volatile int nstrace_num = 0;
volatile int nstrace_depth = 0;
/* When 0, no trace is emitted. This is used by NSTRACE_WHEN and
NSTRACE_UNLESS to silence functions called.
TODO: This should really be a thread-local variable, to avoid that
a function with disabled trace thread silence trace output in
another. However, in practice this seldom is a problem. */
volatile int nstrace_enabled_global = 1;
/* Called when nstrace_enabled goes out of scope. */
void nstrace_leave(int * pointer_to_nstrace_enabled)
{
if (*pointer_to_nstrace_enabled)
{
--nstrace_depth;
}
}
/* Called when nstrace_saved_enabled_global goes out of scope. */
void nstrace_restore_global_trace_state(int * pointer_to_saved_enabled_global)
{
nstrace_enabled_global = *pointer_to_saved_enabled_global;
}
char const * nstrace_fullscreen_type_name (int fs_type)
{
switch (fs_type)
{
case -1: return "-1";
case FULLSCREEN_NONE: return "FULLSCREEN_NONE";
case FULLSCREEN_WIDTH: return "FULLSCREEN_WIDTH";
case FULLSCREEN_HEIGHT: return "FULLSCREEN_HEIGHT";
case FULLSCREEN_BOTH: return "FULLSCREEN_BOTH";
case FULLSCREEN_MAXIMIZED: return "FULLSCREEN_MAXIMIZED";
default: return "FULLSCREEN_?????";
}
}
#endif
/* ==========================================================================
NSColor, EmacsColor category.
========================================================================== */
@implementation NSColor (EmacsColor)
+ (NSColor *)colorForEmacsRed:(CGFloat)red green:(CGFloat)green
blue:(CGFloat)blue alpha:(CGFloat)alpha
{
#if defined (NS_IMPL_COCOA) \
&& MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
if (ns_use_srgb_colorspace
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
&& [NSColor respondsToSelector:
@selector(colorWithSRGBRed:green:blue:alpha:)]
#endif
)
return [NSColor colorWithSRGBRed: red
green: green
blue: blue
alpha: alpha];
#endif
return [NSColor colorWithCalibratedRed: red
green: green
blue: blue
alpha: alpha];
}
- (NSColor *)colorUsingDefaultColorSpace
{
/* FIXME: We're checking for colorWithSRGBRed here so this will only
work in the same place as in the method above. It should really
be a check whether we're on macOS 10.7 or above. */
#if defined (NS_IMPL_COCOA) \
&& MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
if ([NSColor respondsToSelector:
@selector(colorWithSRGBRed:green:blue:alpha:)])
#endif
{
if (ns_use_srgb_colorspace)
return [self colorUsingColorSpace: [NSColorSpace sRGBColorSpace]];
else
return [self colorUsingColorSpace: [NSColorSpace deviceRGBColorSpace]];
}
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1070
else
#endif
#endif /* NS_IMPL_COCOA && MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 */
#if defined (NS_IMPL_GNUSTEP) || MAC_OS_X_VERSION_MIN_REQUIRED < 1070
return [self colorUsingColorSpaceName: NSCalibratedRGBColorSpace];
#endif
}
@end
/* ==========================================================================
Local declarations
========================================================================== */
/* Convert a symbol indexed with an NSxxx value to a value as defined
in keyboard.c (lispy_function_key). I hope this is a correct way
of doing things... */
static unsigned convert_ns_to_X_keysym[] =
{
NSHomeFunctionKey, 0x50,
NSLeftArrowFunctionKey, 0x51,
NSUpArrowFunctionKey, 0x52,
NSRightArrowFunctionKey, 0x53,
NSDownArrowFunctionKey, 0x54,
NSPageUpFunctionKey, 0x55,
NSPageDownFunctionKey, 0x56,
NSEndFunctionKey, 0x57,
NSBeginFunctionKey, 0x58,
NSSelectFunctionKey, 0x60,
NSPrintFunctionKey, 0x61,
NSClearLineFunctionKey, 0x0B,
NSExecuteFunctionKey, 0x62,
NSInsertFunctionKey, 0x63,
NSUndoFunctionKey, 0x65,
NSRedoFunctionKey, 0x66,
NSMenuFunctionKey, 0x67,
NSFindFunctionKey, 0x68,
NSHelpFunctionKey, 0x6A,
NSBreakFunctionKey, 0x6B,
NSF1FunctionKey, 0xBE,
NSF2FunctionKey, 0xBF,
NSF3FunctionKey, 0xC0,
NSF4FunctionKey, 0xC1,
NSF5FunctionKey, 0xC2,
NSF6FunctionKey, 0xC3,
NSF7FunctionKey, 0xC4,
NSF8FunctionKey, 0xC5,
NSF9FunctionKey, 0xC6,
NSF10FunctionKey, 0xC7,
NSF11FunctionKey, 0xC8,
NSF12FunctionKey, 0xC9,
NSF13FunctionKey, 0xCA,
NSF14FunctionKey, 0xCB,
NSF15FunctionKey, 0xCC,
NSF16FunctionKey, 0xCD,
NSF17FunctionKey, 0xCE,
NSF18FunctionKey, 0xCF,
NSF19FunctionKey, 0xD0,
NSF20FunctionKey, 0xD1,
NSF21FunctionKey, 0xD2,
NSF22FunctionKey, 0xD3,
NSF23FunctionKey, 0xD4,
NSF24FunctionKey, 0xD5,
NSBackspaceCharacter, 0x08, /* 8: Not on some KBs. */
NSDeleteCharacter, 0xFF, /* 127: Big 'delete' key upper right. */
NSDeleteFunctionKey, 0x9F, /* 63272: Del forw key off main array. */
NSTabCharacter, 0x09,
0x19, 0x09, /* left tab->regular since pass shift */
NSCarriageReturnCharacter, 0x0D,
NSNewlineCharacter, 0x0D,
NSEnterCharacter, 0x8D,
0x41|NSEventModifierFlagNumericPad, 0xAE, /* KP_Decimal */
0x43|NSEventModifierFlagNumericPad, 0xAA, /* KP_Multiply */
0x45|NSEventModifierFlagNumericPad, 0xAB, /* KP_Add */
0x4B|NSEventModifierFlagNumericPad, 0xAF, /* KP_Divide */
0x4E|NSEventModifierFlagNumericPad, 0xAD, /* KP_Subtract */
0x51|NSEventModifierFlagNumericPad, 0xBD, /* KP_Equal */
0x52|NSEventModifierFlagNumericPad, 0xB0, /* KP_0 */
0x53|NSEventModifierFlagNumericPad, 0xB1, /* KP_1 */
0x54|NSEventModifierFlagNumericPad, 0xB2, /* KP_2 */
0x55|NSEventModifierFlagNumericPad, 0xB3, /* KP_3 */
0x56|NSEventModifierFlagNumericPad, 0xB4, /* KP_4 */
0x57|NSEventModifierFlagNumericPad, 0xB5, /* KP_5 */
0x58|NSEventModifierFlagNumericPad, 0xB6, /* KP_6 */
0x59|NSEventModifierFlagNumericPad, 0xB7, /* KP_7 */
0x5B|NSEventModifierFlagNumericPad, 0xB8, /* KP_8 */
0x5C|NSEventModifierFlagNumericPad, 0xB9, /* KP_9 */
0x1B, 0x1B /* escape */
};
/* On macOS picks up the default NSGlobalDomain AppleAntiAliasingThreshold,
the maximum font size to NOT antialias. On GNUstep there is currently
no way to control this behavior. */
float ns_antialias_threshold;
NSArray *ns_send_types = 0, *ns_return_types = 0;
static NSArray *ns_drag_types = 0;
NSString *ns_app_name = @"Emacs"; /* default changed later */
/* Display variables */
struct ns_display_info *x_display_list; /* Chain of existing displays */
long context_menu_value = 0;
/* display update */
static int ns_window_num = 0;
static BOOL ns_fake_keydown = NO;
#ifdef NS_IMPL_COCOA
static BOOL ns_menu_bar_is_hidden = NO;
#endif
/* static int debug_lock = 0; */
/* event loop */
static BOOL send_appdefined = YES;
#define NO_APPDEFINED_DATA (-8)
static int last_appdefined_event_data = NO_APPDEFINED_DATA;
static NSTimer *timed_entry = 0;
static NSTimer *scroll_repeat_entry = nil;
static fd_set select_readfds, select_writefds;
enum { SELECT_HAVE_READ = 1, SELECT_HAVE_WRITE = 2, SELECT_HAVE_TMO = 4 };
static int select_nfds = 0, select_valid = 0;
static struct timespec select_timeout = { 0, 0 };
static int selfds[2] = { -1, -1 };
static pthread_mutex_t select_mutex;
static NSAutoreleasePool *outerpool;
static struct input_event *emacs_event = NULL;
static struct input_event *q_event_ptr = NULL;
static int n_emacs_events_pending = 0;
static NSMutableArray *ns_pending_files, *ns_pending_service_names,
*ns_pending_service_args;
static BOOL ns_do_open_file = NO;
static BOOL ns_last_use_native_fullscreen;
/* Non-zero means that a HELP_EVENT has been generated since Emacs
start. */
static BOOL any_help_event_p = NO;
static struct {
struct input_event *q;
int nr, cap;
} hold_event_q = {
NULL, 0, 0
};
#ifdef NS_IMPL_COCOA
/*
* State for pending menu activation:
* MENU_NONE Normal state
* MENU_PENDING A menu has been clicked on, but has been canceled so we can
* run lisp to update the menu.
* MENU_OPENING Menu is up to date, and the click event is redone so the menu
* will open.
*/
#define MENU_NONE 0
#define MENU_PENDING 1
#define MENU_OPENING 2
static int menu_will_open_state = MENU_NONE;
/* Saved position for menu click. */
static CGPoint menu_mouse_point;
#endif
/* Convert modifiers in a NeXTstep event to emacs style modifiers. */
#define NS_FUNCTION_KEY_MASK 0x800000
#define NSLeftControlKeyMask (0x000001 | NSEventModifierFlagControl)
#define NSRightControlKeyMask (0x002000 | NSEventModifierFlagControl)
#define NSLeftCommandKeyMask (0x000008 | NSEventModifierFlagCommand)
#define NSRightCommandKeyMask (0x000010 | NSEventModifierFlagCommand)
#define NSLeftAlternateKeyMask (0x000020 | NSEventModifierFlagOption)
#define NSRightAlternateKeyMask (0x000040 | NSEventModifierFlagOption)
static unsigned int
ev_modifiers_helper (unsigned int flags, unsigned int left_mask,
unsigned int right_mask, unsigned int either_mask,
Lisp_Object left_modifier, Lisp_Object right_modifier)
{
unsigned int modifiers = 0;
if (flags & either_mask)
{
BOOL left_key = (flags & left_mask) == left_mask;
BOOL right_key = (flags & right_mask) == right_mask
&& ! EQ (right_modifier, Qleft);
if (right_key)
modifiers |= parse_solitary_modifier (right_modifier);
/* GNUstep (and possibly macOS in certain circumstances) doesn't
differentiate between the left and right keys, so if we can't
identify which key it is, we use the left key setting. */
if (left_key || ! right_key)
modifiers |= parse_solitary_modifier (left_modifier);
}
return modifiers;
}
#define EV_MODIFIERS2(flags) \
(((flags & NSEventModifierFlagHelp) ? \
hyper_modifier : 0) \
| ((flags & NSEventModifierFlagShift) ? \
shift_modifier : 0) \
| ((flags & NS_FUNCTION_KEY_MASK) ? \
parse_solitary_modifier (ns_function_modifier) : 0) \
| ev_modifiers_helper (flags, NSLeftControlKeyMask, \
NSRightControlKeyMask, \
NSEventModifierFlagControl, \
ns_control_modifier, \
ns_right_control_modifier) \
| ev_modifiers_helper (flags, NSLeftCommandKeyMask, \
NSRightCommandKeyMask, \
NSEventModifierFlagCommand, \
ns_command_modifier, \
ns_right_command_modifier) \
| ev_modifiers_helper (flags, NSLeftAlternateKeyMask, \
NSRightAlternateKeyMask, \
NSEventModifierFlagOption, \
ns_alternate_modifier, \
ns_right_alternate_modifier))
#define EV_MODIFIERS(e) EV_MODIFIERS2 ([e modifierFlags])
#define EV_UDMODIFIERS(e) \
((([e type] == NSEventTypeLeftMouseDown) ? down_modifier : 0) \
| (([e type] == NSEventTypeRightMouseDown) ? down_modifier : 0) \
| (([e type] == NSEventTypeOtherMouseDown) ? down_modifier : 0) \
| (([e type] == NSEventTypeLeftMouseDragged) ? down_modifier : 0) \
| (([e type] == NSEventTypeRightMouseDragged) ? down_modifier : 0) \
| (([e type] == NSEventTypeOtherMouseDragged) ? down_modifier : 0) \
| (([e type] == NSEventTypeLeftMouseUp) ? up_modifier : 0) \
| (([e type] == NSEventTypeRightMouseUp) ? up_modifier : 0) \
| (([e type] == NSEventTypeOtherMouseUp) ? up_modifier : 0))
#define EV_BUTTON(e) \
((([e type] == NSEventTypeLeftMouseDown) || ([e type] == NSEventTypeLeftMouseUp)) ? 0 : \
(([e type] == NSEventTypeRightMouseDown) || ([e type] == NSEventTypeRightMouseUp)) ? 2 : \
[e buttonNumber] - 1)
/* Convert the time field to a timestamp in milliseconds. */
#define EV_TIMESTAMP(e) ([e timestamp] * 1000)
/* This is a piece of code which is common to all the event handling
methods. Maybe it should even be a function. */
#define EV_TRAILER(e) \
{ \
XSETFRAME (emacs_event->frame_or_window, emacsframe); \
EV_TRAILER2 (e); \
}
#define EV_TRAILER2(e) \
{ \
if (e) emacs_event->timestamp = EV_TIMESTAMP (e); \
if (q_event_ptr) \
{ \
Lisp_Object tem = Vinhibit_quit; \
Vinhibit_quit = Qt; \
n_emacs_events_pending++; \
kbd_buffer_store_event_hold (emacs_event, q_event_ptr); \
Vinhibit_quit = tem; \
} \
else \
hold_event (emacs_event); \
EVENT_INIT (*emacs_event); \
ns_send_appdefined (-1); \
}
/* These flags will be OR'd or XOR'd with the NSWindow's styleMask
property depending on what we're doing. */
#define FRAME_UNDECORATED_FLAGS (NSWindowStyleMaskResizable \
| NSWindowStyleMaskMiniaturizable \
| NSWindowStyleMaskClosable)
/* TODO: Get rid of need for these forward declarations. */
static void ns_condemn_scroll_bars (struct frame *f);
static void ns_judge_scroll_bars (struct frame *f);
/* ==========================================================================
Utilities
========================================================================== */
void
ns_init_events (struct input_event *ev)
{
EVENT_INIT (*ev);
emacs_event = ev;
}
void
ns_finish_events (void)
{
emacs_event = NULL;
}
static void
hold_event (struct input_event *event)
{
if (hold_event_q.nr == hold_event_q.cap)
{
if (hold_event_q.cap == 0) hold_event_q.cap = 10;
else hold_event_q.cap *= 2;
hold_event_q.q =
xrealloc (hold_event_q.q, hold_event_q.cap * sizeof *hold_event_q.q);
}
hold_event_q.q[hold_event_q.nr++] = *event;
/* Make sure ns_read_socket is called, i.e. we have input. */
raise (SIGIO);
send_appdefined = YES;
}
static Lisp_Object
append2 (Lisp_Object list, Lisp_Object item)
/* --------------------------------------------------------------------------
Utility to append to a list
-------------------------------------------------------------------------- */
{
return nconc2 (list, list (item));
}
const char *
ns_etc_directory (void)
/* If running as a self-contained app bundle, return as a string the
filename of the etc directory, if present; else nil. */
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *resourceDir = [bundle resourcePath];
NSString *resourcePath;
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir;
resourcePath = [resourceDir stringByAppendingPathComponent: @"etc"];
if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir])
{
if (isDir) return [resourcePath UTF8String];
}
return NULL;
}
const char *
ns_exec_path (void)
/* If running as a self-contained app bundle, return as a path string
the filenames of the libexec and bin directories, ie libexec:bin.
Otherwise, return nil.
Normally, Emacs does not add its own bin/ directory to the PATH.
However, a self-contained NS build has a different layout, with
bin/ and libexec/ subdirectories in the directory that contains
Emacs.app itself.
We put libexec first, because init_callproc_1 uses the first
element to initialize exec-directory. An alternative would be
for init_callproc to check for invocation-directory/libexec.
*/
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *resourceDir = [bundle resourcePath];
NSString *binDir = [bundle bundlePath];
NSString *resourcePath, *resourcePaths;
NSRange range;
NSString *pathSeparator = [NSString stringWithFormat: @"%c", SEPCHAR];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths;
NSEnumerator *pathEnum;
BOOL isDir;
range = [resourceDir rangeOfString: @"Contents"];
if (range.location != NSNotFound)
{
binDir = [binDir stringByAppendingPathComponent: @"Contents"];
#ifdef NS_IMPL_COCOA
binDir = [binDir stringByAppendingPathComponent: @"MacOS"];
#endif
}
paths = [binDir stringsByAppendingPaths:
[NSArray arrayWithObjects: @"libexec", @"bin", nil]];
pathEnum = [paths objectEnumerator];
resourcePaths = @"";
while ((resourcePath = [pathEnum nextObject]))
{
if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir])
if (isDir)
{
if ([resourcePaths length] > 0)
resourcePaths
= [resourcePaths stringByAppendingString: pathSeparator];
resourcePaths
= [resourcePaths stringByAppendingString: resourcePath];
}
}
if ([resourcePaths length] > 0) return [resourcePaths UTF8String];
return NULL;
}
const char *
ns_load_path (void)
/* If running as a self-contained app bundle, return as a path string
the filenames of the site-lisp and lisp directories.
Ie, site-lisp:lisp. Otherwise, return nil. */
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *resourceDir = [bundle resourcePath];
NSString *resourcePath, *resourcePaths;
NSString *pathSeparator = [NSString stringWithFormat: @"%c", SEPCHAR];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir;
NSArray *paths = [resourceDir stringsByAppendingPaths:
[NSArray arrayWithObjects:
@"site-lisp", @"lisp", nil]];
NSEnumerator *pathEnum = [paths objectEnumerator];
resourcePaths = @"";
/* Hack to skip site-lisp. */
if (no_site_lisp) resourcePath = [pathEnum nextObject];
while ((resourcePath = [pathEnum nextObject]))
{
if ([fileManager fileExistsAtPath: resourcePath isDirectory: &isDir])
if (isDir)
{
if ([resourcePaths length] > 0)
resourcePaths
= [resourcePaths stringByAppendingString: pathSeparator];
resourcePaths
= [resourcePaths stringByAppendingString: resourcePath];
}
}
if ([resourcePaths length] > 0) return [resourcePaths UTF8String];
return NULL;
}
void
ns_init_locale (void)
/* macOS doesn't set any environment variables for the locale when run
from the GUI. Get the locale from the OS and set LANG. */
{
NSLocale *locale = [NSLocale currentLocale];
NSTRACE ("ns_init_locale");
@try
{
/* It seems macOS should probably use UTF-8 everywhere.
'localeIdentifier' does not specify the encoding, and I can't
find any way to get the OS to tell us which encoding to use,
so hard-code '.UTF-8'. */
NSString *localeID = [NSString stringWithFormat:@"%@.UTF-8",
[locale localeIdentifier]];
/* Set LANG to locale, but not if LANG is already set. */
setenv("LANG", [localeID UTF8String], 0);
}
@catch (NSException *e)
{
NSLog (@"Locale detection failed: %@: %@", [e name], [e reason]);
}
}
void
ns_release_object (void *obj)
/* --------------------------------------------------------------------------
Release an object (callable from C)
-------------------------------------------------------------------------- */
{
[(id)obj release];
}
void
ns_retain_object (void *obj)
/* --------------------------------------------------------------------------
Retain an object (callable from C)
-------------------------------------------------------------------------- */
{
[(id)obj retain];
}
void *
ns_alloc_autorelease_pool (void)
/* --------------------------------------------------------------------------
Allocate a pool for temporary objects (callable from C)
-------------------------------------------------------------------------- */
{
return [[NSAutoreleasePool alloc] init];
}
void
ns_release_autorelease_pool (void *pool)
/* --------------------------------------------------------------------------
Free a pool and temporary objects it refers to (callable from C)
-------------------------------------------------------------------------- */
{
ns_release_object (pool);
}
static BOOL
ns_menu_bar_should_be_hidden (void)
/* True, if the menu bar should be hidden. */
{
return !NILP (ns_auto_hide_menu_bar)
&& [NSApp respondsToSelector:@selector(setPresentationOptions:)];
}
struct EmacsMargins
{
CGFloat top;
CGFloat bottom;
CGFloat left;
CGFloat right;
};
static struct EmacsMargins
ns_screen_margins (NSScreen *screen)
/* The parts of SCREEN used by the operating system. */
{
NSTRACE ("ns_screen_margins");
struct EmacsMargins margins;
NSRect screenFrame = [screen frame];
NSRect screenVisibleFrame = [screen visibleFrame];
/* Sometimes, visibleFrame isn't up-to-date with respect to a hidden
menu bar, check this explicitly. */
if (ns_menu_bar_should_be_hidden())
{
margins.top = 0;
}
else
{
CGFloat frameTop = screenFrame.origin.y + screenFrame.size.height;
CGFloat visibleFrameTop = (screenVisibleFrame.origin.y
+ screenVisibleFrame.size.height);
margins.top = frameTop - visibleFrameTop;
}
{
CGFloat frameRight = screenFrame.origin.x + screenFrame.size.width;
CGFloat visibleFrameRight = (screenVisibleFrame.origin.x
+ screenVisibleFrame.size.width);
margins.right = frameRight - visibleFrameRight;
}
margins.bottom = screenVisibleFrame.origin.y - screenFrame.origin.y;
margins.left = screenVisibleFrame.origin.x - screenFrame.origin.x;
NSTRACE_MSG ("left:%g right:%g top:%g bottom:%g",
margins.left,
margins.right,
margins.top,
margins.bottom);
return margins;
}
/* A screen margin between 1 and DOCK_IGNORE_LIMIT (inclusive) is
assumed to contain a hidden dock. macOS currently use 4 pixels for
this, however, to be future compatible, a larger value is used. */
#define DOCK_IGNORE_LIMIT 6
static struct EmacsMargins
ns_screen_margins_ignoring_hidden_dock (NSScreen *screen)
/* The parts of SCREEN used by the operating system, excluding the parts
reserved for a hidden dock. */
{
NSTRACE ("ns_screen_margins_ignoring_hidden_dock");
struct EmacsMargins margins = ns_screen_margins(screen);
/* macOS (currently) reserved 4 pixels along the edge where a hidden
dock is located. Unfortunately, it's not possible to find the
location and information about if the dock is hidden. Instead,
it is assumed that if the margin of an edge is less than
DOCK_IGNORE_LIMIT, it contains a hidden dock. */
if (margins.left <= DOCK_IGNORE_LIMIT)
{
margins.left = 0;
}
if (margins.right <= DOCK_IGNORE_LIMIT)
{
margins.right = 0;
}
if (margins.top <= DOCK_IGNORE_LIMIT)
{
margins.top = 0;
}
/* Note: This doesn't occur in current versions of macOS, but
included for completeness and future compatibility. */
if (margins.bottom <= DOCK_IGNORE_LIMIT)
{
margins.bottom = 0;
}
NSTRACE_MSG ("left:%g right:%g top:%g bottom:%g",
margins.left,
margins.right,
margins.top,
margins.bottom);
return margins;
}
static CGFloat
ns_menu_bar_height (NSScreen *screen)
/* The height of the menu bar, if visible.
Note: Don't use this when fullscreen is enabled -- the screen
sometimes includes, sometimes excludes the menu bar area. */
{
struct EmacsMargins margins = ns_screen_margins(screen);
CGFloat res = margins.top;
NSTRACE ("ns_menu_bar_height " NSTRACE_FMT_RETURN " %.0f", res);
return res;
}
static NSRect
ns_row_rect (struct window *w, struct glyph_row *row,
enum glyph_row_area area)
/* Get the row as an NSRect. */
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
NSRect rect;
int window_x, window_y, window_width;
window_box (w, area, &window_x, &window_y, &window_width, 0);
rect.origin.x = window_x;
rect.origin.y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, row->y));
rect.origin.y = max (rect.origin.y, window_y);
rect.size.width = window_width;
rect.size.height = row->visible_height;
return rect;
}
/* ==========================================================================
Focus (clipping) and screen update
========================================================================== */
//
// Window constraining
// -------------------
//
// To ensure that the windows are not placed under the menu bar, they
// are typically moved by the call-back constrainFrameRect. However,
// by overriding it, it's possible to inhibit this, leaving the window
// in it's original position.
//
// It's possible to hide the menu bar. However, technically, it's only
// possible to hide it when the application is active. To ensure that
// this work properly, the menu bar and window constraining are
// deferred until the application becomes active.
//
// Even though it's not possible to manually move a window above the
// top of the screen, it is allowed if it's done programmatically,
// when the menu is hidden. This allows the editable area to cover the
// full screen height.
//
// Test cases
// ----------
//
// Use the following extra files:
//
// init.el:
// ;; Hide menu and place frame slightly above the top of the screen.
// (setq ns-auto-hide-menu-bar t)
// (set-frame-position (selected-frame) 0 -20)
//
// Test 1:
//
// emacs -Q -l init.el
//
// Result: No menu bar, and the title bar should be above the screen.
//
// Test 2:
//
// emacs -Q
//
// Result: Menu bar visible, frame placed immediately below the menu.
//
static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
{
NSTRACE ("constrain_frame_rect(" NSTRACE_FMT_RECT ")",
NSTRACE_ARG_RECT (frameRect));
// --------------------
// Collect information about the screen the frame is covering.
//
NSArray *screens = [NSScreen screens];
NSUInteger nr_screens = [screens count];
int i;
// The height of the menu bar, if present in any screen the frame is
// displayed in.
int menu_bar_height = 0;
// A rectangle covering all the screen the frame is displayed in.
NSRect multiscreenRect = NSMakeRect(0, 0, 0, 0);
for (i = 0; i < nr_screens; ++i )
{
NSScreen *s = [screens objectAtIndex: i];
NSRect scrRect = [s frame];
NSTRACE_MSG ("Screen %d: " NSTRACE_FMT_RECT,
i, NSTRACE_ARG_RECT (scrRect));
if (NSIntersectionRect (frameRect, scrRect).size.height != 0)
{
multiscreenRect = NSUnionRect (multiscreenRect, scrRect);
if (!isFullscreen)
{
CGFloat screen_menu_bar_height = ns_menu_bar_height (s);
menu_bar_height = max(menu_bar_height, screen_menu_bar_height);
}
}
}
NSTRACE_RECT ("multiscreenRect", multiscreenRect);
NSTRACE_MSG ("menu_bar_height: %d", menu_bar_height);
if (multiscreenRect.size.width == 0
|| multiscreenRect.size.height == 0)
{
// Failed to find any monitor, give up.
NSTRACE_MSG ("multiscreenRect empty");
NSTRACE_RETURN_RECT (frameRect);
return frameRect;
}
// --------------------
// Find a suitable placement.
//
if (ns_menu_bar_should_be_hidden())
{
// When the menu bar is hidden, the user may place part of the
// frame above the top of the screen, for example to hide the
// title bar.
//
// Hence, keep the original position.
}
else
{
// Ensure that the frame is below the menu bar, or below the top
// of the screen.
//
// This assume that the menu bar is placed at the top in the
// rectangle that covers the monitors. (It doesn't have to be,
// but if it's not it's hard to do anything useful.)
CGFloat topOfWorkArea = (multiscreenRect.origin.y
+ multiscreenRect.size.height
- menu_bar_height);
CGFloat topOfFrame = frameRect.origin.y + frameRect.size.height;
if (topOfFrame > topOfWorkArea)
{
frameRect.origin.y -= topOfFrame - topOfWorkArea;
NSTRACE_RECT ("After placement adjust", frameRect);
}
}
// Include the following section to restrict frame to the screens.
// (If so, update it to allow the frame to stretch down below the
// screen.)
#if 0
// --------------------
// Ensure frame doesn't stretch below the screens.
//
CGFloat diff = multiscreenRect.origin.y - frameRect.origin.y;
if (diff > 0)
{
frameRect.origin.y = multiscreenRect.origin.y;
frameRect.size.height -= diff;
}
#endif
NSTRACE_RETURN_RECT (frameRect);
return frameRect;
}
static void
ns_constrain_all_frames (void)
/* --------------------------------------------------------------------------
Ensure that the menu bar doesn't cover any frames.
-------------------------------------------------------------------------- */
{
Lisp_Object tail, frame;
NSTRACE ("ns_constrain_all_frames");
block_input ();
FOR_EACH_FRAME (tail, frame)
{
struct frame *f = XFRAME (frame);
if (FRAME_NS_P (f))
{
EmacsView *view = FRAME_NS_VIEW (f);
if (![view isFullscreen])
{
[[view window]
setFrame:constrain_frame_rect([[view window] frame], false)
display:NO];
}
}
}
unblock_input ();
}
static void
ns_update_auto_hide_menu_bar (void)
/* --------------------------------------------------------------------------
Show or hide the menu bar, based on user setting.
-------------------------------------------------------------------------- */
{
#ifdef NS_IMPL_COCOA
NSTRACE ("ns_update_auto_hide_menu_bar");
block_input ();
if (NSApp != nil && [NSApp isActive])
{
// Note, "setPresentationOptions" triggers an error unless the
// application is active.
BOOL menu_bar_should_be_hidden = ns_menu_bar_should_be_hidden ();
if (menu_bar_should_be_hidden != ns_menu_bar_is_hidden)
{
NSApplicationPresentationOptions options
= NSApplicationPresentationDefault;
if (menu_bar_should_be_hidden)
options |= NSApplicationPresentationAutoHideMenuBar
| NSApplicationPresentationAutoHideDock;
[NSApp setPresentationOptions: options];
ns_menu_bar_is_hidden = menu_bar_should_be_hidden;
if (!ns_menu_bar_is_hidden)
{
ns_constrain_all_frames ();
}
}
}
unblock_input ();
#endif
}
static void
ns_update_begin (struct frame *f)
/* --------------------------------------------------------------------------
Prepare for a grouped sequence of drawing calls
external (RIF) call; whole frame, called before gui_update_window_begin
-------------------------------------------------------------------------- */
{
#ifdef NS_IMPL_COCOA
EmacsView *view = FRAME_NS_VIEW (f);
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_begin");
ns_update_auto_hide_menu_bar ();
if ([view isFullscreen] && [view fsIsNative])
{
// Fix reappearing tool bar in fullscreen for Mac OS X 10.7
BOOL tbar_visible = FRAME_EXTERNAL_TOOL_BAR (f) ? YES : NO;
NSToolbar *toolbar = [FRAME_NS_VIEW (f) toolbar];
if (! tbar_visible != ! [toolbar isVisible])
[toolbar setVisible: tbar_visible];
}
#endif
}
static void
ns_update_end (struct frame *f)
/* --------------------------------------------------------------------------
Finished a grouped sequence of drawing calls
external (RIF) call; for whole frame, called after gui_update_window_end
-------------------------------------------------------------------------- */
{
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_end");
/* if (f == MOUSE_HL_INFO (f)->mouse_face_mouse_frame) */
MOUSE_HL_INFO (f)->mouse_face_defer = 0;
}
static BOOL
ns_clip_to_rect (struct frame *f, NSRect *r, int n)
/* --------------------------------------------------------------------------
Clip the drawing area to rectangle r in frame f. If drawing is not
currently possible mark r as dirty and return NO, otherwise return
YES.
-------------------------------------------------------------------------- */
{
NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_clip_to_rect");
if (r)
{
NSTRACE_RECT ("r", *r);
if ([NSView focusView] == FRAME_NS_VIEW (f))
{
[[NSGraphicsContext currentContext] saveGraphicsState];
if (n == 2)
NSRectClipList (r, 2);
else
NSRectClip (*r);
return YES;
}
else
{
NSView *view = FRAME_NS_VIEW (f);
int i;
for (i = 0 ; i < n ; i++)
[view setNeedsDisplayInRect:r[i]];
}
}
return NO;
}
static void
ns_reset_clipping (struct frame *f)
/* Internal: Restore the previous graphics state, unsetting any
clipping areas. */
{
NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_reset_clipping");
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
/* ==========================================================================
Visible bell and beep.
========================================================================== */
// This bell implementation shows the visual bell image asynchronously
// from the rest of Emacs. This is done by adding a NSView to the
// superview of the Emacs window and removing it using a timer.
//
// Unfortunately, some Emacs operations, like scrolling, is done using
// low-level primitives that copy the content of the window, including
// the bell image. To some extent, this is handled by removing the
// image prior to scrolling and marking that the window is in need for
// redisplay.
//
// To test this code, make sure that there is no artifacts of the bell
// image in the following situations. Use a non-empty buffer (like the
// tutorial) to ensure that a scroll is performed:
//
// * Single-window: C-g C-v
//
// * Side-by-windows: C-x 3 C-g C-v
//
// * Windows above each other: C-x 2 C-g C-v
@interface EmacsBell : NSImageView
{
// Number of currently active bells.
unsigned int nestCount;
NSView * mView;
bool isAttached;
}
- (void)show:(NSView *)view;
- (void)hide;
- (void)remove;
@end
@implementation EmacsBell
- (id)init
{
NSTRACE ("[EmacsBell init]");
if ((self = [super init]))
{
nestCount = 0;
isAttached = false;
#ifdef NS_IMPL_GNUSTEP
// GNUstep doesn't provide named images. This was reported in
// 2011, see https://savannah.gnu.org/bugs/?33396
//
// As a drop in replacement, a semitransparent gray square is used.
self.image = [[NSImage alloc] initWithSize:NSMakeSize(32 * 5, 32 * 5)];
[self.image lockFocus];
[[NSColor colorForEmacsRed:0.5 green:0.5 blue:0.5 alpha:0.5] set];
NSRectFill(NSMakeRect(0, 0, 32, 32));
[self.image unlockFocus];
#else
self.image = [NSImage imageNamed:NSImageNameCaution];
[self.image setSize:NSMakeSize(self.image.size.width * 5,
self.image.size.height * 5)];
#endif
}
return self;
}
- (void)show:(NSView *)view
{
NSTRACE ("[EmacsBell show:]");
NSTRACE_MSG ("nestCount: %u", nestCount);
// Show the image, unless it's already shown.
if (nestCount == 0)
{
NSRect rect = [view bounds];
NSPoint pos;
pos.x = rect.origin.x + (rect.size.width - self.image.size.width )/2;
pos.y = rect.origin.y + (rect.size.height - self.image.size.height)/2;
[self setFrameOrigin:pos];
[self setFrameSize:self.image.size];
isAttached = true;
mView = view;
[[[view window] contentView] addSubview:self
positioned:NSWindowAbove
relativeTo:nil];
}
++nestCount;
[self performSelector:@selector(hide) withObject:self afterDelay:0.5];
}
- (void)hide
{
// Note: Trace output from this method isn't shown, reason unknown.
// NSTRACE ("[EmacsBell hide]");
if (nestCount > 0)
--nestCount;
// Remove the image once the last bell became inactive.
if (nestCount == 0)
{
[self remove];
}
}
-(void)remove
{
NSTRACE ("[EmacsBell remove]");
if (isAttached)
{
NSTRACE_MSG ("removeFromSuperview");
[self removeFromSuperview];
mView.needsDisplay = YES;
isAttached = false;
}
}
@end
static EmacsBell * bell_view = nil;
static void
ns_ring_bell (struct frame *f)
/* --------------------------------------------------------------------------
"Beep" routine
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_ring_bell");
if (visible_bell)
{
struct frame *frame = SELECTED_FRAME ();
NSView *view;
if (bell_view == nil)
{
bell_view = [[EmacsBell alloc] init];
[bell_view retain];
}
block_input ();
view = FRAME_NS_VIEW (frame);
if (view != nil)
{
[bell_view show:view];
}
unblock_input ();
}
else
{
NSBeep ();
}
}
static void
hide_bell (void)
/* --------------------------------------------------------------------------
Ensure the bell is hidden.
-------------------------------------------------------------------------- */
{
NSTRACE ("hide_bell");
if (bell_view != nil)
{
[bell_view remove];
}
}
/* ==========================================================================
Frame / window manager related functions
========================================================================== */
static Lisp_Object
ns_get_focus_frame (struct frame *f)
/* --------------------------------------------------------------------------
External (hook)
-------------------------------------------------------------------------- */
{
Lisp_Object lisp_focus;
struct frame *focus = FRAME_DISPLAY_INFO (f)->ns_focus_frame;
if (!focus)
return Qnil;
XSETFRAME (lisp_focus, focus);
return lisp_focus;
}
static void
ns_focus_frame (struct frame *f, bool noactivate)
/* --------------------------------------------------------------------------
External (hook)
-------------------------------------------------------------------------- */
{
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
if (dpyinfo->ns_focus_frame != f)
{
EmacsView *view = FRAME_NS_VIEW (f);
block_input ();
[NSApp activateIgnoringOtherApps: YES];
[[view window] makeKeyAndOrderFront: view];
unblock_input ();
}
}
static void
ns_raise_frame (struct frame *f, BOOL make_key)
/* --------------------------------------------------------------------------
Bring window to foreground and if make_key is YES, give it focus.
-------------------------------------------------------------------------- */
{
NSView *view;
check_window_system (f);
view = FRAME_NS_VIEW (f);
block_input ();
if (FRAME_VISIBLE_P (f))
{
if (make_key)
[[view window] makeKeyAndOrderFront: NSApp];
else
[[view window] orderFront: NSApp];
}
unblock_input ();
}
static void
ns_lower_frame (struct frame *f)
/* --------------------------------------------------------------------------
Send window to back
-------------------------------------------------------------------------- */
{
NSView *view;
check_window_system (f);
view = FRAME_NS_VIEW (f);
block_input ();
[[view window] orderBack: NSApp];
unblock_input ();
}
static void
ns_frame_raise_lower (struct frame *f, bool raise)
/* --------------------------------------------------------------------------
External (hook)
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_frame_raise_lower");
if (raise)
ns_raise_frame (f, YES);
else
ns_lower_frame (f);
}
static void ns_set_frame_alpha (struct frame *f);
static void
ns_frame_rehighlight (struct frame *frame)
/* --------------------------------------------------------------------------
External (hook): called on things like window switching within frame
-------------------------------------------------------------------------- */
{
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
struct frame *old_highlight = dpyinfo->highlight_frame;
NSTRACE ("ns_frame_rehighlight");
if (dpyinfo->ns_focus_frame)
{
dpyinfo->highlight_frame
= (FRAMEP (FRAME_FOCUS_FRAME (dpyinfo->ns_focus_frame))
? XFRAME (FRAME_FOCUS_FRAME (dpyinfo->ns_focus_frame))
: dpyinfo->ns_focus_frame);
if (!FRAME_LIVE_P (dpyinfo->highlight_frame))
{
fset_focus_frame (dpyinfo->ns_focus_frame, Qnil);
dpyinfo->highlight_frame = dpyinfo->ns_focus_frame;
}
}
else
dpyinfo->highlight_frame = 0;
if (dpyinfo->highlight_frame &&
dpyinfo->highlight_frame != old_highlight)
{
if (old_highlight)
{
gui_update_cursor (old_highlight, 1);
ns_set_frame_alpha (old_highlight);
}
if (dpyinfo->highlight_frame)
{
gui_update_cursor (dpyinfo->highlight_frame, 1);
ns_set_frame_alpha (dpyinfo->highlight_frame);
}
}
}
void
ns_make_frame_visible (struct frame *f)
/* --------------------------------------------------------------------------
External: Show the window (X11 semantics)
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_make_frame_visible");
/* XXX: at some points in past this was not needed, as the only place that
called this (frame.c:Fraise_frame ()) also called raise_lower;
if this ends up the case again, comment this out again. */
if (!FRAME_VISIBLE_P (f))
{
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSWindow *window = [view window];
SET_FRAME_VISIBLE (f, 1);
ns_raise_frame (f, ! FRAME_NO_FOCUS_ON_MAP (f));
/* Making a new frame from a fullscreen frame will make the new frame
fullscreen also. So skip handleFS as this will print an error. */
if ([view fsIsNative] && f->want_fullscreen == FULLSCREEN_BOTH
&& [view isFullscreen])
return;
if (f->want_fullscreen != FULLSCREEN_NONE)
{
block_input ();
[view handleFS];
unblock_input ();
}
/* Making a frame invisible seems to break the parent->child
relationship, so reinstate it. */
if ([window parentWindow] == nil && FRAME_PARENT_FRAME (f) != NULL)
{
NSWindow *parent = [FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)) window];
block_input ();
[parent addChildWindow: window
ordered: NSWindowAbove];
unblock_input ();
/* If the parent frame moved while the child frame was
invisible, the child frame's position won't have been
updated. Make sure it's in the right place now. */
ns_set_offset(f, f->left_pos, f->top_pos, 0);
}
}
}
static void
ns_make_frame_invisible (struct frame *f)
/* --------------------------------------------------------------------------
Hide the window (X11 semantics)
-------------------------------------------------------------------------- */
{
NSView *view;
NSTRACE ("ns_make_frame_invisible");
check_window_system (f);
view = FRAME_NS_VIEW (f);
[[view window] orderOut: NSApp];
SET_FRAME_VISIBLE (f, 0);
SET_FRAME_ICONIFIED (f, 0);
}
static void
ns_make_frame_visible_invisible (struct frame *f, bool visible)
/* --------------------------------------------------------------------------
External (hook)
-------------------------------------------------------------------------- */
{
if (visible)
ns_make_frame_visible (f);
else
ns_make_frame_invisible (f);
}
void
ns_iconify_frame (struct frame *f)
/* --------------------------------------------------------------------------
External (hook): Iconify window
-------------------------------------------------------------------------- */
{
NSView *view;
struct ns_display_info *dpyinfo;
NSTRACE ("ns_iconify_frame");
check_window_system (f);
view = FRAME_NS_VIEW (f);
dpyinfo = FRAME_DISPLAY_INFO (f);
if (dpyinfo->highlight_frame == f)
dpyinfo->highlight_frame = 0;
if ([[view window] windowNumber] <= 0)
{
/* The window is still deferred. Make it very small, bring it
on screen and order it out. */
NSRect s = { { 100, 100}, {0, 0} };
NSRect t;
t = [[view window] frame];
[[view window] setFrame: s display: NO];
[[view window] orderBack: NSApp];
[[view window] orderOut: NSApp];
[[view window] setFrame: t display: NO];
}
/* Processing input while Emacs is being minimized can cause a
crash, so block it for the duration. */
block_input();
[[view window] miniaturize: NSApp];
unblock_input();
}
/* Free resources of frame F. */
void
ns_free_frame_resources (struct frame *f)
{
NSView *view;
struct ns_display_info *dpyinfo;
Mouse_HLInfo *hlinfo;
NSTRACE ("ns_free_frame_resources");
check_window_system (f);
view = FRAME_NS_VIEW (f);
dpyinfo = FRAME_DISPLAY_INFO (f);
hlinfo = MOUSE_HL_INFO (f);
[(EmacsView *)view setWindowClosing: YES]; /* may not have been informed */
block_input ();
free_frame_menubar (f);
free_frame_faces (f);
if (f == dpyinfo->ns_focus_frame)
dpyinfo->ns_focus_frame = 0;
if (f == dpyinfo->highlight_frame)
dpyinfo->highlight_frame = 0;
if (f == hlinfo->mouse_face_mouse_frame)
reset_mouse_highlight (hlinfo);
if (f->output_data.ns->miniimage != nil)
[f->output_data.ns->miniimage release];
[[view window] close];
[view release];
xfree (f->output_data.ns);
unblock_input ();
}
static void
ns_destroy_window (struct frame *f)
/* --------------------------------------------------------------------------
External: Delete the window
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_destroy_window");
/* If this frame has a parent window, detach it as not doing so can
cause a crash in GNUStep. */
if (FRAME_PARENT_FRAME (f) != NULL)
{
NSWindow *child = [FRAME_NS_VIEW (f) window];
NSWindow *parent = [FRAME_NS_VIEW (FRAME_PARENT_FRAME (f)) window];
[parent removeChildWindow: child];
}
check_window_system (f);
ns_free_frame_resources (f);
ns_window_num--;
}
void
ns_set_offset (struct frame *f, int xoff, int yoff, int change_grav)
/* --------------------------------------------------------------------------
External: Position the window
-------------------------------------------------------------------------- */
{
NSView *view = FRAME_NS_VIEW (f);
NSScreen *screen = [[view window] screen];
NSTRACE ("ns_set_offset");
block_input ();
f->left_pos = xoff;
f->top_pos = yoff;
if (view != nil)
{
if (FRAME_PARENT_FRAME (f) == NULL && screen)
{
f->left_pos = f->size_hint_flags & XNegative
? [screen visibleFrame].size.width + f->left_pos - FRAME_PIXEL_WIDTH (f)
: f->left_pos;
/* We use visibleFrame here to take menu bar into account.
Ideally we should also adjust left/top with visibleFrame.origin. */
f->top_pos = f->size_hint_flags & YNegative
? ([screen visibleFrame].size.height + f->top_pos
- FRAME_PIXEL_HEIGHT (f) - FRAME_NS_TITLEBAR_HEIGHT (f)
- FRAME_TOOLBAR_HEIGHT (f))
: f->top_pos;
#ifdef NS_IMPL_GNUSTEP
if (f->left_pos < 100)
f->left_pos = 100; /* don't overlap menu */
#endif
}
else if (FRAME_PARENT_FRAME (f) != NULL)
{
struct frame *parent = FRAME_PARENT_FRAME (f);
/* On X negative values for child frames always result in
positioning relative to the bottom right corner of the
parent frame. */
if (f->left_pos < 0)
f->left_pos = FRAME_PIXEL_WIDTH (parent) - FRAME_PIXEL_WIDTH (f) + f->left_pos;
if (f->top_pos < 0)
f->top_pos = FRAME_PIXEL_HEIGHT (parent) + FRAME_TOOLBAR_HEIGHT (parent)
- FRAME_PIXEL_HEIGHT (f) + f->top_pos;
}
/* Constrain the setFrameTopLeftPoint so we don't move behind the
menu bar. */
NSPoint pt = NSMakePoint (SCREENMAXBOUND (f->left_pos
+ NS_PARENT_WINDOW_LEFT_POS (f)),
SCREENMAXBOUND (NS_PARENT_WINDOW_TOP_POS (f)
- f->top_pos));
NSTRACE_POINT ("setFrameTopLeftPoint", pt);
[[view window] setFrameTopLeftPoint: pt];
f->size_hint_flags &= ~(XNegative|YNegative);
}
unblock_input ();
}
static void
ns_set_window_size (struct frame *f,
bool change_gravity,
int width,
int height,
bool pixelwise)
/* --------------------------------------------------------------------------
Adjust window pixel size based on given character grid size
Impl is a bit more complex than other terms, need to do some
internal clipping.
-------------------------------------------------------------------------- */
{
EmacsView *view = FRAME_NS_VIEW (f);
NSWindow *window = [view window];
NSRect wr = [window frame];
int pixelwidth, pixelheight;
int orig_height = wr.size.height;
NSTRACE ("ns_set_window_size");
if (view == nil)
return;
NSTRACE_RECT ("current", wr);
NSTRACE_MSG ("Width:%d Height:%d Pixelwise:%d", width, height, pixelwise);
NSTRACE_MSG ("Font %d x %d", FRAME_COLUMN_WIDTH (f), FRAME_LINE_HEIGHT (f));
block_input ();
if (pixelwise)
{
pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
pixelheight = FRAME_TEXT_TO_PIXEL_HEIGHT (f, height);
}
else
{
pixelwidth = FRAME_TEXT_COLS_TO_PIXEL_WIDTH (f, width);
pixelheight = FRAME_TEXT_LINES_TO_PIXEL_HEIGHT (f, height);
}
wr.size.width = pixelwidth + f->border_width;
wr.size.height = pixelheight;
if (! [view isFullscreen])
wr.size.height += FRAME_NS_TITLEBAR_HEIGHT (f)
+ FRAME_TOOLBAR_HEIGHT (f);
/* Do not try to constrain to this screen. We may have multiple
screens, and want Emacs to span those. Constraining to screen
prevents that, and that is not nice to the user. */
if (f->output_data.ns->zooming)
f->output_data.ns->zooming = 0;
else
wr.origin.y += orig_height - wr.size.height;
frame_size_history_add
(f, Qx_set_window_size_1, width, height,
list5 (Fcons (make_fixnum (pixelwidth), make_fixnum (pixelheight)),
Fcons (make_fixnum (wr.size.width), make_fixnum (wr.size.height)),
make_fixnum (f->border_width),
make_fixnum (FRAME_NS_TITLEBAR_HEIGHT (f)),
make_fixnum (FRAME_TOOLBAR_HEIGHT (f))));
[window setFrame: wr display: YES];
[view updateFrameSize: NO];
unblock_input ();
}
#ifdef NS_IMPL_COCOA
void
ns_set_undecorated (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* --------------------------------------------------------------------------
Set frame F's `undecorated' parameter. If non-nil, F's window-system
window is drawn without decorations, title, minimize/maximize boxes
and external borders. This usually means that the window cannot be
dragged, resized, iconified, maximized or deleted with the mouse. If
nil, draw the frame with all the elements listed above unless these
have been suspended via window manager settings.
GNUStep cannot change an existing window's style.
-------------------------------------------------------------------------- */
{
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSWindow *window = [view window];
NSTRACE ("ns_set_undecorated");
if (!EQ (new_value, old_value))
{
block_input ();
[window setToolbar: nil];
/* Do I need to release the toolbar here? */
FRAME_UNDECORATED (f) = true;
[window setStyleMask: (window.styleMask | FRAME_UNDECORATED_FLAGS)];
/* At this point it seems we don't have an active NSResponder,
so some key presses (TAB) are swallowed by the system. */
[window makeFirstResponder: view];
[view updateFrameSize: NO];
unblock_input ();
}
}
#endif /* NS_IMPL_COCOA */
void
ns_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* --------------------------------------------------------------------------
Set frame F's `parent-frame' parameter. If non-nil, make F a child
frame of the frame specified by that parameter. Technically, this
makes F's window-system window a child window of the parent frame's
window-system window. If nil, make F's window-system window a
top-level window--a child of its display's root window.
A child frame's `left' and `top' parameters specify positions
relative to the top-left corner of its parent frame's native
rectangle. On macOS moving a parent frame moves all its child
frames too, keeping their position relative to the parent
unaltered. When a parent frame is iconified or made invisible, its
child frames are made invisible. When a parent frame is deleted,
its child frames are deleted too.
Whether a child frame has a tool bar may be window-system or window
manager dependent. It's advisable to disable it via the frame
parameter settings.
Some window managers may not honor this parameter.
-------------------------------------------------------------------------- */
{
struct frame *p = NULL;
NSWindow *parent, *child;
NSTRACE ("ns_set_parent_frame");
if (!NILP (new_value)
&& (!FRAMEP (new_value)
|| !FRAME_LIVE_P (p = XFRAME (new_value))
|| !FRAME_NS_P (p)))
{
store_frame_param (f, Qparent_frame, old_value);
error ("Invalid specification of `parent-frame'");
}
if (p != FRAME_PARENT_FRAME (f))
{
block_input ();
child = [FRAME_NS_VIEW (f) window];
if ([child parentWindow] != nil)
{
[[child parentWindow] removeChildWindow:child];
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
if ([child respondsToSelector:@selector(setAccessibilitySubrole:)])
#endif
[child setAccessibilitySubrole:NSAccessibilityStandardWindowSubrole];
#endif
}
if (!NILP (new_value))
{
parent = [FRAME_NS_VIEW (p) window];
[parent addChildWindow: child
ordered: NSWindowAbove];
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
if ([child respondsToSelector:@selector(setAccessibilitySubrole:)])
#endif
[child setAccessibilitySubrole:NSAccessibilityFloatingWindowSubrole];
#endif
}
unblock_input ();
fset_parent_frame (f, new_value);
}
}
void
ns_set_no_focus_on_map (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* Set frame F's `no-focus-on-map' parameter which, if non-nil, means
* that F's window-system window does not want to receive input focus
* when it is mapped. (A frame's window is mapped when the frame is
* displayed for the first time and when the frame changes its state
* from `iconified' or `invisible' to `visible'.)
*
* Some window managers may not honor this parameter. */
{
NSTRACE ("ns_set_no_focus_on_map");
if (!EQ (new_value, old_value))
{
FRAME_NO_FOCUS_ON_MAP (f) = !NILP (new_value);
}
}
void
ns_set_no_accept_focus (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* Set frame F's `no-accept-focus' parameter which, if non-nil, hints
* that F's window-system window does not want to receive input focus
* via mouse clicks or by moving the mouse into it.
*
* If non-nil, this may have the unwanted side-effect that a user cannot
* scroll a non-selected frame with the mouse.
*
* Some window managers may not honor this parameter. */
{
NSTRACE ("ns_set_no_accept_focus");
if (!EQ (new_value, old_value))
FRAME_NO_ACCEPT_FOCUS (f) = !NILP (new_value);
}
void
ns_set_z_group (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
/* Set frame F's `z-group' parameter. If `above', F's window-system
window is displayed above all windows that do not have the `above'
property set. If nil, F's window is shown below all windows that
have the `above' property set and above all windows that have the
`below' property set. If `below', F's window is displayed below
all windows that do.
Some window managers may not honor this parameter. */
{
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSWindow *window = [view window];
NSTRACE ("ns_set_z_group");
if (NILP (new_value))
{
window.level = NSNormalWindowLevel;
FRAME_Z_GROUP (f) = z_group_none;
}
else if (EQ (new_value, Qabove))
{
window.level = NSNormalWindowLevel + 1;
FRAME_Z_GROUP (f) = z_group_above;
}
else if (EQ (new_value, Qabove_suspended))
{
/* Not sure what level this should be. */
window.level = NSNormalWindowLevel + 1;
FRAME_Z_GROUP (f) = z_group_above_suspended;
}
else if (EQ (new_value, Qbelow))
{
window.level = NSNormalWindowLevel - 1;
FRAME_Z_GROUP (f) = z_group_below;
}
else
error ("Invalid z-group specification");
}
#ifdef NS_IMPL_COCOA
void
ns_set_appearance (struct frame *f, Lisp_Object new_value, Lisp_Object old_value)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSWindow *window = [view window];
NSTRACE ("ns_set_appearance");
#ifndef NSAppKitVersionNumber10_10
#define NSAppKitVersionNumber10_10 1343
#endif
if (NSAppKitVersionNumber < NSAppKitVersionNumber10_10)
return;
if (EQ (new_value, Qdark))
{
window.appearance = [NSAppearance
appearanceNamed: NSAppearanceNameVibrantDark];
FRAME_NS_APPEARANCE (f) = ns_appearance_vibrant_dark;
}
else
{
window.appearance = [NSAppearance
appearanceNamed: NSAppearanceNameAqua];
FRAME_NS_APPEARANCE (f) = ns_appearance_aqua;
}
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}
void
ns_set_transparent_titlebar (struct frame *f, Lisp_Object new_value,
Lisp_Object old_value)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSWindow *window = [view window];
NSTRACE ("ns_set_transparent_titlebar");
if ([window respondsToSelector: @selector(titlebarAppearsTransparent)]
&& !EQ (new_value, old_value))
{
window.titlebarAppearsTransparent = !NILP (new_value);
FRAME_NS_TRANSPARENT_TITLEBAR (f) = !NILP (new_value);
}
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 */
}
#endif /* NS_IMPL_COCOA */
static void
ns_fullscreen_hook (struct frame *f)
{
EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
NSTRACE ("ns_fullscreen_hook");
if (!FRAME_VISIBLE_P (f))
return;
if (! [view fsIsNative] && f->want_fullscreen == FULLSCREEN_BOTH)
{
/* Old style fs don't initiate correctly if created from
init/default-frame alist, so use a timer (not nice...). */
[NSTimer scheduledTimerWithTimeInterval: 0.5 target: view
selector: @selector (handleFS)
userInfo: nil repeats: NO];
return;
}
block_input ();
[view handleFS];
unblock_input ();
}
/* ==========================================================================
Color management
========================================================================== */
NSColor *
ns_lookup_indexed_color (unsigned long idx, struct frame *f)
{
struct ns_color_table *color_table = FRAME_DISPLAY_INFO (f)->color_table;
if (idx < 1 || idx >= color_table->avail)
return nil;
return color_table->colors[idx];
}
unsigned long
ns_index_color (NSColor *color, struct frame *f)
{
struct ns_color_table *color_table = FRAME_DISPLAY_INFO (f)->color_table;
ptrdiff_t idx;
ptrdiff_t i;
if (!color_table->colors)
{
color_table->size = NS_COLOR_CAPACITY;
color_table->avail = 1; /* skip idx=0 as marker */
color_table->colors = xmalloc (color_table->size * sizeof (NSColor *));
color_table->colors[0] = nil;
color_table->empty_indices = [[NSMutableSet alloc] init];
}
/* Do we already have this color? */
for (i = 1; i < color_table->avail; i++)
if (color_table->colors[i] && [color_table->colors[i] isEqual: color])
return i;
if ([color_table->empty_indices count] > 0)
{
NSNumber *index = [color_table->empty_indices anyObject];
[color_table->empty_indices removeObject: index];
idx = [index unsignedLongValue];
}
else
{
if (color_table->avail == color_table->size)
color_table->colors =
xpalloc (color_table->colors, &color_table->size, 1,
min (ULONG_MAX, PTRDIFF_MAX), sizeof *color_table->colors);
idx = color_table->avail++;
}
color_table->colors[idx] = color;
[color retain];
/* fprintf(stderr, "color_table: allocated %d\n",idx); */
return idx;
}
static int
ns_get_color (const char *name, NSColor **col)
/* --------------------------------------------------------------------------
Parse a color name
-------------------------------------------------------------------------- */
/* On *Step, we attempt to mimic the X11 platform here, down to installing an
X11 rgb.txt-compatible color list in Emacs.clr (see ns_term_init()).
See https://lists.gnu.org/r/emacs-devel/2009-07/msg01203.html. */
{
NSColor *new = nil;
static char hex[20];
int scaling = 0;
float r = -1.0, g, b;
NSString *nsname = [NSString stringWithUTF8String: name];
NSTRACE ("ns_get_color(%s, **)", name);
block_input ();
if ([nsname isEqualToString: @"ns_selection_bg_color"])
{
#ifdef NS_IMPL_COCOA
NSString *defname = [[NSUserDefaults standardUserDefaults]
stringForKey: @"AppleHighlightColor"];
if (defname != nil)
nsname = defname;
else
#endif
if ((new = [NSColor selectedTextBackgroundColor]) != nil)
{
*col = [new colorUsingDefaultColorSpace];
unblock_input ();
return 0;
}
else
nsname = NS_SELECTION_BG_COLOR_DEFAULT;
name = [nsname UTF8String];
}
else if ([nsname isEqualToString: @"ns_selection_fg_color"])
{
/* NOTE: macOS applications normally don't set foreground
selection, but text may be unreadable if we don't. */
if ((new = [NSColor selectedTextColor]) != nil)
{
*col = [new colorUsingDefaultColorSpace];
unblock_input ();
return 0;
}
nsname = NS_SELECTION_FG_COLOR_DEFAULT;
name = [nsname UTF8String];
}
/* First, check for some sort of numeric specification. */
hex[0] = '\0';
if (name[0] == '0' || name[0] == '1' || name[0] == '.') /* RGB decimal */
{
NSScanner *scanner = [NSScanner scannerWithString: nsname];
[scanner scanFloat: &r];
[scanner scanFloat: &g];
[scanner scanFloat: &b];
}
else if (!strncmp(name, "rgb:", 4)) /* A newer X11 format -- rgb:r/g/b */
scaling = (snprintf (hex, sizeof hex, "%s", name + 4) - 2) / 3;
else if (name[0] == '#') /* An old X11 format; convert to newer */
{
int len = (strlen(name) - 1);
int start = (len % 3 == 0) ? 1 : len / 4 + 1;
int i;
scaling = strlen(name+start) / 3;
for (i = 0; i < 3; i++)
sprintf (hex + i * (scaling + 1), "%.*s/", scaling,
name + start + i * scaling);
hex[3 * (scaling + 1) - 1] = '\0';
}
if (hex[0])
{
unsigned int rr, gg, bb;
float fscale = scaling == 4 ? 65535.0 : (scaling == 2 ? 255.0 : 15.0);
if (sscanf (hex, "%x/%x/%x", &rr, &gg, &bb))
{
r = rr / fscale;
g = gg / fscale;
b = bb / fscale;
}
}
if (r >= 0.0F)
{
*col = [NSColor colorForEmacsRed: r green: g blue: b alpha: 1.0];
unblock_input ();
return 0;
}
/* Otherwise, color is expected to be from a list */
{
NSEnumerator *lenum, *cenum;
NSString *name;
NSColorList *clist;
#ifdef NS_IMPL_GNUSTEP
/* XXX: who is wrong, the requestor or the implementation? */
if ([nsname compare: @"Highlight" options: NSCaseInsensitiveSearch]
== NSOrderedSame)
nsname = @"highlightColor";
#endif
lenum = [[NSColorList availableColorLists] objectEnumerator];
while ( (clist = [lenum nextObject]) && new == nil)
{
cenum = [[clist allKeys] objectEnumerator];
while ( (name = [cenum nextObject]) && new == nil )
{
if ([name compare: nsname
options: NSCaseInsensitiveSearch] == NSOrderedSame )
new = [clist colorWithKey: name];
}
}
}
if (new)
*col = [new colorUsingDefaultColorSpace];
unblock_input ();
return new ? 0 : 1;
}
int
ns_lisp_to_color (Lisp_Object color, NSColor **col)
/* --------------------------------------------------------------------------
Convert a Lisp string object to a NS color.
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_lisp_to_color");
if (STRINGP (color))
return ns_get_color (SSDATA (color), col);
else if (SYMBOLP (color))
return ns_get_color (SSDATA (SYMBOL_NAME (color)), col);
return 1;
}
/* Convert an index into the color table into an RGBA value. Used in
xdisp.c:extend_face_to_end_of_line when comparing faces and frame
color values. */
unsigned long
ns_color_index_to_rgba(int idx, struct frame *f)
{
NSColor *col;
col = ns_lookup_indexed_color (idx, f);
EmacsCGFloat r, g, b, a;
[col getRed: &r green: &g blue: &b alpha: &a];
return ARGB_TO_ULONG((int)(a*255),
(int)(r*255), (int)(g*255), (int)(b*255));
}
void
ns_query_color(void *col, Emacs_Color *color_def, bool setPixel)
/* --------------------------------------------------------------------------
Get ARGB values out of NSColor col and put them into color_def.
If setPixel, set the pixel to a concatenated version.
and set color_def pixel to the resulting index.
-------------------------------------------------------------------------- */
{
EmacsCGFloat r, g, b, a;
[((NSColor *)col) getRed: &r green: &g blue: &b alpha: &a];
color_def->red = r * 65535;
color_def->green = g * 65535;
color_def->blue = b * 65535;
if (setPixel == YES)
color_def->pixel
= ARGB_TO_ULONG((int)(a*255),
(int)(r*255), (int)(g*255), (int)(b*255));
}
bool
ns_defined_color (struct frame *f,
const char *name,
Emacs_Color *color_def,
bool alloc,
bool makeIndex)
/* --------------------------------------------------------------------------
Return true if named color found, and set color_def rgb accordingly.
If makeIndex and alloc are nonzero put the color in the color_table,
and set color_def pixel to the resulting index.
If makeIndex is zero, set color_def pixel to ARGB.
Return false if not found.
-------------------------------------------------------------------------- */
{
NSColor *col;
NSTRACE_WHEN (NSTRACE_GROUP_COLOR, "ns_defined_color");
block_input ();
if (ns_get_color (name, &col) != 0) /* Color not found */
{
unblock_input ();
return 0;
}
if (makeIndex && alloc)
color_def->pixel = ns_index_color (col, f);
ns_query_color (col, color_def, !makeIndex);
unblock_input ();
return 1;
}
static void
ns_query_frame_background_color (struct frame *f, Emacs_Color *bgcolor)
/* --------------------------------------------------------------------------
External (hook): Store F's background color into *BGCOLOR
-------------------------------------------------------------------------- */
{
ns_query_color (FRAME_BACKGROUND_COLOR (f), bgcolor, true);
}
static void
ns_set_frame_alpha (struct frame *f)
/* --------------------------------------------------------------------------
change the entire-frame transparency
-------------------------------------------------------------------------- */
{
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
double alpha = 1.0;
double alpha_min = 1.0;
NSTRACE ("ns_set_frame_alpha");
if (dpyinfo->highlight_frame == f)
alpha = f->alpha[0];
else
alpha = f->alpha[1];
if (FLOATP (Vframe_alpha_lower_limit))
alpha_min = XFLOAT_DATA (Vframe_alpha_lower_limit);
else if (FIXNUMP (Vframe_alpha_lower_limit))
alpha_min = (XFIXNUM (Vframe_alpha_lower_limit)) / 100.0;
if (alpha < 0.0)
return;
else if (1.0 < alpha)
alpha = 1.0;
else if (0.0 <= alpha && alpha < alpha_min && alpha_min <= 1.0)
alpha = alpha_min;
#ifdef NS_IMPL_COCOA
{
EmacsView *view = FRAME_NS_VIEW (f);
[[view window] setAlphaValue: alpha];
}
#endif
}
/* ==========================================================================
Mouse handling
========================================================================== */
void
frame_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
/* --------------------------------------------------------------------------
Programmatically reposition mouse pointer in pixel coordinates
-------------------------------------------------------------------------- */
{
NSTRACE ("frame_set_mouse_pixel_position");
/* FIXME: what about GNUstep? */
#ifdef NS_IMPL_COCOA
CGPoint mouse_pos =
CGPointMake(f->left_pos + pix_x,
f->top_pos + pix_y +
FRAME_NS_TITLEBAR_HEIGHT(f) + FRAME_TOOLBAR_HEIGHT(f));
CGWarpMouseCursorPosition (mouse_pos);
#endif
}
static int
ns_note_mouse_movement (struct frame *frame, CGFloat x, CGFloat y)
/* ------------------------------------------------------------------------
Called by EmacsView on mouseMovement events. Passes on
to emacs mainstream code if we moved off of a rect of interest
known as last_mouse_glyph.
------------------------------------------------------------------------ */
{
struct ns_display_info *dpyinfo = FRAME_DISPLAY_INFO (frame);
NSRect *r;
// NSTRACE ("note_mouse_movement");
dpyinfo->last_mouse_motion_frame = frame;
r = &dpyinfo->last_mouse_glyph;
/* Note, this doesn't get called for enter/leave, since we don't have a
position. Those are taken care of in the corresponding NSView methods. */
/* Has movement gone beyond last rect we were tracking? */
if (x < r->origin.x || x >= r->origin.x + r->size.width
|| y < r->origin.y || y >= r->origin.y + r->size.height)
{
ns_update_begin (frame);
frame->mouse_moved = 1;
note_mouse_highlight (frame, x, y);
remember_mouse_glyph (frame, x, y, r);
ns_update_end (frame);
return 1;
}
return 0;
}
static void
ns_mouse_position (struct frame **fp, int insist, Lisp_Object *bar_window,
enum scroll_bar_part *part, Lisp_Object *x, Lisp_Object *y,
Time *time)
/* --------------------------------------------------------------------------
External (hook): inform emacs about mouse position and hit parts.
If a scrollbar is being dragged, set bar_window, part, x, y, time.
x & y should be position in the scrollbar (the whole bar, not the handle)
and length of scrollbar respectively.
-------------------------------------------------------------------------- */
{
id view;
NSPoint position;
Lisp_Object frame, tail;
struct frame *f;
struct ns_display_info *dpyinfo;
NSTRACE ("ns_mouse_position");
if (*fp == NULL)
{
fprintf (stderr, "Warning: ns_mouse_position () called with null *fp.\n");
return;
}
dpyinfo = FRAME_DISPLAY_INFO (*fp);
block_input ();
/* Clear the mouse-moved flag for every frame on this display. */
FOR_EACH_FRAME (tail, frame)
if (FRAME_NS_P (XFRAME (frame)))
XFRAME (frame)->mouse_moved = 0;
dpyinfo->last_mouse_scroll_bar = nil;
if (dpyinfo->last_mouse_frame
&& FRAME_LIVE_P (dpyinfo->last_mouse_frame))
f = dpyinfo->last_mouse_frame;
else
f = dpyinfo->ns_focus_frame ? dpyinfo->ns_focus_frame : SELECTED_FRAME ();
if (f && FRAME_NS_P (f))
{
view = FRAME_NS_VIEW (f);
position = [[view window] mouseLocationOutsideOfEventStream];
position = [view convertPoint: position fromView: nil];
remember_mouse_glyph (f, position.x, position.y,
&dpyinfo->last_mouse_glyph);
NSTRACE_POINT ("position", position);
if (bar_window) *bar_window = Qnil;
if (part) *part = scroll_bar_above_handle;
if (x) XSETINT (*x, lrint (position.x));
if (y) XSETINT (*y, lrint (position.y));
if (time)
*time = dpyinfo->last_mouse_movement_time;
*fp = f;
}
unblock_input ();
}
static void
ns_frame_up_to_date (struct frame *f)
/* --------------------------------------------------------------------------
External (hook): Fix up mouse highlighting right after a full update.
Can't use FRAME_MOUSE_UPDATE due to ns_frame_begin and ns_frame_end calls.
-------------------------------------------------------------------------- */
{
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_frame_up_to_date");
if (FRAME_NS_P (f))
{
Mouse_HLInfo *hlinfo = MOUSE_HL_INFO (f);
if (f == hlinfo->mouse_face_mouse_frame)
{
block_input ();
ns_update_begin(f);
note_mouse_highlight (hlinfo->mouse_face_mouse_frame,
hlinfo->mouse_face_mouse_x,
hlinfo->mouse_face_mouse_y);
ns_update_end(f);
unblock_input ();
}
}
}
static void
ns_define_frame_cursor (struct frame *f, Emacs_Cursor cursor)
/* --------------------------------------------------------------------------
External (RIF): set frame mouse pointer type.
-------------------------------------------------------------------------- */
{
NSTRACE ("ns_define_frame_cursor");
if (FRAME_POINTER_TYPE (f) != cursor)
{
EmacsView *view = FRAME_NS_VIEW (f);
FRAME_POINTER_TYPE (f) = cursor;
[[view window] invalidateCursorRectsForView: view];
/* Redisplay assumes this function also draws the changed frame
cursor, but this function doesn't, so do it explicitly. */
gui_update_cursor (f, 1);
}
}
/* ==========================================================================
Keyboard handling
========================================================================== */
static unsigned
ns_convert_key (unsigned code)
/* --------------------------------------------------------------------------
Internal call used by NSView-keyDown.
-------------------------------------------------------------------------- */
{
const unsigned last_keysym = ARRAYELTS (convert_ns_to_X_keysym);
unsigned keysym;
/* An array would be faster, but less easy to read. */
for (keysym = 0; keysym < last_keysym; keysym += 2)
if (code == convert_ns_to_X_keysym[keysym])
return 0xFF00 | convert_ns_to_X_keysym[keysym+1];
return 0;
/* if decide to use keyCode and Carbon table, use this line:
return code > 0xff ? 0 : 0xFF00 | ns_keycode_to_xkeysym_table[code]; */
}
char *
get_keysym_name (int keysym)
/* --------------------------------------------------------------------------
Called by keyboard.c. Not sure if the return val is important, except
that it be unique.
-------------------------------------------------------------------------- */
{
static char value[16];
NSTRACE ("get_keysym_name");
sprintf (value, "%d", keysym);
return value;
}
#ifdef NS_IMPL_COCOA
static UniChar
ns_get_shifted_character (NSEvent *event)
/* Look up the character corresponding to the key pressed on the
current keyboard layout and the currently configured shift-like
modifiers. This ignores the control-like modifiers that cause
[event characters] to give us the wrong result.
Although UCKeyTranslate doesn't require the Carbon framework, some
of the surrounding paraphernalia does, so this function makes
Carbon a requirement. */
{
static UInt32 dead_key_state;
/* UCKeyTranslate may return up to 255 characters. If the buffer
isn't large enough then it produces an error. What kind of
keyboard inputs 255 characters in a single keypress? */
UniChar buf[255];
UniCharCount max_string_length = 255;
UniCharCount actual_string_length = 0;
OSStatus result;
CFDataRef layout_ref = (CFDataRef) TISGetInputSourceProperty
(TISCopyCurrentKeyboardLayoutInputSource (), kTISPropertyUnicodeKeyLayoutData);
UCKeyboardLayout* layout = (UCKeyboardLayout*) CFDataGetBytePtr (layout_ref);
UInt32 flags = [event modifierFlags];
UInt32 modifiers = (flags & NSEventModifierFlagShift) ? shiftKey : 0;
NSTRACE ("ns_get_shifted_character");
if ((flags & NSRightAlternateKeyMask) == NSRightAlternateKeyMask
&& (EQ (ns_right_alternate_modifier, Qnone)
|| (EQ (ns_right_alternate_modifier, Qleft)
&& EQ (ns_alternate_modifier, Qnone))))
modifiers |= rightOptionKey;
if ((flags & NSLeftAlternateKeyMask) == NSLeftAlternateKeyMask
&& EQ (ns_alternate_modifier, Qnone))
modifiers |= optionKey;
if ((flags & NSRightCommandKeyMask) == NSRightCommandKeyMask
&& (EQ (ns_right_command_modifier, Qnone)
|| (EQ (ns_right_command_modifier, Qleft)
&& EQ (ns_command_modifier, Qnone))))
/* Carbon doesn't differentiate between left and right command
keys. */
modifiers |= cmdKey;
if ((flags & NSLeftCommandKeyMask) == NSLeftCommandKeyMask
&& EQ (ns_command_modifier, Qnone))
modifiers |= cmdKey;
result = UCKeyTranslate (layout, [event keyCode], kUCKeyActionDown,
(modifiers >> 8) & 0xFF, LMGetKbdType (),
kUCKeyTranslateNoDeadKeysBit, &dead_key_state,
max_string_length, &actual_string_length, buf);
if (result != 0)
{
NSLog(@"Failed to translate character '%@' with modifiers %x",
[event characters], modifiers);
return 0;
}
/* FIXME: What do we do if more than one code unit is returned? */
if (actual_string_length > 0)
return buf[0];
return 0;
}
#endif /* NS_IMPL_COCOA */
/* ==========================================================================
Block drawing operations
========================================================================== */
static void
ns_redraw_scroll_bars (struct frame *f)
{
int i;
id view;
NSArray *subviews = [[FRAME_NS_VIEW (f) superview] subviews];
NSTRACE ("ns_redraw_scroll_bars");
for (i =[subviews count]-1; i >= 0; i--)
{
view = [subviews objectAtIndex: i];
if (![view isKindOfClass: [EmacsScroller class]]) continue;
[view display];
}
}
void
ns_clear_frame (struct frame *f)
/* --------------------------------------------------------------------------
External (hook): Erase the entire frame
-------------------------------------------------------------------------- */
{
NSView *view = FRAME_NS_VIEW (f);
NSRect r;
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_clear_frame");
/* comes on initial frame because we have
after-make-frame-functions = select-frame */
if (!FRAME_DEFAULT_FACE (f))
return;
mark_window_cursors_off (XWINDOW (FRAME_ROOT_WINDOW (f)));
r = [view bounds];
block_input ();
if (ns_clip_to_rect (f, &r, 1))
{
[ns_lookup_indexed_color (NS_FACE_BACKGROUND
(FACE_FROM_ID (f, DEFAULT_FACE_ID)), f) set];
NSRectFill (r);
ns_reset_clipping (f);
/* as of 2006/11 or so this is now needed */
ns_redraw_scroll_bars (f);
}
unblock_input ();
}
static void
ns_clear_frame_area (struct frame *f, int x, int y, int width, int height)
/* --------------------------------------------------------------------------
External (RIF): Clear section of frame
-------------------------------------------------------------------------- */
{
NSRect r = NSMakeRect (x, y, width, height);
NSView *view = FRAME_NS_VIEW (f);
struct face *face = FRAME_DEFAULT_FACE (f);
if (!view || !face)
return;
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_clear_frame_area");
r = NSIntersectionRect (r, [view frame]);
if (ns_clip_to_rect (f, &r, 1))
{
[ns_lookup_indexed_color (NS_FACE_BACKGROUND (face), f) set];
NSRectFill (r);
ns_reset_clipping (f);
}
}
static void
ns_copy_bits (struct frame *f, NSRect src, NSRect dest)
{
NSSize delta = NSMakeSize (dest.origin.x - src.origin.x,
dest.origin.y - src.origin.y);
NSTRACE ("ns_copy_bits");
if (FRAME_NS_VIEW (f))
{
hide_bell(); // Ensure the bell image isn't scrolled.
/* FIXME: scrollRect:by: is deprecated in macOS 10.14. There is
no obvious replacement so we may have to come up with our own. */
[FRAME_NS_VIEW (f) scrollRect: src by: delta];
#ifdef NS_IMPL_COCOA
/* As far as I can tell from the documentation, scrollRect:by:,
above, should copy the dirty rectangles from our source
rectangle to our destination, however it appears it clips the
operation to src. As a result we need to use
translateRectsNeedingDisplayInRect:by: below, and we have to
union src and dest so it can pick up the dirty rectangles,
and place them, as it also clips to the rectangle.
FIXME: We need a GNUstep equivalent. */
[FRAME_NS_VIEW (f) translateRectsNeedingDisplayInRect:NSUnionRect (src, dest)
by:delta];
#endif
}
}
static void
ns_scroll_run (struct window *w, struct run *run)
/* --------------------------------------------------------------------------
External (RIF): Insert or delete n lines at line vpos.
-------------------------------------------------------------------------- */
{
struct frame *f = XFRAME (w->frame);
int x, y, width, height, from_y, to_y, bottom_y;
NSTRACE ("ns_scroll_run");
/* begin copy from other terms */
/* Get frame-relative bounding box of the text display area of W,
without mode lines. Include in this box the left and right
fringe of W. */
window_box (w, ANY_AREA, &x, &y, &width, &height);
from_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->current_y);
to_y = WINDOW_TO_FRAME_PIXEL_Y (w, run->desired_y);
bottom_y = y + height;
if (to_y < from_y)
{
/* Scrolling up. Make sure we don't copy part of the mode
line at the bottom. */
if (from_y + run->height > bottom_y)
height = bottom_y - from_y;
else
height = run->height;
}
else
{
/* Scrolling down. Make sure we don't copy over the mode line.
at the bottom. */
if (to_y + run->height > bottom_y)
height = bottom_y - to_y;
else
height = run->height;
}
/* end copy from other terms */
if (height == 0)
return;
block_input ();
gui_clear_cursor (w);
{
NSRect srcRect = NSMakeRect (x, from_y, width, height);
NSRect dstRect = NSMakeRect (x, to_y, width, height);
ns_copy_bits (f, srcRect , dstRect);
}
unblock_input ();
}
static void
ns_after_update_window_line (struct window *w, struct glyph_row *desired_row)
/* --------------------------------------------------------------------------
External (RIF): preparatory to fringe update after text was updated
-------------------------------------------------------------------------- */
{
struct frame *f;
int width, height;
NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_after_update_window_line");
/* begin copy from other terms */
eassert (w);
if (!desired_row->mode_line_p && !w->pseudo_window_p)
desired_row->redraw_fringe_bitmaps_p = 1;
/* When a window has disappeared, make sure that no rest of
full-width rows stays visible in the internal border. */
if (windows_or_buffers_changed
&& desired_row->full_width_p
&& (f = XFRAME (w->frame),
width = FRAME_INTERNAL_BORDER_WIDTH (f),
width != 0)
&& (height = desired_row->visible_height,
height > 0))
{
int y = WINDOW_TO_FRAME_PIXEL_Y (w, max (0, desired_row->y));
block_input ();
ns_clear_frame_area (f, 0, y, width, height);
ns_clear_frame_area (f,
FRAME_PIXEL_WIDTH (f) - width,
y, width, height);
unblock_input ();
}
}
static void
ns_shift_glyphs_for_insert (struct frame *f,
int x, int y, int width, int height,
int shift_by)
/* --------------------------------------------------------------------------
External (RIF): copy an area horizontally, don't worry about clearing src
-------------------------------------------------------------------------- */
{
//NSRect srcRect = NSMakeRect (x, y, width, height);
NSRect dstRect = NSMakeRect (x+shift_by, y, width, height);
NSTRACE ("ns_shift_glyphs_for_insert");
/* This doesn't work now as we copy the "bits" before we've had a
chance to actually draw any changes to the screen. This means in
certain circumstances we end up with copies of the cursor all
over the place. Just mark the area dirty so it is redrawn later.
FIXME: Work out how to do this properly. */
// ns_copy_bits (f, srcRect, dstRect);
[FRAME_NS_VIEW (f) setNeedsDisplayInRect:dstRect];
}
/* ==========================================================================
Character encoding and metrics
========================================================================== */
static void
ns_compute_glyph_string_overhangs (struct glyph_string *s)
/* --------------------------------------------------------------------------
External (RIF); compute left/right overhang of whole string and set in s
-------------------------------------------------------------------------- */
{
struct font *font = s->font;
if (s->char2b)
{
struct font_metrics metrics;
unsigned int codes[2];
codes[0] = *(s->char2b);
codes[1] = *(s->char2b + s->nchars - 1);
font->driver->text_extents (font, codes, 2, &metrics);
s->left_overhang = -metrics.lbearing;
s->right_overhang
= metrics.rbearing > metrics.width
? metrics.rbearing - metrics.width : 0;
}
else
{
s->left_overhang = 0;
if (EQ (font->driver->type, Qns))
s->right_overhang = ((struct nsfont_info *)font)->ital ?
FONT_HEIGHT (font) * 0.2 : 0;
else
s->right_overhang = 0;
}
}
/* ==========================================================================
Fringe and cursor drawing
========================================================================== */
extern int max_used_fringe_bitmap;
static void
ns_draw_fringe_bitmap (struct window *w, struct glyph_row *row,
struct draw_fringe_bitmap_params *p)
/* --------------------------------------------------------------------------
External (RIF); fringe-related
-------------------------------------------------------------------------- */
{
/* Fringe bitmaps comes in two variants, normal and periodic. A
periodic bitmap is used to create a continuous pattern. Since a
bitmap is rendered one text line at a time, the start offset (dh)
of the bitmap varies. Concretely, this is used for the empty
line indicator.
For a bitmap, "h + dh" is the full height and is always
invariant. For a normal bitmap "dh" is zero.
For example, when the period is three and the full height is 72
the following combinations exists:
h=72 dh=0
h=71 dh=1
h=70 dh=2 */
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face = p->face;
static EmacsImage **bimgs = NULL;
static int nBimgs = 0;
NSRect clearRect = NSZeroRect;
NSRect imageRect = NSZeroRect;
NSRect rowRect = ns_row_rect (w, row, ANY_AREA);
NSTRACE_WHEN (NSTRACE_GROUP_FRINGE, "ns_draw_fringe_bitmap");
NSTRACE_MSG ("which:%d cursor:%d overlay:%d width:%d height:%d period:%d",
p->which, p->cursor_p, p->overlay_p, p->wd, p->h, p->dh);
/* grow bimgs if needed */
if (nBimgs < max_used_fringe_bitmap)
{
bimgs = xrealloc (bimgs, max_used_fringe_bitmap * sizeof *bimgs);
memset (bimgs + nBimgs, 0,
(max_used_fringe_bitmap - nBimgs) * sizeof *bimgs);
nBimgs = max_used_fringe_bitmap;
}
/* Work out the rectangle we will composite into. */
if (p->which)
imageRect = NSMakeRect (p->x, p->y, p->wd, p->h);
/* Work out the rectangle we will need to clear. Because we're
compositing rather than blitting, we need to clear the area under
the image regardless of anything else. */
if (p->bx >= 0 && !p->overlay_p)
{
clearRect = NSMakeRect (p->bx, p->by, p->nx, p->ny);
clearRect = NSUnionRect (clearRect, imageRect);
}
else
{
clearRect = imageRect;
}
/* Handle partially visible rows. */
clearRect = NSIntersectionRect (clearRect, rowRect);
/* The visible portion of imageRect will always be contained within
clearRect. */
if (ns_clip_to_rect (f, &clearRect, 1))
{
if (! NSIsEmptyRect (clearRect))
{
NSTRACE_RECT ("clearRect", clearRect);
[ns_lookup_indexed_color(face->background, f) set];
NSRectFill (clearRect);
}
if (p->which)
{
EmacsImage *img = bimgs[p->which - 1];
if (!img)
{
// Note: For "periodic" images, allocate one EmacsImage for
// the base image, and use it for all dh:s.
unsigned short *bits = p->bits;
int full_height = p->h + p->dh;
int i;
unsigned char *cbits = xmalloc (full_height);
for (i = 0; i < full_height; i++)
cbits[i] = bits[i];
img = [[EmacsImage alloc] initFromXBM: cbits width: 8
height: full_height
fg: 0 bg: 0];
bimgs[p->which - 1] = img;
xfree (cbits);
}
{
NSColor *bm_color;
if (!p->cursor_p)
bm_color = ns_lookup_indexed_color(face->foreground, f);
else if (p->overlay_p)
bm_color = ns_lookup_indexed_color(face->background, f);
else
bm_color = f->output_data.ns->cursor_color;
[img setXBMColor: bm_color];
}
// Note: For periodic images, the full image height is "h + hd".
// By using the height h, a suitable part of the image is used.
NSRect fromRect = NSMakeRect(0, 0, p->wd, p->h);
NSTRACE_RECT ("fromRect", fromRect);
[img drawInRect: imageRect
fromRect: fromRect
operation: NSCompositingOperationSourceOver
fraction: 1.0
respectFlipped: YES
hints: nil];
}
ns_reset_clipping (f);
}
}
static void
ns_draw_window_cursor (struct window *w, struct glyph_row *glyph_row,
int x, int y, enum text_cursor_kinds cursor_type,
int cursor_width, bool on_p, bool active_p)
/* --------------------------------------------------------------------------
External call (RIF): draw cursor.
Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
-------------------------------------------------------------------------- */
{
NSRect r, s;
int fx, fy, h, cursor_height;
struct frame *f = WINDOW_XFRAME (w);
struct glyph *phys_cursor_glyph;
struct glyph *cursor_glyph;
struct face *face;
NSColor *hollow_color = FRAME_BACKGROUND_COLOR (f);
/* If cursor is out of bounds, don't draw garbage. This can happen
in mini-buffer windows when switching between echo area glyphs
and mini-buffer. */
NSTRACE ("ns_draw_window_cursor");
if (!on_p)
return;
w->phys_cursor_type = cursor_type;
w->phys_cursor_on_p = on_p;
if (cursor_type == NO_CURSOR)
{
w->phys_cursor_width = 0;
return;
}
if ((phys_cursor_glyph = get_phys_cursor_glyph (w)) == NULL)
{
if (glyph_row->exact_window_width_line_p
&& w->phys_cursor.hpos >= glyph_row->used[TEXT_AREA])
{
glyph_row->cursor_in_fringe_p = 1;
draw_fringe_bitmap (w, glyph_row, 0);
}
return;
}
/* We draw the cursor (with NSRectFill), then draw the glyph on top
(other terminals do it the other way round). We must set
w->phys_cursor_width to the cursor width. For bar cursors, that
is CURSOR_WIDTH; for box cursors, it is the glyph width. */
get_phys_cursor_geometry (w, glyph_row, phys_cursor_glyph, &fx, &fy, &h);
/* The above get_phys_cursor_geometry call set w->phys_cursor_width
to the glyph width; replace with CURSOR_WIDTH for (V)BAR cursors. */
if (cursor_type == BAR_CURSOR)
{
if (cursor_width < 1)
cursor_width = max (FRAME_CURSOR_WIDTH (f), 1);
/* The bar cursor should never be wider than the glyph. */
if (cursor_width < w->phys_cursor_width)
w->phys_cursor_width = cursor_width;
}
/* If we have an HBAR, "cursor_width" MAY specify height. */
else if (cursor_type == HBAR_CURSOR)
{
cursor_height = (cursor_width < 1) ? lrint (0.25 * h) : cursor_width;
if (cursor_height > glyph_row->height)
cursor_height = glyph_row->height;
if (h > cursor_height) // Cursor smaller than line height, move down
fy += h - cursor_height;
h = cursor_height;
}
r.origin.x = fx, r.origin.y = fy;
r.size.height = h;
r.size.width = w->phys_cursor_width;
/* Prevent the cursor from being drawn outside the text area. */
r = NSIntersectionRect (r, ns_row_rect (w, glyph_row, TEXT_AREA));
if (ns_clip_to_rect (f, &r, 1))
{
face = FACE_FROM_ID_OR_NULL (f, phys_cursor_glyph->face_id);
if (face && NS_FACE_BACKGROUND (face)
== ns_index_color (FRAME_CURSOR_COLOR (f), f))
{
[ns_lookup_indexed_color (NS_FACE_FOREGROUND (face), f) set];
hollow_color = FRAME_CURSOR_COLOR (f);
}
else
[FRAME_CURSOR_COLOR (f) set];
switch (cursor_type)
{
case DEFAULT_CURSOR:
case NO_CURSOR:
break;
case FILLED_BOX_CURSOR:
NSRectFill (r);
break;
case HOLLOW_BOX_CURSOR:
NSRectFill (r);
[hollow_color set];
NSRectFill (NSInsetRect (r, 1, 1));
[FRAME_CURSOR_COLOR (f) set];
break;
case HBAR_CURSOR:
NSRectFill (r);
break;
case BAR_CURSOR:
s = r;
/* If the character under cursor is R2L, draw the bar cursor
on the right of its glyph, rather than on the left. */
cursor_glyph = get_phys_cursor_glyph (w);
if ((cursor_glyph->resolved_level & 1) != 0)
s.origin.x += cursor_glyph->pixel_width - s.size.width;
NSRectFill (s);
break;
}
/* Draw the character under the cursor. Other terms only draw
the character on top of box cursors, so do the same here. */
if (cursor_type == FILLED_BOX_CURSOR || cursor_type == HOLLOW_BOX_CURSOR)
draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
ns_reset_clipping (f);
}
else if (! redisplaying_p)
{
/* If this function is called outside redisplay, it probably
means we need an immediate update. */
[FRAME_NS_VIEW (f) display];
}
}
static void
ns_draw_vertical_window_border (struct window *w, int x, int y0, int y1)
/* --------------------------------------------------------------------------
External (RIF): Draw a vertical line.
-------------------------------------------------------------------------- */
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face;
NSRect r = NSMakeRect (x, y0, 1, y1-y0);
NSTRACE ("ns_draw_vertical_window_border");
face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
if (ns_clip_to_rect (f, &r, 1))
{
if (face)
[ns_lookup_indexed_color(face->foreground, f) set];
NSRectFill(r);
ns_reset_clipping (f);
}
}
static void
ns_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1)
/* --------------------------------------------------------------------------
External (RIF): Draw a window divider.
-------------------------------------------------------------------------- */
{
struct frame *f = XFRAME (WINDOW_FRAME (w));
struct face *face = FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FACE_ID);
struct face *face_first
= FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_FIRST_PIXEL_FACE_ID);
struct face *face_last
= FACE_FROM_ID_OR_NULL (f, WINDOW_DIVIDER_LAST_PIXEL_FACE_ID);
unsigned long color = face ? face->foreground : FRAME_FOREGROUND_PIXEL (f);
unsigned long color_first = (face_first
? face_first->foreground
: FRAME_FOREGROUND_PIXEL (f));
unsigned long color_last = (face_last
? face_last->foreground
: FRAME_FOREGROUND_PIXEL (f));
NSRect divider = NSMakeRect (x0, y0, x1-x0, y1-y0);
NSTRACE ("ns_draw_window_divider");
if (ns_clip_to_rect (f, &divider, 1))
{
if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
/* A vertical divider, at least three pixels wide: Draw first and
last pixels differently. */
{
[ns_lookup_indexed_color(color_first, f) set];
NSRectFill(NSMakeRect (x0, y0, 1, y1 - y0));
[ns_lookup_indexed_color(color, f) set];
NSRectFill(NSMakeRect (x0 + 1, y0, x1 - x0 - 2, y1 - y0));
[ns_lookup_indexed_color(color_last, f) set];
NSRectFill(NSMakeRect (x1 - 1, y0, 1, y1 - y0));
}
else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
/* A horizontal divider, at least three pixels high: Draw first and
last pixels differently. */
{
[ns_lookup_indexed_color(color_first, f) set];
NSRectFill(NSMakeRect (x0, y0, x1 - x0, 1));
[ns_lookup_indexed_color(color, f) set];
NSRectFill(NSMakeRect (x0, y0 + 1, x1 - x0, y1 - y0 - 2));
[ns_lookup_indexed_color(color_last, f) set];
NSRectFill(NSMakeRect (x0, y1 - 1, x1 - x0, 1));
}
else
{
/* In any other case do not draw the first and last pixels
differently. */
[ns_lookup_indexed_color(color, f) set];
NSRectFill(divider);
}
ns_reset_clipping (f);
}
}
static void
ns_show_hourglass (struct frame *f)
{
/* TODO: add NSProgressIndicator to all frames. */
}
static void
ns_hide_hourglass (struct frame *f)
{
/* TODO: remove NSProgressIndicator from all frames. */
}
/* ==========================================================================
Glyph drawing operations
========================================================================== */
static int
ns_get_glyph_string_clip_rect (struct glyph_string *s, NativeRectangle *nr)
/* --------------------------------------------------------------------------
Wrapper utility to account for internal border width on full-width lines,
and allow top full-width rows to hit the frame top. nr should be pointer
to two successive NSRects. Number of rects actually used is returned.
-------------------------------------------------------------------------- */
{
int n = get_glyph_string_clip_rects (s, nr, 2);
return n;
}
/* --------------------------------------------------------------------
Draw a wavy line under glyph string s. The wave fills wave_height
pixels from y.
x wave_length = 2
--
y * * * * *
|* * * * * * * * *
wave_height = 3 | * * * *
--------------------------------------------------------------------- */
static void
ns_draw_underwave (struct glyph_string *s, EmacsCGFloat width, EmacsCGFloat x)
{
int wave_height = 3, wave_length = 2;
int y, dx, dy, odd, xmax;
NSPoint a, b;
NSRect waveClip;
dx = wave_length;
dy = wave_height - 1;
y = s->ybase - wave_height + 3;
xmax = x + width;
/* Find and set clipping rectangle */
waveClip = NSMakeRect (x, y, width, wave_height);
[[NSGraphicsContext currentContext] saveGraphicsState];
NSRectClip (waveClip);
/* Draw the waves */
a.x = x - ((int)(x) % dx) + (EmacsCGFloat) 0.5;
b.x = a.x + dx;
odd = (int)(a.x/dx) % 2;
a.y = b.y = y + 0.5;
if (odd)
a.y += dy;
else
b.y += dy;
while (a.x <= xmax)
{
[NSBezierPath strokeLineFromPoint:a toPoint:b];
a.x = b.x, a.y = b.y;
b.x += dx, b.y = y + 0.5 + odd*dy;
odd = !odd;
}
/* Restore previous clipping rectangle(s) */
[[NSGraphicsContext currentContext] restoreGraphicsState];
}
static void
ns_draw_text_decoration (struct glyph_string *s, struct face *face,
NSColor *defaultCol, CGFloat width, CGFloat x)
/* --------------------------------------------------------------------------
Draw underline, overline, and strike-through on glyph string s.
-------------------------------------------------------------------------- */
{
if (s->for_overlaps)
return;
/* Do underline. */
if (face->underline_p)
{
if (s->face->underline_type == FACE_UNDER_WAVE)
{
if (face->underline_defaulted_p)
[defaultCol set];
else
[ns_lookup_indexed_color (face->underline_color, s->f) set];
ns_draw_underwave (s, width, x);
}
else if (s->face->underline_type == FACE_UNDER_LINE)
{
NSRect r;
unsigned long thickness, position;
/* If the prev was underlined, match its appearance. */
if (s->prev && s->prev->face->underline_p
&& s->prev->face->underline_type == FACE_UNDER_LINE
&& s->prev->underline_thickness > 0)
{
thickness = s->prev->underline_thickness;
position = s->prev->underline_position;
}
else
{
struct font *font = font_for_underline_metrics (s);
unsigned long descent = s->y + s->height - s->ybase;
unsigned long minimum_offset;
BOOL underline_at_descent_line, use_underline_position_properties;
Lisp_Object val = buffer_local_value (Qunderline_minimum_offset,
s->w->contents);
if (FIXNUMP (val))
minimum_offset = XFIXNAT (val);
else
minimum_offset = 1;
val = buffer_local_value (Qx_underline_at_descent_line,
s->w->contents);
underline_at_descent_line = !(NILP (val) || EQ (val, Qunbound));
val = buffer_local_value (Qx_use_underline_position_properties,
s->w->contents);
use_underline_position_properties =
!(NILP (val) || EQ (val, Qunbound));
/* Use underline thickness of font, defaulting to 1. */
thickness = (font && font->underline_thickness > 0)
? font->underline_thickness : 1;
/* Determine the offset of underlining from the baseline. */
if (underline_at_descent_line)
position = descent - thickness;
else if (use_underline_position_properties
&& font && font->underline_position >= 0)
position = font->underline_position;
else if (font)
position = lround (font->descent / 2);
else
position = minimum_offset;
position = max (position, minimum_offset);
/* Ensure underlining is not cropped. */
if (descent <= position)
{
position = descent - 1;
thickness = 1;
}
else if (descent < position + thickness)
thickness = 1;
}
s->underline_thickness = thickness;
s->underline_position = position;
r = NSMakeRect (x, s->ybase + position, width, thickness);
if (face->underline_defaulted_p)
[defaultCol set];
else
[ns_lookup_indexed_color (face->underline_color, s->f) set];
NSRectFill (r);
}
}
/* Do overline. We follow other terms in using a thickness of 1
and ignoring overline_margin. */
if (face->overline_p)
{
NSRect r;
r = NSMakeRect (x, s->y, width, 1);
if (face->overline_color_defaulted_p)
[defaultCol set];
else
[ns_lookup_indexed_color (face->overline_color, s->f) set];
NSRectFill (r);
}
/* Do strike-through. We follow other terms for thickness and
vertical position. */
if (face->strike_through_p)
{
NSRect r;
/* Y-coordinate and height of the glyph string's first glyph.
We cannot use s->y and s->height because those could be
larger if there are taller display elements (e.g., characters
displayed with a larger font) in the same glyph row. */
int glyph_y = s->ybase - s->first_glyph->ascent;
int glyph_height = s->first_glyph->ascent + s->first_glyph->descent;
/* Strike-through width and offset from the glyph string's
top edge. */
unsigned long h = 1;
unsigned long dy;
dy = lrint ((glyph_height - h) / 2);
r = NSMakeRect (x, glyph_y + dy, width, 1);
if (face->strike_through_color_defaulted_p)
[defaultCol set];
else
[ns_lookup_indexed_color (face->strike_through_color, s->f) set];
NSRectFill (r);
}
}
static void
ns_draw_box (NSRect r, CGFloat thickness, NSColor *col,
char left_p, char right_p)
/* --------------------------------------------------------------------------
Draw an unfilled rect inside r, optionally leaving left and/or right open.
Note we can't just use an NSDrawRect command, because of the possibility
of some sides not being drawn, and because the rect will be filled.
-------------------------------------------------------------------------- */
{
NSRect s = r;
[col set];
/* top, bottom */
s.size.height = thickness;
NSRectFill (s);
s.origin.y += r.size.height - thickness;
NSRectFill (s);
s.size.height = r.size.height;
s.origin.y = r.origin.y;
/* left, right (optional) */
s.size.width = thickness;
if (left_p)
NSRectFill (s);
if (right_p)
{
s.origin.x += r.size.width - thickness;
NSRectFill (s);
}
}
static void
ns_draw_relief (NSRect r, int thickness, char raised_p,
char top_p, char bottom_p, char left_p, char right_p,
struct glyph_string *s)
/* --------------------------------------------------------------------------
Draw a relief rect inside r, optionally leaving some sides open.
Note we can't just use an NSDrawBezel command, because of the possibility
of some sides not being drawn, and because the rect will be filled.
-------------------------------------------------------------------------- */
{
static NSColor *baseCol = nil, *lightCol = nil, *darkCol = nil;
NSColor *newBaseCol = nil;
NSRect sr = r;
NSTRACE ("ns_draw_relief");
/* set up colors */
if (s->face->use_box_color_for_shadows_p)
{
newBaseCol = ns_lookup_indexed_color (s->face->box_color, s->f);
}
/* else if (s->first_glyph->type == IMAGE_GLYPH
&& s->img->pixmap
&& !IMAGE_BACKGROUND_TRANSPARENT (s->img, s->f, 0))
{
newBaseCol = IMAGE_BACKGROUND (s->img, s->f, 0);
} */
else
{
newBaseCol = ns_lookup_indexed_color (s->face->background, s->f);
}
if (newBaseCol == nil)
newBaseCol = [NSColor grayColor];
if (newBaseCol != baseCol) /* TODO: better check */
{
[baseCol release];
baseCol = [newBaseCol retain];
[lightCol release];
lightCol = [[baseCol highlightWithLevel: 0.2] retain];
[darkCol release];
darkCol = [[baseCol shadowWithLevel: 0.3] retain];
}
[(raised_p ? lightCol : darkCol) set];
/* TODO: mitering. Using NSBezierPath doesn't work because of color switch. */
/* top */
sr.size.height = thickness;
if (top_p) NSRectFill (sr);
/* left */
sr.size.height = r.size.height;
sr.size.width = thickness;
if (left_p) NSRectFill (sr);
[(raised_p ? darkCol : lightCol) set];
</