Page 1 of 1

Sliding Dialogs (Sheets) for MacOS X Leopard

Posted: Mon May 05, 2008 2:40 am
by joric
Is there a way to get a sliding dialog like this (on MacOS):

Image

(This one from msw Safari, but you know what I'm saying.) Leopard is crammed full of such dialogs.

Posted: Mon May 05, 2008 2:36 pm
by Auria
You'd need to use native code (a very similar question poped-up recently, if you search a bit you can probably find it. not sure if it contained any additional info though)

Posted: Mon May 05, 2008 6:49 pm
by joric
Auria wrote:You'd need to use native code (a very similar question poped-up recently, if you search a bit you can probably find it
Can't find anything related =( Could you please find it? Sorry for inconvenience.

Posted: Mon May 05, 2008 7:51 pm
by Ryan Wilcox
They're called Sheets in OS X Terminology.

I thought there was this capacity in wxMac already, I'll do some more research tonight....

Posted: Tue May 06, 2008 4:12 am
by Ryan Wilcox
Right, so I guess it's not in wx itself.

Apple's Dev Docs should help a bit here, but I think something like this should work (but untested...)

Code: Select all


bool MySheetWindow::Show(bool visible)
{
   if (visible)
   {
     ::ShowSheetWindow( MacGetWindowRef(), GetParent()->MacGetWindowRef() );
     Refresh();
   }
   else
    ::HideSheetWindow( MacGetWindowRef() );

  return true;
}

//Then just construct a MySheetWindow in the normal way, making sure to specify, as the parent parameter, the window you want to be "under" the sheet.

//(OR, if the parent doesn't work, make a freshly named instance variable...)
 
It's important to override the Show() method (and NOT call inherited) because we're showing the sheet in a different way.[/code]

Posted: Tue May 06, 2008 9:45 am
by joric
Thank you very much, that's it!
One more question: should it be done by patching wx source or it's possible to do locally? I didn't deal with subclassing in wx.

Posted: Tue May 06, 2008 12:03 pm
by Ryan Wilcox
I meant for it to be used in a subclass of wxFrame. It's not that hard :-)

Posted: Tue May 06, 2008 2:55 pm
by joric
I've searched in the forum about ShowSheetWindow and got this topic: http://forums.wxwidgets.org/viewtopic.php?t=19076
As there were no implementation details i've hacked a source.

Code: Select all

//wxWidgets-2.8.7/src/mac/carbon/dialog.cpp
...
bool wxDialog::Show(bool show)
{

/*
    if ( !wxDialogBase::Show(show) )
        // nothing to do
        return false;
*/

	wxFrame * dialog = (wxFrame*)this;
	wxFrame * frame = (wxFrame*)GetParent();

	if (show)
	{
		SetWindowClass (
			(WindowRef) dialog->MacGetWindowRef(),
			kSheetWindowClass );

		ShowSheetWindow (
			(WindowRef) dialog->MacGetWindowRef(),
			(WindowRef) frame->MacGetWindowRef()); 
	} 
	else
	{
		HideSheetWindow(
			(WindowRef) dialog->MacGetWindowRef());
	}

...
Then I've recompiled entire wxWidgets with make && sudo make install (one single file, so it's pretty fast) and it works like a charm!
To remove dialog captions you have to replace wxDEFAULT_DIALOG_STYLE to wxBORDER_NONE (an empty style gives ugly thin black border).
I didn't make a sleeker implementation, but it worth to be implemented as, say, additional dialog style in any next release of wxMac.

Posted: Tue May 06, 2008 3:20 pm
by Auria
That sounds like a great possibility indeed. Maybe it could be submitted on the feature request tracker

Posted: Tue May 06, 2008 4:44 pm
by joric
I've made a patch.

Code: Select all

Name: wxWidgets 2.8.7 patch to enable MacOS sliding dialogs (sheets)
Version: 0.0.1
Usage: add wxBORDER_NONE style to dialog (does not affect anything else)
Maintainers: Joric (http://joreg.livejournal.com) 
===================================================================
--- wxWidgets-2.8.7/src/mac/dialog.cpp	2007-11-21 17:43:24.000000000 +0500
+++ wxWidgets-2.8.7/src/mac/dialog.cpp	2008-05-06 22:16:04.000000000 +0600
@@ -49,6 +49,9 @@
 
     // ...but not these styles
     style &= ~(wxYES | wxOK | wxNO); // | wxCANCEL
+    
+    if (style & wxBORDER_NONE)
+    	style = wxBORDER_NONE;
 
     if ( !wxTopLevelWindow::Create( parent, id, title, pos, size, style, name ) )
         return false;
@@ -106,9 +109,20 @@
 
 bool wxDialog::Show(bool show)
 {
-    if ( !wxDialogBase::Show(show) )
-        // nothing to do
-        return false;
+     if ( ( GetWindowStyleFlag() & wxBORDER_NONE ) && GetParent() )
+     {
+        if (show)
+        {
+            SetWindowClass ( (WindowRef) MacGetWindowRef(), kSheetWindowClass );
+
+            ShowSheetWindow ( (WindowRef) MacGetWindowRef(),
+                (WindowRef) ( ( wxFrame* ) GetParent() )->MacGetWindowRef() );
+        }
+        else
+            HideSheetWindow( (WindowRef) MacGetWindowRef() );
+    }
+    else
+    {
+        if ( !wxDialogBase::Show(show) )
+            return false;          
+    }
 
     if ( show )
         // usually will result in TransferDataToWindow() being called

Posted: Tue May 06, 2008 7:28 pm
by joric
Unfortunately there is a significant difference between these sheets and native leopard sheets. They are not semi-transparent and have bigger shadow (i've tried several styles). I don't know why, does anybody know?

Image

P.S. It maybe because i've linked in 10.4u compatibility mode, haven't checked with 10.5 SDK yet (it tooks awful amount of time to recompile the whole lib). But I think it's not the real case... Btw it looks much more native on Tiger.

P.P.S. I've tried to use style = 0, it gives the proper (small) shadow, but no transparency and the black border returned. Do you know a proper style for it?

Posted: Wed May 07, 2008 12:56 am
by joric
One more patch (for alerts / message dialogs). Looks like transparent, compiled on 10.4u.

Image

Code: Select all

Name: wxWidgets 2.8.7 patch to enable "Sheet" style for MacOS message dialogs
Version: 0.0.1
Usage: affects all message dialogs, no flags needed
Maintainers: Joric (http://joreg.livejournal.com) 
=================================================================== 
--- wxWidgets-2.8.7/src/mac/msgdlg.cpp	2007-11-21 18:43:25.000000000 +0500
+++ wxWidgets-2.8.7/src/mac/msgdlg.cpp	2008-05-07 06:39:28.000000000 +0600
@@ -34,6 +34,42 @@
     SetMessageDialogStyle(style);
 }
 
+pascal OSStatus mySheetHandler(  EventHandlerCallRef inRef,
+                                                EventRef inEvent,
+                                                void* userData)
+{
+
+    ControlRef control;
+    WindowRef sheet;
+    WindowRef parent;
+    UInt32 cmd;
+    GetEventParameter(inEvent, kEventParamDirectObject, typeControlRef, NULL, sizeof(ControlRef), NULL, &control);
+    GetControlCommandID(control, &cmd);    
+    sheet = GetControlOwner(control);
+    GetSheetWindowParent(sheet, &parent);
+    HideSheetWindow(sheet);
+    DisposeWindow(sheet);
+    QuitAppModalLoopForWindow(parent);
+    short result = -1;
+    switch(cmd)	
+    {
+        case kHICommandOK:
+            result = 1;
+        break;
+				
+        case kHICommandCancel:
+            result = 2;
+        break;
+								
+        case kHICommandOther:
+            result = 3;
+        break;						
+    }							
+    *(short*)userData = result;				
+    return noErr;
+}
+
+
 int wxMessageDialog::ShowModal()
 {
     int resultbutton = wxID_CANCEL;
@@ -177,9 +213,13 @@
         param.position = kWindowDefaultPosition;
         if ( !skipDialog )
         {
-            DialogRef alertRef;
-            CreateStandardAlert( alertType, cfTitle, cfText, &param, &alertRef );
-            RunStandardAlert( alertRef, NULL, &result );
+            DialogRef sheet = NULL;
+	     static EventTypeSpec controlEvent = { kEventClassControl, kEventControlHit };          
+            WindowRef parent = (WindowRef)((wxTopLevelWindow*) m_parent)->MacGetWindowRef();						
+            CreateStandardSheet(alertType, cfTitle, cfText, &param, GetWindowEventTarget(parent), &sheet);
+            InstallWindowEventHandler(GetDialogWindow(sheet), NewEventHandlerUPP(mySheetHandler), 1, &controlEvent, &result, NULL);
+            ShowSheetWindow(GetDialogWindow(sheet), parent);
+            RunAppModalLoopForWindow(parent);
         }
         else
         {
A bit agressive patch =) Turns all modal message boxes to "sheets".

Posted: Wed May 07, 2008 3:03 am
by Ryan Wilcox
My thoughts are two fold: a) you don't really want all your dialogs turning into sheets (and what if there's no window up?) b) BUT usually Stefan Csomor (the lead wxMac guy) is really patient about working to get a patch right.

Posted: Sun May 11, 2008 12:01 am
by joric
Subclassed version, no need to patch now.
If you meant this kind of subclassing (i.e. copypasting), it may for sure follow compatibility issues with future versions - dunno about wxFrame code tho, maybe it's better structured. BTW it does not allow to use DECLARE_DYNAMIC_CLASS or IMPLEMENT_DYNAMIC_CLASS, don't know why ("no appropriate default constructor available" even with wxMyMessageDialog(){}). But since XRC does not support message boxes it probably doesn't matter.

Code: Select all

// wxMyMessageDialog.h (for 2.8.7) enables "sheets" on MacOS
// compiled and checked on MacOS 10.4.4 and MacOS 10.5.1, both with 10.4u SDK

#if defined(__WXMAC__) && !defined(__WXMAC_CLASSIC__) && !defined(TARGET_API_MAC_OSX)

#include "wx/wxprec.h"

#include "wx/msgdlg.h"

#ifndef WX_PRECOMP
    #include "wx/intl.h"
    #include "wx/app.h"
#endif

#include "wx/mac/uma.h"

pascal OSStatus mySheetHandler(EventHandlerCallRef inRef, EventRef inEvent, void* userData)
{
    ControlRef control;
    WindowRef sheet;
    WindowRef parent;
    UInt32 cmd;
    GetEventParameter(inEvent, kEventParamDirectObject, typeControlRef, NULL, sizeof(ControlRef), NULL, &control);
    GetControlCommandID(control, &cmd);   
    sheet = GetControlOwner(control);
    GetSheetWindowParent(sheet, &parent);
    HideSheetWindow(sheet);
    DisposeWindow(sheet);
    QuitAppModalLoopForWindow(parent);
    short result = -1;
    switch(cmd)       
    {
        case kHICommandOK:
            result = 1;
        break;
                            
        case kHICommandCancel:
            result = 2;
        break;
                                                        
        case kHICommandOther:
            result = 3;
        break;      
    }             
    *(short*)userData = result;                               
    return noErr;
} 

class wxMyMessageDialog : public wxMessageDialog
{           
    public:
    wxMyMessageDialog(wxWindow *parent, const wxString& message, const wxString& caption = wxMessageBoxCaptionStr, 
    long style = wxOK|wxCENTRE, const wxPoint& pos = wxDefaultPosition)
    : wxMessageDialog(parent, message, caption, style, pos){};

    int ShowModal() 
    {
        int resultbutton = wxID_CANCEL;

        const long style = GetMessageDialogStyle();

        wxASSERT_MSG( (style & 0x3F) != wxYES, wxT("this style is not supported on Mac") );

        AlertType alertType = kAlertPlainAlert;
        if (style & wxICON_EXCLAMATION)
            alertType = kAlertCautionAlert;
        else if (style & wxICON_HAND)
            alertType = kAlertStopAlert;
        else if (style & wxICON_INFORMATION)
            alertType = kAlertNoteAlert;
        else if (style & wxICON_QUESTION)
            alertType = kAlertNoteAlert;

        
        short result;

        AlertStdCFStringAlertParamRec param;
        wxMacCFStringHolder cfNoString( _("No"), m_font.GetEncoding() );
        wxMacCFStringHolder cfYesString( _("Yes"), m_font.GetEncoding() );

        wxMacCFStringHolder cfTitle( m_caption, m_font.GetEncoding() );
        wxMacCFStringHolder cfText( m_message, m_font.GetEncoding() );

        param.movable = true;
        param.flags = 0;
        param.version = kStdCFStringAlertVersionOne;

        bool skipDialog = false;

        if (style & wxYES_NO)
        {
            if (style & wxCANCEL)
            {
                param.defaultText = cfYesString;
                param.cancelText = (CFStringRef) kAlertDefaultCancelText;
                param.otherText = cfNoString;
                param.helpButton = false;
                param.defaultButton = style & wxNO_DEFAULT ? kAlertStdAlertOtherButton : kAlertStdAlertOKButton;
                param.cancelButton = kAlertStdAlertCancelButton;
            }
            else
            {
                param.defaultText = cfYesString;
                param.cancelText = NULL;
                param.otherText = cfNoString;
                param.helpButton = false;
                param.defaultButton = style & wxNO_DEFAULT ? kAlertStdAlertOtherButton : kAlertStdAlertOKButton;
                param.cancelButton = 0;
            }
        }
        // the MSW implementation even shows an OK button if it is not specified, we'll do the same
        else
        {
            if (style & wxCANCEL)
            {
                // that's a cancel missing
                param.defaultText = (CFStringRef) kAlertDefaultOKText;
                param.cancelText = (CFStringRef) kAlertDefaultCancelText;
                param.otherText = NULL;
                param.helpButton = false;
                param.defaultButton = kAlertStdAlertOKButton;
                param.cancelButton = 0;
            }
            else
            {
                param.defaultText = (CFStringRef) kAlertDefaultOKText;
                param.cancelText = NULL;
                param.otherText = NULL;
                param.helpButton = false;
                param.defaultButton = kAlertStdAlertOKButton;
                param.cancelButton = 0;
            }
        }

        param.position = kWindowDefaultPosition;
        if ( !skipDialog )
        {          
            if (m_parent == NULL) 
            {
                // if no parent, use the old version
       	    DialogRef alertRef;
                CreateStandardAlert( alertType, cfTitle, cfText, &param, &alertRef );
                RunStandardAlert( alertRef, NULL, &result );
            }
            else 
            {
                // else use the new one
    	    DialogRef sheet = NULL;
    	    static EventTypeSpec controlEvent = { kEventClassControl, kEventControlHit };         
                WindowRef parent = (WindowRef)((wxTopLevelWindow*) m_parent)->MacGetWindowRef();                  
                CreateStandardSheet(alertType, cfTitle, cfText, &param, GetWindowEventTarget(parent), &sheet);
                InstallWindowEventHandler(GetDialogWindow(sheet), NewEventHandlerUPP(mySheetHandler), 1, 
                    &controlEvent, &result, NULL);
                ShowSheetWindow(GetDialogWindow(sheet), parent);
                RunAppModalLoopForWindow(parent); 
            }
        }
        else
        {
            return wxID_CANCEL;
        }

        if (style & wxOK)
        {
            switch ( result )
            {
            case 1:
                resultbutton = wxID_OK;
                break;

            case 2:
                // TODO: add Cancel button
                // if (style & wxCANCEL)
                //     resultbutton = wxID_CANCEL;
                break;

            case 3:
            default:
                break;
            }
        }
        else if (style & wxYES_NO)
        {
            switch ( result )
            {
            case 1:
                resultbutton = wxID_YES;
                break;

            case 2:
                if (!(style & wxCANCEL))
                    resultbutton = wxID_CANCEL;
                break;

            case 3:
                resultbutton = wxID_NO;
                break;

            default:
                break;
            }
        }        
    return resultbutton;
    }
};

#else 

class wxMyMessageDialog : public wxMessageDialog
{           
    public:
    wxMyMessageDialog(wxWindow *parent, const wxString& message, 
    const wxString& caption = wxMessageBoxCaptionStr, long style = wxOK|wxCENTRE, 
        const wxPoint& pos = wxDefaultPosition) 
    : wxMessageDialog(parent, message, caption, style, pos) {};
};

#endif

Posted: Sat May 17, 2008 9:50 pm
by joric