Back to index

texmacs  1.0.7.15
mac_utilities.mm
Go to the documentation of this file.
00001 /******************************************************************************
00002  * MODULE     : mac_utilities.mm
00003  * DESCRIPTION: Cocoa related utilites (also for TeXmacs/Qt)
00004  * COPYRIGHT  : (C) 2010  Massimiliano Gubinelli
00005  *******************************************************************************
00006  * This software falls under the GNU general public license version 3 or later.
00007  * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
00008  * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
00009  ******************************************************************************/
00010 
00011 #include "url.hpp"
00012 #include "mac_utilities.h"
00013 #include "timer.hpp"
00014 #include "gui.hpp"
00015 
00016 #define extend CARBON_extends // avoid name collision
00017 #include "Cocoa/mac_cocoa.h"
00018 #include <Carbon/Carbon.h>
00019 #include "HIDRemote.h"
00020 #undef extend
00021 
00022 #ifdef QTTEXMACS
00023 #include <QtGui>
00024 #include "Qt/QTMWidget.hpp"
00025 #include "Qt/qt_gui.hpp"
00026 #endif
00027 
00028 bool 
00029 mac_alternate_startup () {
00030 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
00031   NSUInteger nsmods = [NSEvent modifierFlags];
00032   return (nsmods &  NSAlternateKeyMask);
00033 #else
00034   return ((CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) 
00035            & NSDeviceIndependentModifierFlagsMask) == kCGEventFlagMaskAlternate);
00036 #endif
00037 }
00038 
00039 
00040 void 
00041 mac_fix_paths () {
00042   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
00043   /* add appropriate TEXMACS_PATH to the current environment */
00044 #if 0
00045   setenv("TEXMACS_PATH",
00046          [[[[NSBundle mainBundle] resourcePath] 
00047            stringByAppendingPathComponent:@"share/TeXmacs"] 
00048           cStringUsingEncoding:NSUTF8StringEncoding],
00049          1);
00050 #endif
00051   /* add TeX directory */
00052   /* FIXME: make this user-defined */
00053   // FIXME: encoding here is not quite correct!!!
00054   setenv("PATH",
00055          [[[NSString stringWithCString:getenv("PATH") encoding:NSASCIIStringEncoding] 
00056            stringByAppendingString:@":/usr/texbin"]
00057           cStringUsingEncoding:NSUTF8StringEncoding],
00058          1); 
00059   setenv("GUILE_LOAD_PATH","/opt/local/share/guile/1.8",1);
00060   system("printenv");
00061   [pool release];  
00062 }
00063 
00064 
00065 
00066 
00067 #ifdef QTTEXMACS
00068 #if 1
00069 //HACK:
00070 // the following code fixes a bug in Qt/Cocoa which do not correctly handle
00071 // Ctrl+Tab key combination. In particular no QKeyDown event is generated for
00072 // Shift+Tab and Ctrl+Tab. For this reason we intercept the event at the Cocoa
00073 // level and just perform manually the translation to equivalent Qt event which
00074 // is then sent directly to the focused widget. 
00075 // It is rather simplistic approach but seems to work.
00076 // Since it is an hack, the filter is  installed  only if we link againts the 
00077 // bugged version of Qt.
00078 // This filter is installed in qt_gui.cpp
00079 // To use the API we need to compile in ObjC 2.0 since blocks are required
00080 
00081 NSEvent *
00082 mac_handler_body (NSEvent *event) {
00083   if (([event type] == NSKeyDown) || ([event type] == NSKeyUp)) {
00084     NSString *nss = [event charactersIgnoringModifiers];
00085     if ([nss length] > 0) {
00086       unichar key = [nss characterAtIndex:0];
00087       if ((key == NSTabCharacter) || (key == NSBackTabCharacter) ) {
00088         NSUInteger nsmods = [event modifierFlags];
00089         Qt::KeyboardModifiers modifs = 0;
00090         if (key == NSBackTabCharacter) modifs |= Qt::ShiftModifier;
00091         if (nsmods &  NSControlKeyMask) modifs |= Qt::MetaModifier;
00092         if (nsmods &  NSAlternateKeyMask) modifs |= Qt::AltModifier;
00093         if (nsmods &  NSCommandKeyMask) modifs |= Qt::ControlModifier;
00094         
00095 #if 0 // DEBUGGING CODE
00096         QString str;
00097         if (key == NSBackTabCharacter) str.append("Shift+");
00098         if (nsmods &  NSControlKeyMask) str.append("Ctrl+");
00099         if (nsmods &  NSAlternateKeyMask) str.append("Alt+");
00100         if (nsmods &  NSCommandKeyMask) str.append("Meta+");
00101         str.append("Tab");
00102         cout << str.toAscii().constData() << LF;
00103 #endif      
00104         
00105         QKeyEvent *qe = new QKeyEvent(([event type] == NSKeyDown) ? 
00106                                       QEvent::KeyPress : QEvent::KeyRelease, 
00107                                       Qt::Key_Tab, modifs);
00108         QApplication::postEvent(qApp->focusWidget(), qe);
00109         return nil;
00110       }
00111     }
00112   }
00113   return event;
00114 }
00115   
00116 void 
00117 mac_install_filter () {
00118 #if NS_BLOCKS_AVAILABLE
00119   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
00120   
00121   NSEvent * (^mac_handler)(NSEvent * ) = ^ (NSEvent *event) {
00122     return mac_handler_body(event);
00123   };
00124   [NSEvent addLocalMonitorForEventsMatchingMask: NSKeyDownMask | NSKeyUpMask 
00125                                         handler:mac_handler];
00126   [pool release];
00127   
00128 #endif
00129 }
00130 
00131 #endif // HACK
00132 #endif // QTTEXMACS
00133 
00134 //#ifdef Q_WS_MAC
00135 #if 0
00136 
00137 // this code is not used. It was an hack. Maybe sometimes in the future we
00138 // should drop it
00139 
00140 void 
00141 cancel_tracking (NSMenu *menu) {
00142   [menu cancelTracking];
00143   for (NSMenuItem *item in [menu itemArray]) {
00144     if ([item submenu]) {
00145       cancel_tracking([item submenu]);
00146     }
00147   }
00148 }
00149 
00150 
00151 void 
00152 mac_cancel_menu_tracking () {
00153 #ifdef QT_MAC_USE_COCOA
00154   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
00155   NSMenu *mainMenu = [  [NSApplication sharedApplication]
00156                       mainMenu];
00157   [mainMenu cancelTrackingWithoutAnimation];
00158   {
00159     NSString *nss = [NSString stringWithCString:"\x1b" encoding:NSASCIIStringEncoding];
00160     NSEvent *ke = [NSEvent keyEventWithType: NSKeyDown location:NSMakePoint(0,0) modifierFlags:0 
00161                                   timestamp:1 windowNumber:0 context:0 characters:nss 
00162                 charactersIgnoringModifiers:nss isARepeat:NO keyCode:0x1b];
00163     [mainMenu performKeyEquivalent:ke];
00164   }
00165   cancel_tracking(mainMenu);
00166   [pool release];
00167 #else
00168   CancelMenuTracking(AcquireRootMenu(), true, 0);
00169 #endif
00170 }
00171 #endif
00172 
00173 /*********************/
00174 /* remote controller */
00175 /*********************/
00176 
00177 @interface TMRemoteDelegate : NSObject <HIDRemoteDelegate> 
00178 {
00179   // -- HID Remote --
00180        HIDRemote                   *hidRemote;
00181 }
00182 - (void) setupRemote ;
00183 - (void) cleanupRemote ;
00184 - (void) startStopRemote:(bool) _start;
00185 - (NSString *) buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode;
00186 @end
00187 
00188 @implementation TMRemoteDelegate
00189 - (void) setupRemote
00190 {
00191        if (!hidRemote)
00192        {
00193               if ((hidRemote = [[HIDRemote alloc] init]) != nil)
00194               {
00195                      [hidRemote setDelegate:self];
00196               }
00197        }
00198 }
00199 
00200 extern string utf8_to_cork (string input);
00201 
00202 static string 
00203 from_nsstring (NSString *s) {
00204   const char *cstr = [s cStringUsingEncoding:NSUTF8StringEncoding];
00205   return utf8_to_cork(string((char*)cstr));
00206 }
00207 
00208 
00209 - (void) hidRemote:(HIDRemote *)theHidRemote
00210    eventWithButton:(HIDRemoteButtonCode)buttonCode
00211          isPressed:(BOOL)isPressed
00212 fromHardwareWithAttributes:(NSMutableDictionary *)attributes
00213 {
00214   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
00215   mac_remote_button (from_nsstring([self buttonNameForButtonCode:buttonCode]), isPressed);
00216   [pool release];
00217 }
00218 
00219 - (void) cleanupRemote
00220 {
00221        if ([hidRemote isStarted])
00222        {
00223               [hidRemote stopRemoteControl];
00224        }
00225        [hidRemote setDelegate:nil];
00226        [hidRemote release];
00227        hidRemote = nil;
00228 }
00229 
00230 - (NSString *)buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode
00231 {
00232        switch (buttonCode)
00233        {
00234               case kHIDRemoteButtonCodeUp:
00235                      return (@"ir-up");
00236       break;
00237       
00238               case kHIDRemoteButtonCodeDown:
00239                      return (@"ir-down");
00240       break;
00241       
00242               case kHIDRemoteButtonCodeLeft:
00243                      return (@"ir-left");
00244       break;
00245       
00246               case kHIDRemoteButtonCodeRight:
00247                      return (@"ir-right");
00248       break;
00249       
00250               case kHIDRemoteButtonCodeCenter:
00251                      return (@"ir-center");
00252       break;
00253       
00254               case kHIDRemoteButtonCodePlay:
00255                      return (@"ir-play");
00256       break;
00257       
00258               case kHIDRemoteButtonCodeMenu:
00259                      return (@"ir-menu");
00260       break;
00261       
00262               case kHIDRemoteButtonCodeUpHold:
00263                      return (@"ir-up-hold");
00264       break;
00265       
00266               case kHIDRemoteButtonCodeDownHold:
00267                      return (@"ir-down-hold");
00268       break;
00269       
00270               case kHIDRemoteButtonCodeLeftHold:
00271                      return (@"ir-left-hold");
00272       break;
00273       
00274               case kHIDRemoteButtonCodeRightHold:
00275                      return (@"ir-right-hold");
00276       break;
00277       
00278               case kHIDRemoteButtonCodeCenterHold:
00279                      return (@"ir-center-hold");
00280       break;
00281       
00282               case kHIDRemoteButtonCodePlayHold:
00283                      return (@"ir-play-hold");
00284       break;
00285       
00286               case kHIDRemoteButtonCodeMenuHold:
00287                      return (@"ir-menu-hold");
00288       break;
00289       
00290     default:
00291       ;
00292        }
00293        
00294   return ([NSString stringWithFormat:@"ir-button-%x", (int)buttonCode]);
00295 }
00296 
00297 - (void) startStopRemote:(bool) _start
00298 {
00299        // Has the HID Remote already been started?
00300        if ([hidRemote isStarted] && (!_start))
00301        {
00302               // HID Remote already started. Stop it.
00303               [hidRemote stopRemoteControl];
00304        }
00305        else if (_start)
00306        {
00307               // HID Remote has not been started yet. Start it.
00308               HIDRemoteMode remoteMode = kHIDRemoteModeNone;
00309               NSString *remoteModeName = nil;
00310 
00311 #ifdef X11TEXMACS
00312     int mode = 1;
00313 #else
00314     int mode = 2;
00315 #endif
00316 
00317               switch (mode)
00318               {
00319                      case 0:
00320                             remoteMode = kHIDRemoteModeShared;
00321                             remoteModeName = @"shared";
00322         break;
00323         
00324                      case 1:
00325                             remoteMode = kHIDRemoteModeExclusive;
00326                             remoteModeName = @"exclusive";
00327         break;
00328         
00329                      case 2:
00330                             remoteMode = kHIDRemoteModeExclusiveAuto;
00331                             remoteModeName = @"exclusive (auto)";
00332         break;
00333               }
00334     
00335               // Check whether the installation of Candelair is required to reliably operate in this mode
00336               if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:remoteMode])
00337               {
00338                      // Reliable usage of the remote in this mode under this operating system version
00339                      // requires the Candelair driver to be installed. Tell the user about it.
00340                      NSAlert *alert;
00341                      
00342                      if ((alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Candelair driver installation necessary", @"")
00343                                                   defaultButton:NSLocalizedString(@"Download", @"")
00344                                  alternateButton:NSLocalizedString(@"More information", @"")
00345                                      otherButton:NSLocalizedString(@"Cancel", @"")
00346                        informativeTextWithFormat:NSLocalizedString(@"An additional driver needs to be installed before %@ can reliably access the remote under the OS version installed on your computer.", @""), [[NSBundle mainBundle] objectForInfoDictionaryKey:(id)kCFBundleNameKey]]) != nil)
00347                      {
00348                             switch ([alert runModal])
00349                             {
00350                                    case NSAlertDefaultReturn:
00351                                           [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.candelair.com/download/"]];
00352             break;
00353             
00354                                    case NSAlertAlternateReturn:
00355                                           [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.candelair.com/"]];
00356             break;
00357                             }
00358                      }
00359               }      
00360               else
00361               {
00362                      // Candelair is either already installed or not required under this OS release => proceed!
00363                      if ([hidRemote startRemoteControl:remoteMode])
00364                      {
00365                             // Start was successful, perform UI changes and log it.
00366 //                          [self appendToLog:[NSString stringWithFormat:@"-- Starting HID Remote in %@ mode successful --", remoteModeName]];
00367 //                          [startStopButton setTitle:@"Stop"];
00368 //                          [modeButton setEnabled:NO];
00369                      }
00370                      else
00371                      {
00372                             // Start failed. Log about it
00373 //                          [self appendToLog:[NSString stringWithFormat:@"Starting HID Remote in %@ mode failed", remoteModeName]];
00374                      }
00375               }
00376        }
00377 }
00378 
00379 @end
00380 
00381 
00382 TMRemoteDelegate* remote_delegate = nil;
00383 
00384 void 
00385 mac_begin_remote () {
00386   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
00387   if (!remote_delegate) {
00388     remote_delegate = [[TMRemoteDelegate alloc] init];
00389   }
00390   [remote_delegate setupRemote];
00391   [remote_delegate startStopRemote:true];
00392   [pool release];
00393 }
00394 
00395 void 
00396 mac_end_remote () {
00397   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
00398   [remote_delegate startStopRemote:false];
00399   [remote_delegate cleanupRemote];
00400   [pool release];
00401 }
00402 
00403 void 
00404 mac_remote_button (string button, bool pressed) {
00405   if (pressed) external_event (button, texmacs_time ());
00406 }