Mercurial > vim
view src/gui_mac.c @ 21225:46dce2da6919
Added tag v8.2.1163 for changeset 7509aea3a6714bbd60071ef078745e2e744921c1
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Wed, 08 Jul 2020 22:45:04 +0200 |
parents | c2570baa2e4c |
children | fcccc29bd386 |
line wrap: on
line source
/* 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 = ALLOC_MULT(char_u *, *numFiles); // 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 #if 0 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); #endif #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); #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 0 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); } } #endif } /* * ------------------------------------------------------------ * 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 = 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); // Unify modifiers somewhat. No longer use ALT to set the 8th bit. key_char = extract_modifiers(key_char, (int *)&vimModifiers, FALSE, NULL); 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 technique returns 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 = ALLOC_MULT(char_u *, count); 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. #if 0 ATSUAttributeTag fallbackTags[] = { kATSULineFontFallbacksTag }; ByteCount fallbackSizes[] = { sizeof(ATSUFontFallbacks) }; ATSUCreateFontFallbacks(&gFontFallbacks); ATSUSetObjFontFallbacks(gFontFallbacks, ); #endif 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 0 if (dragRectControl == kCreateEmpty) { dragRgn = NULL; dragRectControl = kNothing; } else #endif 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(Clipboard_T *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 = alloc(scrapSize+1); HLock(textOfClip); error = GetScrapFlavorData(scrap, flavor ? VIMSCRAPFLAVOR : SCRAPTEXTFLAVOR, &scrapSize, *textOfClip); scrapSize -= flavor; if (flavor) type = **textOfClip; else type = MAUTO; tempclip = alloc(scrapSize + 1); 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(Clipboard_T *cbd) { /* * TODO: Really nothing to do? */ } int clip_mch_own_selection(Clipboard_T *cbd) { return OK; } /* * Send the current selection to the clipboard. */ void clip_mch_set_selection(Clipboard_T *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, FSK_SIMPLIFY, NULL); 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 0 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); } #endif 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) { #if 0 // TODO: TODO: TODO: TODO: blink_waittime = wait; blink_ontime = on; blink_offtime = off; #endif } /* * 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); #if 0 // TODO: TODO: TODO: TODO: gui_w32_rm_blink_timer(); if (blink_state == BLINK_OFF) gui_update_cursor(TRUE, FALSE); blink_state = BLINK_NONE; #endif } /* * 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 0 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); } #endif } /* * 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 CLEAR_FIELD(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