Back to index

lightning-sunbird  0.9+nobinonly
nsMacMessagePump.cpp
Go to the documentation of this file.
00001 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Mark Mentovai <mark@moxienet.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 /*
00040  * Dispatcher for a variety of application-scope events.  The real event loop
00041  * is in nsAppShell.
00042  */
00043 
00044 #include "nsMacMessagePump.h"
00045 
00046 #include "nsIEventSink.h"
00047 #include "nsIRollupListener.h"
00048 #include "nsIWidget.h"
00049 #include "nsPIWidgetMac.h"
00050 
00051 #include "nsGfxUtils.h"
00052 #include "nsGUIEvent.h"
00053 #include "nsMacTSMMessagePump.h"
00054 #include "nsToolkit.h"
00055 
00056 #include <Carbon/Carbon.h>
00057 
00058 #ifndef botRight
00059 #define botRight(r) (((Point *) &(r))[1])
00060 #endif
00061 
00062 const short kMinWindowWidth = 125;
00063 const short kMinWindowHeight = 150;
00064 
00065 extern nsIRollupListener * gRollupListener;
00066 extern nsIWidget         * gRollupWidget;
00067 
00068 nsMacMessagePump::nsMacMessagePump(nsToolkit *aToolkit)
00069 : mToolkit(aToolkit)
00070 , mMouseClickEventHandler(NULL)
00071 , mWNETransitionEventHandler(NULL)
00072 , mProcessEvents(PR_FALSE)
00073 {
00074   NS_ASSERTION(mToolkit, "No toolkit");
00075   
00076   // This list encompasses Carbon events that can be converted into
00077   // EventRecords, and that still require dispatch through
00078   // nsMacMessagePump::DispatchEvent because they haven't yet been converted
00079   // completetly to Carbon events.
00080   const EventTypeSpec kWNETransitionEventList[] = {
00081     { kEventClassMouse,       kEventMouseDown },
00082     { kEventClassMouse,       kEventMouseUp },
00083     { kEventClassMouse,       kEventMouseMoved },
00084     { kEventClassMouse,       kEventMouseDragged },
00085     { kEventClassAppleEvent,  kEventAppleEvent },
00086     { kEventClassControl,     kEventControlTrack },
00087   };
00088 
00089   static EventHandlerUPP sWNETransitionEventHandlerUPP;
00090   if (!sWNETransitionEventHandlerUPP)
00091     sWNETransitionEventHandlerUPP =
00092                                ::NewEventHandlerUPP(WNETransitionEventHandler);
00093 
00094   OSStatus err =
00095    ::InstallApplicationEventHandler(sWNETransitionEventHandlerUPP,
00096                                     GetEventTypeCount(kWNETransitionEventList),
00097                                     kWNETransitionEventList,
00098                                     NS_STATIC_CAST(void*, this),
00099                                     &mWNETransitionEventHandler);
00100 
00101   NS_ASSERTION(err == noErr, "Could not install WNETransitionEventHandler");
00102 
00103   // For middle clicks.  Install this handler second, because
00104   // WNETransitionEventHandler swallows all events, and MouseClickEventHandler
00105   // needs to be able to handle mouse-click events (punting non-middle-click
00106   // ones).
00107   const EventTypeSpec kMouseClickEventList[] = {
00108     { kEventClassMouse, kEventMouseDown },
00109     { kEventClassMouse, kEventMouseUp },
00110   };
00111 
00112   static EventHandlerUPP sMouseClickEventHandlerUPP;
00113   if (!sMouseClickEventHandlerUPP)
00114     sMouseClickEventHandlerUPP = ::NewEventHandlerUPP(MouseClickEventHandler);
00115 
00116   err =
00117    ::InstallApplicationEventHandler(sMouseClickEventHandlerUPP,
00118                                     GetEventTypeCount(kMouseClickEventList),
00119                                     kMouseClickEventList,
00120                                     NS_STATIC_CAST(void*, this),
00121                                     &mMouseClickEventHandler);
00122   NS_ASSERTION(err == noErr, "Could not install MouseClickEventHandler");
00123 
00124   //
00125   // create the TSM Message Pump
00126   //
00127   nsMacTSMMessagePump* tsmMessagePump = nsMacTSMMessagePump::GetSingleton();
00128   NS_ASSERTION(tsmMessagePump, "Unable to create TSM Message Pump");
00129 }
00130 
00131 nsMacMessagePump::~nsMacMessagePump()
00132 {
00133   if (mMouseClickEventHandler)
00134     ::RemoveEventHandler(mMouseClickEventHandler);
00135 
00136   if (mWNETransitionEventHandler)
00137     ::RemoveEventHandler(mWNETransitionEventHandler);
00138 
00139   nsMacTSMMessagePump::Shutdown();
00140 }
00141 
00142 PRBool nsMacMessagePump::ProcessEvents(PRBool aProcessEvents)
00143 {
00144   PRBool wasProcessing = mProcessEvents;
00145   mProcessEvents = aProcessEvents;
00146   return wasProcessing;
00147 }
00148 
00149 //=================================================================
00150 /*  Dispatch a single event
00151  *  @param   anEvent - the event to dispatch
00152  *  @return  A boolean which states whether we handled the event
00153  */
00154 PRBool nsMacMessagePump::DispatchEvent(EventRecord *anEvent)
00155 {
00156   PRBool handled = PR_FALSE;
00157 
00158   if (!mProcessEvents)
00159     return handled;
00160   
00161   switch(anEvent->what) {
00162     // diskEvt is gone in Carbon, and so is unhandled here.
00163     // keyUp, keyDown, and autoKey now have Carbon event handlers in
00164     //  nsMacWindow.
00165 
00166     case mouseDown:
00167       handled = DoMouseDown(*anEvent);
00168       break;
00169 
00170     case mouseUp:
00171       handled = DoMouseUp(*anEvent);
00172       break;
00173 
00174     case osEvt: {
00175       unsigned char eventType = ((anEvent->message >> 24) & 0x00ff);
00176       if (eventType == mouseMovedMessage)
00177         handled = DoMouseMove(*anEvent);
00178       break;
00179     }
00180       
00181     case kHighLevelEvent:
00182       ::AEProcessAppleEvent(anEvent);
00183       handled = PR_TRUE;
00184       break;
00185   }
00186 
00187   return handled;
00188 }
00189 
00190 //-------------------------------------------------------------------------
00191 //
00192 // DoMouseDown
00193 //
00194 //-------------------------------------------------------------------------
00195 PRBool nsMacMessagePump::DoMouseDown(EventRecord &anEvent)
00196 {
00197   WindowPtr     whichWindow;
00198   WindowPartCode        partCode;
00199   PRBool  handled = PR_FALSE;
00200   
00201   partCode = ::FindWindow(anEvent.where, &whichWindow);
00202   
00203   switch (partCode)
00204   {
00205       case inNoWindow:
00206         break;
00207 
00208       case inCollapseBox:   // we never seem to get this.
00209       case inSysWindow:
00210         if ( gRollupListener && gRollupWidget )
00211           gRollupListener->Rollup();
00212         break;
00213 
00214       case inMenuBar:
00215       {
00216         // If a xul popup is displayed, roll it up and don't allow the click
00217         // through to the menu code. This is how MacOS context menus work, so
00218         // I think this is a valid solution.
00219         if ( gRollupListener && gRollupWidget )
00220         {
00221           gRollupListener->Rollup();
00222         }
00223         else
00224         {
00225           ::MenuSelect(anEvent.where);
00226           handled = PR_TRUE;
00227         }
00228         
00229         break;
00230       }
00231 
00232       case inContent:
00233       {
00234         nsGraphicsUtils::SafeSetPortWindowPort(whichWindow);
00235         if ( IsWindowHilited(whichWindow) || (gRollupListener && gRollupWidget) )
00236           handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00237         else {
00238           nsCOMPtr<nsIWidget> topWidget;
00239           nsToolkit::GetTopWidget ( whichWindow, getter_AddRefs(topWidget) );
00240           nsCOMPtr<nsPIWidgetMac> macWindow ( do_QueryInterface(topWidget) );
00241           if ( macWindow ) {
00242             // a click occurred in a background window. Use WaitMouseMove() to determine if
00243             // it was a click or a drag. If it was a drag, send a drag gesture to the
00244             // background window. We don't need to rely on the ESM to track the gesture,
00245             // the OS has just told us.  If it was a click, bring it to the front like normal.
00246             Boolean initiateDragFromBGWindow = ::WaitMouseMoved(anEvent.where);
00247             if ( initiateDragFromBGWindow ) {
00248               nsCOMPtr<nsIEventSink> sink ( do_QueryInterface(topWidget) );
00249               if ( sink ) {
00250                 // dispach a mousedown, an update event to paint any changes,
00251                 // then the drag gesture event
00252                 PRBool handled = PR_FALSE;
00253                 sink->DispatchEvent ( &anEvent, &handled );
00254                 
00255                 EventRecord updateEvent = anEvent;
00256                 updateEvent.what = updateEvt;
00257                 updateEvent.message = NS_REINTERPRET_CAST(UInt32, whichWindow);
00258                 sink->DispatchEvent ( &updateEvent, &handled );
00259                 
00260                 sink->DragEvent ( NS_DRAGDROP_GESTURE, anEvent.where.h, anEvent.where.v, 0L, &handled );                
00261               }
00262             }
00263             else {
00264               PRBool enabled;
00265               if (NS_SUCCEEDED(topWidget->IsEnabled(&enabled)) && !enabled)
00266                 ::SysBeep(1);
00267               else
00268                 macWindow->ComeToFront();
00269             }
00270             handled = PR_TRUE;
00271           }
00272         }
00273         break;
00274       }
00275 
00276       case inDrag:
00277       {
00278         nsGraphicsUtils::SafeSetPortWindowPort(whichWindow);
00279 
00280         Point   oldTopLeft = {0, 0};
00281         ::LocalToGlobal(&oldTopLeft);
00282         
00283         // roll up popups BEFORE we start the drag
00284         if ( gRollupListener && gRollupWidget )
00285           gRollupListener->Rollup();
00286 
00287         Rect screenRect;
00288         ::GetRegionBounds(::GetGrayRgn(), &screenRect);
00289         ::DragWindow(whichWindow, anEvent.where, &screenRect);
00290 
00291         Point   newTopLeft = {0, 0};
00292         ::LocalToGlobal(&newTopLeft);
00293 
00294         // only activate if the command key is not down
00295         if (!(anEvent.modifiers & cmdKey))
00296         {
00297           nsCOMPtr<nsIWidget> topWidget;
00298           nsToolkit::GetTopWidget(whichWindow, getter_AddRefs(topWidget));
00299           
00300           nsCOMPtr<nsPIWidgetMac> macWindow ( do_QueryInterface(topWidget) );
00301           if ( macWindow )
00302             macWindow->ComeToFront();
00303         }
00304         
00305         // Dispatch the event because some windows may want to know that they have been moved.
00306         anEvent.where.h += newTopLeft.h - oldTopLeft.h;
00307         anEvent.where.v += newTopLeft.v - oldTopLeft.v;
00308         
00309         handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00310         break;
00311       }
00312 
00313       case inGrow:
00314       {
00315         nsGraphicsUtils::SafeSetPortWindowPort(whichWindow);
00316 
00317         Rect sizeLimit;
00318         sizeLimit.top = kMinWindowHeight;
00319         sizeLimit.left = kMinWindowWidth;
00320         sizeLimit.bottom = 0x7FFF;
00321         sizeLimit.right = 0x7FFF;
00322 
00323         Rect newSize;
00324         ::ResizeWindow(whichWindow, anEvent.where, &sizeLimit, &newSize);
00325 
00326         Point newPt = botRight(newSize);
00327         ::LocalToGlobal(&newPt);
00328         newPt.h -= 8, newPt.v -= 8;
00329         anEvent.where = newPt;  // important!
00330         handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00331 
00332         break;
00333       }
00334 
00335       case inGoAway:
00336       {
00337         nsGraphicsUtils::SafeSetPortWindowPort(whichWindow);
00338         if (::TrackGoAway(whichWindow, anEvent.where)) {
00339           handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00340         }
00341         break;
00342       }
00343 
00344       case inZoomIn:
00345       case inZoomOut:
00346         if (::TrackBox(whichWindow, anEvent.where, partCode))
00347         {
00348           if (partCode == inZoomOut)
00349           {
00350             nsCOMPtr<nsIWidget> topWidget;
00351             nsToolkit::GetTopWidget ( whichWindow, getter_AddRefs(topWidget) );
00352             nsCOMPtr<nsPIWidgetMac> macWindow ( do_QueryInterface(topWidget) );
00353             if ( macWindow )
00354               macWindow->CalculateAndSetZoomedSize();
00355           }
00356           // !!!  Do not call ZoomWindow before calling DispatchOSEventToRaptor
00357           //    otherwise nsMacEventHandler::HandleMouseDownEvent won't get
00358           //    the right partcode for the click location
00359           
00360           handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00361         }
00362         break;
00363 
00364       case inToolbarButton:           // Mac OS X only
00365         nsGraphicsUtils::SafeSetPortWindowPort(whichWindow);
00366         handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00367         break;
00368 
00369   }
00370 
00371   return handled;
00372 }
00373 
00374 //-------------------------------------------------------------------------
00375 //
00376 // DoMouseUp
00377 //
00378 //-------------------------------------------------------------------------
00379 PRBool nsMacMessagePump::DoMouseUp(EventRecord &anEvent)
00380 {
00381     WindowPtr     whichWindow;
00382     PRInt16       partCode;
00383 
00384   partCode = ::FindWindow(anEvent.where, &whichWindow);
00385   if (whichWindow == nil)
00386   {
00387     // We need to report the event even when it happens over no window:
00388     // when the user clicks a widget, keeps the mouse button pressed and
00389     // releases it outside the window, the event needs to be reported to
00390     // the widget so that it can deactivate itself.
00391     whichWindow = ::FrontWindow();
00392   }
00393   
00394   PRBool handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00395   // consume mouse ups in the title bar, since nsMacWindow doesn't do that for us
00396   if (partCode == inDrag)
00397     handled = PR_TRUE;
00398   return handled;
00399 }
00400 
00401 //-------------------------------------------------------------------------
00402 //
00403 // DoMouseMove
00404 //
00405 //-------------------------------------------------------------------------
00406 PRBool nsMacMessagePump::DoMouseMove(EventRecord &anEvent)
00407 {
00408   // same thing as DoMouseUp
00409   WindowPtr     whichWindow;
00410   PRInt16       partCode;
00411   PRBool        handled = PR_FALSE;
00412   
00413   partCode = ::FindWindow(anEvent.where, &whichWindow);
00414   if (whichWindow == nil)
00415     whichWindow = ::FrontWindow();
00416 
00417   /* Disable mouse moved events for windowshaded windows -- this prevents tooltips
00418      from popping up in empty space.
00419   */
00420   if (whichWindow == nil || !::IsWindowCollapsed(whichWindow))
00421     handled = DispatchOSEventToRaptor(anEvent, whichWindow);
00422   return handled;
00423 }
00424 
00425 //-------------------------------------------------------------------------
00426 //
00427 // DispatchOSEventToRaptor
00428 //
00429 //-------------------------------------------------------------------------
00430 
00431 PRBool  nsMacMessagePump::DispatchOSEventToRaptor(
00432                           EventRecord   &anEvent,
00433                           WindowPtr     aWindow)
00434 {
00435   PRBool handled = PR_FALSE;
00436   nsCOMPtr<nsIEventSink> sink;
00437   nsToolkit::GetWindowEventSink ( aWindow, getter_AddRefs(sink) );
00438   if ( sink )
00439     sink->DispatchEvent ( &anEvent, &handled );
00440   return handled;
00441 }
00442 
00443 pascal OSStatus
00444 nsMacMessagePump::MouseClickEventHandler(EventHandlerCallRef aHandlerCallRef,
00445                                          EventRef            aEvent,
00446                                          void*               aUserData)
00447 {
00448   EventMouseButton button;
00449   OSErr err = ::GetEventParameter(aEvent, kEventParamMouseButton,
00450                                   typeMouseButton, NULL,
00451                                   sizeof(EventMouseButton), NULL, &button);
00452 
00453   // Only handle middle click events here.  Let the rest fall through.
00454   if (err != noErr || button != kEventMouseButtonTertiary)
00455     return eventNotHandledErr;
00456 
00457   EventRecord eventRecord;
00458   if (!::ConvertEventRefToEventRecord(aEvent, &eventRecord)) {
00459     // This will return FALSE on a middle click event; that's to let us know
00460     // it's giving us a nullEvent, which is expected since Classic events
00461     // don't support the middle button normally.
00462     //
00463     // We know better, so let's restore the actual event kind.
00464     UInt32 kind = ::GetEventKind(aEvent);
00465     eventRecord.what = (kind == kEventMouseDown) ? mouseDown : mouseUp;
00466   }
00467 
00468   // Classic mouse events don't record the button specifier. The message
00469   // parameter is unused in mouse click events, so let's stuff it there.
00470   // We'll pick it up in nsMacEventHandler::HandleMouseDownEvent().
00471   eventRecord.message = NS_STATIC_CAST(UInt32, button);
00472 
00473   // Process the modified event internally
00474   nsMacMessagePump* self = NS_STATIC_CAST(nsMacMessagePump*, aUserData);
00475   PRBool handled = self->DispatchEvent(&eventRecord);
00476 
00477   if (handled)
00478     return noErr;
00479 
00480   return eventNotHandledErr;
00481 }
00482 
00483 // WNETransitionEventHandler
00484 //
00485 // Transitional WaitNextEvent handler.  Accepts Carbon events from
00486 // kWNETransitionEventList, converts them into EventRecords, and
00487 // dispatches them through the path they would have gone if they
00488 // had been received as EventRecords from WaitNextEvent.
00489 //
00490 // protected static
00491 pascal OSStatus
00492 nsMacMessagePump::WNETransitionEventHandler(EventHandlerCallRef aHandlerCallRef,
00493                                             EventRef            aEvent,
00494                                             void*               aUserData)
00495 {
00496   nsMacMessagePump* self = NS_STATIC_CAST(nsMacMessagePump*, aUserData);
00497 
00498   EventRecord eventRecord;
00499   ::ConvertEventRefToEventRecord(aEvent, &eventRecord);
00500 
00501   PRBool handled = self->DispatchEvent(&eventRecord);
00502 
00503   if (!handled)
00504     return eventNotHandledErr;
00505 
00506   return noErr;
00507 }