diff options
Diffstat (limited to 'src/gui_mac.c')
-rw-r--r-- | src/gui_mac.c | 6731 |
1 files changed, 6731 insertions, 0 deletions
diff --git a/src/gui_mac.c b/src/gui_mac.c new file mode 100644 index 0000000..232b6f2 --- /dev/null +++ b/src/gui_mac.c @@ -0,0 +1,6731 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * GUI/Motif support by Robert Webb + * Macintosh port by Dany St-Amant + * and Axel Kielhorn + * Port to MPW by Bernhard Pruemmer + * Initial Carbon port by Ammon Skidmore + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * NOTES: - Vim 7+ does not support classic MacOS. Please use Vim 6.x + * - Comments mentioning FAQ refer to the book: + * "Macworld Mac Programming FAQs" from "IDG Books" + */ + +/* + * TODO: Change still to merge from the macvim's iDisk + * + * error_ga, mch_errmsg, Navigation's changes in gui_mch_browse + * uses of MenuItemIndex, changes in gui_mch_set_shellsize, + * ScrapManager error handling. + * Comments about function remaining to Carbonize. + * + */ + +/* TODO (Jussi) + * * Clipboard does not work (at least some cases) + * * ATSU font rendering has some problems + * * Investigate and remove dead code (there is still lots of that) + */ + +#include <Devices.h> /* included first to avoid CR problems */ +#include "vim.h" + +#define USE_CARBONIZED +#define USE_AEVENT /* Enable AEVENT */ +#undef USE_OFFSETED_WINDOW /* Debugging feature: start Vim window OFFSETed */ + +/* Compile as CodeWarrior External Editor */ +#if defined(FEAT_CW_EDITOR) && !defined(USE_AEVENT) +# define USE_AEVENT /* Need Apple Event Support */ +#endif + +/* Vim's Scrap flavor. */ +#define VIMSCRAPFLAVOR 'VIM!' +#define SCRAPTEXTFLAVOR kScrapFlavorTypeUnicode + +static EventHandlerUPP mouseWheelHandlerUPP = NULL; +SInt32 gMacSystemVersion; + +#ifdef MACOS_CONVERT +# define USE_CARBONKEYHANDLER + +static int im_is_active = FALSE; +# if 0 + /* TODO: Implement me! */ +static int im_start_row = 0; +static int im_start_col = 0; +# endif + +# define NR_ELEMS(x) (sizeof(x) / sizeof(x[0])) + +static TSMDocumentID gTSMDocument; + +static void im_on_window_switch(int active); +static EventHandlerUPP keyEventHandlerUPP = NULL; +static EventHandlerUPP winEventHandlerUPP = NULL; + +static pascal OSStatus gui_mac_handle_window_activate( + EventHandlerCallRef nextHandler, EventRef theEvent, void *data); + +static pascal OSStatus gui_mac_handle_text_input( + EventHandlerCallRef nextHandler, EventRef theEvent, void *data); + +static pascal OSStatus gui_mac_update_input_area( + EventHandlerCallRef nextHandler, EventRef theEvent); + +static pascal OSStatus gui_mac_unicode_key_event( + EventHandlerCallRef nextHandler, EventRef theEvent); + +#endif + + +/* Include some file. TODO: move into os_mac.h */ +#include <Menus.h> +#include <Resources.h> +#include <Processes.h> +#ifdef USE_AEVENT +# include <AppleEvents.h> +# include <AERegistry.h> +#endif +# include <Gestalt.h> +#if UNIVERSAL_INTERFACES_VERSION >= 0x0330 +# include <ControlDefinitions.h> +# include <Navigation.h> /* Navigation only part of ?? */ +#endif + +/* Help Manager (balloon.h, HM prefixed functions) are not supported + * under Carbon (Jussi) */ +# if 0 +/* New Help Interface for Mac, not implemented yet.*/ +# include <MacHelp.h> +# endif + +/* + * These seem to be rectangle options. Why are they not found in + * headers? (Jussi) + */ +#define kNothing 0 +#define kCreateEmpty 2 /*1*/ +#define kCreateRect 2 +#define kDestroy 3 + +/* + * Dany: Don't like those... + */ +#define topLeft(r) (((Point*)&(r))[0]) +#define botRight(r) (((Point*)&(r))[1]) + + +/* Time of last mouse click, to detect double-click */ +static long lastMouseTick = 0; + +/* ??? */ +static RgnHandle cursorRgn; +static RgnHandle dragRgn; +static Rect dragRect; +static short dragRectEnbl; +static short dragRectControl; + +/* This variable is set when waiting for an event, which is the only moment + * scrollbar dragging can be done directly. It's not allowed while commands + * are executed, because it may move the cursor and that may cause unexpected + * problems (e.g., while ":s" is working). + */ +static int allow_scrollbar = FALSE; + +/* Last mouse click caused contextual menu, (to provide proper release) */ +static short clickIsPopup; + +/* Feedback Action for Scrollbar */ +ControlActionUPP gScrollAction; +ControlActionUPP gScrollDrag; + +/* Keeping track of which scrollbar is being dragged */ +static ControlHandle dragged_sb = NULL; + +/* Vector of char_u --> control index for hotkeys in dialogs */ +static short *gDialogHotKeys; + +static struct +{ + FMFontFamily family; + FMFontSize size; + FMFontStyle style; + Boolean isPanelVisible; +} gFontPanelInfo = { 0, 0, 0, false }; + +#ifdef MACOS_CONVERT +# define USE_ATSUI_DRAWING +int p_macatsui_last; +ATSUStyle gFontStyle; +ATSUStyle gWideFontStyle; +Boolean gIsFontFallbackSet; +UInt32 useAntialias_cached = 0x0; +#endif + +/* Colors Macros */ +#define RGB(r,g,b) ((r) << 16) + ((g) << 8) + (b) +#define Red(c) ((c & 0x00FF0000) >> 16) +#define Green(c) ((c & 0x0000FF00) >> 8) +#define Blue(c) ((c & 0x000000FF) >> 0) + +/* Key mapping */ + +#define vk_Esc 0x35 /* -> 1B */ + +#define vk_F1 0x7A /* -> 10 */ +#define vk_F2 0x78 /*0x63*/ +#define vk_F3 0x63 /*0x76*/ +#define vk_F4 0x76 /*0x60*/ +#define vk_F5 0x60 /*0x61*/ +#define vk_F6 0x61 /*0x62*/ +#define vk_F7 0x62 /*0x63*/ /*?*/ +#define vk_F8 0x64 +#define vk_F9 0x65 +#define vk_F10 0x6D +#define vk_F11 0x67 +#define vk_F12 0x6F +#define vk_F13 0x69 +#define vk_F14 0x6B +#define vk_F15 0x71 + +#define vk_Clr 0x47 /* -> 1B (ESC) */ +#define vk_Enter 0x4C /* -> 03 */ + +#define vk_Space 0x31 /* -> 20 */ +#define vk_Tab 0x30 /* -> 09 */ +#define vk_Return 0x24 /* -> 0D */ +/* This is wrong for OSX, what is it for? */ +#define vk_Delete 0X08 /* -> 08 BackSpace */ + +#define vk_Help 0x72 /* -> 05 */ +#define vk_Home 0x73 /* -> 01 */ +#define vk_PageUp 0x74 /* -> 0D */ +#define vk_FwdDelete 0x75 /* -> 7F */ +#define vk_End 0x77 /* -> 04 */ +#define vk_PageDown 0x79 /* -> 0C */ + +#define vk_Up 0x7E /* -> 1E */ +#define vk_Down 0x7D /* -> 1F */ +#define vk_Left 0x7B /* -> 1C */ +#define vk_Right 0x7C /* -> 1D */ + +#define vk_Undo vk_F1 +#define vk_Cut vk_F2 +#define vk_Copy vk_F3 +#define vk_Paste vk_F4 +#define vk_PrintScreen vk_F13 +#define vk_SCrollLock vk_F14 +#define vk_Pause vk_F15 +#define vk_NumLock vk_Clr +#define vk_Insert vk_Help + +#define KeySym char + +static struct +{ + KeySym key_sym; + char_u vim_code0; + char_u vim_code1; +} special_keys[] = +{ + {vk_Up, 'k', 'u'}, + {vk_Down, 'k', 'd'}, + {vk_Left, 'k', 'l'}, + {vk_Right, 'k', 'r'}, + + {vk_F1, 'k', '1'}, + {vk_F2, 'k', '2'}, + {vk_F3, 'k', '3'}, + {vk_F4, 'k', '4'}, + {vk_F5, 'k', '5'}, + {vk_F6, 'k', '6'}, + {vk_F7, 'k', '7'}, + {vk_F8, 'k', '8'}, + {vk_F9, 'k', '9'}, + {vk_F10, 'k', ';'}, + + {vk_F11, 'F', '1'}, + {vk_F12, 'F', '2'}, + {vk_F13, 'F', '3'}, + {vk_F14, 'F', '4'}, + {vk_F15, 'F', '5'}, + +/* {XK_Help, '%', '1'}, */ +/* {XK_Undo, '&', '8'}, */ +/* {XK_BackSpace, 'k', 'b'}, */ +/* {vk_Delete, 'k', 'b'}, */ + {vk_Insert, 'k', 'I'}, + {vk_FwdDelete, 'k', 'D'}, + {vk_Home, 'k', 'h'}, + {vk_End, '@', '7'}, +/* {XK_Prior, 'k', 'P'}, */ +/* {XK_Next, 'k', 'N'}, */ +/* {XK_Print, '%', '9'}, */ + + {vk_PageUp, 'k', 'P'}, + {vk_PageDown, 'k', 'N'}, + + /* End of list marker: */ + {(KeySym)0, 0, 0} +}; + +/* + * ------------------------------------------------------------ + * Forward declaration (for those needed) + * ------------------------------------------------------------ + */ + +#ifdef USE_AEVENT +OSErr HandleUnusedParms(const AppleEvent *theAEvent); +#endif + +#ifdef FEAT_GUI_TABLINE +static void initialise_tabline(void); +static WindowRef drawer = NULL; // TODO: put into gui.h +#endif + +#ifdef USE_ATSUI_DRAWING +static void gui_mac_set_font_attributes(GuiFont font); +#endif + +/* + * ------------------------------------------------------------ + * Conversion Utility + * ------------------------------------------------------------ + */ + +/* + * C2Pascal_save + * + * Allocate memory and convert the C-String passed in + * into a pascal string + * + */ + + char_u * +C2Pascal_save(char_u *Cstring) +{ + char_u *PascalString; + int len; + + if (Cstring == NULL) + return NULL; + + len = STRLEN(Cstring); + + if (len > 255) /* Truncate if necessary */ + len = 255; + + PascalString = alloc(len + 1); + if (PascalString != NULL) + { + mch_memmove(PascalString + 1, Cstring, len); + PascalString[0] = len; + } + + return PascalString; +} + +/* + * C2Pascal_save_and_remove_backslash + * + * Allocate memory and convert the C-String passed in + * into a pascal string. Also remove the backslash at the same time + * + */ + + char_u * +C2Pascal_save_and_remove_backslash(char_u *Cstring) +{ + char_u *PascalString; + int len; + char_u *p, *c; + + len = STRLEN(Cstring); + + if (len > 255) /* Truncate if necessary */ + len = 255; + + PascalString = alloc(len + 1); + if (PascalString != NULL) + { + for (c = Cstring, p = PascalString+1, len = 0; (*c != 0) && (len < 255); c++) + { + if ((*c == '\\') && (c[1] != 0)) + { + c++; + } + *p = *c; + p++; + len++; + } + PascalString[0] = len; + } + + return PascalString; +} + +/* + * Convert the modifiers of an Event into vim's modifiers (mouse) + */ + + int_u +EventModifiers2VimMouseModifiers(EventModifiers macModifiers) +{ + int_u vimModifiers = 0x00; + + if (macModifiers & (shiftKey | rightShiftKey)) + vimModifiers |= MOUSE_SHIFT; + if (macModifiers & (controlKey | rightControlKey)) + vimModifiers |= MOUSE_CTRL; + if (macModifiers & (optionKey | rightOptionKey)) + vimModifiers |= MOUSE_ALT; +#if 0 + /* Not yet supported */ + if (macModifiers & (cmdKey)) /* There's no rightCmdKey */ + vimModifiers |= MOUSE_CMD; +#endif + return (vimModifiers); +} + +/* + * Convert the modifiers of an Event into vim's modifiers (keys) + */ + + static int_u +EventModifiers2VimModifiers(EventModifiers macModifiers) +{ + int_u vimModifiers = 0x00; + + if (macModifiers & (shiftKey | rightShiftKey)) + vimModifiers |= MOD_MASK_SHIFT; + if (macModifiers & (controlKey | rightControlKey)) + vimModifiers |= MOD_MASK_CTRL; + if (macModifiers & (optionKey | rightOptionKey)) + vimModifiers |= MOD_MASK_ALT; +#ifdef USE_CMD_KEY + if (macModifiers & (cmdKey)) /* There's no rightCmdKey */ + vimModifiers |= MOD_MASK_CMD; +#endif + return (vimModifiers); +} + +/* Convert a string representing a point size into pixels. The string should + * be a positive decimal number, with an optional decimal point (eg, "12", or + * "10.5"). The pixel value is returned, and a pointer to the next unconverted + * character is stored in *end. The flag "vertical" says whether this + * calculation is for a vertical (height) size or a horizontal (width) one. + * + * From gui_w48.c + */ + static int +points_to_pixels(char_u *str, char_u **end, int vertical) +{ + int pixels; + int points = 0; + int divisor = 0; + + while (*str) + { + if (*str == '.' && divisor == 0) + { + /* Start keeping a divisor, for later */ + divisor = 1; + continue; + } + + if (!isdigit(*str)) + break; + + points *= 10; + points += *str - '0'; + divisor *= 10; + + ++str; + } + + if (divisor == 0) + divisor = 1; + + pixels = points/divisor; + *end = str; + return pixels; +} + +#ifdef MACOS_CONVERT +/* + * Deletes all traces of any Windows-style mnemonic text (including any + * parentheses) from a menu item and returns the cleaned menu item title. + * The caller is responsible for releasing the returned string. + */ + static CFStringRef +menu_title_removing_mnemonic(vimmenu_T *menu) +{ + CFStringRef name; + size_t menuTitleLen; + CFIndex displayLen; + CFRange mnemonicStart; + CFRange mnemonicEnd; + CFMutableStringRef cleanedName; + + menuTitleLen = STRLEN(menu->dname); + name = (CFStringRef) mac_enc_to_cfstring(menu->dname, menuTitleLen); + + if (name) + { + /* Simple mnemonic-removal algorithm, assumes single parenthesized + * mnemonic character towards the end of the menu text */ + mnemonicStart = CFStringFind(name, CFSTR("("), kCFCompareBackwards); + displayLen = CFStringGetLength(name); + + if (mnemonicStart.location != kCFNotFound + && (mnemonicStart.location + 2) < displayLen + && CFStringGetCharacterAtIndex(name, + mnemonicStart.location + 1) == (UniChar)menu->mnemonic) + { + if (CFStringFindWithOptions(name, CFSTR(")"), + CFRangeMake(mnemonicStart.location + 1, + displayLen - mnemonicStart.location - 1), + kCFCompareBackwards, &mnemonicEnd) && + (mnemonicStart.location + 2) == mnemonicEnd.location) + { + cleanedName = CFStringCreateMutableCopy(NULL, 0, name); + if (cleanedName) + { + CFStringDelete(cleanedName, + CFRangeMake(mnemonicStart.location, + mnemonicEnd.location + 1 - + mnemonicStart.location)); + + CFRelease(name); + name = cleanedName; + } + } + } + } + + return name; +} +#endif + +/* + * Convert a list of FSSpec aliases into a list of fullpathname + * character strings. + */ + + char_u ** +new_fnames_from_AEDesc(AEDesc *theList, long *numFiles, OSErr *error) +{ + char_u **fnames = NULL; + OSErr newError; + long fileCount; + FSSpec fileToOpen; + long actualSize; + AEKeyword dummyKeyword; + DescType dummyType; + + /* Get number of files in list */ + *error = AECountItems(theList, numFiles); + if (*error) + return fnames; + + /* Allocate the pointer list */ + fnames = (char_u **) alloc(*numFiles * sizeof(char_u *)); + + /* Empty out the list */ + for (fileCount = 0; fileCount < *numFiles; fileCount++) + fnames[fileCount] = NULL; + + /* Scan the list of FSSpec */ + for (fileCount = 1; fileCount <= *numFiles; fileCount++) + { + /* Get the alias for the nth file, convert to an FSSpec */ + newError = AEGetNthPtr(theList, fileCount, typeFSS, + &dummyKeyword, &dummyType, + (Ptr) &fileToOpen, sizeof(FSSpec), &actualSize); + if (newError) + { + /* Caller is able to clean up */ + /* TODO: Should be clean up or not? For safety. */ + return fnames; + } + + /* Convert the FSSpec to a pathname */ + fnames[fileCount - 1] = FullPathFromFSSpec_save(fileToOpen); + } + + return (fnames); +} + +/* + * ------------------------------------------------------------ + * CodeWarrior External Editor Support + * ------------------------------------------------------------ + */ +#ifdef FEAT_CW_EDITOR + +/* + * Handle the Window Search event from CodeWarrior + * + * Description + * ----------- + * + * The IDE sends the Window Search AppleEvent to the editor when it + * needs to know whether a particular file is open in the editor. + * + * Event Reply + * ----------- + * + * None. Put data in the location specified in the structure received. + * + * Remarks + * ------- + * + * When the editor receives this event, determine whether the specified + * file is open. If it is, return the modification date/time for that file + * in the appropriate location specified in the structure. If the file is + * not opened, put the value fnfErr(file not found) in that location. + * + */ + +typedef struct WindowSearch WindowSearch; +struct WindowSearch /* for handling class 'KAHL', event 'SRCH', keyDirectObject typeChar*/ +{ + FSSpec theFile; // identifies the file + long *theDate; // where to put the modification date/time +}; + + pascal OSErr +Handle_KAHL_SRCH_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + buf_T *buf; + int foundFile = false; + DescType typeCode; + WindowSearch SearchData; + Size actualSize; + + error = AEGetParamPtr(theAEvent, keyDirectObject, typeChar, &typeCode, (Ptr) &SearchData, sizeof(WindowSearch), &actualSize); + if (error) + return error; + + error = HandleUnusedParms(theAEvent); + if (error) + return error; + + FOR_ALL_BUFFERS(buf) + if (buf->b_ml.ml_mfp != NULL + && SearchData.theFile.parID == buf->b_FSSpec.parID + && SearchData.theFile.name[0] == buf->b_FSSpec.name[0] + && STRNCMP(SearchData.theFile.name, buf->b_FSSpec.name, buf->b_FSSpec.name[0] + 1) == 0) + { + foundFile = true; + break; + } + + if (foundFile == false) + *SearchData.theDate = fnfErr; + else + *SearchData.theDate = buf->b_mtime; + + return error; +}; + +/* + * Handle the Modified (from IDE to Editor) event from CodeWarrior + * + * Description + * ----------- + * + * The IDE sends this event to the external editor when it wants to + * know which files that are open in the editor have been modified. + * + * Parameters None. + * ---------- + * + * Event Reply + * ----------- + * The reply for this event is: + * + * keyDirectObject typeAEList required + * each element in the list is a structure of typeChar + * + * Remarks + * ------- + * + * When building the reply event, include one element in the list for + * each open file that has been modified. + * + */ + +typedef struct ModificationInfo ModificationInfo; +struct ModificationInfo /* for replying to class 'KAHL', event 'MOD ', keyDirectObject typeAEList*/ +{ + FSSpec theFile; // identifies the file + long theDate; // the date/time the file was last modified + short saved; // set this to zero when replying, unused +}; + + pascal OSErr +Handle_KAHL_MOD_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + AEDescList replyList; + long numFiles; + ModificationInfo theFile; + buf_T *buf; + + theFile.saved = 0; + + error = HandleUnusedParms(theAEvent); + if (error) + return error; + + /* Send the reply */ +/* replyObject.descriptorType = typeNull; + replyObject.dataHandle = nil;*/ + +/* AECreateDesc(typeChar, (Ptr)&title[1], title[0], &data) */ + error = AECreateList(nil, 0, false, &replyList); + if (error) + return error; + +#if 0 + error = AECountItems(&replyList, &numFiles); + + /* AEPutKeyDesc(&replyList, keyAEPnject, &aDesc) + * AEPutKeyPtr(&replyList, keyAEPosition, typeChar, (Ptr)&theType, + * sizeof(DescType)) + */ + + /* AEPutDesc */ +#endif + + numFiles = 0; + FOR_ALL_BUFFERS(buf) + if (buf->b_ml.ml_mfp != NULL) + { + /* Add this file to the list */ + theFile.theFile = buf->b_FSSpec; + theFile.theDate = buf->b_mtime; +/* theFile.theDate = time(NULL) & (time_t) 0xFFFFFFF0; */ + error = AEPutPtr(&replyList, numFiles, typeChar, (Ptr) &theFile, sizeof(theFile)); + }; + +#if 0 + error = AECountItems(&replyList, &numFiles); +#endif + + /* We can add data only if something to reply */ + error = AEPutParamDesc(theReply, keyDirectObject, &replyList); + + if (replyList.dataHandle) + AEDisposeDesc(&replyList); + + return error; +}; + +/* + * Handle the Get Text event from CodeWarrior + * + * Description + * ----------- + * + * The IDE sends the Get Text AppleEvent to the editor when it needs + * the source code from a file. For example, when the user issues a + * Check Syntax or Compile command, the compiler needs access to + * the source code contained in the file. + * + * Event Reply + * ----------- + * + * None. Put data in locations specified in the structure received. + * + * Remarks + * ------- + * + * When the editor receives this event, it must set the size of the handle + * in theText to fit the data in the file. It must then copy the entire + * contents of the specified file into the memory location specified in + * theText. + * + */ + +typedef struct CW_GetText CW_GetText; +struct CW_GetText /* for handling class 'KAHL', event 'GTTX', keyDirectObject typeChar*/ +{ + FSSpec theFile; /* identifies the file */ + Handle theText; /* the location where you return the text (must be resized properly) */ + long *unused; /* 0 (not used) */ + long *theDate; /* where to put the modification date/time */ +}; + + pascal OSErr +Handle_KAHL_GTTX_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + buf_T *buf; + int foundFile = false; + DescType typeCode; + CW_GetText GetTextData; + Size actualSize; + char_u *line; + char_u *fullbuffer = NULL; + long linesize; + long lineStart; + long BufferSize; + long lineno; + + error = AEGetParamPtr(theAEvent, keyDirectObject, typeChar, &typeCode, (Ptr) &GetTextData, sizeof(GetTextData), &actualSize); + + if (error) + return error; + + FOR_ALL_BUFFERS(buf) + if (buf->b_ml.ml_mfp != NULL) + if (GetTextData.theFile.parID == buf->b_FSSpec.parID) + { + foundFile = true; + break; + } + + if (foundFile) + { + BufferSize = 0; /* GetHandleSize(GetTextData.theText); */ + for (lineno = 0; lineno <= buf->b_ml.ml_line_count; lineno++) + { + /* Must use the right buffer */ + line = ml_get_buf(buf, (linenr_T) lineno, FALSE); + linesize = STRLEN(line) + 1; + lineStart = BufferSize; + BufferSize += linesize; + /* Resize handle to linesize+1 to include the linefeed */ + SetHandleSize(GetTextData.theText, BufferSize); + if (GetHandleSize(GetTextData.theText) != BufferSize) + { + break; /* Simple handling for now */ + } + else + { + HLock(GetTextData.theText); + fullbuffer = (char_u *) *GetTextData.theText; + STRCPY((char_u *)(fullbuffer + lineStart), line); + fullbuffer[BufferSize-1] = '\r'; + HUnlock(GetTextData.theText); + } + } + if (fullbuffer != NULL) + { + HLock(GetTextData.theText); + fullbuffer[BufferSize-1] = 0; + HUnlock(GetTextData.theText); + } + if (foundFile == false) + *GetTextData.theDate = fnfErr; + else +/* *GetTextData.theDate = time(NULL) & (time_t) 0xFFFFFFF0;*/ + *GetTextData.theDate = buf->b_mtime; + } + + error = HandleUnusedParms(theAEvent); + + return error; +} + +/* + * + */ + +/* Taken from MoreAppleEvents:ProcessHelpers*/ + pascal OSErr +FindProcessBySignature( + const OSType targetType, + const OSType targetCreator, + ProcessSerialNumberPtr psnPtr) +{ + OSErr anErr = noErr; + Boolean lookingForProcess = true; + + ProcessInfoRec infoRec; + + infoRec.processInfoLength = sizeof(ProcessInfoRec); + infoRec.processName = nil; + infoRec.processAppSpec = nil; + + psnPtr->lowLongOfPSN = kNoProcess; + psnPtr->highLongOfPSN = kNoProcess; + + while (lookingForProcess) + { + anErr = GetNextProcess(psnPtr); + if (anErr != noErr) + lookingForProcess = false; + else + { + anErr = GetProcessInformation(psnPtr, &infoRec); + if ((anErr == noErr) + && (infoRec.processType == targetType) + && (infoRec.processSignature == targetCreator)) + lookingForProcess = false; + } + } + + return anErr; +}//end FindProcessBySignature + + void +Send_KAHL_MOD_AE(buf_T *buf) +{ + OSErr anErr = noErr; + AEDesc targetAppDesc = { typeNull, nil }; + ProcessSerialNumber psn = { kNoProcess, kNoProcess }; + AppleEvent theReply = { typeNull, nil }; + AESendMode sendMode; + AppleEvent theEvent = {typeNull, nil }; + AEIdleUPP idleProcUPP = nil; + ModificationInfo ModData; + + + anErr = FindProcessBySignature('APPL', 'CWIE', &psn); + if (anErr == noErr) + { + anErr = AECreateDesc(typeProcessSerialNumber, &psn, + sizeof(ProcessSerialNumber), &targetAppDesc); + + if (anErr == noErr) + { + anErr = AECreateAppleEvent( 'KAHL', 'MOD ', &targetAppDesc, + kAutoGenerateReturnID, kAnyTransactionID, &theEvent); + } + + AEDisposeDesc(&targetAppDesc); + + /* Add the parms */ + ModData.theFile = buf->b_FSSpec; + ModData.theDate = buf->b_mtime; + + if (anErr == noErr) + anErr = AEPutParamPtr(&theEvent, keyDirectObject, typeChar, &ModData, sizeof(ModData)); + + if (idleProcUPP == nil) + sendMode = kAENoReply; + else + sendMode = kAEWaitReply; + + if (anErr == noErr) + anErr = AESend(&theEvent, &theReply, sendMode, kAENormalPriority, kNoTimeOut, idleProcUPP, nil); + if (anErr == noErr && sendMode == kAEWaitReply) + { +/* anErr = AEHGetHandlerError(&theReply);*/ + } + (void) AEDisposeDesc(&theReply); + } +} +#endif /* FEAT_CW_EDITOR */ + +/* + * ------------------------------------------------------------ + * Apple Event Handling procedure + * ------------------------------------------------------------ + */ +#ifdef USE_AEVENT + +/* + * Handle the Unused parms of an AppleEvent + */ + + OSErr +HandleUnusedParms(const AppleEvent *theAEvent) +{ + OSErr error; + long actualSize; + DescType dummyType; + AEKeyword missedKeyword; + + /* Get the "missed keyword" attribute from the AppleEvent. */ + error = AEGetAttributePtr(theAEvent, keyMissedKeywordAttr, + typeKeyword, &dummyType, + (Ptr)&missedKeyword, sizeof(missedKeyword), + &actualSize); + + /* If the descriptor isn't found, then we got the required parameters. */ + if (error == errAEDescNotFound) + { + error = noErr; + } + else + { +#if 0 + /* Why is this removed? */ + error = errAEEventNotHandled; +#endif + } + + return error; +} + + +/* + * Handle the ODoc AppleEvent + * + * Deals with all files dragged to the application icon. + * + */ + +typedef struct SelectionRange SelectionRange; +struct SelectionRange /* for handling kCoreClassEvent:kOpenDocuments:keyAEPosition typeChar */ +{ + short unused1; // 0 (not used) + short lineNum; // line to select (<0 to specify range) + long startRange; // start of selection range (if line < 0) + long endRange; // end of selection range (if line < 0) + long unused2; // 0 (not used) + long theDate; // modification date/time +}; + +static long drop_numFiles; +static short drop_gotPosition; +static SelectionRange drop_thePosition; + + static void +drop_callback(void *cookie UNUSED) +{ + /* TODO: Handle the goto/select line more cleanly */ + if ((drop_numFiles == 1) & (drop_gotPosition)) + { + if (drop_thePosition.lineNum >= 0) + { + lnum = drop_thePosition.lineNum + 1; + /* oap->motion_type = MLINE; + setpcmark();*/ + if (lnum < 1L) + lnum = 1L; + else if (lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + /* beginline(BL_SOL | BL_FIX);*/ + } + else + goto_byte(drop_thePosition.startRange + 1); + } + + /* Update the screen display */ + update_screen(NOT_VALID); + + /* Select the text if possible */ + if (drop_gotPosition) + { + VIsual_active = TRUE; + VIsual_select = FALSE; + VIsual = curwin->w_cursor; + if (drop_thePosition.lineNum < 0) + { + VIsual_mode = 'v'; + goto_byte(drop_thePosition.endRange); + } + else + { + VIsual_mode = 'V'; + VIsual.col = 0; + } + } +} + +/* The IDE uses the optional keyAEPosition parameter to tell the ed- + itor the selection range. If lineNum is zero or greater, scroll the text + to the specified line. If lineNum is less than zero, use the values in + startRange and endRange to select the specified characters. Scroll + the text to display the selection. If lineNum, startRange, and + endRange are all negative, there is no selection range specified. + */ + + pascal OSErr +HandleODocAE(const AppleEvent *theAEvent, AppleEvent *theReply, long refCon) +{ + /* + * TODO: Clean up the code with convert the AppleEvent into + * a ":args" + */ + OSErr error = noErr; +// OSErr firstError = noErr; +// short numErrors = 0; + AEDesc theList; + DescType typeCode; + long numFiles; + // long fileCount; + char_u **fnames; +// char_u fname[256]; + Size actualSize; + SelectionRange thePosition; + short gotPosition = false; + long lnum; + + /* the direct object parameter is the list of aliases to files (one or more) */ + error = AEGetParamDesc(theAEvent, keyDirectObject, typeAEList, &theList); + if (error) + return error; + + + error = AEGetParamPtr(theAEvent, keyAEPosition, typeChar, &typeCode, (Ptr) &thePosition, sizeof(SelectionRange), &actualSize); + if (error == noErr) + gotPosition = true; + if (error == errAEDescNotFound) + error = noErr; + if (error) + return error; + +/* + error = AEGetParamDesc(theAEvent, keyAEPosition, typeChar, &thePosition); + + if (^error) then + { + if (thePosition.lineNum >= 0) + { + // Goto this line + } + else + { + // Set the range char wise + } + } + */ + + reset_VIsual(); + fnames = new_fnames_from_AEDesc(&theList, &numFiles, &error); + + if (error) + { + /* TODO: empty fnames[] first */ + vim_free(fnames); + return (error); + } + + if (starting > 0) + { + int i; + char_u *p; + int fnum = -1; + + /* these are the initial files dropped on the Vim icon */ + for (i = 0 ; i < numFiles; i++) + { + if (ga_grow(&global_alist.al_ga, 1) == FAIL + || (p = vim_strsave(fnames[i])) == NULL) + mch_exit(2); + else + alist_add(&global_alist, p, 2); + if (fnum == -1) + fnum = GARGLIST[GARGCOUNT - 1].ae_fnum; + } + + /* If the file name was already in the buffer list we need to switch + * to it. */ + if (curbuf->b_fnum != fnum) + { + char_u cmd[30]; + + vim_snprintf((char *)cmd, 30, "silent %dbuffer", fnum); + do_cmdline_cmd(cmd); + } + + /* Change directory to the location of the first file. */ + if (GARGCOUNT > 0 + && vim_chdirfile(alist_name(&GARGLIST[0]), "drop") == OK) + shorten_fnames(TRUE); + + goto finished; + } + + /* Handle the drop, :edit to get to the file */ + drop_numFiles = numFiles; + drop_gotPosition = gotPosition; + drop_thePosition = thePosition; + handle_drop(numFiles, fnames, FALSE, drop_callback, NULL); + + setcursor(); + out_flush(); + + /* Fake mouse event to wake from stall */ + PostEvent(mouseUp, 0); + +finished: + AEDisposeDesc(&theList); /* dispose what we allocated */ + + error = HandleUnusedParms(theAEvent); + return error; +} + +/* + * + */ + + pascal OSErr +Handle_aevt_oapp_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + + error = HandleUnusedParms(theAEvent); + return error; +} + +/* + * + */ + + pascal OSErr +Handle_aevt_quit_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + + error = HandleUnusedParms(theAEvent); + if (error) + return error; + + /* Need to fake a :confirm qa */ + do_cmdline_cmd((char_u *)"confirm qa"); + + return error; +} + +/* + * + */ + + pascal OSErr +Handle_aevt_pdoc_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + + error = HandleUnusedParms(theAEvent); + + return error; +} + +/* + * Handling of unknown AppleEvent + * + * (Just get rid of all the parms) + */ + pascal OSErr +Handle_unknown_AE( + const AppleEvent *theAEvent, + AppleEvent *theReply, + long refCon) +{ + OSErr error = noErr; + + error = HandleUnusedParms(theAEvent); + + return error; +} + + +/* + * Install the various AppleEvent Handlers + */ + OSErr +InstallAEHandlers(void) +{ + OSErr error; + + /* install open application handler */ + error = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, + NewAEEventHandlerUPP(Handle_aevt_oapp_AE), 0, false); + if (error) + { + return error; + } + + /* install quit application handler */ + error = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, + NewAEEventHandlerUPP(Handle_aevt_quit_AE), 0, false); + if (error) + { + return error; + } + + /* install open document handler */ + error = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, + NewAEEventHandlerUPP(HandleODocAE), 0, false); + if (error) + { + return error; + } + + /* install print document handler */ + error = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments, + NewAEEventHandlerUPP(Handle_aevt_pdoc_AE), 0, false); + +/* Install Core Suite */ +/* error = AEInstallEventHandler(kAECoreSuite, kAEClone, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEClose, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAECountElements, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAECreateElement, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEDelete, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEDoObjectsExist, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEGetData, + NewAEEventHandlerUPP(Handle_unknown_AE), kAEGetData, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEGetDataSize, + NewAEEventHandlerUPP(Handle_unknown_AE), kAEGetDataSize, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEGetClassInfo, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEGetEventInfo, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAEMove, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAESave, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); + + error = AEInstallEventHandler(kAECoreSuite, kAESetData, + NewAEEventHandlerUPP(Handle_unknown_AE), nil, false); +*/ + +#ifdef FEAT_CW_EDITOR + /* + * Bind codewarrior support handlers + */ + error = AEInstallEventHandler('KAHL', 'GTTX', + NewAEEventHandlerUPP(Handle_KAHL_GTTX_AE), 0, false); + if (error) + { + return error; + } + error = AEInstallEventHandler('KAHL', 'SRCH', + NewAEEventHandlerUPP(Handle_KAHL_SRCH_AE), 0, false); + if (error) + { + return error; + } + error = AEInstallEventHandler('KAHL', 'MOD ', + NewAEEventHandlerUPP(Handle_KAHL_MOD_AE), 0, false); + if (error) + { + return error; + } +#endif + + return error; + +} +#endif /* USE_AEVENT */ + + +/* + * Callback function, installed by InstallFontPanelHandler(), below, + * to handle Font Panel events. + */ + static OSStatus +FontPanelHandler( + EventHandlerCallRef inHandlerCallRef, + EventRef inEvent, + void *inUserData) +{ + if (GetEventKind(inEvent) == kEventFontPanelClosed) + { + gFontPanelInfo.isPanelVisible = false; + return noErr; + } + + if (GetEventKind(inEvent) == kEventFontSelection) + { + OSStatus status; + FMFontFamily newFamily; + FMFontSize newSize; + FMFontStyle newStyle; + + /* Retrieve the font family ID number. */ + status = GetEventParameter(inEvent, kEventParamFMFontFamily, + /*inDesiredType=*/typeFMFontFamily, /*outActualType=*/NULL, + /*inBufferSize=*/sizeof(FMFontFamily), /*outActualSize=*/NULL, + &newFamily); + if (status == noErr) + gFontPanelInfo.family = newFamily; + + /* Retrieve the font size. */ + status = GetEventParameter(inEvent, kEventParamFMFontSize, + typeFMFontSize, NULL, sizeof(FMFontSize), NULL, &newSize); + if (status == noErr) + gFontPanelInfo.size = newSize; + + /* Retrieve the font style (bold, etc.). Currently unused. */ + status = GetEventParameter(inEvent, kEventParamFMFontStyle, + typeFMFontStyle, NULL, sizeof(FMFontStyle), NULL, &newStyle); + if (status == noErr) + gFontPanelInfo.style = newStyle; + } + return noErr; +} + + + static void +InstallFontPanelHandler(void) +{ + EventTypeSpec eventTypes[2]; + EventHandlerUPP handlerUPP; + /* EventHandlerRef handlerRef; */ + + eventTypes[0].eventClass = kEventClassFont; + eventTypes[0].eventKind = kEventFontSelection; + eventTypes[1].eventClass = kEventClassFont; + eventTypes[1].eventKind = kEventFontPanelClosed; + + handlerUPP = NewEventHandlerUPP(FontPanelHandler); + + InstallApplicationEventHandler(handlerUPP, /*numTypes=*/2, eventTypes, + /*userData=*/NULL, /*handlerRef=*/NULL); +} + + +/* + * Fill the buffer pointed to by outName with the name and size + * of the font currently selected in the Font Panel. + */ +#define FONT_STYLE_BUFFER_SIZE 32 + static void +GetFontPanelSelection(char_u *outName) +{ + Str255 buf; + ByteCount fontNameLen = 0; + ATSUFontID fid; + char_u styleString[FONT_STYLE_BUFFER_SIZE]; + + if (!outName) + return; + + if (FMGetFontFamilyName(gFontPanelInfo.family, buf) == noErr) + { + /* Canonicalize localized font names */ + if (FMGetFontFromFontFamilyInstance(gFontPanelInfo.family, + gFontPanelInfo.style, &fid, NULL) != noErr) + return; + + /* Request font name with Mac encoding (otherwise we could + * get an unwanted utf-16 name) */ + if (ATSUFindFontName(fid, kFontFullName, kFontMacintoshPlatform, + kFontNoScriptCode, kFontNoLanguageCode, + 255, (char *)outName, &fontNameLen, NULL) != noErr) + return; + + /* Only encode font size, because style (bold, italic, etc) is + * already part of the font full name */ + vim_snprintf((char *)styleString, FONT_STYLE_BUFFER_SIZE, ":h%d", + gFontPanelInfo.size/*, + ((gFontPanelInfo.style & bold)!=0 ? ":b" : ""), + ((gFontPanelInfo.style & italic)!=0 ? ":i" : ""), + ((gFontPanelInfo.style & underline)!=0 ? ":u" : "")*/); + + if ((fontNameLen + STRLEN(styleString)) < 255) + STRCPY(outName + fontNameLen, styleString); + } + else + { + *outName = NUL; + } +} + + +/* + * ------------------------------------------------------------ + * Unfiled yet + * ------------------------------------------------------------ + */ + +/* + * gui_mac_get_menu_item_index + * + * Returns the index inside the menu where + */ + short /* Should we return MenuItemIndex? */ +gui_mac_get_menu_item_index(vimmenu_T *pMenu) +{ + short index; + short itemIndex = -1; + vimmenu_T *pBrother; + + /* Only menu without parent are the: + * -menu in the menubar + * -popup menu + * -toolbar (guess) + * + * Which are not items anyway. + */ + if (pMenu->parent) + { + /* Start from the Oldest Brother */ + pBrother = pMenu->parent->children; + index = 1; + while ((pBrother) && (itemIndex == -1)) + { + if (pBrother == pMenu) + itemIndex = index; + index++; + pBrother = pBrother->next; + } + } + return itemIndex; +} + + static vimmenu_T * +gui_mac_get_vim_menu(short menuID, short itemIndex, vimmenu_T *pMenu) +{ + short index; + vimmenu_T *pChildMenu; + vimmenu_T *pElder = pMenu->parent; + + + /* Only menu without parent are the: + * -menu in the menubar + * -popup menu + * -toolbar (guess) + * + * Which are not items anyway. + */ + + if ((pElder) && (pElder->submenu_id == menuID)) + { + for (index = 1; (index != itemIndex) && (pMenu != NULL); index++) + pMenu = pMenu->next; + } + else + { + for (; pMenu != NULL; pMenu = pMenu->next) + { + if (pMenu->children != NULL) + { + pChildMenu = gui_mac_get_vim_menu + (menuID, itemIndex, pMenu->children); + if (pChildMenu) + { + pMenu = pChildMenu; + break; + } + } + } + } + return pMenu; +} + +/* + * ------------------------------------------------------------ + * MacOS Feedback procedures + * ------------------------------------------------------------ + */ + pascal + void +gui_mac_drag_thumb(ControlHandle theControl, short partCode) +{ + scrollbar_T *sb; + int value, dragging; + ControlHandle theControlToUse; + int dont_scroll_save = dont_scroll; + + theControlToUse = dragged_sb; + + sb = gui_find_scrollbar((long) GetControlReference(theControlToUse)); + + if (sb == NULL) + return; + + /* Need to find value by diff between Old Poss New Pos */ + value = GetControl32BitValue(theControlToUse); + dragging = (partCode != 0); + + /* When "allow_scrollbar" is FALSE still need to remember the new + * position, but don't actually scroll by setting "dont_scroll". */ + dont_scroll = !allow_scrollbar; + gui_drag_scrollbar(sb, value, dragging); + dont_scroll = dont_scroll_save; +} + + pascal + void +gui_mac_scroll_action(ControlHandle theControl, short partCode) +{ + /* TODO: have live support */ + scrollbar_T *sb, *sb_info; + long data; + long value; + int page; + int dragging = FALSE; + int dont_scroll_save = dont_scroll; + + sb = gui_find_scrollbar((long)GetControlReference(theControl)); + + if (sb == NULL) + return; + + if (sb->wp != NULL) /* Left or right scrollbar */ + { + /* + * Careful: need to get scrollbar info out of first (left) scrollbar + * for window, but keep real scrollbar too because we must pass it to + * gui_drag_scrollbar(). + */ + sb_info = &sb->wp->w_scrollbars[0]; + + if (sb_info->size > 5) + page = sb_info->size - 2; /* use two lines of context */ + else + page = sb_info->size; + } + else /* Bottom scrollbar */ + { + sb_info = sb; + page = curwin->w_width - 5; + } + + switch (partCode) + { + case kControlUpButtonPart: data = -1; break; + case kControlDownButtonPart: data = 1; break; + case kControlPageDownPart: data = page; break; + case kControlPageUpPart: data = -page; break; + default: data = 0; break; + } + + value = sb_info->value + data; +/* if (value > sb_info->max) + value = sb_info->max; + else if (value < 0) + value = 0;*/ + + /* When "allow_scrollbar" is FALSE still need to remember the new + * position, but don't actually scroll by setting "dont_scroll". */ + dont_scroll = !allow_scrollbar; + gui_drag_scrollbar(sb, value, dragging); + dont_scroll = dont_scroll_save; + + out_flush(); + gui_mch_set_scrollbar_thumb(sb, value, sb_info->size, sb_info->max); + +/* if (sb_info->wp != NULL) + { + win_T *wp; + int sb_num; + + sb_num = 0; + for (wp = firstwin; wp != sb->wp && wp != NULL; wp = W_NEXT(wp)) + sb_num++; + + if (wp != NULL) + { + current_scrollbar = sb_num; + scrollbar_value = value; + gui_do_scroll(); + gui_mch_set_scrollbar_thumb(sb, value, sb_info->size, sb_info->max); + } + }*/ +} + +/* + * ------------------------------------------------------------ + * MacOS Click Handling procedures + * ------------------------------------------------------------ + */ + + +/* + * Handle a click inside the window, it may happens in the + * scrollbar or the contents. + * + * TODO: Add support for potential TOOLBAR + */ + void +gui_mac_doInContentClick(EventRecord *theEvent, WindowPtr whichWindow) +{ + Point thePoint; + int_u vimModifiers; + short thePortion; + ControlHandle theControl; + int vimMouseButton; + short dblClick; + + thePoint = theEvent->where; + GlobalToLocal(&thePoint); + SelectWindow(whichWindow); + + thePortion = FindControl(thePoint, whichWindow, &theControl); + + if (theControl != NUL) + { + /* We hit a scrollbar */ + + if (thePortion != kControlIndicatorPart) + { + dragged_sb = theControl; + TrackControl(theControl, thePoint, gScrollAction); + dragged_sb = NULL; + } + else + { + dragged_sb = theControl; +#if 1 + TrackControl(theControl, thePoint, gScrollDrag); +#else + TrackControl(theControl, thePoint, NULL); +#endif + /* pass 0 as the part to tell gui_mac_drag_thumb, that the mouse + * button has been released */ + gui_mac_drag_thumb(theControl, 0); /* Should it be thePortion ? (Dany) */ + dragged_sb = NULL; + } + } + else + { + /* We are inside the contents */ + + /* Convert the CTRL, OPTION, SHIFT and CMD key */ + vimModifiers = EventModifiers2VimMouseModifiers(theEvent->modifiers); + + /* Defaults to MOUSE_LEFT as there's only one mouse button */ + vimMouseButton = MOUSE_LEFT; + + /* Convert the CTRL_MOUSE_LEFT to MOUSE_RIGHT */ + /* TODO: NEEDED? */ + clickIsPopup = FALSE; + + if (mouse_model_popup() && IsShowContextualMenuClick(theEvent)) + { + vimMouseButton = MOUSE_RIGHT; + vimModifiers &= ~MOUSE_CTRL; + clickIsPopup = TRUE; + } + + /* Is it a double click ? */ + dblClick = ((theEvent->when - lastMouseTick) < GetDblTime()); + + /* Send the mouse click to Vim */ + gui_send_mouse_event(vimMouseButton, thePoint.h, + thePoint.v, dblClick, vimModifiers); + + /* Create the rectangle around the cursor to detect + * the mouse dragging + */ +#if 0 + /* TODO: Do we need to this even for the contextual menu? + * It may be require for popup_setpos, but for popup? + */ + if (vimMouseButton == MOUSE_LEFT) +#endif + { + SetRect(&dragRect, FILL_X(X_2_COL(thePoint.h)), + FILL_Y(Y_2_ROW(thePoint.v)), + FILL_X(X_2_COL(thePoint.h)+1), + FILL_Y(Y_2_ROW(thePoint.v)+1)); + + dragRectEnbl = TRUE; + dragRectControl = kCreateRect; + } + } +} + +/* + * Handle the click in the titlebar (to move the window) + */ + void +gui_mac_doInDragClick(Point where, WindowPtr whichWindow) +{ + Rect movingLimits; + Rect *movingLimitsPtr = &movingLimits; + + /* TODO: may try to prevent move outside screen? */ + movingLimitsPtr = GetRegionBounds(GetGrayRgn(), &movingLimits); + DragWindow(whichWindow, where, movingLimitsPtr); +} + +/* + * Handle the click in the grow box + */ + void +gui_mac_doInGrowClick(Point where, WindowPtr whichWindow) +{ + + long newSize; + unsigned short newWidth; + unsigned short newHeight; + Rect resizeLimits; + Rect *resizeLimitsPtr = &resizeLimits; + Rect NewContentRect; + + resizeLimitsPtr = GetRegionBounds(GetGrayRgn(), &resizeLimits); + + /* Set the minimum size */ + /* TODO: Should this come from Vim? */ + resizeLimits.top = 100; + resizeLimits.left = 100; + + newSize = ResizeWindow(whichWindow, where, &resizeLimits, &NewContentRect); + newWidth = NewContentRect.right - NewContentRect.left; + newHeight = NewContentRect.bottom - NewContentRect.top; + gui_resize_shell(newWidth, newHeight); + gui_mch_set_bg_color(gui.back_pixel); + gui_set_shellsize(TRUE, FALSE, RESIZE_BOTH); +} + +/* + * Handle the click in the zoom box + */ + static void +gui_mac_doInZoomClick(EventRecord *theEvent, WindowPtr whichWindow) +{ + Rect r; + Point p; + short thePart; + + /* ideal width is current */ + p.h = Columns * gui.char_width + 2 * gui.border_offset; + if (gui.which_scrollbars[SBAR_LEFT]) + p.h += gui.scrollbar_width; + if (gui.which_scrollbars[SBAR_RIGHT]) + p.h += gui.scrollbar_width; + /* ideal height is as high as we can get */ + p.v = 15 * 1024; + + thePart = IsWindowInStandardState(whichWindow, &p, &r) + ? inZoomIn : inZoomOut; + + if (!TrackBox(whichWindow, theEvent->where, thePart)) + return; + + /* use returned width */ + p.h = r.right - r.left; + /* adjust returned height */ + p.v = r.bottom - r.top - 2 * gui.border_offset; + if (gui.which_scrollbars[SBAR_BOTTOM]) + p.v -= gui.scrollbar_height; + p.v -= p.v % gui.char_height; + p.v += 2 * gui.border_width; + if (gui.which_scrollbars[SBAR_BOTTOM]) + p.v += gui.scrollbar_height; + + ZoomWindowIdeal(whichWindow, thePart, &p); + + GetWindowBounds(whichWindow, kWindowContentRgn, &r); + gui_resize_shell(r.right - r.left, r.bottom - r.top); + gui_mch_set_bg_color(gui.back_pixel); + gui_set_shellsize(TRUE, FALSE, RESIZE_BOTH); +} + +/* + * ------------------------------------------------------------ + * MacOS Event Handling procedure + * ------------------------------------------------------------ + */ + +/* + * Handle the Update Event + */ + + void +gui_mac_doUpdateEvent(EventRecord *event) +{ + WindowPtr whichWindow; + GrafPtr savePort; + RgnHandle updateRgn; + Rect updateRect; + Rect *updateRectPtr; + Rect rc; + Rect growRect; + RgnHandle saveRgn; + + + updateRgn = NewRgn(); + if (updateRgn == NULL) + return; + + /* This could be done by the caller as we + * don't require anything else out of the event + */ + whichWindow = (WindowPtr) event->message; + + /* Save Current Port */ + GetPort(&savePort); + + /* Select the Window's Port */ + SetPortWindowPort(whichWindow); + + /* Let's update the window */ + BeginUpdate(whichWindow); + /* Redraw the biggest rectangle covering the area + * to be updated. + */ + GetPortVisibleRegion(GetWindowPort(whichWindow), updateRgn); +# if 0 + /* Would be more appropriate to use the following but doesn't + * seem to work under MacOS X (Dany) + */ + GetWindowRegion(whichWindow, kWindowUpdateRgn, updateRgn); +# endif + + /* Use the HLock useless in Carbon? Is it harmful?*/ + HLock((Handle) updateRgn); + + updateRectPtr = GetRegionBounds(updateRgn, &updateRect); +# if 0 + /* Code from original Carbon Port (using GetWindowRegion. + * I believe the UpdateRgn is already in local (Dany) + */ + GlobalToLocal(&topLeft(updateRect)); /* preCarbon? */ + GlobalToLocal(&botRight(updateRect)); +# endif + /* Update the content (i.e. the text) */ + gui_redraw(updateRectPtr->left, updateRectPtr->top, + updateRectPtr->right - updateRectPtr->left, + updateRectPtr->bottom - updateRectPtr->top); + /* Clear the border areas if needed */ + gui_mch_set_bg_color(gui.back_pixel); + if (updateRectPtr->left < FILL_X(0)) + { + SetRect(&rc, 0, 0, FILL_X(0), FILL_Y(Rows)); + EraseRect(&rc); + } + if (updateRectPtr->top < FILL_Y(0)) + { + SetRect(&rc, 0, 0, FILL_X(Columns), FILL_Y(0)); + EraseRect(&rc); + } + if (updateRectPtr->right > FILL_X(Columns)) + { + SetRect(&rc, FILL_X(Columns), 0, + FILL_X(Columns) + gui.border_offset, FILL_Y(Rows)); + EraseRect(&rc); + } + if (updateRectPtr->bottom > FILL_Y(Rows)) + { + SetRect(&rc, 0, FILL_Y(Rows), FILL_X(Columns) + gui.border_offset, + FILL_Y(Rows) + gui.border_offset); + EraseRect(&rc); + } + HUnlock((Handle) updateRgn); + DisposeRgn(updateRgn); + + /* Update scrollbars */ + DrawControls(whichWindow); + + /* Update the GrowBox */ + /* Taken from FAQ 33-27 */ + saveRgn = NewRgn(); + GetWindowBounds(whichWindow, kWindowGrowRgn, &growRect); + GetClip(saveRgn); + ClipRect(&growRect); + DrawGrowIcon(whichWindow); + SetClip(saveRgn); + DisposeRgn(saveRgn); + EndUpdate(whichWindow); + + /* Restore original Port */ + SetPort(savePort); +} + +/* + * Handle the activate/deactivate event + * (apply to a window) + */ + void +gui_mac_doActivateEvent(EventRecord *event) +{ + WindowPtr whichWindow; + + whichWindow = (WindowPtr) event->message; + /* Dim scrollbars */ + if (whichWindow == gui.VimWindow) + { + ControlRef rootControl; + GetRootControl(gui.VimWindow, &rootControl); + if ((event->modifiers) & activeFlag) + ActivateControl(rootControl); + else + DeactivateControl(rootControl); + } + + /* Activate */ + gui_focus_change((event->modifiers) & activeFlag); +} + + +/* + * Handle the suspend/resume event + * (apply to the application) + */ + void +gui_mac_doSuspendEvent(EventRecord *event) +{ + /* The frontmost application just changed */ + + /* NOTE: the suspend may happen before the deactivate + * seen on MacOS X + */ + + /* May not need to change focus as the window will + * get an activate/deactivate event + */ + if (event->message & 1) + /* Resume */ + gui_focus_change(TRUE); + else + /* Suspend */ + gui_focus_change(FALSE); +} + +/* + * Handle the key + */ +#ifdef USE_CARBONKEYHANDLER + static pascal OSStatus +gui_mac_handle_window_activate( + EventHandlerCallRef nextHandler, + EventRef theEvent, + void *data) +{ + UInt32 eventClass = GetEventClass(theEvent); + UInt32 eventKind = GetEventKind(theEvent); + + if (eventClass == kEventClassWindow) + { + switch (eventKind) + { + case kEventWindowActivated: + im_on_window_switch(TRUE); + return noErr; + + case kEventWindowDeactivated: + im_on_window_switch(FALSE); + return noErr; + } + } + + return eventNotHandledErr; +} + + static pascal OSStatus +gui_mac_handle_text_input( + EventHandlerCallRef nextHandler, + EventRef theEvent, + void *data) +{ + UInt32 eventClass = GetEventClass(theEvent); + UInt32 eventKind = GetEventKind(theEvent); + + if (eventClass != kEventClassTextInput) + return eventNotHandledErr; + + if ((kEventTextInputUpdateActiveInputArea != eventKind) && + (kEventTextInputUnicodeForKeyEvent != eventKind) && + (kEventTextInputOffsetToPos != eventKind) && + (kEventTextInputPosToOffset != eventKind) && + (kEventTextInputGetSelectedText != eventKind)) + return eventNotHandledErr; + + switch (eventKind) + { + case kEventTextInputUpdateActiveInputArea: + return gui_mac_update_input_area(nextHandler, theEvent); + case kEventTextInputUnicodeForKeyEvent: + return gui_mac_unicode_key_event(nextHandler, theEvent); + + case kEventTextInputOffsetToPos: + case kEventTextInputPosToOffset: + case kEventTextInputGetSelectedText: + break; + } + + return eventNotHandledErr; +} + + static pascal +OSStatus gui_mac_update_input_area( + EventHandlerCallRef nextHandler, + EventRef theEvent) +{ + return eventNotHandledErr; +} + +static int dialog_busy = FALSE; /* TRUE when gui_mch_dialog() wants the + keys */ + +# define INLINE_KEY_BUFFER_SIZE 80 + static pascal OSStatus +gui_mac_unicode_key_event( + EventHandlerCallRef nextHandler, + EventRef theEvent) +{ + /* Multibyte-friendly key event handler */ + OSStatus err = -1; + UInt32 actualSize; + UniChar *text; + char_u result[INLINE_KEY_BUFFER_SIZE]; + short len = 0; + UInt32 key_sym; + char charcode; + int key_char; + UInt32 modifiers, vimModifiers; + size_t encLen; + char_u *to = NULL; + Boolean isSpecial = FALSE; + int i; + EventRef keyEvent; + + /* Mask the mouse (as per user setting) */ + if (p_mh) + ObscureCursor(); + + /* Don't use the keys when the dialog wants them. */ + if (dialog_busy) + return eventNotHandledErr; + + if (noErr != GetEventParameter(theEvent, kEventParamTextInputSendText, + typeUnicodeText, NULL, 0, &actualSize, NULL)) + return eventNotHandledErr; + + text = (UniChar *)alloc(actualSize); + if (!text) + return eventNotHandledErr; + + err = GetEventParameter(theEvent, kEventParamTextInputSendText, + typeUnicodeText, NULL, actualSize, NULL, text); + require_noerr(err, done); + + err = GetEventParameter(theEvent, kEventParamTextInputSendKeyboardEvent, + typeEventRef, NULL, sizeof(EventRef), NULL, &keyEvent); + require_noerr(err, done); + + err = GetEventParameter(keyEvent, kEventParamKeyModifiers, + typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); + require_noerr(err, done); + + err = GetEventParameter(keyEvent, kEventParamKeyCode, + typeUInt32, NULL, sizeof(UInt32), NULL, &key_sym); + require_noerr(err, done); + + err = GetEventParameter(keyEvent, kEventParamKeyMacCharCodes, + typeChar, NULL, sizeof(char), NULL, &charcode); + require_noerr(err, done); + +#ifndef USE_CMD_KEY + if (modifiers & cmdKey) + goto done; /* Let system handle Cmd+... */ +#endif + + key_char = charcode; + vimModifiers = EventModifiers2VimModifiers(modifiers); + + /* Find the special key (eg., for cursor keys) */ + if (actualSize <= sizeof(UniChar) && + ((text[0] < 0x20) || (text[0] == 0x7f))) + { + for (i = 0; special_keys[i].key_sym != (KeySym)0; ++i) + if (special_keys[i].key_sym == key_sym) + { + key_char = TO_SPECIAL(special_keys[i].vim_code0, + special_keys[i].vim_code1); + key_char = simplify_key(key_char, + (int *)&vimModifiers); + isSpecial = TRUE; + break; + } + } + + /* Intercept CMD-. and CTRL-c */ + if (((modifiers & controlKey) && key_char == 'c') || + ((modifiers & cmdKey) && key_char == '.')) + got_int = TRUE; + + if (!isSpecial) + { + /* remove SHIFT for keys that are already shifted, e.g., + * '(' and '*' */ + if (key_char < 0x100 && !isalpha(key_char) && isprint(key_char)) + vimModifiers &= ~MOD_MASK_SHIFT; + + /* remove CTRL from keys that already have it */ + if (key_char < 0x20) + vimModifiers &= ~MOD_MASK_CTRL; + + /* don't process unicode characters here */ + if (!IS_SPECIAL(key_char)) + { + /* Following code to simplify and consolidate vimModifiers + * taken liberally from gui_w48.c */ + key_char = simplify_key(key_char, (int *)&vimModifiers); + + /* Interpret META, include SHIFT, etc. */ + key_char = extract_modifiers(key_char, (int *)&vimModifiers); + if (key_char == CSI) + key_char = K_CSI; + + if (IS_SPECIAL(key_char)) + isSpecial = TRUE; + } + } + + if (vimModifiers) + { + result[len++] = CSI; + result[len++] = KS_MODIFIER; + result[len++] = vimModifiers; + } + + if (isSpecial && IS_SPECIAL(key_char)) + { + result[len++] = CSI; + result[len++] = K_SECOND(key_char); + result[len++] = K_THIRD(key_char); + } + else + { + encLen = actualSize; + to = mac_utf16_to_enc(text, actualSize, &encLen); + if (to) + { + /* This is basically add_to_input_buf_csi() */ + for (i = 0; i < encLen && len < (INLINE_KEY_BUFFER_SIZE-1); ++i) + { + result[len++] = to[i]; + if (to[i] == CSI) + { + result[len++] = KS_EXTRA; + result[len++] = (int)KE_CSI; + } + } + vim_free(to); + } + } + + add_to_input_buf(result, len); + err = noErr; + +done: + vim_free(text); + if (err == noErr) + { + /* Fake event to wake up WNE (required to get + * key repeat working */ + PostEvent(keyUp, 0); + return noErr; + } + + return eventNotHandledErr; +} +#else + void +gui_mac_doKeyEvent(EventRecord *theEvent) +{ + /* TODO: add support for COMMAND KEY */ + long menu; + unsigned char string[20]; + short num, i; + short len = 0; + KeySym key_sym; + int key_char; + int modifiers; + int simplify = FALSE; + + /* Mask the mouse (as per user setting) */ + if (p_mh) + ObscureCursor(); + + /* Get the key code and its ASCII representation */ + key_sym = ((theEvent->message & keyCodeMask) >> 8); + key_char = theEvent->message & charCodeMask; + num = 1; + + /* Intercept CTRL-C */ + if (theEvent->modifiers & controlKey) + { + if (key_char == Ctrl_C && ctrl_c_interrupts) + got_int = TRUE; + else if ((theEvent->modifiers & ~(controlKey|shiftKey)) == 0 + && (key_char == '2' || key_char == '6')) + { + /* CTRL-^ and CTRL-@ don't work in the normal way. */ + if (key_char == '2') + key_char = Ctrl_AT; + else + key_char = Ctrl_HAT; + theEvent->modifiers = 0; + } + } + + /* Intercept CMD-. */ + if (theEvent->modifiers & cmdKey) + if (key_char == '.') + got_int = TRUE; + + /* Handle command key as per menu */ + /* TODO: should override be allowed? Require YAO or could use 'winaltkey' */ + if (theEvent->modifiers & cmdKey) + /* Only accept CMD alone or with CAPLOCKS and the mouse button. + * Why the mouse button? */ + if ((theEvent->modifiers & (~(cmdKey | btnState | alphaLock))) == 0) + { + menu = MenuKey(key_char); + if (HiWord(menu)) + { + gui_mac_handle_menu(menu); + return; + } + } + + /* Convert the modifiers */ + modifiers = EventModifiers2VimModifiers(theEvent->modifiers); + + + /* Handle special keys. */ +#if 0 + /* Why has this been removed? */ + if (!(theEvent->modifiers & (cmdKey | controlKey | rightControlKey))) +#endif + { + /* Find the special key (for non-printable keyt_char) */ + if ((key_char < 0x20) || (key_char == 0x7f)) + for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) + if (special_keys[i].key_sym == key_sym) + { +# if 0 + /* We currently don't have not so special key */ + if (special_keys[i].vim_code1 == NUL) + key_char = special_keys[i].vim_code0; + else +# endif + key_char = TO_SPECIAL(special_keys[i].vim_code0, + special_keys[i].vim_code1); + simplify = TRUE; + break; + } + } + + /* For some keys the modifier is included in the char itself. */ + if (simplify || key_char == TAB || key_char == ' ') + key_char = simplify_key(key_char, &modifiers); + + /* Add the modifier to the input bu if needed */ + /* Do not want SHIFT-A or CTRL-A with modifier */ + if (!IS_SPECIAL(key_char) + && key_sym != vk_Space + && key_sym != vk_Tab + && key_sym != vk_Return + && key_sym != vk_Enter + && key_sym != vk_Esc) + { +#if 1 + /* Clear modifiers when only one modifier is set */ + if ((modifiers == MOD_MASK_SHIFT) + || (modifiers == MOD_MASK_CTRL) + || (modifiers == MOD_MASK_ALT)) + modifiers = 0; +#else + if (modifiers & MOD_MASK_CTRL) + modifiers = modifiers & ~MOD_MASK_CTRL; + if (modifiers & MOD_MASK_ALT) + modifiers = modifiers & ~MOD_MASK_ALT; + if (modifiers & MOD_MASK_SHIFT) + modifiers = modifiers & ~MOD_MASK_SHIFT; +#endif + } + if (modifiers) + { + string[len++] = CSI; + string[len++] = KS_MODIFIER; + string[len++] = modifiers; + } + + if (IS_SPECIAL(key_char)) + { + string[len++] = CSI; + string[len++] = K_SECOND(key_char); + string[len++] = K_THIRD(key_char); + } + else + { + /* Convert characters when needed (e.g., from MacRoman to latin1). + * This doesn't work for the NUL byte. */ + if (input_conv.vc_type != CONV_NONE && key_char > 0) + { + char_u from[2], *to; + int l; + + from[0] = key_char; + from[1] = NUL; + l = 1; + to = string_convert(&input_conv, from, &l); + if (to != NULL) + { + for (i = 0; i < l && len < 19; i++) + { + if (to[i] == CSI) + { + string[len++] = KS_EXTRA; + string[len++] = KE_CSI; + } + else + string[len++] = to[i]; + } + vim_free(to); + } + else + string[len++] = key_char; + } + else + string[len++] = key_char; + } + + if (len == 1 && string[0] == CSI) + { + /* Turn CSI into K_CSI. */ + string[ len++ ] = KS_EXTRA; + string[ len++ ] = KE_CSI; + } + + add_to_input_buf(string, len); +} +#endif + +/* + * Handle MouseClick + */ + void +gui_mac_doMouseDownEvent(EventRecord *theEvent) +{ + short thePart; + WindowPtr whichWindow; + + thePart = FindWindow(theEvent->where, &whichWindow); + +#ifdef FEAT_GUI_TABLINE + /* prevent that the vim window size changes if it's activated by a + click into the tab pane */ + if (whichWindow == drawer) + return; +#endif + + switch (thePart) + { + case (inDesk): + /* TODO: what to do? */ + break; + + case (inMenuBar): + gui_mac_handle_menu(MenuSelect(theEvent->where)); + break; + + case (inContent): + gui_mac_doInContentClick(theEvent, whichWindow); + break; + + case (inDrag): + gui_mac_doInDragClick(theEvent->where, whichWindow); + break; + + case (inGrow): + gui_mac_doInGrowClick(theEvent->where, whichWindow); + break; + + case (inGoAway): + if (TrackGoAway(whichWindow, theEvent->where)) + gui_shell_closed(); + break; + + case (inZoomIn): + case (inZoomOut): + gui_mac_doInZoomClick(theEvent, whichWindow); + break; + } +} + +/* + * Handle MouseMoved + * [this event is a moving in and out of a region] + */ + void +gui_mac_doMouseMovedEvent(EventRecord *event) +{ + Point thePoint; + int_u vimModifiers; + + thePoint = event->where; + GlobalToLocal(&thePoint); + vimModifiers = EventModifiers2VimMouseModifiers(event->modifiers); + + if (!Button()) + gui_mouse_moved(thePoint.h, thePoint.v); + else + if (!clickIsPopup) + gui_send_mouse_event(MOUSE_DRAG, thePoint.h, + thePoint.v, FALSE, vimModifiers); + + /* Reset the region from which we move in and out */ + SetRect(&dragRect, FILL_X(X_2_COL(thePoint.h)), + FILL_Y(Y_2_ROW(thePoint.v)), + FILL_X(X_2_COL(thePoint.h)+1), + FILL_Y(Y_2_ROW(thePoint.v)+1)); + + if (dragRectEnbl) + dragRectControl = kCreateRect; + +} + +/* + * Handle the mouse release + */ + void +gui_mac_doMouseUpEvent(EventRecord *theEvent) +{ + Point thePoint; + int_u vimModifiers; + + /* TODO: Properly convert the Contextual menu mouse-up */ + /* Potential source of the double menu */ + lastMouseTick = theEvent->when; + dragRectEnbl = FALSE; + dragRectControl = kCreateEmpty; + thePoint = theEvent->where; + GlobalToLocal(&thePoint); + + vimModifiers = EventModifiers2VimMouseModifiers(theEvent->modifiers); + if (clickIsPopup) + { + vimModifiers &= ~MOUSE_CTRL; + clickIsPopup = FALSE; + } + gui_send_mouse_event(MOUSE_RELEASE, thePoint.h, thePoint.v, FALSE, vimModifiers); +} + + static pascal OSStatus +gui_mac_mouse_wheel(EventHandlerCallRef nextHandler, EventRef theEvent, + void *data) +{ + Point point; + Rect bounds; + UInt32 mod; + SInt32 delta; + int_u vim_mod; + EventMouseWheelAxis axis; + + if (noErr == GetEventParameter(theEvent, kEventParamMouseWheelAxis, + typeMouseWheelAxis, NULL, sizeof(axis), NULL, &axis) + && axis != kEventMouseWheelAxisY) + goto bail; /* Vim only does up-down scrolling */ + + if (noErr != GetEventParameter(theEvent, kEventParamMouseWheelDelta, + typeSInt32, NULL, sizeof(SInt32), NULL, &delta)) + goto bail; + if (noErr != GetEventParameter(theEvent, kEventParamMouseLocation, + typeQDPoint, NULL, sizeof(Point), NULL, &point)) + goto bail; + if (noErr != GetEventParameter(theEvent, kEventParamKeyModifiers, + typeUInt32, NULL, sizeof(UInt32), NULL, &mod)) + goto bail; + + vim_mod = 0; + if (mod & shiftKey) + vim_mod |= MOUSE_SHIFT; + if (mod & controlKey) + vim_mod |= MOUSE_CTRL; + if (mod & optionKey) + vim_mod |= MOUSE_ALT; + + if (noErr == GetWindowBounds(gui.VimWindow, kWindowContentRgn, &bounds)) + { + point.h -= bounds.left; + point.v -= bounds.top; + } + + gui_send_mouse_event((delta > 0) ? MOUSE_4 : MOUSE_5, + point.h, point.v, FALSE, vim_mod); + + /* post a bogus event to wake up WaitNextEvent */ + PostEvent(keyUp, 0); + + return noErr; + +bail: + /* + * when we fail give any additional callback handler a chance to perform + * its actions + */ + return CallNextEventHandler(nextHandler, theEvent); +} + + void +gui_mch_mousehide(int hide) +{ + /* TODO */ +} + +#if 0 + +/* + * This would be the normal way of invoking the contextual menu + * but the Vim API doesn't seem to a support a request to get + * the menu that we should display + */ + void +gui_mac_handle_contextual_menu(EventRecord *event) +{ +/* + * Clone PopUp to use menu + * Create a object descriptor for the current selection + * Call the procedure + */ + +// Call to Handle Popup + OSStatus status = ContextualMenuSelect(CntxMenu, event->where, false, kCMHelpItemNoHelp, "", NULL, &CntxType, &CntxMenuID, &CntxMenuItem); + + if (status != noErr) + return; + + if (CntxType == kCMMenuItemSelected) + { + /* Handle the menu CntxMenuID, CntxMenuItem */ + /* The submenu can be handle directly by gui_mac_handle_menu */ + /* But what about the current menu, is the many changed by ContextualMenuSelect */ + gui_mac_handle_menu((CntxMenuID << 16) + CntxMenuItem); + } + else if (CntxMenuID == kCMShowHelpSelected) + { + /* Should come up with the help */ + } + +} +#endif + +/* + * Handle menubar selection + */ + void +gui_mac_handle_menu(long menuChoice) +{ + short menu = HiWord(menuChoice); + short item = LoWord(menuChoice); + vimmenu_T *theVimMenu = root_menu; + + if (menu == 256) /* TODO: use constant or gui.xyz */ + { + if (item == 1) + gui_mch_beep(); /* TODO: Popup dialog or do :intro */ + } + else if (item != 0) + { + theVimMenu = gui_mac_get_vim_menu(menu, item, root_menu); + + if (theVimMenu) + gui_menu_cb(theVimMenu); + } + HiliteMenu(0); +} + +/* + * Dispatch the event to proper handler + */ + + void +gui_mac_handle_event(EventRecord *event) +{ + OSErr error; + + /* Handle contextual menu right now (if needed) */ + if (IsShowContextualMenuClick(event)) + { +# if 0 + gui_mac_handle_contextual_menu(event); +# else + gui_mac_doMouseDownEvent(event); +# endif + return; + } + + /* Handle normal event */ + switch (event->what) + { +#ifndef USE_CARBONKEYHANDLER + case (keyDown): + case (autoKey): + gui_mac_doKeyEvent(event); + break; +#endif + case (keyUp): + /* We don't care about when the key is released */ + break; + + case (mouseDown): + gui_mac_doMouseDownEvent(event); + break; + + case (mouseUp): + gui_mac_doMouseUpEvent(event); + break; + + case (updateEvt): + gui_mac_doUpdateEvent(event); + break; + + case (diskEvt): + /* We don't need special handling for disk insertion */ + break; + + case (activateEvt): + gui_mac_doActivateEvent(event); + break; + + case (osEvt): + switch ((event->message >> 24) & 0xFF) + { + case (0xFA): /* mouseMovedMessage */ + gui_mac_doMouseMovedEvent(event); + break; + case (0x01): /* suspendResumeMessage */ + gui_mac_doSuspendEvent(event); + break; + } + break; + +#ifdef USE_AEVENT + case (kHighLevelEvent): + /* Someone's talking to us, through AppleEvents */ + error = AEProcessAppleEvent(event); /* TODO: Error Handling */ + break; +#endif + } +} + +/* + * ------------------------------------------------------------ + * Unknown Stuff + * ------------------------------------------------------------ + */ + + + GuiFont +gui_mac_find_font(char_u *font_name) +{ + char_u c; + char_u *p; + char_u pFontName[256]; + Str255 systemFontname; + short font_id; + short size=9; + GuiFont font; +#if 0 + char_u *fontNamePtr; +#endif + + for (p = font_name; ((*p != 0) && (*p != ':')); p++) + ; + + c = *p; + *p = 0; + +#if 1 + STRCPY(&pFontName[1], font_name); + pFontName[0] = STRLEN(font_name); + *p = c; + + /* Get the font name, minus the style suffix (:h, etc) */ + char_u fontName[256]; + char_u *styleStart = vim_strchr(font_name, ':'); + size_t fontNameLen = styleStart ? styleStart - font_name : STRLEN(fontName); + vim_strncpy(fontName, font_name, fontNameLen); + + ATSUFontID fontRef; + FMFontStyle fontStyle; + font_id = 0; + + if (ATSUFindFontFromName(&pFontName[1], pFontName[0], kFontFullName, + kFontMacintoshPlatform, kFontNoScriptCode, kFontNoLanguageCode, + &fontRef) == noErr) + { + if (FMGetFontFamilyInstanceFromFont(fontRef, &font_id, &fontStyle) != noErr) + font_id = 0; + } + + if (font_id == 0) + { + /* + * Try again, this time replacing underscores in the font name + * with spaces (:set guifont allows the two to be used + * interchangeably; the Font Manager doesn't). + */ + int i, changed = FALSE; + + for (i = pFontName[0]; i > 0; --i) + { + if (pFontName[i] == '_') + { + pFontName[i] = ' '; + changed = TRUE; + } + } + if (changed) + if (ATSUFindFontFromName(&pFontName[1], pFontName[0], + kFontFullName, kFontNoPlatformCode, kFontNoScriptCode, + kFontNoLanguageCode, &fontRef) == noErr) + { + if (FMGetFontFamilyInstanceFromFont(fontRef, &font_id, &fontStyle) != noErr) + font_id = 0; + } + } + +#else + /* name = C2Pascal_save(menu->dname); */ + fontNamePtr = C2Pascal_save_and_remove_backslash(font_name); + + GetFNum(fontNamePtr, &font_id); +#endif + + + if (font_id == 0) + { + /* Oups, the system font was it the one the user want */ + + if (FMGetFontFamilyName(systemFont, systemFontname) != noErr) + return NOFONT; + if (!EqualString(pFontName, systemFontname, false, false)) + return NOFONT; + } + if (*p == ':') + { + p++; + /* Set the values found after ':' */ + while (*p) + { + switch (*p++) + { + case 'h': + size = points_to_pixels(p, &p, TRUE); + break; + /* + * TODO: Maybe accept width and styles + */ + } + while (*p == ':') + p++; + } + } + + if (size < 1) + size = 1; /* Avoid having a size of 0 with system font */ + + font = (size << 16) + ((long) font_id & 0xFFFF); + + return font; +} + +/* + * ------------------------------------------------------------ + * GUI_MCH functionality + * ------------------------------------------------------------ + */ + +/* + * Parse the GUI related command-line arguments. Any arguments used are + * deleted from argv, and *argc is decremented accordingly. This is called + * when vim is started, whether or not the GUI has been started. + */ + void +gui_mch_prepare(int *argc, char **argv) +{ + /* TODO: Move most of this stuff toward gui_mch_init */ +#ifdef USE_EXE_NAME + FSSpec applDir; +# ifndef USE_FIND_BUNDLE_PATH + short applVRefNum; + long applDirID; + Str255 volName; +# else + ProcessSerialNumber psn; + FSRef applFSRef; +# endif +#endif + +#if 0 + InitCursor(); + + RegisterAppearanceClient(); + +#ifdef USE_AEVENT + (void) InstallAEHandlers(); +#endif + + pomme = NewMenu(256, "\p\024"); /* 0x14= = Apple Menu */ + + AppendMenu(pomme, "\pAbout VIM"); + + InsertMenu(pomme, 0); + + DrawMenuBar(); + + +#ifndef USE_OFFSETED_WINDOW + SetRect(&windRect, 10, 48, 10+80*7 + 16, 48+24*11); +#else + SetRect(&windRect, 300, 40, 300+80*7 + 16, 40+24*11); +#endif + + + CreateNewWindow(kDocumentWindowClass, + kWindowResizableAttribute | kWindowCollapseBoxAttribute, + &windRect, &gui.VimWindow); + SetPortWindowPort(gui.VimWindow); + + gui.char_width = 7; + gui.char_height = 11; + gui.char_ascent = 6; + gui.num_rows = 24; + gui.num_cols = 80; + gui.in_focus = TRUE; /* For the moment -> syn. of front application */ + + gScrollAction = NewControlActionUPP(gui_mac_scroll_action); + gScrollDrag = NewControlActionUPP(gui_mac_drag_thumb); + + dragRectEnbl = FALSE; + dragRgn = NULL; + dragRectControl = kCreateEmpty; + cursorRgn = NewRgn(); +#endif +#ifdef USE_EXE_NAME +# ifndef USE_FIND_BUNDLE_PATH + HGetVol(volName, &applVRefNum, &applDirID); + /* TN2015: mention a possible bad VRefNum */ + FSMakeFSSpec(applVRefNum, applDirID, "\p", &applDir); +# else + /* OSErr GetApplicationBundleFSSpec(FSSpecPtr theFSSpecPtr) + * of TN2015 + */ + (void)GetCurrentProcess(&psn); + /* if (err != noErr) return err; */ + + (void)GetProcessBundleLocation(&psn, &applFSRef); + /* if (err != noErr) return err; */ + + (void)FSGetCatalogInfo(&applFSRef, kFSCatInfoNone, NULL, NULL, &applDir, NULL); + + /* This technic return NIL when we disallow_gui */ +# endif + exe_name = FullPathFromFSSpec_save(applDir); +#endif +} + +#ifndef ALWAYS_USE_GUI +/* + * Check if the GUI can be started. Called before gvimrc is sourced. + * Return OK or FAIL. + */ + int +gui_mch_init_check(void) +{ + /* TODO: For MacOS X find a way to return FAIL, if the user logged in + * using the >console + */ + if (disallow_gui) /* see main.c for reason to disallow */ + return FAIL; + return OK; +} +#endif + + static OSErr +receiveHandler(WindowRef theWindow, void *handlerRefCon, DragRef theDrag) +{ + int x, y; + int_u modifiers; + char_u **fnames = NULL; + int count; + int i, j; + + /* Get drop position, modifiers and count of items */ + { + Point point; + SInt16 mouseUpModifiers; + UInt16 countItem; + + GetDragMouse(theDrag, &point, NULL); + GlobalToLocal(&point); + x = point.h; + y = point.v; + GetDragModifiers(theDrag, NULL, NULL, &mouseUpModifiers); + modifiers = EventModifiers2VimMouseModifiers(mouseUpModifiers); + CountDragItems(theDrag, &countItem); + count = countItem; + } + + fnames = (char_u **)alloc(count * sizeof(char_u *)); + if (fnames == NULL) + return dragNotAcceptedErr; + + /* Get file names dropped */ + for (i = j = 0; i < count; ++i) + { + DragItemRef item; + OSErr err; + Size size; + FlavorType type = flavorTypeHFS; + HFSFlavor hfsFlavor; + + fnames[i] = NULL; + GetDragItemReferenceNumber(theDrag, i + 1, &item); + err = GetFlavorDataSize(theDrag, item, type, &size); + if (err != noErr || size > sizeof(hfsFlavor)) + continue; + err = GetFlavorData(theDrag, item, type, &hfsFlavor, &size, 0); + if (err != noErr) + continue; + fnames[j++] = FullPathFromFSSpec_save(hfsFlavor.fileSpec); + } + count = j; + + gui_handle_drop(x, y, modifiers, fnames, count); + + /* Fake mouse event to wake from stall */ + PostEvent(mouseUp, 0); + + return noErr; +} + +/* + * Initialise the GUI. Create all the windows, set up all the call-backs + * etc. + */ + int +gui_mch_init(void) +{ + /* TODO: Move most of this stuff toward gui_mch_init */ + Rect windRect; + MenuHandle pomme; + EventHandlerRef mouseWheelHandlerRef; + EventTypeSpec eventTypeSpec; + ControlRef rootControl; + + if (Gestalt(gestaltSystemVersion, &gMacSystemVersion) != noErr) + gMacSystemVersion = 0x1000; /* TODO: Default to minimum sensible value */ + +#if 1 + InitCursor(); + + RegisterAppearanceClient(); + +#ifdef USE_AEVENT + (void) InstallAEHandlers(); +#endif + + pomme = NewMenu(256, "\p\024"); /* 0x14= = Apple Menu */ + + AppendMenu(pomme, "\pAbout VIM"); + + InsertMenu(pomme, 0); + + DrawMenuBar(); + + +#ifndef USE_OFFSETED_WINDOW + SetRect(&windRect, 10, 48, 10+80*7 + 16, 48+24*11); +#else + SetRect(&windRect, 300, 40, 300+80*7 + 16, 40+24*11); +#endif + + gui.VimWindow = NewCWindow(nil, &windRect, "\pgVim on Macintosh", true, + zoomDocProc, + (WindowPtr)-1L, true, 0); + CreateRootControl(gui.VimWindow, &rootControl); + InstallReceiveHandler((DragReceiveHandlerUPP)receiveHandler, + gui.VimWindow, NULL); + SetPortWindowPort(gui.VimWindow); + + gui.char_width = 7; + gui.char_height = 11; + gui.char_ascent = 6; + gui.num_rows = 24; + gui.num_cols = 80; + gui.in_focus = TRUE; /* For the moment -> syn. of front application */ + + gScrollAction = NewControlActionUPP(gui_mac_scroll_action); + gScrollDrag = NewControlActionUPP(gui_mac_drag_thumb); + + /* Install Carbon event callbacks. */ + (void)InstallFontPanelHandler(); + + dragRectEnbl = FALSE; + dragRgn = NULL; + dragRectControl = kCreateEmpty; + cursorRgn = NewRgn(); +#endif + /* Display any pending error messages */ + display_errors(); + + /* Get background/foreground colors from system */ + /* TODO: do the appropriate call to get real defaults */ + gui.norm_pixel = 0x00000000; + gui.back_pixel = 0x00FFFFFF; + + /* Get the colors from the "Normal" group (set in syntax.c or in a vimrc + * file). */ + set_normal_colors(); + + /* + * Check that none of the colors are the same as the background color. + * Then store the current values as the defaults. + */ + gui_check_colors(); + gui.def_norm_pixel = gui.norm_pixel; + gui.def_back_pixel = gui.back_pixel; + + /* Get the colors for the highlight groups (gui_check_colors() might have + * changed them) */ + highlight_gui_started(); + + /* + * Setting the gui constants + */ +#ifdef FEAT_MENU + gui.menu_height = 0; +#endif + gui.scrollbar_height = gui.scrollbar_width = 15; /* cheat 1 overlap */ + gui.border_offset = gui.border_width = 2; + + /* If Quartz-style text anti aliasing is available (see + gui_mch_draw_string() below), enable it for all font sizes. */ + vim_setenv((char_u *)"QDTEXT_MINSIZE", (char_u *)"1"); + + eventTypeSpec.eventClass = kEventClassMouse; + eventTypeSpec.eventKind = kEventMouseWheelMoved; + mouseWheelHandlerUPP = NewEventHandlerUPP(gui_mac_mouse_wheel); + if (noErr != InstallApplicationEventHandler(mouseWheelHandlerUPP, 1, + &eventTypeSpec, NULL, &mouseWheelHandlerRef)) + { + mouseWheelHandlerRef = NULL; + DisposeEventHandlerUPP(mouseWheelHandlerUPP); + mouseWheelHandlerUPP = NULL; + } + +#ifdef USE_CARBONKEYHANDLER + InterfaceTypeList supportedServices = { kUnicodeDocument }; + NewTSMDocument(1, supportedServices, &gTSMDocument, 0); + + /* We don't support inline input yet, use input window by default */ + UseInputWindow(gTSMDocument, TRUE); + + /* Should we activate the document by default? */ + // ActivateTSMDocument(gTSMDocument); + + EventTypeSpec textEventTypes[] = { + { kEventClassTextInput, kEventTextInputUpdateActiveInputArea }, + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }, + { kEventClassTextInput, kEventTextInputPosToOffset }, + { kEventClassTextInput, kEventTextInputOffsetToPos }, + }; + + keyEventHandlerUPP = NewEventHandlerUPP(gui_mac_handle_text_input); + if (noErr != InstallApplicationEventHandler(keyEventHandlerUPP, + NR_ELEMS(textEventTypes), + textEventTypes, NULL, NULL)) + { + DisposeEventHandlerUPP(keyEventHandlerUPP); + keyEventHandlerUPP = NULL; + } + + EventTypeSpec windowEventTypes[] = { + { kEventClassWindow, kEventWindowActivated }, + { kEventClassWindow, kEventWindowDeactivated }, + }; + + /* Install window event handler to support TSMDocument activate and + * deactivate */ + winEventHandlerUPP = NewEventHandlerUPP(gui_mac_handle_window_activate); + if (noErr != InstallWindowEventHandler(gui.VimWindow, + winEventHandlerUPP, + NR_ELEMS(windowEventTypes), + windowEventTypes, NULL, NULL)) + { + DisposeEventHandlerUPP(winEventHandlerUPP); + winEventHandlerUPP = NULL; + } +#endif + +#ifdef FEAT_GUI_TABLINE + /* + * Create the tabline + */ + initialise_tabline(); +#endif + + /* TODO: Load bitmap if using TOOLBAR */ + return OK; +} + +/* + * Called when the foreground or background color has been changed. + */ + void +gui_mch_new_colors(void) +{ + /* TODO: + * This proc is called when Normal is set to a value + * so what must be done? I don't know + */ +} + +/* + * Open the GUI window which was created by a call to gui_mch_init(). + */ + int +gui_mch_open(void) +{ + ShowWindow(gui.VimWindow); + + if (gui_win_x != -1 && gui_win_y != -1) + gui_mch_set_winpos(gui_win_x, gui_win_y); + + /* + * Make the GUI the foreground process (in case it was launched + * from the Terminal or via :gui). + */ + { + ProcessSerialNumber psn; + if (GetCurrentProcess(&psn) == noErr) + SetFrontProcess(&psn); + } + + return OK; +} + +#ifdef USE_ATSUI_DRAWING + static void +gui_mac_dispose_atsui_style(void) +{ + if (p_macatsui && gFontStyle) + ATSUDisposeStyle(gFontStyle); + if (p_macatsui && gWideFontStyle) + ATSUDisposeStyle(gWideFontStyle); +} +#endif + + void +gui_mch_exit(int rc) +{ + /* TODO: find out all what is missing here? */ + DisposeRgn(cursorRgn); + +#ifdef USE_CARBONKEYHANDLER + if (keyEventHandlerUPP) + DisposeEventHandlerUPP(keyEventHandlerUPP); +#endif + + if (mouseWheelHandlerUPP != NULL) + DisposeEventHandlerUPP(mouseWheelHandlerUPP); + +#ifdef USE_ATSUI_DRAWING + gui_mac_dispose_atsui_style(); +#endif + +#ifdef USE_CARBONKEYHANDLER + FixTSMDocument(gTSMDocument); + DeactivateTSMDocument(gTSMDocument); + DeleteTSMDocument(gTSMDocument); +#endif + + /* Exit to shell? */ + exit(rc); +} + +/* + * Get the position of the top left corner of the window. + */ + int +gui_mch_get_winpos(int *x, int *y) +{ + /* TODO */ + Rect bounds; + OSStatus status; + + /* Carbon >= 1.0.2, MacOS >= 8.5 */ + status = GetWindowBounds(gui.VimWindow, kWindowStructureRgn, &bounds); + + if (status != noErr) + return FAIL; + *x = bounds.left; + *y = bounds.top; + return OK; +} + +/* + * Set the position of the top left corner of the window to the given + * coordinates. + */ + void +gui_mch_set_winpos(int x, int y) +{ + /* TODO: Should make sure the window is move within range + * e.g.: y > ~16 [Menu bar], x > 0, x < screen width + */ + MoveWindowStructure(gui.VimWindow, x, y); +} + + void +gui_mch_set_shellsize( + int width, + int height, + int min_width, + int min_height, + int base_width, + int base_height, + int direction) +{ + CGrafPtr VimPort; + Rect VimBound; + + if (gui.which_scrollbars[SBAR_LEFT]) + { + VimPort = GetWindowPort(gui.VimWindow); + GetPortBounds(VimPort, &VimBound); + VimBound.left = -gui.scrollbar_width; /* + 1;*/ + SetPortBounds(VimPort, &VimBound); + /* GetWindowBounds(gui.VimWindow, kWindowGlobalPortRgn, &winPortRect); ??*/ + } + else + { + VimPort = GetWindowPort(gui.VimWindow); + GetPortBounds(VimPort, &VimBound); + VimBound.left = 0; + SetPortBounds(VimPort, &VimBound); + } + + SizeWindow(gui.VimWindow, width, height, TRUE); + + gui_resize_shell(width, height); +} + +/* + * Get the screen dimensions. + * Allow 10 pixels for horizontal borders, 40 for vertical borders. + * Is there no way to find out how wide the borders really are? + * TODO: Add live update of those value on suspend/resume. + */ + void +gui_mch_get_screen_dimensions(int *screen_w, int *screen_h) +{ + GDHandle dominantDevice = GetMainDevice(); + Rect screenRect = (**dominantDevice).gdRect; + + *screen_w = screenRect.right - 10; + *screen_h = screenRect.bottom - 40; +} + + +/* + * Open the Font Panel and wait for the user to select a font and + * close the panel. Then fill the buffer pointed to by font_name with + * the name and size of the selected font and return the font's handle, + * or NOFONT in case of an error. + */ + static GuiFont +gui_mac_select_font(char_u *font_name) +{ + GuiFont selected_font = NOFONT; + OSStatus status; + FontSelectionQDStyle curr_font; + + /* Initialize the Font Panel with the current font. */ + curr_font.instance.fontFamily = gui.norm_font & 0xFFFF; + curr_font.size = (gui.norm_font >> 16); + /* TODO: set fontStyle once styles are supported in gui_mac_find_font() */ + curr_font.instance.fontStyle = 0; + curr_font.hasColor = false; + curr_font.version = 0; /* version number of the style structure */ + status = SetFontInfoForSelection(kFontSelectionQDType, + /*numStyles=*/1, &curr_font, /*eventTarget=*/NULL); + + gFontPanelInfo.family = curr_font.instance.fontFamily; + gFontPanelInfo.style = curr_font.instance.fontStyle; + gFontPanelInfo.size = curr_font.size; + + /* Pop up the Font Panel. */ + status = FPShowHideFontPanel(); + if (status == noErr) + { + /* + * The Font Panel is modeless. We really need it to be modal, + * so we spin in an event loop until the panel is closed. + */ + gFontPanelInfo.isPanelVisible = true; + while (gFontPanelInfo.isPanelVisible) + { + EventRecord e; + WaitNextEvent(everyEvent, &e, /*sleep=*/20, /*mouseRgn=*/NULL); + } + + GetFontPanelSelection(font_name); + selected_font = gui_mac_find_font(font_name); + } + return selected_font; +} + +#ifdef USE_ATSUI_DRAWING + static void +gui_mac_create_atsui_style(void) +{ + if (p_macatsui && gFontStyle == NULL) + { + if (ATSUCreateStyle(&gFontStyle) != noErr) + gFontStyle = NULL; + } + if (p_macatsui && gWideFontStyle == NULL) + { + if (ATSUCreateStyle(&gWideFontStyle) != noErr) + gWideFontStyle = NULL; + } + + p_macatsui_last = p_macatsui; +} +#endif + +/* + * Initialise vim to use the font with the given name. Return FAIL if the font + * could not be loaded, OK otherwise. + */ + int +gui_mch_init_font(char_u *font_name, int fontset) +{ + /* TODO: Add support for bold italic underline proportional etc... */ + Str255 suggestedFont = "\pMonaco"; + int suggestedSize = 10; + FontInfo font_info; + short font_id; + GuiFont font; + char_u used_font_name[512]; + +#ifdef USE_ATSUI_DRAWING + gui_mac_create_atsui_style(); +#endif + + if (font_name == NULL) + { + /* First try to get the suggested font */ + GetFNum(suggestedFont, &font_id); + + if (font_id == 0) + { + /* Then pickup the standard application font */ + font_id = GetAppFont(); + STRCPY(used_font_name, "default"); + } + else + STRCPY(used_font_name, "Monaco"); + font = (suggestedSize << 16) + ((long) font_id & 0xFFFF); + } + else if (STRCMP(font_name, "*") == 0) + { + char_u *new_p_guifont; + + font = gui_mac_select_font(used_font_name); + if (font == NOFONT) + return FAIL; + + /* Set guifont to the name of the selected font. */ + new_p_guifont = alloc(STRLEN(used_font_name) + 1); + if (new_p_guifont != NULL) + { + STRCPY(new_p_guifont, used_font_name); + vim_free(p_guifont); + p_guifont = new_p_guifont; + /* Replace spaces in the font name with underscores. */ + for ( ; *new_p_guifont; ++new_p_guifont) + { + if (*new_p_guifont == ' ') + *new_p_guifont = '_'; + } + } + } + else + { + font = gui_mac_find_font(font_name); + vim_strncpy(used_font_name, font_name, sizeof(used_font_name) - 1); + + if (font == NOFONT) + return FAIL; + } + + gui.norm_font = font; + + hl_set_font_name(used_font_name); + + TextSize(font >> 16); + TextFont(font & 0xFFFF); + + GetFontInfo(&font_info); + + gui.char_ascent = font_info.ascent; + gui.char_width = CharWidth('_'); + gui.char_height = font_info.ascent + font_info.descent + p_linespace; + +#ifdef USE_ATSUI_DRAWING + if (p_macatsui && gFontStyle) + gui_mac_set_font_attributes(font); +#endif + + return OK; +} + +/* + * Adjust gui.char_height (after 'linespace' was changed). + */ + int +gui_mch_adjust_charheight(void) +{ + FontInfo font_info; + + GetFontInfo(&font_info); + gui.char_height = font_info.ascent + font_info.descent + p_linespace; + gui.char_ascent = font_info.ascent + p_linespace / 2; + return OK; +} + +/* + * Get a font structure for highlighting. + */ + GuiFont +gui_mch_get_font(char_u *name, int giveErrorIfMissing) +{ + GuiFont font; + + font = gui_mac_find_font(name); + + if (font == NOFONT) + { + if (giveErrorIfMissing) + semsg(_(e_font), name); + return NOFONT; + } + /* + * TODO : Accept only monospace + */ + + return font; +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Return the name of font "font" in allocated memory. + * Don't know how to get the actual name, thus use the provided name. + */ + char_u * +gui_mch_get_fontname(GuiFont font, char_u *name) +{ + if (name == NULL) + return NULL; + return vim_strsave(name); +} +#endif + +#ifdef USE_ATSUI_DRAWING + static void +gui_mac_set_font_attributes(GuiFont font) +{ + ATSUFontID fontID; + Fixed fontSize; + Fixed fontWidth; + + fontID = font & 0xFFFF; + fontSize = Long2Fix(font >> 16); + fontWidth = Long2Fix(gui.char_width); + + ATSUAttributeTag attribTags[] = + { + kATSUFontTag, kATSUSizeTag, kATSUImposeWidthTag, + kATSUMaxATSUITagValue + 1 + }; + + ByteCount attribSizes[] = + { + sizeof(ATSUFontID), sizeof(Fixed), sizeof(fontWidth), + sizeof(font) + }; + + ATSUAttributeValuePtr attribValues[] = + { + &fontID, &fontSize, &fontWidth, &font + }; + + if (FMGetFontFromFontFamilyInstance(fontID, 0, &fontID, NULL) == noErr) + { + if (ATSUSetAttributes(gFontStyle, + (sizeof attribTags) / sizeof(ATSUAttributeTag), + attribTags, attribSizes, attribValues) != noErr) + { +# ifndef NDEBUG + fprintf(stderr, "couldn't set font style\n"); +# endif + ATSUDisposeStyle(gFontStyle); + gFontStyle = NULL; + } + + if (has_mbyte) + { + /* FIXME: we should use a more mbyte sensitive way to support + * wide font drawing */ + fontWidth = Long2Fix(gui.char_width * 2); + + if (ATSUSetAttributes(gWideFontStyle, + (sizeof attribTags) / sizeof(ATSUAttributeTag), + attribTags, attribSizes, attribValues) != noErr) + { + ATSUDisposeStyle(gWideFontStyle); + gWideFontStyle = NULL; + } + } + } +} +#endif + +/* + * Set the current text font. + */ + void +gui_mch_set_font(GuiFont font) +{ +#ifdef USE_ATSUI_DRAWING + GuiFont currFont; + ByteCount actualFontByteCount; + + if (p_macatsui && gFontStyle) + { + /* Avoid setting same font again */ + if (ATSUGetAttribute(gFontStyle, kATSUMaxATSUITagValue + 1, + sizeof(font), &currFont, &actualFontByteCount) == noErr + && actualFontByteCount == (sizeof font)) + { + if (currFont == font) + return; + } + + gui_mac_set_font_attributes(font); + } + + if (p_macatsui && !gIsFontFallbackSet) + { + /* Setup automatic font substitution. The user's guifontwide + * is tried first, then the system tries other fonts. */ +/* + ATSUAttributeTag fallbackTags[] = { kATSULineFontFallbacksTag }; + ByteCount fallbackSizes[] = { sizeof(ATSUFontFallbacks) }; + ATSUCreateFontFallbacks(&gFontFallbacks); + ATSUSetObjFontFallbacks(gFontFallbacks, ); +*/ + if (gui.wide_font) + { + ATSUFontID fallbackFonts; + gIsFontFallbackSet = TRUE; + + if (FMGetFontFromFontFamilyInstance( + (gui.wide_font & 0xFFFF), + 0, + &fallbackFonts, + NULL) == noErr) + { + ATSUSetFontFallbacks((sizeof fallbackFonts)/sizeof(ATSUFontID), + &fallbackFonts, + kATSUSequentialFallbacksPreferred); + } +/* + ATSUAttributeValuePtr fallbackValues[] = { }; +*/ + } + } +#endif + TextSize(font >> 16); + TextFont(font & 0xFFFF); +} + +/* + * If a font is not going to be used, free its structure. + */ + void +gui_mch_free_font(GuiFont font) +{ + /* + * Free font when "font" is not 0. + * Nothing to do in the current implementation, since + * nothing is allocated for each font used. + */ +} + +/* + * Return the Pixel value (color) for the given color name. This routine was + * pretty much taken from example code in the Silicon Graphics OSF/Motif + * Programmer's Guide. + * Return INVALCOLOR when failed. + */ + guicolor_T +gui_mch_get_color(char_u *name) +{ + /* TODO: Add support for the new named color of MacOS 8 + */ + RGBColor MacColor; + + if (STRICMP(name, "hilite") == 0) + { + LMGetHiliteRGB(&MacColor); + return (RGB(MacColor.red >> 8, MacColor.green >> 8, MacColor.blue >> 8)); + } + return gui_get_color_cmn(name); +} + + guicolor_T +gui_mch_get_rgb_color(int r, int g, int b) +{ + return gui_get_rgb_color_cmn(r, g, b); +} + +/* + * Set the current text foreground color. + */ + void +gui_mch_set_fg_color(guicolor_T color) +{ + RGBColor TheColor; + + TheColor.red = Red(color) * 0x0101; + TheColor.green = Green(color) * 0x0101; + TheColor.blue = Blue(color) * 0x0101; + + RGBForeColor(&TheColor); +} + +/* + * Set the current text background color. + */ + void +gui_mch_set_bg_color(guicolor_T color) +{ + RGBColor TheColor; + + TheColor.red = Red(color) * 0x0101; + TheColor.green = Green(color) * 0x0101; + TheColor.blue = Blue(color) * 0x0101; + + RGBBackColor(&TheColor); +} + +RGBColor specialColor; + +/* + * Set the current text special color. + */ + void +gui_mch_set_sp_color(guicolor_T color) +{ + specialColor.red = Red(color) * 0x0101; + specialColor.green = Green(color) * 0x0101; + specialColor.blue = Blue(color) * 0x0101; +} + +/* + * Draw undercurl at the bottom of the character cell. + */ + static void +draw_undercurl(int flags, int row, int col, int cells) +{ + int x; + int offset; + const static int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 }; + int y = FILL_Y(row + 1) - 1; + + RGBForeColor(&specialColor); + + offset = val[FILL_X(col) % 8]; + MoveTo(FILL_X(col), y - offset); + + for (x = FILL_X(col); x < FILL_X(col + cells); ++x) + { + offset = val[x % 8]; + LineTo(x, y - offset); + } +} + + + static void +draw_string_QD(int row, int col, char_u *s, int len, int flags) +{ + char_u *tofree = NULL; + + if (output_conv.vc_type != CONV_NONE) + { + tofree = string_convert(&output_conv, s, &len); + if (tofree != NULL) + s = tofree; + } + + /* + * On OS X, try using Quartz-style text antialiasing. + */ + if (gMacSystemVersion >= 0x1020) + { + /* Quartz antialiasing is available only in OS 10.2 and later. */ + UInt32 qd_flags = (p_antialias ? + kQDUseCGTextRendering | kQDUseCGTextMetrics : 0); + QDSwapTextFlags(qd_flags); + } + + /* + * When antialiasing we're using srcOr mode, we have to clear the block + * before drawing the text. + * Also needed when 'linespace' is non-zero to remove the cursor and + * underlining. + * But not when drawing transparently. + * The following is like calling gui_mch_clear_block(row, col, row, col + + * len - 1), but without setting the bg color to gui.back_pixel. + */ + if (((gMacSystemVersion >= 0x1020 && p_antialias) || p_linespace != 0) + && !(flags & DRAW_TRANSP)) + { + Rect rc; + + rc.left = FILL_X(col); + rc.top = FILL_Y(row); + /* Multibyte computation taken from gui_w32.c */ + if (has_mbyte) + { + /* Compute the length in display cells. */ + rc.right = FILL_X(col + mb_string2cells(s, len)); + } + else + rc.right = FILL_X(col + len) + (col + len == Columns); + rc.bottom = FILL_Y(row + 1); + EraseRect(&rc); + } + + if (gMacSystemVersion >= 0x1020 && p_antialias) + { + StyleParameter face; + + face = normal; + if (flags & DRAW_BOLD) + face |= bold; + if (flags & DRAW_UNDERL) + face |= underline; + TextFace(face); + + /* Quartz antialiasing works only in srcOr transfer mode. */ + TextMode(srcOr); + + MoveTo(TEXT_X(col), TEXT_Y(row)); + DrawText((char*)s, 0, len); + } + else + { + /* Use old-style, non-antialiased QuickDraw text rendering. */ + TextMode(srcCopy); + TextFace(normal); + + /* SelectFont(hdc, gui.currFont); */ + + if (flags & DRAW_TRANSP) + { + TextMode(srcOr); + } + + MoveTo(TEXT_X(col), TEXT_Y(row)); + DrawText((char *)s, 0, len); + + if (flags & DRAW_BOLD) + { + TextMode(srcOr); + MoveTo(TEXT_X(col) + 1, TEXT_Y(row)); + DrawText((char *)s, 0, len); + } + + if (flags & DRAW_UNDERL) + { + MoveTo(FILL_X(col), FILL_Y(row + 1) - 1); + LineTo(FILL_X(col + len) - 1, FILL_Y(row + 1) - 1); + } + if (flags & DRAW_STRIKE) + { + MoveTo(FILL_X(col), FILL_Y(row + 1) - gui.char_height/2); + LineTo(FILL_X(col + len) - 1, FILL_Y(row + 1) - gui.char_height/2); + } + } + + if (flags & DRAW_UNDERC) + draw_undercurl(flags, row, col, len); + + vim_free(tofree); +} + +#ifdef USE_ATSUI_DRAWING + + static void +draw_string_ATSUI(int row, int col, char_u *s, int len, int flags) +{ + /* ATSUI requires utf-16 strings */ + UniCharCount utf16_len; + UniChar *tofree = mac_enc_to_utf16(s, len, (size_t *)&utf16_len); + utf16_len /= sizeof(UniChar); + + /* - ATSUI automatically antialiases text (Someone) + * - for some reason it does not work... (Jussi) */ +#ifdef MAC_ATSUI_DEBUG + fprintf(stderr, "row = %d, col = %d, len = %d: '%c'\n", + row, col, len, len == 1 ? s[0] : ' '); +#endif + /* + * When antialiasing we're using srcOr mode, we have to clear the block + * before drawing the text. + * Also needed when 'linespace' is non-zero to remove the cursor and + * underlining. + * But not when drawing transparently. + * The following is like calling gui_mch_clear_block(row, col, row, col + + * len - 1), but without setting the bg color to gui.back_pixel. + */ + if ((flags & DRAW_TRANSP) == 0) + { + Rect rc; + + rc.left = FILL_X(col); + rc.top = FILL_Y(row); + /* Multibyte computation taken from gui_w32.c */ + if (has_mbyte) + { + /* Compute the length in display cells. */ + rc.right = FILL_X(col + mb_string2cells(s, len)); + } + else + rc.right = FILL_X(col + len) + (col + len == Columns); + + rc.bottom = FILL_Y(row + 1); + EraseRect(&rc); + } + + { + TextMode(srcCopy); + TextFace(normal); + + /* SelectFont(hdc, gui.currFont); */ + if (flags & DRAW_TRANSP) + { + TextMode(srcOr); + } + + MoveTo(TEXT_X(col), TEXT_Y(row)); + + if (gFontStyle && flags & DRAW_BOLD) + { + Boolean attValue = true; + ATSUAttributeTag attribTags[] = { kATSUQDBoldfaceTag }; + ByteCount attribSizes[] = { sizeof(Boolean) }; + ATSUAttributeValuePtr attribValues[] = { &attValue }; + + ATSUSetAttributes(gFontStyle, 1, attribTags, attribSizes, attribValues); + } + + UInt32 useAntialias = p_antialias ? kATSStyleApplyAntiAliasing + : kATSStyleNoAntiAliasing; + if (useAntialias != useAntialias_cached) + { + ATSUAttributeTag attribTags[] = { kATSUStyleRenderingOptionsTag }; + ByteCount attribSizes[] = { sizeof(UInt32) }; + ATSUAttributeValuePtr attribValues[] = { &useAntialias }; + + if (gFontStyle) + ATSUSetAttributes(gFontStyle, 1, attribTags, + attribSizes, attribValues); + if (gWideFontStyle) + ATSUSetAttributes(gWideFontStyle, 1, attribTags, + attribSizes, attribValues); + + useAntialias_cached = useAntialias; + } + + if (has_mbyte) + { + int n, width_in_cell, last_width_in_cell; + UniCharArrayOffset offset = 0; + UniCharCount yet_to_draw = 0; + ATSUTextLayout textLayout; + ATSUStyle textStyle; + + last_width_in_cell = 1; + ATSUCreateTextLayout(&textLayout); + ATSUSetTextPointerLocation(textLayout, tofree, + kATSUFromTextBeginning, + kATSUToTextEnd, utf16_len); + /* + ATSUSetRunStyle(textLayout, gFontStyle, + kATSUFromTextBeginning, kATSUToTextEnd); */ + + /* Compute the length in display cells. */ + for (n = 0; n < len; n += MB_BYTE2LEN(s[n])) + { + width_in_cell = (*mb_ptr2cells)(s + n); + + /* probably we are switching from single byte character + * to multibyte characters (which requires more than one + * cell to draw) */ + if (width_in_cell != last_width_in_cell) + { +#ifdef MAC_ATSUI_DEBUG + fprintf(stderr, "\tn = %2d, (%d-%d), offset = %d, yet_to_draw = %d\n", + n, last_width_in_cell, width_in_cell, offset, yet_to_draw); +#endif + textStyle = last_width_in_cell > 1 ? gWideFontStyle + : gFontStyle; + + ATSUSetRunStyle(textLayout, textStyle, offset, yet_to_draw); + offset += yet_to_draw; + yet_to_draw = 0; + last_width_in_cell = width_in_cell; + } + + yet_to_draw++; + } + + if (yet_to_draw) + { +#ifdef MAC_ATSUI_DEBUG + fprintf(stderr, "\tn = %2d, (%d-%d), offset = %d, yet_to_draw = %d\n", + n, last_width_in_cell, width_in_cell, offset, yet_to_draw); +#endif + /* finish the rest style */ + textStyle = width_in_cell > 1 ? gWideFontStyle : gFontStyle; + ATSUSetRunStyle(textLayout, textStyle, offset, kATSUToTextEnd); + } + + ATSUSetTransientFontMatching(textLayout, TRUE); + ATSUDrawText(textLayout, + kATSUFromTextBeginning, kATSUToTextEnd, + kATSUUseGrafPortPenLoc, kATSUUseGrafPortPenLoc); + ATSUDisposeTextLayout(textLayout); + } + else + { + ATSUTextLayout textLayout; + + if (ATSUCreateTextLayoutWithTextPtr(tofree, + kATSUFromTextBeginning, kATSUToTextEnd, + utf16_len, + (gFontStyle ? 1 : 0), &utf16_len, + (gFontStyle ? &gFontStyle : NULL), + &textLayout) == noErr) + { + ATSUSetTransientFontMatching(textLayout, TRUE); + + ATSUDrawText(textLayout, + kATSUFromTextBeginning, kATSUToTextEnd, + kATSUUseGrafPortPenLoc, kATSUUseGrafPortPenLoc); + + ATSUDisposeTextLayout(textLayout); + } + } + + /* drawing is done, now reset bold to normal */ + if (gFontStyle && flags & DRAW_BOLD) + { + Boolean attValue = false; + + ATSUAttributeTag attribTags[] = { kATSUQDBoldfaceTag }; + ByteCount attribSizes[] = { sizeof(Boolean) }; + ATSUAttributeValuePtr attribValues[] = { &attValue }; + + ATSUSetAttributes(gFontStyle, 1, attribTags, attribSizes, + attribValues); + } + } + + if (flags & DRAW_UNDERC) + draw_undercurl(flags, row, col, len); + + vim_free(tofree); +} +#endif + + void +gui_mch_draw_string(int row, int col, char_u *s, int len, int flags) +{ +#if defined(USE_ATSUI_DRAWING) + if (p_macatsui == 0 && p_macatsui_last != 0) + /* switch from macatsui to nomacatsui */ + gui_mac_dispose_atsui_style(); + else if (p_macatsui != 0 && p_macatsui_last == 0) + /* switch from nomacatsui to macatsui */ + gui_mac_create_atsui_style(); + + if (p_macatsui) + draw_string_ATSUI(row, col, s, len, flags); + else +#endif + draw_string_QD(row, col, s, len, flags); +} + +/* + * Return OK if the key with the termcap name "name" is supported. + */ + int +gui_mch_haskey(char_u *name) +{ + int i; + + for (i = 0; special_keys[i].key_sym != (KeySym)0; i++) + if (name[0] == special_keys[i].vim_code0 && + name[1] == special_keys[i].vim_code1) + return OK; + return FAIL; +} + + void +gui_mch_beep(void) +{ + SysBeep(1); /* Should this be 0? (????) */ +} + + void +gui_mch_flash(int msec) +{ + /* Do a visual beep by reversing the foreground and background colors */ + Rect rc; + + /* + * Note: InvertRect() excludes right and bottom of rectangle. + */ + rc.left = 0; + rc.top = 0; + rc.right = gui.num_cols * gui.char_width; + rc.bottom = gui.num_rows * gui.char_height; + InvertRect(&rc); + + ui_delay((long)msec, TRUE); /* wait for some msec */ + + InvertRect(&rc); +} + +/* + * Invert a rectangle from row r, column c, for nr rows and nc columns. + */ + void +gui_mch_invert_rectangle(int r, int c, int nr, int nc) +{ + Rect rc; + + /* + * Note: InvertRect() excludes right and bottom of rectangle. + */ + rc.left = FILL_X(c); + rc.top = FILL_Y(r); + rc.right = rc.left + nc * gui.char_width; + rc.bottom = rc.top + nr * gui.char_height; + InvertRect(&rc); +} + +/* + * Iconify the GUI window. + */ + void +gui_mch_iconify(void) +{ + /* TODO: find out what could replace iconify + * -window shade? + * -hide application? + */ +} + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * Bring the Vim window to the foreground. + */ + void +gui_mch_set_foreground(void) +{ + /* TODO */ +} +#endif + +/* + * Draw a cursor without focus. + */ + void +gui_mch_draw_hollow_cursor(guicolor_T color) +{ + Rect rc; + + /* + * Note: FrameRect() excludes right and bottom of rectangle. + */ + rc.left = FILL_X(gui.col); + rc.top = FILL_Y(gui.row); + rc.right = rc.left + gui.char_width; + if (mb_lefthalve(gui.row, gui.col)) + rc.right += gui.char_width; + rc.bottom = rc.top + gui.char_height; + + gui_mch_set_fg_color(color); + + FrameRect(&rc); +} + +/* + * Draw part of a cursor, only w pixels wide, and h pixels high. + */ + void +gui_mch_draw_part_cursor(int w, int h, guicolor_T color) +{ + Rect rc; + +#ifdef FEAT_RIGHTLEFT + /* vertical line should be on the right of current point */ + if (CURSOR_BAR_RIGHT) + rc.left = FILL_X(gui.col + 1) - w; + else +#endif + rc.left = FILL_X(gui.col); + rc.top = FILL_Y(gui.row) + gui.char_height - h; + rc.right = rc.left + w; + rc.bottom = rc.top + h; + + gui_mch_set_fg_color(color); + + FrameRect(&rc); +// PaintRect(&rc); +} + + + +/* + * Catch up with any queued X events. This may put keyboard input into the + * input buffer, call resize call-backs, trigger timers etc. If there is + * nothing in the X event queue (& no timers pending), then we return + * immediately. + */ + void +gui_mch_update(void) +{ + /* TODO: find what to do + * maybe call gui_mch_wait_for_chars (0) + * more like look at EventQueue then + * call heart of gui_mch_wait_for_chars; + * + * if (eventther) + * gui_mac_handle_event(&event); + */ + EventRecord theEvent; + + if (EventAvail(everyEvent, &theEvent)) + if (theEvent.what != nullEvent) + gui_mch_wait_for_chars(0); +} + +/* + * Simple wrapper to neglect more easily the time + * spent inside WaitNextEvent while profiling. + */ + + pascal + Boolean +WaitNextEventWrp(EventMask eventMask, EventRecord *theEvent, UInt32 sleep, RgnHandle mouseRgn) +{ + if (((long) sleep) < -1) + sleep = 32767; + return WaitNextEvent(eventMask, theEvent, sleep, mouseRgn); +} + +/* + * GUI input routine called by gui_wait_for_chars(). Waits for a character + * from the keyboard. + * wtime == -1 Wait forever. + * wtime == 0 This should never happen. + * wtime > 0 Wait wtime milliseconds for a character. + * Returns OK if a character was found to be available within the given time, + * or FAIL otherwise. + */ + int +gui_mch_wait_for_chars(int wtime) +{ + EventMask mask = (everyEvent); + EventRecord event; + long entryTick; + long currentTick; + long sleeppyTick; + + /* If we are providing life feedback with the scrollbar, + * we don't want to try to wait for an event, or else + * there won't be any life feedback. + */ + if (dragged_sb != NULL) + return FAIL; + /* TODO: Check if FAIL is the proper return code */ + + entryTick = TickCount(); + + allow_scrollbar = TRUE; + + do + { +/* if (dragRectControl == kCreateEmpty) + { + dragRgn = NULL; + dragRectControl = kNothing; + } + else*/ if (dragRectControl == kCreateRect) + { + dragRgn = cursorRgn; + RectRgn(dragRgn, &dragRect); + dragRectControl = kNothing; + } + /* + * Don't use gui_mch_update() because then we will spin-lock until a + * char arrives, instead we use WaitNextEventWrp() to hang until an + * event arrives. No need to check for input_buf_full because we are + * returning as soon as it contains a single char. + */ + /* TODO: reduce wtime accordingly??? */ + if (wtime > -1) + sleeppyTick = 60 * wtime / 1000; + else + sleeppyTick = 32767; + + if (WaitNextEventWrp(mask, &event, sleeppyTick, dragRgn)) + { + gui_mac_handle_event(&event); + if (input_available()) + { + allow_scrollbar = FALSE; + return OK; + } + } + currentTick = TickCount(); + } + while ((wtime == -1) || ((currentTick - entryTick) < 60*wtime/1000)); + + allow_scrollbar = FALSE; + return FAIL; +} + +/* + * Output routines. + */ + +/* Flush any output to the screen */ + void +gui_mch_flush(void) +{ + /* TODO: Is anything needed here? */ +} + +/* + * Clear a rectangular region of the screen from text pos (row1, col1) to + * (row2, col2) inclusive. + */ + void +gui_mch_clear_block(int row1, int col1, int row2, int col2) +{ + Rect rc; + + /* + * Clear one extra pixel at the far right, for when bold characters have + * spilled over to the next column. + */ + rc.left = FILL_X(col1); + rc.top = FILL_Y(row1); + rc.right = FILL_X(col2 + 1) + (col2 == Columns - 1); + rc.bottom = FILL_Y(row2 + 1); + + gui_mch_set_bg_color(gui.back_pixel); + EraseRect(&rc); +} + +/* + * Clear the whole text window. + */ + void +gui_mch_clear_all(void) +{ + Rect rc; + + rc.left = 0; + rc.top = 0; + rc.right = Columns * gui.char_width + 2 * gui.border_width; + rc.bottom = Rows * gui.char_height + 2 * gui.border_width; + + gui_mch_set_bg_color(gui.back_pixel); + EraseRect(&rc); +/* gui_mch_set_fg_color(gui.norm_pixel); + FrameRect(&rc); +*/ +} + +/* + * Delete the given number of lines from the given row, scrolling up any + * text further down within the scroll region. + */ + void +gui_mch_delete_lines(int row, int num_lines) +{ + Rect rc; + + /* changed without checking! */ + rc.left = FILL_X(gui.scroll_region_left); + rc.right = FILL_X(gui.scroll_region_right + 1); + rc.top = FILL_Y(row); + rc.bottom = FILL_Y(gui.scroll_region_bot + 1); + + gui_mch_set_bg_color(gui.back_pixel); + ScrollRect(&rc, 0, -num_lines * gui.char_height, (RgnHandle) nil); + + gui_clear_block(gui.scroll_region_bot - num_lines + 1, + gui.scroll_region_left, + gui.scroll_region_bot, gui.scroll_region_right); +} + +/* + * Insert the given number of lines before the given row, scrolling down any + * following text within the scroll region. + */ + void +gui_mch_insert_lines(int row, int num_lines) +{ + Rect rc; + + rc.left = FILL_X(gui.scroll_region_left); + rc.right = FILL_X(gui.scroll_region_right + 1); + rc.top = FILL_Y(row); + rc.bottom = FILL_Y(gui.scroll_region_bot + 1); + + gui_mch_set_bg_color(gui.back_pixel); + + ScrollRect(&rc, 0, gui.char_height * num_lines, (RgnHandle) nil); + + /* Update gui.cursor_row if the cursor scrolled or copied over */ + if (gui.cursor_row >= gui.row + && gui.cursor_col >= gui.scroll_region_left + && gui.cursor_col <= gui.scroll_region_right) + { + if (gui.cursor_row <= gui.scroll_region_bot - num_lines) + gui.cursor_row += num_lines; + else if (gui.cursor_row <= gui.scroll_region_bot) + gui.cursor_is_valid = FALSE; + } + + gui_clear_block(row, gui.scroll_region_left, + row + num_lines - 1, gui.scroll_region_right); +} + + /* + * TODO: add a vim format to the clipboard which remember + * LINEWISE, CHARWISE, BLOCKWISE + */ + + void +clip_mch_request_selection(VimClipboard *cbd) +{ + + Handle textOfClip; + int flavor = 0; + Size scrapSize; + ScrapFlavorFlags scrapFlags; + ScrapRef scrap = nil; + OSStatus error; + int type; + char *searchCR; + char_u *tempclip; + + + error = GetCurrentScrap(&scrap); + if (error != noErr) + return; + + error = GetScrapFlavorFlags(scrap, VIMSCRAPFLAVOR, &scrapFlags); + if (error == noErr) + { + error = GetScrapFlavorSize(scrap, VIMSCRAPFLAVOR, &scrapSize); + if (error == noErr && scrapSize > 1) + flavor = 1; + } + + if (flavor == 0) + { + error = GetScrapFlavorFlags(scrap, SCRAPTEXTFLAVOR, &scrapFlags); + if (error != noErr) + return; + + error = GetScrapFlavorSize(scrap, SCRAPTEXTFLAVOR, &scrapSize); + if (error != noErr) + return; + } + + ReserveMem(scrapSize); + + /* In CARBON we don't need a Handle, a pointer is good */ + textOfClip = NewHandle(scrapSize); + + /* tempclip = lalloc(scrapSize+1, TRUE); */ + HLock(textOfClip); + error = GetScrapFlavorData(scrap, + flavor ? VIMSCRAPFLAVOR : SCRAPTEXTFLAVOR, + &scrapSize, *textOfClip); + scrapSize -= flavor; + + if (flavor) + type = **textOfClip; + else + type = MAUTO; + + tempclip = lalloc(scrapSize + 1, TRUE); + mch_memmove(tempclip, *textOfClip + flavor, scrapSize); + tempclip[scrapSize] = 0; + +#ifdef MACOS_CONVERT + { + /* Convert from utf-16 (clipboard) */ + size_t encLen = 0; + char_u *to = mac_utf16_to_enc((UniChar *)tempclip, scrapSize, &encLen); + + if (to != NULL) + { + scrapSize = encLen; + vim_free(tempclip); + tempclip = to; + } + } +#endif + + searchCR = (char *)tempclip; + while (searchCR != NULL) + { + searchCR = strchr(searchCR, '\r'); + if (searchCR != NULL) + *searchCR = '\n'; + } + + clip_yank_selection(type, tempclip, scrapSize, cbd); + + vim_free(tempclip); + HUnlock(textOfClip); + + DisposeHandle(textOfClip); +} + + void +clip_mch_lose_selection(VimClipboard *cbd) +{ + /* + * TODO: Really nothing to do? + */ +} + + int +clip_mch_own_selection(VimClipboard *cbd) +{ + return OK; +} + +/* + * Send the current selection to the clipboard. + */ + void +clip_mch_set_selection(VimClipboard *cbd) +{ + Handle textOfClip; + long scrapSize; + int type; + ScrapRef scrap; + + char_u *str = NULL; + + if (!cbd->owned) + return; + + clip_get_selection(cbd); + + /* + * Once we set the clipboard, lose ownership. If another application sets + * the clipboard, we don't want to think that we still own it. + */ + cbd->owned = FALSE; + + type = clip_convert_selection(&str, (long_u *)&scrapSize, cbd); + +#ifdef MACOS_CONVERT + size_t utf16_len = 0; + UniChar *to = mac_enc_to_utf16(str, scrapSize, &utf16_len); + if (to) + { + scrapSize = utf16_len; + vim_free(str); + str = (char_u *)to; + } +#endif + + if (type >= 0) + { + ClearCurrentScrap(); + + textOfClip = NewHandle(scrapSize + 1); + HLock(textOfClip); + + **textOfClip = type; + mch_memmove(*textOfClip + 1, str, scrapSize); + GetCurrentScrap(&scrap); + PutScrapFlavor(scrap, SCRAPTEXTFLAVOR, kScrapFlavorMaskNone, + scrapSize, *textOfClip + 1); + PutScrapFlavor(scrap, VIMSCRAPFLAVOR, kScrapFlavorMaskNone, + scrapSize + 1, *textOfClip); + HUnlock(textOfClip); + DisposeHandle(textOfClip); + } + + vim_free(str); +} + + void +gui_mch_set_text_area_pos(int x, int y, int w, int h) +{ + Rect VimBound; + +/* HideWindow(gui.VimWindow); */ + GetWindowBounds(gui.VimWindow, kWindowGlobalPortRgn, &VimBound); + + if (gui.which_scrollbars[SBAR_LEFT]) + { + VimBound.left = -gui.scrollbar_width + 1; + } + else + { + VimBound.left = 0; + } + + SetWindowBounds(gui.VimWindow, kWindowGlobalPortRgn, &VimBound); + + ShowWindow(gui.VimWindow); +} + +/* + * Menu stuff. + */ + + void +gui_mch_enable_menu(int flag) +{ + /* + * Menu is always active. + */ +} + + void +gui_mch_set_menu_pos(int x, int y, int w, int h) +{ + /* + * The menu is always at the top of the screen. + */ +} + +/* + * Add a sub menu to the menu bar. + */ + void +gui_mch_add_menu(vimmenu_T *menu, int idx) +{ + /* + * TODO: Try to use only menu_id instead of both menu_id and menu_handle. + * TODO: use menu->mnemonic and menu->actext + * TODO: Try to reuse menu id + * Carbon Help suggest to use only id between 1 and 235 + */ + static long next_avail_id = 128; + long menu_after_me = 0; /* Default to the end */ + CFStringRef name; + short index; + vimmenu_T *parent = menu->parent; + vimmenu_T *brother = menu->next; + + /* Cannot add a menu if ... */ + if ((parent != NULL && parent->submenu_id == 0)) + return; + + /* menu ID greater than 1024 are reserved for ??? */ + if (next_avail_id == 1024) + return; + + /* My brother could be the PopUp, find my real brother */ + while ((brother != NULL) && (!menu_is_menubar(brother->name))) + brother = brother->next; + + /* Find where to insert the menu (for MenuBar) */ + if ((parent == NULL) && (brother != NULL)) + menu_after_me = brother->submenu_id; + + /* If the menu is not part of the menubar (and its submenus), add it 'nowhere' */ + if (!menu_is_menubar(menu->name)) + menu_after_me = hierMenu; + + /* Convert the name */ +#ifdef MACOS_CONVERT + name = menu_title_removing_mnemonic(menu); +#else + name = C2Pascal_save(menu->dname); +#endif + if (name == NULL) + return; + + /* Create the menu unless it's the help menu */ + { + /* Carbon suggest use of + * OSStatus CreateNewMenu(MenuID, MenuAttributes, MenuRef *); + * OSStatus SetMenuTitle(MenuRef, ConstStr255Param title); + */ + menu->submenu_id = next_avail_id; + if (CreateNewMenu(menu->submenu_id, 0, (MenuRef *)&menu->submenu_handle) == noErr) + SetMenuTitleWithCFString((MenuRef)menu->submenu_handle, name); + next_avail_id++; + } + + if (parent == NULL) + { + /* Adding a menu to the menubar, or in the no mans land (for PopUp) */ + + /* TODO: Verify if we could only Insert Menu if really part of the + * menubar The Inserted menu are scanned or the Command-key combos + */ + + /* Insert the menu */ + InsertMenu(menu->submenu_handle, menu_after_me); /* insert before */ +#if 1 + /* Vim should normally update it. TODO: verify */ + DrawMenuBar(); +#endif + } + else + { + /* Adding as a submenu */ + + index = gui_mac_get_menu_item_index(menu); + + /* Call InsertMenuItem followed by SetMenuItemText + * to avoid special character recognition by InsertMenuItem + */ + InsertMenuItem(parent->submenu_handle, "\p ", idx); /* afterItem */ + SetMenuItemTextWithCFString(parent->submenu_handle, idx+1, name); + SetItemCmd(parent->submenu_handle, idx+1, 0x1B); + SetItemMark(parent->submenu_handle, idx+1, menu->submenu_id); + InsertMenu(menu->submenu_handle, hierMenu); + } + + CFRelease(name); + +#if 0 + /* Done by Vim later on */ + DrawMenuBar(); +#endif +} + +/* + * Add a menu item to a menu + */ + void +gui_mch_add_menu_item(vimmenu_T *menu, int idx) +{ + CFStringRef name; + vimmenu_T *parent = menu->parent; + int menu_inserted; + + /* Cannot add item, if the menu have not been created */ + if (parent->submenu_id == 0) + return; + + /* Could call SetMenuRefCon [CARBON] to associate with the Menu, + for older OS call GetMenuItemData (menu, item, isCommandID?, data) */ + + /* Convert the name */ +#ifdef MACOS_CONVERT + name = menu_title_removing_mnemonic(menu); +#else + name = C2Pascal_save(menu->dname); +#endif + + /* Where are just a menu item, so no handle, no id */ + menu->submenu_id = 0; + menu->submenu_handle = NULL; + + menu_inserted = 0; + if (menu->actext) + { + /* If the accelerator text for the menu item looks like it describes + * a command key (e.g., "<D-S-t>" or "<C-7>"), display it as the + * item's command equivalent. + */ + int key = 0; + int modifiers = 0; + char_u *p_actext; + + p_actext = menu->actext; + key = find_special_key(&p_actext, &modifiers, FALSE, FALSE, FALSE); + if (*p_actext != 0) + key = 0; /* error: trailing text */ + /* find_special_key() returns a keycode with as many of the + * specified modifiers as appropriate already applied (e.g., for + * "<D-C-x>" it returns Ctrl-X as the keycode and MOD_MASK_CMD + * as the only modifier). Since we want to display all of the + * modifiers, we need to convert the keycode back to a printable + * character plus modifiers. + * TODO: Write an alternative find_special_key() that doesn't + * apply modifiers. + */ + if (key > 0 && key < 32) + { + /* Convert a control key to an uppercase letter. Note that + * by this point it is no longer possible to distinguish + * between, e.g., Ctrl-S and Ctrl-Shift-S. + */ + modifiers |= MOD_MASK_CTRL; + key += '@'; + } + /* If the keycode is an uppercase letter, set the Shift modifier. + * If it is a lowercase letter, don't set the modifier, but convert + * the letter to uppercase for display in the menu. + */ + else if (key >= 'A' && key <= 'Z') + modifiers |= MOD_MASK_SHIFT; + else if (key >= 'a' && key <= 'z') + key += 'A' - 'a'; + /* Note: keycodes below 0x22 are reserved by Apple. */ + if (key >= 0x22 && vim_isprintc_strict(key)) + { + int valid = 1; + char_u mac_mods = kMenuNoModifiers; + /* Convert Vim modifier codes to Menu Manager equivalents. */ + if (modifiers & MOD_MASK_SHIFT) + mac_mods |= kMenuShiftModifier; + if (modifiers & MOD_MASK_CTRL) + mac_mods |= kMenuControlModifier; + if (!(modifiers & MOD_MASK_CMD)) + mac_mods |= kMenuNoCommandModifier; + if (modifiers & MOD_MASK_ALT || modifiers & MOD_MASK_MULTI_CLICK) + valid = 0; /* TODO: will Alt someday map to Option? */ + if (valid) + { + char_u item_txt[10]; + /* Insert the menu item after idx, with its command key. */ + item_txt[0] = 3; item_txt[1] = ' '; item_txt[2] = '/'; + item_txt[3] = key; + InsertMenuItem(parent->submenu_handle, item_txt, idx); + /* Set the modifier keys. */ + SetMenuItemModifiers(parent->submenu_handle, idx+1, mac_mods); + menu_inserted = 1; + } + } + } + /* Call InsertMenuItem followed by SetMenuItemText + * to avoid special character recognition by InsertMenuItem + */ + if (!menu_inserted) + InsertMenuItem(parent->submenu_handle, "\p ", idx); /* afterItem */ + /* Set the menu item name. */ + SetMenuItemTextWithCFString(parent->submenu_handle, idx+1, name); + +#if 0 + /* Called by Vim */ + DrawMenuBar(); +#endif + + CFRelease(name); +} + + void +gui_mch_toggle_tearoffs(int enable) +{ + /* no tearoff menus */ +} + +/* + * Destroy the machine specific menu widget. + */ + void +gui_mch_destroy_menu(vimmenu_T *menu) +{ + short index = gui_mac_get_menu_item_index(menu); + + if (index > 0) + { + if (menu->parent) + { + { + /* For now just don't delete help menu items. (Huh? Dany) */ + DeleteMenuItem(menu->parent->submenu_handle, index); + + /* Delete the Menu if it was a hierarchical Menu */ + if (menu->submenu_id != 0) + { + DeleteMenu(menu->submenu_id); + DisposeMenu(menu->submenu_handle); + } + } + } +#ifdef DEBUG_MAC_MENU + else + { + printf("gmdm 2\n"); + } +#endif + } + else + { + { + DeleteMenu(menu->submenu_id); + DisposeMenu(menu->submenu_handle); + } + } + /* Shouldn't this be already done by Vim. TODO: Check */ + DrawMenuBar(); +} + +/* + * Make a menu either grey or not grey. + */ + void +gui_mch_menu_grey(vimmenu_T *menu, int grey) +{ + /* TODO: Check if menu really exists */ + short index = gui_mac_get_menu_item_index(menu); +/* + index = menu->index; +*/ + if (grey) + { + if (menu->children) + DisableMenuItem(menu->submenu_handle, index); + if (menu->parent) + if (menu->parent->submenu_handle) + DisableMenuItem(menu->parent->submenu_handle, index); + } + else + { + if (menu->children) + EnableMenuItem(menu->submenu_handle, index); + if (menu->parent) + if (menu->parent->submenu_handle) + EnableMenuItem(menu->parent->submenu_handle, index); + } +} + +/* + * Make menu item hidden or not hidden + */ + void +gui_mch_menu_hidden(vimmenu_T *menu, int hidden) +{ + /* There's no hidden mode on MacOS */ + gui_mch_menu_grey(menu, hidden); +} + + +/* + * This is called after setting all the menus to grey/hidden or not. + */ + void +gui_mch_draw_menubar(void) +{ + DrawMenuBar(); +} + + +/* + * Scrollbar stuff. + */ + + void +gui_mch_enable_scrollbar( + scrollbar_T *sb, + int flag) +{ + if (flag) + ShowControl(sb->id); + else + HideControl(sb->id); + +#ifdef DEBUG_MAC_SB + printf("enb_sb (%x) %x\n",sb->id, flag); +#endif +} + + void +gui_mch_set_scrollbar_thumb( + scrollbar_T *sb, + long val, + long size, + long max) +{ + SetControl32BitMaximum (sb->id, max); + SetControl32BitMinimum (sb->id, 0); + SetControl32BitValue (sb->id, val); + SetControlViewSize (sb->id, size); +#ifdef DEBUG_MAC_SB + printf("thumb_sb (%x) %lx, %lx,%lx\n",sb->id, val, size, max); +#endif +} + + void +gui_mch_set_scrollbar_pos( + scrollbar_T *sb, + int x, + int y, + int w, + int h) +{ + gui_mch_set_bg_color(gui.back_pixel); +/* if (gui.which_scrollbars[SBAR_LEFT]) + { + MoveControl(sb->id, x-16, y); + SizeControl(sb->id, w + 1, h); + } + else + { + MoveControl(sb->id, x, y); + SizeControl(sb->id, w + 1, h); + }*/ + if (sb == &gui.bottom_sbar) + h += 1; + else + w += 1; + + if (gui.which_scrollbars[SBAR_LEFT]) + x -= 15; + + MoveControl(sb->id, x, y); + SizeControl(sb->id, w, h); +#ifdef DEBUG_MAC_SB + printf("size_sb (%x) %x, %x, %x, %x\n",sb->id, x, y, w, h); +#endif +} + + void +gui_mch_create_scrollbar( + scrollbar_T *sb, + int orient) /* SBAR_VERT or SBAR_HORIZ */ +{ + Rect bounds; + + bounds.top = -16; + bounds.bottom = -10; + bounds.right = -10; + bounds.left = -16; + + sb->id = NewControl(gui.VimWindow, + &bounds, + "\pScrollBar", + TRUE, + 0, /* current*/ + 0, /* top */ + 0, /* bottom */ + kControlScrollBarLiveProc, + (long) sb->ident); +#ifdef DEBUG_MAC_SB + printf("create_sb (%x) %x\n",sb->id, orient); +#endif +} + + void +gui_mch_destroy_scrollbar(scrollbar_T *sb) +{ + gui_mch_set_bg_color(gui.back_pixel); + DisposeControl(sb->id); +#ifdef DEBUG_MAC_SB + printf("dest_sb (%x) \n",sb->id); +#endif +} + + int +gui_mch_is_blinking(void) +{ + return FALSE; +} + + int +gui_mch_is_blink_off(void) +{ + return FALSE; +} + +/* + * Cursor blink functions. + * + * This is a simple state machine: + * BLINK_NONE not blinking at all + * BLINK_OFF blinking, cursor is not shown + * BLINK_ON blinking, cursor is shown + */ + void +gui_mch_set_blinking(long wait, long on, long off) +{ + /* TODO: TODO: TODO: TODO: */ +/* blink_waittime = wait; + blink_ontime = on; + blink_offtime = off;*/ +} + +/* + * Stop the cursor blinking. Show the cursor if it wasn't shown. + */ + void +gui_mch_stop_blink(int may_call_gui_update_cursor) +{ + if (may_call_gui_update_cursor) + gui_update_cursor(TRUE, FALSE); + /* TODO: TODO: TODO: TODO: */ +/* gui_w32_rm_blink_timer(); + if (blink_state == BLINK_OFF) + gui_update_cursor(TRUE, FALSE); + blink_state = BLINK_NONE;*/ +} + +/* + * Start the cursor blinking. If it was already blinking, this restarts the + * waiting time and shows the cursor. + */ + void +gui_mch_start_blink(void) +{ + gui_update_cursor(TRUE, FALSE); + /* TODO: TODO: TODO: TODO: */ +/* gui_w32_rm_blink_timer(); */ + + /* Only switch blinking on if none of the times is zero */ +/* if (blink_waittime && blink_ontime && blink_offtime) + { + blink_timer = SetTimer(NULL, 0, (UINT)blink_waittime, + (TIMERPROC)_OnBlinkTimer); + blink_state = BLINK_ON; + gui_update_cursor(TRUE, FALSE); + }*/ +} + +/* + * Return the RGB value of a pixel as long. + */ + guicolor_T +gui_mch_get_rgb(guicolor_T pixel) +{ + return (guicolor_T)((Red(pixel) << 16) + (Green(pixel) << 8) + Blue(pixel)); +} + + + +#ifdef FEAT_BROWSE +/* + * Pop open a file browser and return the file selected, in allocated memory, + * or NULL if Cancel is hit. + * saving - TRUE if the file will be saved to, FALSE if it will be opened. + * title - Title message for the file browser dialog. + * dflt - Default name of file. + * ext - Default extension to be added to files without extensions. + * initdir - directory in which to open the browser (NULL = current dir) + * filter - Filter for matched files to choose from. + * Has a format like this: + * "C Files (*.c)\0*.c\0" + * "All Files\0*.*\0\0" + * If these two strings were concatenated, then a choice of two file + * filters will be selectable to the user. Then only matching files will + * be shown in the browser. If NULL, the default allows all files. + * + * *NOTE* - the filter string must be terminated with TWO nulls. + */ + char_u * +gui_mch_browse( + int saving, + char_u *title, + char_u *dflt, + char_u *ext, + char_u *initdir, + char_u *filter) +{ + /* TODO: Add Ammon's safety check (Dany) */ + NavReplyRecord reply; + char_u *fname = NULL; + char_u **fnames = NULL; + long numFiles; + NavDialogOptions navOptions; + OSErr error; + + /* Get Navigation Service Defaults value */ + NavGetDefaultDialogOptions(&navOptions); + + + /* TODO: If we get a :browse args, set the Multiple bit. */ + navOptions.dialogOptionFlags = kNavAllowInvisibleFiles + | kNavDontAutoTranslate + | kNavDontAddTranslateItems + /* | kNavAllowMultipleFiles */ + | kNavAllowStationery; + + (void) C2PascalString(title, &navOptions.message); + (void) C2PascalString(dflt, &navOptions.savedFileName); + /* Could set clientName? + * windowTitle? (there's no title bar?) + */ + + if (saving) + { + /* Change first parm AEDesc (typeFSS) *defaultLocation to match dflt */ + NavPutFile(NULL, &reply, &navOptions, NULL, 'TEXT', 'VIM!', NULL); + if (!reply.validRecord) + return NULL; + } + else + { + /* Change first parm AEDesc (typeFSS) *defaultLocation to match dflt */ + NavGetFile(NULL, &reply, &navOptions, NULL, NULL, NULL, NULL, NULL); + if (!reply.validRecord) + return NULL; + } + + fnames = new_fnames_from_AEDesc(&reply.selection, &numFiles, &error); + + NavDisposeReply(&reply); + + if (fnames) + { + fname = fnames[0]; + vim_free(fnames); + } + + /* TODO: Shorten the file name if possible */ + return fname; +} +#endif /* FEAT_BROWSE */ + +#ifdef FEAT_GUI_DIALOG +/* + * Stuff for dialogues + */ + +/* + * Create a dialogue dynamically from the parameter strings. + * type = type of dialogue (question, alert, etc.) + * title = dialogue title. may be NULL for default title. + * message = text to display. Dialogue sizes to accommodate it. + * buttons = '\n' separated list of button captions, default first. + * dfltbutton = number of default button. + * + * This routine returns 1 if the first button is pressed, + * 2 for the second, etc. + * + * 0 indicates Esc was pressed. + * -1 for unexpected error + * + * If stubbing out this fn, return 1. + */ + +typedef struct +{ + short idx; + short width; /* Size of the text in pixel */ + Rect box; +} vgmDlgItm; /* Vim Gui_Mac.c Dialog Item */ + +#define MoveRectTo(r,x,y) OffsetRect(r,x-r->left,y-r->top) + + static void +macMoveDialogItem( + DialogRef theDialog, + short itemNumber, + short X, + short Y, + Rect *inBox) +{ +#if 0 /* USE_CARBONIZED */ + /* Untested */ + MoveDialogItem(theDialog, itemNumber, X, Y); + if (inBox != nil) + GetDialogItem(theDialog, itemNumber, &itemType, &itemHandle, inBox); +#else + short itemType; + Handle itemHandle; + Rect localBox; + Rect *itemBox = &localBox; + + if (inBox != nil) + itemBox = inBox; + + GetDialogItem(theDialog, itemNumber, &itemType, &itemHandle, itemBox); + OffsetRect(itemBox, -itemBox->left, -itemBox->top); + OffsetRect(itemBox, X, Y); + /* To move a control (like a button) we need to call both + * MoveControl and SetDialogItem. FAQ 6-18 */ + if (1) /*(itemType & kControlDialogItem) */ + MoveControl((ControlRef) itemHandle, X, Y); + SetDialogItem(theDialog, itemNumber, itemType, itemHandle, itemBox); +#endif +} + + static void +macSizeDialogItem( + DialogRef theDialog, + short itemNumber, + short width, + short height) +{ + short itemType; + Handle itemHandle; + Rect itemBox; + + GetDialogItem(theDialog, itemNumber, &itemType, &itemHandle, &itemBox); + + /* When width or height is zero do not change it */ + if (width == 0) + width = itemBox.right - itemBox.left; + if (height == 0) + height = itemBox.bottom - itemBox.top; + +#if 0 /* USE_CARBONIZED */ + SizeDialogItem(theDialog, itemNumber, width, height); /* Untested */ +#else + /* Resize the bounding box */ + itemBox.right = itemBox.left + width; + itemBox.bottom = itemBox.top + height; + + /* To resize a control (like a button) we need to call both + * SizeControl and SetDialogItem. (deducted from FAQ 6-18) */ + if (itemType & kControlDialogItem) + SizeControl((ControlRef) itemHandle, width, height); + + /* Configure back the item */ + SetDialogItem(theDialog, itemNumber, itemType, itemHandle, &itemBox); +#endif +} + + static void +macSetDialogItemText( + DialogRef theDialog, + short itemNumber, + Str255 itemName) +{ + short itemType; + Handle itemHandle; + Rect itemBox; + + GetDialogItem(theDialog, itemNumber, &itemType, &itemHandle, &itemBox); + + if (itemType & kControlDialogItem) + SetControlTitle((ControlRef) itemHandle, itemName); + else + SetDialogItemText(itemHandle, itemName); +} + + +/* ModalDialog() handler for message dialogs that have hotkey accelerators. + * Expects a mapping of hotkey char to control index in gDialogHotKeys; + * setting gDialogHotKeys to NULL disables any hotkey handling. + */ + static pascal Boolean +DialogHotkeyFilterProc ( + DialogRef theDialog, + EventRecord *event, + DialogItemIndex *itemHit) +{ + char_u keyHit; + + if (event->what == keyDown || event->what == autoKey) + { + keyHit = (event->message & charCodeMask); + + if (gDialogHotKeys && gDialogHotKeys[keyHit]) + { +#ifdef DEBUG_MAC_DIALOG_HOTKEYS + printf("user pressed hotkey '%c' --> item %d\n", keyHit, gDialogHotKeys[keyHit]); +#endif + *itemHit = gDialogHotKeys[keyHit]; + + /* When handing off to StdFilterProc, pretend that the user + * clicked the control manually. Note that this is also supposed + * to cause the button to hilite briefly (to give some user + * feedback), but this seems not to actually work (or it's too + * fast to be seen). + */ + event->what = kEventControlSimulateHit; + + return true; /* we took care of it */ + } + + /* Defer to the OS's standard behavior for this event. + * This ensures that Enter will still activate the default button. */ + return StdFilterProc(theDialog, event, itemHit); + } + return false; /* Let ModalDialog deal with it */ +} + + +/* TODO: There have been some crashes with dialogs, check your inbox + * (Jussi) + */ + int +gui_mch_dialog( + int type, + char_u *title, + char_u *message, + char_u *buttons, + int dfltbutton, + char_u *textfield, + int ex_cmd) +{ + Handle buttonDITL; + Handle iconDITL; + Handle inputDITL; + Handle messageDITL; + Handle itemHandle; + Handle iconHandle; + DialogPtr theDialog; + char_u len; + char_u PascalTitle[256]; /* place holder for the title */ + char_u name[256]; + GrafPtr oldPort; + short itemHit; + char_u *buttonChar; + short hotKeys[256]; /* map of hotkey -> control ID */ + char_u aHotKey; + Rect box; + short button; + short lastButton; + short itemType; + short useIcon; + short width; + short totalButtonWidth = 0; /* the width of all buttons together + including spacing */ + short widestButton = 0; + short dfltButtonEdge = 20; /* gut feeling */ + short dfltElementSpacing = 13; /* from IM:V.2-29 */ + short dfltIconSideSpace = 23; /* from IM:V.2-29 */ + short maximumWidth = 400; /* gut feeling */ + short maxButtonWidth = 175; /* gut feeling */ + + short vertical; + short dialogHeight; + short messageLines = 3; + FontInfo textFontInfo; + + vgmDlgItm iconItm; + vgmDlgItm messageItm; + vgmDlgItm inputItm; + vgmDlgItm buttonItm; + + WindowRef theWindow; + + ModalFilterUPP dialogUPP; + + /* Check 'v' flag in 'guioptions': vertical button placement. */ + vertical = (vim_strchr(p_go, GO_VERTICAL) != NULL); + + /* Create a new Dialog Box from template. */ + theDialog = GetNewDialog(129, nil, (WindowRef) -1); + + /* Get the WindowRef */ + theWindow = GetDialogWindow(theDialog); + + /* Hide the window. + * 1. to avoid seeing slow drawing + * 2. to prevent a problem seen while moving dialog item + * within a visible window. (non-Carbon MacOS 9) + * Could be avoided by changing the resource. + */ + HideWindow(theWindow); + + /* Change the graphical port to the dialog, + * so we can measure the text with the proper font */ + GetPort(&oldPort); + SetPortDialogPort(theDialog); + + /* Get the info about the default text, + * used to calculate the height of the message + * and of the text field */ + GetFontInfo(&textFontInfo); + + /* Set the dialog title */ + if (title != NULL) + { + (void) C2PascalString(title, &PascalTitle); + SetWTitle(theWindow, PascalTitle); + } + + /* Creates the buttons and add them to the Dialog Box. */ + buttonDITL = GetResource('DITL', 130); + buttonChar = buttons; + button = 0; + + /* initialize the hotkey mapping */ + vim_memset(hotKeys, 0, sizeof(hotKeys)); + + for (;*buttonChar != 0;) + { + /* Get the name of the button */ + button++; + len = 0; + for (;((*buttonChar != DLG_BUTTON_SEP) && (*buttonChar != 0) && (len < 255)); buttonChar++) + { + if (*buttonChar != DLG_HOTKEY_CHAR) + name[++len] = *buttonChar; + else + { + aHotKey = (char_u)*(buttonChar+1); + if (aHotKey >= 'A' && aHotKey <= 'Z') + aHotKey = (char_u)((int)aHotKey + (int)'a' - (int)'A'); + hotKeys[aHotKey] = button; +#ifdef DEBUG_MAC_DIALOG_HOTKEYS + printf("### hotKey for button %d is '%c'\n", button, aHotKey); +#endif + } + } + + if (*buttonChar != 0) + buttonChar++; + name[0] = len; + + /* Add the button */ + AppendDITL(theDialog, buttonDITL, overlayDITL); /* appendDITLRight); */ + + /* Change the button's name */ + macSetDialogItemText(theDialog, button, name); + + /* Resize the button to fit its name */ + width = StringWidth(name) + 2 * dfltButtonEdge; + /* Limit the size of any button to an acceptable value. */ + /* TODO: Should be based on the message width */ + if (width > maxButtonWidth) + width = maxButtonWidth; + macSizeDialogItem(theDialog, button, width, 0); + + totalButtonWidth += width; + + if (width > widestButton) + widestButton = width; + } + ReleaseResource(buttonDITL); + lastButton = button; + + /* Add the icon to the Dialog Box. */ + iconItm.idx = lastButton + 1; + iconDITL = GetResource('DITL', 131); + switch (type) + { + case VIM_GENERIC: + case VIM_INFO: + case VIM_QUESTION: useIcon = kNoteIcon; break; + case VIM_WARNING: useIcon = kCautionIcon; break; + case VIM_ERROR: useIcon = kStopIcon; break; + default: useIcon = kStopIcon; + } + AppendDITL(theDialog, iconDITL, overlayDITL); + ReleaseResource(iconDITL); + GetDialogItem(theDialog, iconItm.idx, &itemType, &itemHandle, &box); + /* TODO: Should the item be freed? */ + iconHandle = GetIcon(useIcon); + SetDialogItem(theDialog, iconItm.idx, itemType, iconHandle, &box); + + /* Add the message to the Dialog box. */ + messageItm.idx = lastButton + 2; + messageDITL = GetResource('DITL', 132); + AppendDITL(theDialog, messageDITL, overlayDITL); + ReleaseResource(messageDITL); + GetDialogItem(theDialog, messageItm.idx, &itemType, &itemHandle, &box); + (void) C2PascalString(message, &name); + SetDialogItemText(itemHandle, name); + messageItm.width = StringWidth(name); + + /* Add the input box if needed */ + if (textfield != NULL) + { + /* Cheat for now reuse the message and convert to text edit */ + inputItm.idx = lastButton + 3; + inputDITL = GetResource('DITL', 132); + AppendDITL(theDialog, inputDITL, overlayDITL); + ReleaseResource(inputDITL); + GetDialogItem(theDialog, inputItm.idx, &itemType, &itemHandle, &box); +/* SetDialogItem(theDialog, inputItm.idx, kEditTextDialogItem, itemHandle, &box);*/ + (void) C2PascalString(textfield, &name); + SetDialogItemText(itemHandle, name); + inputItm.width = StringWidth(name); + + /* Hotkeys don't make sense if there's a text field */ + gDialogHotKeys = NULL; + } + else + /* Install hotkey table */ + gDialogHotKeys = (short *)&hotKeys; + + /* Set the <ENTER> and <ESC> button. */ + SetDialogDefaultItem(theDialog, dfltbutton); + SetDialogCancelItem(theDialog, 0); + + /* Reposition element */ + + /* Check if we need to force vertical */ + if (totalButtonWidth > maximumWidth) + vertical = TRUE; + + /* Place icon */ + macMoveDialogItem(theDialog, iconItm.idx, dfltIconSideSpace, dfltElementSpacing, &box); + iconItm.box.right = box.right; + iconItm.box.bottom = box.bottom; + + /* Place Message */ + messageItm.box.left = iconItm.box.right + dfltIconSideSpace; + macSizeDialogItem(theDialog, messageItm.idx, 0, messageLines * (textFontInfo.ascent + textFontInfo.descent)); + macMoveDialogItem(theDialog, messageItm.idx, messageItm.box.left, dfltElementSpacing, &messageItm.box); + + /* Place Input */ + if (textfield != NULL) + { + inputItm.box.left = messageItm.box.left; + inputItm.box.top = messageItm.box.bottom + dfltElementSpacing; + macSizeDialogItem(theDialog, inputItm.idx, 0, textFontInfo.ascent + textFontInfo.descent); + macMoveDialogItem(theDialog, inputItm.idx, inputItm.box.left, inputItm.box.top, &inputItm.box); + /* Convert the static text into a text edit. + * For some reason this change need to be done last (Dany) */ + GetDialogItem(theDialog, inputItm.idx, &itemType, &itemHandle, &inputItm.box); + SetDialogItem(theDialog, inputItm.idx, kEditTextDialogItem, itemHandle, &inputItm.box); + SelectDialogItemText(theDialog, inputItm.idx, 0, 32767); + } + + /* Place Button */ + if (textfield != NULL) + { + buttonItm.box.left = inputItm.box.left; + buttonItm.box.top = inputItm.box.bottom + dfltElementSpacing; + } + else + { + buttonItm.box.left = messageItm.box.left; + buttonItm.box.top = messageItm.box.bottom + dfltElementSpacing; + } + + for (button=1; button <= lastButton; button++) + { + + macMoveDialogItem(theDialog, button, buttonItm.box.left, buttonItm.box.top, &box); + /* With vertical, it's better to have all buttons the same length */ + if (vertical) + { + macSizeDialogItem(theDialog, button, widestButton, 0); + GetDialogItem(theDialog, button, &itemType, &itemHandle, &box); + } + /* Calculate position of next button */ + if (vertical) + buttonItm.box.top = box.bottom + dfltElementSpacing; + else + buttonItm.box.left = box.right + dfltElementSpacing; + } + + /* Resize the dialog box */ + dialogHeight = box.bottom + dfltElementSpacing; + SizeWindow(theWindow, maximumWidth, dialogHeight, TRUE); + + /* Magic resize */ + AutoSizeDialog(theDialog); + /* Need a horizontal resize anyway so not that useful */ + + /* Display it */ + ShowWindow(theWindow); +/* BringToFront(theWindow); */ + SelectWindow(theWindow); + +/* DrawDialog(theDialog); */ +#if 0 + GetPort(&oldPort); + SetPortDialogPort(theDialog); +#endif + +#ifdef USE_CARBONKEYHANDLER + /* Avoid that we use key events for the main window. */ + dialog_busy = TRUE; +#endif + + /* Prepare the shortcut-handling filterProc for handing to the dialog */ + dialogUPP = NewModalFilterUPP(DialogHotkeyFilterProc); + + /* Hang until one of the button is hit */ + do + { + ModalDialog(dialogUPP, &itemHit); + } while ((itemHit < 1) || (itemHit > lastButton)); + +#ifdef USE_CARBONKEYHANDLER + dialog_busy = FALSE; +#endif + + /* Copy back the text entered by the user into the param */ + if (textfield != NULL) + { + GetDialogItem(theDialog, inputItm.idx, &itemType, &itemHandle, &box); + GetDialogItemText(itemHandle, (char_u *) &name); +#if IOSIZE < 256 + /* Truncate the name to IOSIZE if needed */ + if (name[0] > IOSIZE) + name[0] = IOSIZE - 1; +#endif + vim_strncpy(textfield, &name[1], name[0]); + } + + /* Restore the original graphical port */ + SetPort(oldPort); + + /* Free the modal filterProc */ + DisposeRoutineDescriptor(dialogUPP); + + /* Get ride of the dialog (free memory) */ + DisposeDialog(theDialog); + + return itemHit; +/* + * Useful thing which could be used + * SetDialogTimeout(): Auto click a button after timeout + * SetDialogTracksCursor() : Get the I-beam cursor over input box + * MoveDialogItem(): Probably better than SetDialogItem + * SizeDialogItem(): (but is it Carbon Only?) + * AutoSizeDialog(): Magic resize of dialog based on text length + */ +} +#endif /* FEAT_DIALOG_GUI */ + +/* + * Display the saved error message(s). + */ +#ifdef USE_MCH_ERRMSG + void +display_errors(void) +{ + char *p; + char_u pError[256]; + + if (error_ga.ga_data == NULL) + return; + + /* avoid putting up a message box with blanks only */ + for (p = (char *)error_ga.ga_data; *p; ++p) + if (!isspace(*p)) + { + if (STRLEN(p) > 255) + pError[0] = 255; + else + pError[0] = STRLEN(p); + + STRNCPY(&pError[1], p, pError[0]); + ParamText(pError, nil, nil, nil); + Alert(128, nil); + break; + /* TODO: handled message longer than 256 chars + * use auto-sizeable alert + * or dialog with scrollbars (TextEdit zone) + */ + } + ga_clear(&error_ga); +} +#endif + +/* + * Get current mouse coordinates in text window. + */ + void +gui_mch_getmouse(int *x, int *y) +{ + Point where; + + GetMouse(&where); + + *x = where.h; + *y = where.v; +} + + void +gui_mch_setmouse(int x, int y) +{ + /* TODO */ +#if 0 + /* From FAQ 3-11 */ + + CursorDevicePtr myMouse; + Point where; + + if ( NGetTrapAddress(_CursorDeviceDispatch, ToolTrap) + != NGetTrapAddress(_Unimplemented, ToolTrap)) + { + /* New way */ + + /* + * Get first device with one button. + * This will probably be the standard mouse + * start at head of cursor dev list + * + */ + + myMouse = nil; + + do + { + /* Get the next cursor device */ + CursorDeviceNextDevice(&myMouse); + } + while ((myMouse != nil) && (myMouse->cntButtons != 1)); + + CursorDeviceMoveTo(myMouse, x, y); + } + else + { + /* Old way */ + where.h = x; + where.v = y; + + *(Point *)RawMouse = where; + *(Point *)MTemp = where; + *(Ptr) CrsrNew = 0xFFFF; + } +#endif +} + + void +gui_mch_show_popupmenu(vimmenu_T *menu) +{ +/* + * Clone PopUp to use menu + * Create a object descriptor for the current selection + * Call the procedure + */ + + MenuHandle CntxMenu; + Point where; + OSStatus status; + UInt32 CntxType; + SInt16 CntxMenuID; + UInt16 CntxMenuItem; + Str255 HelpName = ""; + GrafPtr savePort; + + /* Save Current Port: On MacOS X we seem to lose the port */ + GetPort(&savePort); /*OSX*/ + + GetMouse(&where); + LocalToGlobal(&where); /*OSX*/ + CntxMenu = menu->submenu_handle; + + /* TODO: Get the text selection from Vim */ + + /* Call to Handle Popup */ + status = ContextualMenuSelect(CntxMenu, where, false, kCMHelpItemRemoveHelp, + HelpName, NULL, &CntxType, &CntxMenuID, &CntxMenuItem); + + if (status == noErr) + { + if (CntxType == kCMMenuItemSelected) + { + /* Handle the menu CntxMenuID, CntxMenuItem */ + /* The submenu can be handle directly by gui_mac_handle_menu */ + /* But what about the current menu, is the menu changed by + * ContextualMenuSelect */ + gui_mac_handle_menu((CntxMenuID << 16) + CntxMenuItem); + } + else if (CntxMenuID == kCMShowHelpSelected) + { + /* Should come up with the help */ + } + } + + /* Restore original Port */ + SetPort(savePort); /*OSX*/ +} + +#if defined(FEAT_CW_EDITOR) || defined(PROTO) +/* TODO: Is it need for MACOS_X? (Dany) */ + void +mch_post_buffer_write(buf_T *buf) +{ + GetFSSpecFromPath(buf->b_ffname, &buf->b_FSSpec); + Send_KAHL_MOD_AE(buf); +} +#endif + +#ifdef FEAT_TITLE +/* + * Set the window title and icon. + * (The icon is not taken care of). + */ + void +gui_mch_settitle(char_u *title, char_u *icon) +{ + /* TODO: Get vim to make sure maxlen (from p_titlelen) is smaller + * that 256. Even better get it to fit nicely in the titlebar. + */ +#ifdef MACOS_CONVERT + CFStringRef windowTitle; + size_t windowTitleLen; +#else + char_u *pascalTitle; +#endif + + if (title == NULL) /* nothing to do */ + return; + +#ifdef MACOS_CONVERT + windowTitleLen = STRLEN(title); + windowTitle = (CFStringRef)mac_enc_to_cfstring(title, windowTitleLen); + + if (windowTitle) + { + SetWindowTitleWithCFString(gui.VimWindow, windowTitle); + CFRelease(windowTitle); + } +#else + pascalTitle = C2Pascal_save(title); + if (pascalTitle != NULL) + { + SetWTitle(gui.VimWindow, pascalTitle); + vim_free(pascalTitle); + } +#endif +} +#endif + +/* + * Transferred from os_mac.c for MacOS X using os_unix.c prep work + */ + + int +C2PascalString(char_u *CString, Str255 *PascalString) +{ + char_u *PascalPtr = (char_u *) PascalString; + int len; + int i; + + PascalPtr[0] = 0; + if (CString == NULL) + return 0; + + len = STRLEN(CString); + if (len > 255) + len = 255; + + for (i = 0; i < len; i++) + PascalPtr[i+1] = CString[i]; + + PascalPtr[0] = len; + + return 0; +} + + int +GetFSSpecFromPath(char_u *file, FSSpec *fileFSSpec) +{ + /* From FAQ 8-12 */ + Str255 filePascal; + CInfoPBRec myCPB; + OSErr err; + + (void) C2PascalString(file, &filePascal); + + myCPB.dirInfo.ioNamePtr = filePascal; + myCPB.dirInfo.ioVRefNum = 0; + myCPB.dirInfo.ioFDirIndex = 0; + myCPB.dirInfo.ioDrDirID = 0; + + err= PBGetCatInfo(&myCPB, false); + + /* vRefNum, dirID, name */ + FSMakeFSSpec(0, 0, filePascal, fileFSSpec); + + /* TODO: Use an error code mechanism */ + return 0; +} + +/* + * Convert a FSSpec to a full path + */ + +char_u *FullPathFromFSSpec_save(FSSpec file) +{ + /* + * TODO: Add protection for 256 char max. + */ + + CInfoPBRec theCPB; + char_u fname[256]; + char_u *filenamePtr = fname; + OSErr error; + int folder = 1; +#ifdef USE_UNIXFILENAME + SInt16 dfltVol_vRefNum; + SInt32 dfltVol_dirID; + FSRef refFile; + OSStatus status; + UInt32 pathSize = 256; + char_u pathname[256]; + char_u *path = pathname; +#else + Str255 directoryName; + char_u temporary[255]; + char_u *temporaryPtr = temporary; +#endif + +#ifdef USE_UNIXFILENAME + /* Get the default volume */ + /* TODO: Remove as this only work if Vim is on the Boot Volume*/ + error=HGetVol(NULL, &dfltVol_vRefNum, &dfltVol_dirID); + + if (error) + return NULL; +#endif + + /* Start filling fname with file.name */ + vim_strncpy(filenamePtr, &file.name[1], file.name[0]); + + /* Get the info about the file specified in FSSpec */ + theCPB.dirInfo.ioFDirIndex = 0; + theCPB.dirInfo.ioNamePtr = file.name; + theCPB.dirInfo.ioVRefNum = file.vRefNum; + /*theCPB.hFileInfo.ioDirID = 0;*/ + theCPB.dirInfo.ioDrDirID = file.parID; + + /* As ioFDirIndex = 0, get the info of ioNamePtr, + which is relative to ioVrefNum, ioDirID */ + error = PBGetCatInfo(&theCPB, false); + + /* If we are called for a new file we expect fnfErr */ + if ((error) && (error != fnfErr)) + return NULL; + + /* Check if it's a file or folder */ + /* default to file if file don't exist */ + if (((theCPB.hFileInfo.ioFlAttrib & ioDirMask) == 0) || (error)) + folder = 0; /* It's not a folder */ + else + folder = 1; + +#ifdef USE_UNIXFILENAME + /* + * The functions used here are available in Carbon, but do nothing on + * MacOS 8 and 9. + */ + if (error == fnfErr) + { + /* If the file to be saved does not already exist, it isn't possible + to convert its FSSpec into an FSRef. But we can construct an + FSSpec for the file's parent folder (since we have its volume and + directory IDs), and since that folder does exist, we can convert + that FSSpec into an FSRef, convert the FSRef in turn into a path, + and, finally, append the filename. */ + FSSpec dirSpec; + FSRef dirRef; + Str255 emptyFilename = "\p"; + error = FSMakeFSSpec(theCPB.dirInfo.ioVRefNum, + theCPB.dirInfo.ioDrDirID, emptyFilename, &dirSpec); + if (error) + return NULL; + + error = FSpMakeFSRef(&dirSpec, &dirRef); + if (error) + return NULL; + + status = FSRefMakePath(&dirRef, (UInt8*)path, pathSize); + if (status) + return NULL; + + STRCAT(path, "/"); + STRCAT(path, filenamePtr); + } + else + { + /* If the file to be saved already exists, we can get its full path + by converting its FSSpec into an FSRef. */ + error=FSpMakeFSRef(&file, &refFile); + if (error) + return NULL; + + status=FSRefMakePath(&refFile, (UInt8 *) path, pathSize); + if (status) + return NULL; + } + + /* Add a slash at the end if needed */ + if (folder) + STRCAT(path, "/"); + + return (vim_strsave(path)); +#else + /* TODO: Get rid of all USE_UNIXFILENAME below */ + /* Set ioNamePtr, it's the same area which is always reused. */ + theCPB.dirInfo.ioNamePtr = directoryName; + + /* Trick for first entry, set ioDrParID to the first value + * we want for ioDrDirID*/ + theCPB.dirInfo.ioDrParID = file.parID; + theCPB.dirInfo.ioDrDirID = file.parID; + + if ((TRUE) && (file.parID != fsRtDirID /*fsRtParID*/)) + do + { + theCPB.dirInfo.ioFDirIndex = -1; + /* theCPB.dirInfo.ioNamePtr = directoryName; Already done above. */ + theCPB.dirInfo.ioVRefNum = file.vRefNum; + /* theCPB.dirInfo.ioDirID = irrelevant when ioFDirIndex = -1 */ + theCPB.dirInfo.ioDrDirID = theCPB.dirInfo.ioDrParID; + + /* As ioFDirIndex = -1, get the info of ioDrDirID, */ + /* *ioNamePtr[0 TO 31] will be updated */ + error = PBGetCatInfo(&theCPB,false); + + if (error) + return NULL; + + /* Put the new directoryName in front of the current fname */ + STRCPY(temporaryPtr, filenamePtr); + vim_strncpy(filenamePtr, &directoryName[1], directoryName[0]); + STRCAT(filenamePtr, ":"); + STRCAT(filenamePtr, temporaryPtr); + } +#if 1 /* def USE_UNIXFILENAME */ + while ((theCPB.dirInfo.ioDrParID != fsRtDirID) /* && */ + /* (theCPB.dirInfo.ioDrDirID != fsRtDirID)*/); +#else + while (theCPB.dirInfo.ioDrDirID != fsRtDirID); +#endif + + /* Get the information about the volume on which the file reside */ + theCPB.dirInfo.ioFDirIndex = -1; + /* theCPB.dirInfo.ioNamePtr = directoryName; Already done above. */ + theCPB.dirInfo.ioVRefNum = file.vRefNum; + /* theCPB.dirInfo.ioDirID = irrelevant when ioFDirIndex = -1 */ + theCPB.dirInfo.ioDrDirID = theCPB.dirInfo.ioDrParID; + + /* As ioFDirIndex = -1, get the info of ioDrDirID, */ + /* *ioNamePtr[0 TO 31] will be updated */ + error = PBGetCatInfo(&theCPB,false); + + if (error) + return NULL; + + /* For MacOS Classic always add the volume name */ + /* For MacOS X add the volume name preceded by "Volumes" */ + /* when we are not referring to the boot volume */ +#ifdef USE_UNIXFILENAME + if (file.vRefNum != dfltVol_vRefNum) +#endif + { + /* Add the volume name */ + STRCPY(temporaryPtr, filenamePtr); + vim_strncpy(filenamePtr, &directoryName[1], directoryName[0]); + STRCAT(filenamePtr, ":"); + STRCAT(filenamePtr, temporaryPtr); + +#ifdef USE_UNIXFILENAME + STRCPY(temporaryPtr, filenamePtr); + filenamePtr[0] = 0; /* NULL terminate the string */ + STRCAT(filenamePtr, "Volumes:"); + STRCAT(filenamePtr, temporaryPtr); +#endif + } + + /* Append final path separator if it's a folder */ + if (folder) + STRCAT(fname, ":"); + + /* As we use Unix File Name for MacOS X convert it */ +#ifdef USE_UNIXFILENAME + /* Need to insert leading / */ + /* TODO: get the above code to use directly the / */ + STRCPY(&temporaryPtr[1], filenamePtr); + temporaryPtr[0] = '/'; + STRCPY(filenamePtr, temporaryPtr); + { + char *p; + for (p = fname; *p; p++) + if (*p == ':') + *p = '/'; + } +#endif + + return (vim_strsave(fname)); +#endif +} + +#if defined(USE_CARBONKEYHANDLER) || defined(PROTO) +/* + * Input Method Control functions. + */ + +/* + * Notify cursor position to IM. + */ + void +im_set_position(int row, int col) +{ +# if 0 + /* TODO: Implement me! */ + im_start_row = row; + im_start_col = col; +# endif +} + +static ScriptLanguageRecord gTSLWindow; +static ScriptLanguageRecord gTSLInsert; +static ScriptLanguageRecord gTSLDefault = { 0, 0 }; + +static Component gTSCWindow; +static Component gTSCInsert; +static Component gTSCDefault; + +static int im_initialized = 0; + + static void +im_on_window_switch(int active) +{ + ScriptLanguageRecord *slptr = NULL; + OSStatus err; + + if (! gui.in_use) + return; + + if (im_initialized == 0) + { + im_initialized = 1; + + /* save default TSM component (should be U.S.) to default */ + GetDefaultInputMethodOfClass(&gTSCDefault, &gTSLDefault, + kKeyboardInputMethodClass); + } + + if (active == TRUE) + { + im_is_active = TRUE; + ActivateTSMDocument(gTSMDocument); + slptr = &gTSLWindow; + + if (slptr) + { + err = SetDefaultInputMethodOfClass(gTSCWindow, slptr, + kKeyboardInputMethodClass); + if (err == noErr) + err = SetTextServiceLanguage(slptr); + + if (err == noErr) + KeyScript(slptr->fScript | smKeyForceKeyScriptMask); + } + } + else + { + err = GetTextServiceLanguage(&gTSLWindow); + if (err == noErr) + slptr = &gTSLWindow; + + if (slptr) + GetDefaultInputMethodOfClass(&gTSCWindow, slptr, + kKeyboardInputMethodClass); + + im_is_active = FALSE; + DeactivateTSMDocument(gTSMDocument); + } +} + +/* + * Set IM status on ("active" is TRUE) or off ("active" is FALSE). + */ + void +im_set_active(int active) +{ + ScriptLanguageRecord *slptr = NULL; + OSStatus err; + + if (!gui.in_use) + return; + + if (im_initialized == 0) + { + im_initialized = 1; + + /* save default TSM component (should be U.S.) to default */ + GetDefaultInputMethodOfClass(&gTSCDefault, &gTSLDefault, + kKeyboardInputMethodClass); + } + + if (active == TRUE) + { + im_is_active = TRUE; + ActivateTSMDocument(gTSMDocument); + slptr = &gTSLInsert; + + if (slptr) + { + err = SetDefaultInputMethodOfClass(gTSCInsert, slptr, + kKeyboardInputMethodClass); + if (err == noErr) + err = SetTextServiceLanguage(slptr); + + if (err == noErr) + KeyScript(slptr->fScript | smKeyForceKeyScriptMask); + } + } + else + { + err = GetTextServiceLanguage(&gTSLInsert); + if (err == noErr) + slptr = &gTSLInsert; + + if (slptr) + GetDefaultInputMethodOfClass(&gTSCInsert, slptr, + kKeyboardInputMethodClass); + + /* restore to default when switch to normal mode, so than we could + * enter commands easier */ + SetDefaultInputMethodOfClass(gTSCDefault, &gTSLDefault, + kKeyboardInputMethodClass); + SetTextServiceLanguage(&gTSLDefault); + + im_is_active = FALSE; + DeactivateTSMDocument(gTSMDocument); + } +} + +/* + * Get IM status. When IM is on, return not 0. Else return 0. + */ + int +im_get_status(void) +{ + if (! gui.in_use) + return 0; + + return im_is_active; +} + +#endif + + + +#if defined(FEAT_GUI_TABLINE) || defined(PROTO) +// drawer implementation +static MenuRef contextMenu = NULL; +enum +{ + kTabContextMenuId = 42 +}; + +// the caller has to CFRelease() the returned string + static CFStringRef +getTabLabel(tabpage_T *page) +{ + get_tabline_label(page, FALSE); +#ifdef MACOS_CONVERT + return (CFStringRef)mac_enc_to_cfstring(NameBuff, STRLEN(NameBuff)); +#else + // TODO: check internal encoding? + return CFStringCreateWithCString(kCFAllocatorDefault, (char *)NameBuff, + kCFStringEncodingMacRoman); +#endif +} + + +#define DRAWER_SIZE 150 +#define DRAWER_INSET 16 + +static ControlRef dataBrowser = NULL; + +// when the tabline is hidden, vim doesn't call update_tabline(). When +// the tabline is shown again, show_tabline() is called before update_tabline(), +// and because of this, the tab labels and vim's internal tabs are out of sync +// for a very short time. to prevent inconsistent state, we store the labels +// of the tabs, not pointers to the tabs (which are invalid for a short time). +static CFStringRef *tabLabels = NULL; +static int tabLabelsSize = 0; + +enum +{ + kTabsColumn = 'Tabs' +}; + + static int +getTabCount(void) +{ + tabpage_T *tp; + int numTabs = 0; + + FOR_ALL_TABPAGES(tp) + ++numTabs; + return numTabs; +} + +// data browser item display callback + static OSStatus +dbItemDataCallback(ControlRef browser, + DataBrowserItemID itemID, + DataBrowserPropertyID property /* column id */, + DataBrowserItemDataRef itemData, + Boolean changeValue) +{ + OSStatus status = noErr; + + // assert(property == kTabsColumn); // why is this violated?? + + // changeValue is true if we have a modifiable list and data was changed. + // In our case, it's always false. + // (that is: if (changeValue) updateInternalData(); else return + // internalData(); + if (!changeValue) + { + CFStringRef str; + + assert(itemID - 1 >= 0 && itemID - 1 < tabLabelsSize); + str = tabLabels[itemID - 1]; + status = SetDataBrowserItemDataText(itemData, str); + } + else + status = errDataBrowserPropertyNotSupported; + + return status; +} + +// data browser action callback + static void +dbItemNotificationCallback(ControlRef browser, + DataBrowserItemID item, + DataBrowserItemNotification message) +{ + switch (message) + { + case kDataBrowserItemSelected: + send_tabline_event(item); + break; + } +} + +// callbacks needed for contextual menu: + static void +dbGetContextualMenuCallback(ControlRef browser, + MenuRef *menu, + UInt32 *helpType, + CFStringRef *helpItemString, + AEDesc *selection) +{ + // on mac os 9: kCMHelpItemNoHelp, but it's not the same + *helpType = kCMHelpItemRemoveHelp; // OS X only ;-) + *helpItemString = NULL; + + *menu = contextMenu; +} + + static void +dbSelectContextualMenuCallback(ControlRef browser, + MenuRef menu, + UInt32 selectionType, + SInt16 menuID, + MenuItemIndex menuItem) +{ + if (selectionType == kCMMenuItemSelected) + { + MenuCommand command; + GetMenuItemCommandID(menu, menuItem, &command); + + // get tab that was selected when the context menu appeared + // (there is always one tab selected). TODO: check if the context menu + // isn't opened on an item but on empty space (has to be possible some + // way, the finder does it too ;-) ) + Handle items = NewHandle(0); + if (items != NULL) + { + int numItems; + + GetDataBrowserItems(browser, kDataBrowserNoItem, false, + kDataBrowserItemIsSelected, items); + numItems = GetHandleSize(items) / sizeof(DataBrowserItemID); + if (numItems > 0) + { + int idx; + DataBrowserItemID *itemsPtr; + + HLock(items); + itemsPtr = (DataBrowserItemID *)*items; + idx = itemsPtr[0]; + HUnlock(items); + send_tabline_menu_event(idx, command); + } + DisposeHandle(items); + } + } +} + +// focus callback of the data browser to always leave focus in vim + static OSStatus +dbFocusCallback(EventHandlerCallRef handler, EventRef event, void *data) +{ + assert(GetEventClass(event) == kEventClassControl + && GetEventKind(event) == kEventControlSetFocusPart); + + return paramErr; +} + + +// drawer callback to resize data browser to drawer size + static OSStatus +drawerCallback(EventHandlerCallRef handler, EventRef event, void *data) +{ + switch (GetEventKind(event)) + { + case kEventWindowBoundsChanged: // move or resize + { + UInt32 attribs; + GetEventParameter(event, kEventParamAttributes, typeUInt32, + NULL, sizeof(attribs), NULL, &attribs); + if (attribs & kWindowBoundsChangeSizeChanged) // resize + { + Rect r; + GetWindowBounds(drawer, kWindowContentRgn, &r); + SetRect(&r, 0, 0, r.right - r.left, r.bottom - r.top); + SetControlBounds(dataBrowser, &r); + SetDataBrowserTableViewNamedColumnWidth(dataBrowser, + kTabsColumn, r.right); + } + } + break; + } + + return eventNotHandledErr; +} + +// Load DataBrowserChangeAttributes() dynamically on tiger (and better). +// This way the code works on 10.2 and 10.3 as well (it doesn't have the +// blue highlights in the list view on these systems, though. Oh well.) + + +#import <mach-o/dyld.h> + +enum { kMyDataBrowserAttributeListViewAlternatingRowColors = (1 << 1) }; + + static OSStatus +myDataBrowserChangeAttributes(ControlRef inDataBrowser, + OptionBits inAttributesToSet, + OptionBits inAttributesToClear) +{ + long osVersion; + char *symbolName; + NSSymbol symbol = NULL; + OSStatus (*dataBrowserChangeAttributes)(ControlRef inDataBrowser, + OptionBits inAttributesToSet, OptionBits inAttributesToClear); + + Gestalt(gestaltSystemVersion, &osVersion); + if (osVersion < 0x1040) // only supported for 10.4 (and up) + return noErr; + + // C name mangling... + symbolName = "_DataBrowserChangeAttributes"; + if (!NSIsSymbolNameDefined(symbolName) + || (symbol = NSLookupAndBindSymbol(symbolName)) == NULL) + return noErr; + + dataBrowserChangeAttributes = NSAddressOfSymbol(symbol); + if (dataBrowserChangeAttributes == NULL) + return noErr; // well... + return dataBrowserChangeAttributes(inDataBrowser, + inAttributesToSet, inAttributesToClear); +} + + static void +initialise_tabline(void) +{ + Rect drawerRect = { 0, 0, 0, DRAWER_SIZE }; + DataBrowserCallbacks dbCallbacks; + EventTypeSpec focusEvent = {kEventClassControl, kEventControlSetFocusPart}; + EventTypeSpec resizeEvent = {kEventClassWindow, kEventWindowBoundsChanged}; + DataBrowserListViewColumnDesc colDesc; + + // drawers have to have compositing enabled + CreateNewWindow(kDrawerWindowClass, + kWindowStandardHandlerAttribute + | kWindowCompositingAttribute + | kWindowResizableAttribute + | kWindowLiveResizeAttribute, + &drawerRect, &drawer); + + SetThemeWindowBackground(drawer, kThemeBrushDrawerBackground, true); + SetDrawerParent(drawer, gui.VimWindow); + SetDrawerOffsets(drawer, kWindowOffsetUnchanged, DRAWER_INSET); + + + // create list view embedded in drawer + CreateDataBrowserControl(drawer, &drawerRect, kDataBrowserListView, + &dataBrowser); + + dbCallbacks.version = kDataBrowserLatestCallbacks; + InitDataBrowserCallbacks(&dbCallbacks); + dbCallbacks.u.v1.itemDataCallback = + NewDataBrowserItemDataUPP(dbItemDataCallback); + dbCallbacks.u.v1.itemNotificationCallback = + NewDataBrowserItemNotificationUPP(dbItemNotificationCallback); + dbCallbacks.u.v1.getContextualMenuCallback = + NewDataBrowserGetContextualMenuUPP(dbGetContextualMenuCallback); + dbCallbacks.u.v1.selectContextualMenuCallback = + NewDataBrowserSelectContextualMenuUPP(dbSelectContextualMenuCallback); + + SetDataBrowserCallbacks(dataBrowser, &dbCallbacks); + + SetDataBrowserListViewHeaderBtnHeight(dataBrowser, 0); // no header + SetDataBrowserHasScrollBars(dataBrowser, false, true); // only vertical + SetDataBrowserSelectionFlags(dataBrowser, + kDataBrowserSelectOnlyOne | kDataBrowserNeverEmptySelectionSet); + SetDataBrowserTableViewHiliteStyle(dataBrowser, + kDataBrowserTableViewFillHilite); + Boolean b = false; + SetControlData(dataBrowser, kControlEntireControl, + kControlDataBrowserIncludesFrameAndFocusTag, sizeof(b), &b); + + // enable blue background in data browser (this is only in 10.4 and vim + // has to support older osx versions as well, so we have to load this + // function dynamically) + myDataBrowserChangeAttributes(dataBrowser, + kMyDataBrowserAttributeListViewAlternatingRowColors, 0); + + // install callback that keeps focus in vim and away from the data browser + InstallControlEventHandler(dataBrowser, dbFocusCallback, 1, &focusEvent, + NULL, NULL); + + // install callback that keeps data browser at the size of the drawer + InstallWindowEventHandler(drawer, drawerCallback, 1, &resizeEvent, + NULL, NULL); + + // add "tabs" column to data browser + colDesc.propertyDesc.propertyID = kTabsColumn; + colDesc.propertyDesc.propertyType = kDataBrowserTextType; + + // add if items can be selected (?): kDataBrowserListViewSelectionColumn + colDesc.propertyDesc.propertyFlags = kDataBrowserDefaultPropertyFlags; + + colDesc.headerBtnDesc.version = kDataBrowserListViewLatestHeaderDesc; + colDesc.headerBtnDesc.minimumWidth = 100; + colDesc.headerBtnDesc.maximumWidth = 150; + colDesc.headerBtnDesc.titleOffset = 0; + colDesc.headerBtnDesc.titleString = CFSTR("Tabs"); + colDesc.headerBtnDesc.initialOrder = kDataBrowserOrderIncreasing; + colDesc.headerBtnDesc.btnFontStyle.flags = 0; // use default font + colDesc.headerBtnDesc.btnContentInfo.contentType = kControlContentTextOnly; + + AddDataBrowserListViewColumn(dataBrowser, &colDesc, 0); + + // create tabline popup menu required by vim docs (see :he tabline-menu) + CreateNewMenu(kTabContextMenuId, 0, &contextMenu); + AppendMenuItemTextWithCFString(contextMenu, CFSTR("Close Tab"), 0, + TABLINE_MENU_CLOSE, NULL); + AppendMenuItemTextWithCFString(contextMenu, CFSTR("New Tab"), 0, + TABLINE_MENU_NEW, NULL); + AppendMenuItemTextWithCFString(contextMenu, CFSTR("Open Tab..."), 0, + TABLINE_MENU_OPEN, NULL); +} + + +/* + * Show or hide the tabline. + */ + void +gui_mch_show_tabline(int showit) +{ + if (showit == 0) + CloseDrawer(drawer, true); + else + OpenDrawer(drawer, kWindowEdgeRight, true); +} + +/* + * Return TRUE when tabline is displayed. + */ + int +gui_mch_showing_tabline(void) +{ + WindowDrawerState state = GetDrawerState(drawer); + + return state == kWindowDrawerOpen || state == kWindowDrawerOpening; +} + +/* + * Update the labels of the tabline. + */ + void +gui_mch_update_tabline(void) +{ + tabpage_T *tp; + int numTabs = getTabCount(); + int nr = 1; + int curtabidx = 1; + + // adjust data browser + if (tabLabels != NULL) + { + int i; + + for (i = 0; i < tabLabelsSize; ++i) + CFRelease(tabLabels[i]); + free(tabLabels); + } + tabLabels = (CFStringRef *)malloc(numTabs * sizeof(CFStringRef)); + tabLabelsSize = numTabs; + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next, ++nr) + { + if (tp == curtab) + curtabidx = nr; + tabLabels[nr-1] = getTabLabel(tp); + } + + RemoveDataBrowserItems(dataBrowser, kDataBrowserNoItem, 0, NULL, + kDataBrowserItemNoProperty); + // data browser uses ids 1, 2, 3, ... numTabs per default, so we + // can pass NULL for the id array + AddDataBrowserItems(dataBrowser, kDataBrowserNoItem, numTabs, NULL, + kDataBrowserItemNoProperty); + + DataBrowserItemID item = curtabidx; + SetDataBrowserSelectedItems(dataBrowser, 1, &item, kDataBrowserItemsAssign); +} + +/* + * Set the current tab to "nr". First tab is 1. + */ + void +gui_mch_set_curtab(int nr) +{ + DataBrowserItemID item = nr; + SetDataBrowserSelectedItems(dataBrowser, 1, &item, kDataBrowserItemsAssign); + + // TODO: call something like this?: (or restore scroll position, or...) + RevealDataBrowserItem(dataBrowser, item, kTabsColumn, + kDataBrowserRevealOnly); +} + +#endif // FEAT_GUI_TABLINE |