]> git.saurik.com Git - wxWidgets.git/commitdiff
implement support for bitmaps for all states in wxGTK wxButton
authorVadim Zeitlin <vadim@wxwidgets.org>
Tue, 16 Jun 2009 19:08:59 +0000 (19:08 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Tue, 16 Jun 2009 19:08:59 +0000 (19:08 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61080 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/gtk/button.h
src/gtk/button.cpp

index cdf674aa782ab5497c998b634bcf3cdb29bf04b2..2b7f9a0278751e20713250fc0553a6a6d9b2be68 100644 (file)
@@ -17,7 +17,7 @@
 class WXDLLIMPEXP_CORE wxButton : public wxButtonBase
 {
 public:
-    wxButton() { }
+    wxButton() { Init(); }
     wxButton(wxWindow *parent, wxWindowID id,
            const wxString& label = wxEmptyString,
            const wxPoint& pos = wxDefaultPosition,
@@ -25,6 +25,8 @@ public:
            const wxValidator& validator = wxDefaultValidator,
            const wxString& name = wxButtonNameStr)
     {
+        Init();
+
         Create(parent, id, label, pos, size, style, validator, name);
     }
 
@@ -48,6 +50,13 @@ public:
     // helper to allow access to protected member from GTK callback
     void MoveWindow(int x, int y, int width, int height) { DoMoveWindow(x, y, width, height); }
 
+    // called from GTK callbacks: they update the button state and call
+    // GTKUpdateBitmap()
+    void GTKMouseEnters();
+    void GTKMouseLeaves();
+    void GTKPressed();
+    void GTKReleased();
+
 protected:
     virtual wxSize DoGetBestSize() const;
     virtual void DoApplyWidgetStyle(GtkRcStyle *style);
@@ -59,8 +68,40 @@ protected:
     virtual void DoSetBitmapPosition(wxDirection dir);
 
 private:
+    // common part of all ctors
+    void Init()
+    {
+        m_isCurrent =
+        m_isPressed = false;
+    }
+
+    // focus event handler: calls GTKUpdateBitmap()
+    void GTKOnFocus(wxFocusEvent& event);
+
+    // update the bitmap to correspond to the current button state
+    void GTKUpdateBitmap();
+
+    // return the current button state from m_isXXX flags (which means that it
+    // might not correspond to the real current state as e.g. m_isCurrent will
+    // never be true if we don't have a valid current bitmap)
+    State GTKGetCurrentState() const;
+
+    // show the given bitmap (must be valid)
+    void GTKDoShowBitmap(const wxBitmap& bitmap);
+
+
+    // the bitmaps for the different state of the buttons, all of them may be
+    // invalid and the button only shows a bitmap at all if State_Normal bitmap
+    // is valid
     wxBitmap m_bitmaps[State_Max];
 
+    // true iff mouse is currently over the button
+    bool m_isCurrent;
+
+    // true iff the button is in pressed state
+    bool m_isPressed;
+
+
     DECLARE_DYNAMIC_CLASS(wxButton)
 };
 
index 71b8bd98fff8aeb1e30cf3abf8d5d8658778f3fb..0b7a033626b9d6265b878b026a023a591332cb96 100644 (file)
 
 #include "wx/gtk/private.h"
 
-//-----------------------------------------------------------------------------
-// data
-//-----------------------------------------------------------------------------
-
-extern bool   g_blockEventsOnDrag;
+// ----------------------------------------------------------------------------
+// GTK callbacks
+// ----------------------------------------------------------------------------
 
-//-----------------------------------------------------------------------------
-// "clicked"
-//-----------------------------------------------------------------------------
+extern "C"
+{
 
-extern "C" {
-static void gtk_button_clicked_callback( GtkWidget *WXUNUSED(widget), wxButton *button )
+static void
+wxgtk_button_clicked_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
 {
-    if (!button->m_hasVMT) return;
-    if (g_blockEventsOnDrag) return;
+    if ( button->GTKShouldIgnoreEvent() )
+        return;
 
     wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, button->GetId());
     event.SetEventObject(button);
     button->HandleWindowEvent(event);
 }
+
+static void
+wxgtk_button_enter_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
+{
+    if ( button->GTKShouldIgnoreEvent() )
+        return;
+
+    button->GTKMouseEnters();
+}
+
+static void
+wxgtk_button_leave_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
+{
+    if ( button->GTKShouldIgnoreEvent() )
+        return;
+
+    button->GTKMouseLeaves();
+}
+
+static void
+wxgtk_button_press_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
+{
+    if ( button->GTKShouldIgnoreEvent() )
+        return;
+
+    button->GTKPressed();
+}
+
+static void
+wxgtk_button_released_callback(GtkWidget *WXUNUSED(widget), wxButton *button)
+{
+    if ( button->GTKShouldIgnoreEvent() )
+        return;
+
+    button->GTKReleased();
 }
 
 //-----------------------------------------------------------------------------
 // "style_set" from m_widget
 //-----------------------------------------------------------------------------
 
-extern "C" {
 static void
-gtk_button_style_set_callback(GtkWidget* widget, GtkStyle*, wxButton* win)
+wxgtk_button_style_set_callback(GtkWidget* widget, GtkStyle*, wxButton* win)
 {
     /* the default button has a border around it */
     wxWindow* parent = win->GetParent();
@@ -67,7 +98,8 @@ gtk_button_style_set_callback(GtkWidget* widget, GtkStyle*, wxButton* win)
         }
     }
 }
-}
+
+} // extern "C"
 
 //-----------------------------------------------------------------------------
 // wxButton
@@ -114,11 +146,11 @@ bool wxButton::Create(wxWindow *parent,
        gtk_button_set_relief( GTK_BUTTON(m_widget), GTK_RELIEF_NONE );
 
     g_signal_connect_after (m_widget, "clicked",
-                            G_CALLBACK (gtk_button_clicked_callback),
+                            G_CALLBACK (wxgtk_button_clicked_callback),
                             this);
 
     g_signal_connect_after (m_widget, "style_set",
-                            G_CALLBACK (gtk_button_style_set_callback),
+                            G_CALLBACK (wxgtk_button_style_set_callback),
                             this);
 
     m_parent->DoAddChild( this );
@@ -137,7 +169,7 @@ wxWindow *wxButton::SetDefault()
     gtk_widget_grab_default( m_widget );
 
     // resize for default border
-    gtk_button_style_set_callback( m_widget, NULL, this );
+    wxgtk_button_style_set_callback( m_widget, NULL, this );
 
     return oldDefault;
 }
@@ -220,6 +252,8 @@ bool wxButton::Enable( bool enable )
         GTKFixSensitivity();
     }
 
+    GTKUpdateBitmap();
+
     return true;
 }
 
@@ -295,6 +329,80 @@ wxButton::GetClassDefaultAttributes(wxWindowVariant WXUNUSED(variant))
 // bitmaps support
 // ----------------------------------------------------------------------------
 
+void wxButton::GTKMouseEnters()
+{
+    m_isCurrent = true;
+
+    GTKUpdateBitmap();
+}
+
+void wxButton::GTKMouseLeaves()
+{
+    m_isCurrent = false;
+
+    GTKUpdateBitmap();
+}
+
+void wxButton::GTKPressed()
+{
+    m_isPressed = true;
+
+    GTKUpdateBitmap();
+}
+
+void wxButton::GTKReleased()
+{
+    m_isPressed = false;
+
+    GTKUpdateBitmap();
+}
+
+void wxButton::GTKOnFocus(wxFocusEvent& event)
+{
+    event.Skip();
+
+    GTKUpdateBitmap();
+}
+
+wxButton::State wxButton::GTKGetCurrentState() const
+{
+    if ( !IsThisEnabled() )
+        return m_bitmaps[State_Disabled].IsOk() ? State_Disabled : State_Normal;
+
+    if ( m_isPressed && m_bitmaps[State_Pressed].IsOk() )
+        return State_Pressed;
+
+    if ( m_isCurrent && m_bitmaps[State_Current].IsOk() )
+        return State_Current;
+
+    if ( HasFocus() && m_bitmaps[State_Focused].IsOk() )
+        return State_Focused;
+
+    return State_Normal;
+}
+
+void wxButton::GTKUpdateBitmap()
+{
+    State state = GTKGetCurrentState();
+
+    GTKDoShowBitmap(m_bitmaps[state]);
+}
+
+void wxButton::GTKDoShowBitmap(const wxBitmap& bitmap)
+{
+    wxASSERT_MSG( bitmap.IsOk(), "invalid bitmap" );
+
+#ifdef __WXGTK26__
+    if ( !gtk_check_version(2,6,0) )
+    {
+        GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
+        wxCHECK_RET( image, "must have image widget" );
+
+        gtk_image_set_from_pixbuf(GTK_IMAGE(image), bitmap.GetPixbuf());
+    }
+#endif // __WXGTK26__
+}
+
 wxBitmap wxButton::DoGetBitmap(State which) const
 {
     return m_bitmaps[which];
@@ -302,36 +410,173 @@ wxBitmap wxButton::DoGetBitmap(State which) const
 
 void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which)
 {
-#ifdef __WXGTK26__
-    // normal image is special: setting it enables images for the button and
-    // resetting it to nothing disables all of them
-    if ( which == State_Normal )
+    switch ( which )
     {
-        if ( !gtk_check_version(2,6,0) )
-        {
-            GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
-            if ( image && !bitmap.IsOk() )
+        case State_Normal:
+#ifdef __WXGTK26__
+            // normal image is special: setting it enables images for the button and
+            // resetting it to nothing disables all of them
+            if ( !gtk_check_version(2,6,0) )
             {
-                gtk_container_remove(GTK_CONTAINER(m_widget), image);
+                GtkWidget *image = gtk_button_get_image(GTK_BUTTON(m_widget));
+                if ( image && !bitmap.IsOk() )
+                {
+                    gtk_container_remove(GTK_CONTAINER(m_widget), image);
+                }
+                else if ( !image && bitmap.IsOk() )
+                {
+                    image = gtk_image_new();
+                    gtk_button_set_image(GTK_BUTTON(m_widget), image);
+                }
+                else // image presence or absence didn't change
+                {
+                    // don't invalidate best size below
+                    break;
+                }
+
                 InvalidateBestSize();
             }
-            else if ( !image && bitmap.IsOk() )
+#endif // GTK+ 2.6+
+            break;
+
+        case State_Pressed:
+            if ( bitmap.IsOk() )
+            {
+                if ( !m_bitmaps[which].IsOk() )
+                {
+                    // we need to install the callbacks to be notified about
+                    // the button pressed state change
+                    g_signal_connect
+                    (
+                        m_widget,
+                        "pressed",
+                        G_CALLBACK(wxgtk_button_press_callback),
+                        this
+                    );
+
+                    g_signal_connect
+                    (
+                        m_widget,
+                        "released",
+                        G_CALLBACK(wxgtk_button_released_callback),
+                        this
+                    );
+                }
+            }
+            else // no valid bitmap
             {
-                image = gtk_image_new();
-                gtk_button_set_image(GTK_BUTTON(m_widget), image);
-                InvalidateBestSize();
+                if ( m_bitmaps[which].IsOk() )
+                {
+                    // we don't need to be notified about the button pressed
+                    // state changes any more
+                    g_signal_handlers_disconnect_by_func
+                    (
+                        m_widget,
+                        (gpointer)wxgtk_button_press_callback,
+                        this
+                    );
+
+                    g_signal_handlers_disconnect_by_func
+                    (
+                        m_widget,
+                        (gpointer)wxgtk_button_released_callback,
+                        this
+                    );
+
+                    // also make sure we don't remain stuck in pressed state
+                    if ( m_isPressed )
+                    {
+                        m_isPressed = false;
+                        GTKUpdateBitmap();
+                    }
+                }
             }
-            //else: image presence or absence didn't change
+            break;
 
+        case State_Current:
+            // the logic here is the same as above for State_Pressed: we need
+            // to connect the handlers if we must be notified about the changes
+            // in the button current state and we disconnect them when/if we
+            // don't need them any more
             if ( bitmap.IsOk() )
             {
-                gtk_image_set_from_pixbuf(GTK_IMAGE(image), bitmap.GetPixbuf());
+                if ( !m_bitmaps[which].IsOk() )
+                {
+                    g_signal_connect
+                    (
+                        m_widget,
+                        "enter",
+                        G_CALLBACK(wxgtk_button_enter_callback),
+                        this
+                    );
+
+                    g_signal_connect
+                    (
+                        m_widget,
+                        "leave",
+                        G_CALLBACK(wxgtk_button_leave_callback),
+                        this
+                    );
+                }
             }
-        }
+            else // no valid bitmap
+            {
+                if ( m_bitmaps[which].IsOk() )
+                {
+                    g_signal_handlers_disconnect_by_func
+                    (
+                        m_widget,
+                        (gpointer)wxgtk_button_enter_callback,
+                        this
+                    );
+
+                    g_signal_handlers_disconnect_by_func
+                    (
+                        m_widget,
+                        (gpointer)wxgtk_button_leave_callback,
+                        this
+                    );
+
+                    if ( m_isCurrent )
+                    {
+                        m_isCurrent = false;
+                        GTKUpdateBitmap();
+                    }
+                }
+            }
+            break;
+
+        case State_Focused:
+            if ( bitmap.IsOk() )
+            {
+                Connect(wxEVT_SET_FOCUS,
+                        wxFocusEventHandler(wxButton::GTKOnFocus));
+                Connect(wxEVT_KILL_FOCUS,
+                        wxFocusEventHandler(wxButton::GTKOnFocus));
+            }
+            else // no valid focused bitmap
+            {
+                Disconnect(wxEVT_SET_FOCUS,
+                           wxFocusEventHandler(wxButton::GTKOnFocus));
+                Disconnect(wxEVT_KILL_FOCUS,
+                           wxFocusEventHandler(wxButton::GTKOnFocus));
+            }
+            break;
+
+        default:
+            // no callbacks to connect/disconnect
+            ;
     }
-#endif // GTK+ 2.6+
 
     m_bitmaps[which] = bitmap;
+
+    // update the bitmap immediately if necessary, otherwise it will be done
+    // when the bitmap for the corresponding state is needed the next time by
+    // GTKUpdateBitmap()
+    if ( bitmap.IsOk() && which == GTKGetCurrentState() )
+    {
+        GTKDoShowBitmap(bitmap);
+    }
 }
 
 void wxButton::DoSetBitmapPosition(wxDirection dir)