1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/collpane.cpp
3 // Purpose:     wxCollapsiblePane
4 // Author:      Francesco Montorsi
5 // Modified By:
6 // Created:     8/10/2006
7 // Copyright:   (c) Francesco Montorsi
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 
12 // ----------------------------------------------------------------------------
13 // headers
14 // ----------------------------------------------------------------------------
15 
16 // For compilers that support precompilation, includes "wx.h".
17 #include "wx/wxprec.h"
18 
19 #if wxUSE_COLLPANE && !defined(__WXUNIVERSAL__)
20 
21 #include "wx/collpane.h"
22 #include "wx/toplevel.h"
23 #include "wx/sizer.h"
24 #include "wx/panel.h"
25 
26 #include <gtk/gtk.h>
27 #include "wx/gtk/private.h"
28 #include "wx/gtk/private/gtk2-compat.h"
29 
30 // the lines below duplicate the same definitions in collpaneg.cpp, if we have
31 // another implementation of this class we should extract them to a common file
32 
33 const char wxCollapsiblePaneNameStr[] = "collapsiblePane";
34 
35 wxDEFINE_EVENT( wxEVT_COLLAPSIBLEPANE_CHANGED, wxCollapsiblePaneEvent );
36 
IMPLEMENT_DYNAMIC_CLASS(wxCollapsiblePaneEvent,wxCommandEvent)37 IMPLEMENT_DYNAMIC_CLASS(wxCollapsiblePaneEvent, wxCommandEvent)
38 
39 // ============================================================================
40 // implementation
41 // ============================================================================
42 
43 //-----------------------------------------------------------------------------
44 // "notify::expanded" signal
45 //-----------------------------------------------------------------------------
46 
47 extern "C" {
48 
49 static void
50 gtk_collapsiblepane_expanded_callback(GObject * WXUNUSED(object),
51                                       GParamSpec * WXUNUSED(param_spec),
52                                       wxCollapsiblePane *p)
53 {
54     // NB: unlike for the "activate" signal, when this callback is called, if
55     //     we try to query the "collapsed" status through p->IsCollapsed(), we
56     //     get the right value. I.e. here p->IsCollapsed() will return false if
57     //     this callback has been called at the end of a collapsed->expanded
58     //     transition and viceversa. Inside the "activate" signal callback
59     //     p->IsCollapsed() would return the wrong value!
60 
61     wxSize sz;
62     if ( p->IsExpanded() )
63     {
64         // NB: we cannot use the p->GetBestSize() or p->GetMinSize() functions
65         //     here as they would return the size for the collapsed expander
66         //     even if the collapsed->expanded transition has already been
67         //     completed; we solve this problem doing:
68 
69         sz = p->m_szCollapsed;
70 
71         wxSize panesz = p->GetPane()->GetBestSize();
72         sz.x = wxMax(sz.x, panesz.x);
73         sz.y += gtk_expander_get_spacing(GTK_EXPANDER(p->m_widget)) + panesz.y;
74     }
75     else // collapsed
76     {
77         // same problem described above: using p->Get[Best|Min]Size() here we
78         // would get the size of the control when it is expanded even if the
79         // expanded->collapsed transition should be complete now...
80         // So, we use the size cached at control-creation time...
81         sz = p->m_szCollapsed;
82     }
83 
84     // VERY IMPORTANT:
85     // just calling
86     //          p->OnStateChange(sz);
87     // here would work work BUT:
88     //     1) in the expanded->collapsed transition it provokes a lot of flickering
89     //     2) in the collapsed->expanded transition using the "Change status" wxButton
90     //        in samples/collpane application some strange warnings would be generated
91     //        by the "clearlooks" theme, if that's your theme.
92     //
93     // So we prefer to use some GTK+ native optimized calls, which prevent too many resize
94     // calculations to happen. Note that the following code has been very carefully designed
95     // and tested - be VERY careful when changing it!
96 
97     // 1) need to update our size hints
98     // NB: this function call won't actually do any long operation
99     //     (redraw/relayout/resize) so that it's flicker-free
100     p->SetMinSize(sz);
101 
102     if (p->HasFlag(wxCP_NO_TLW_RESIZE))
103     {
104         // fire an event
105         wxCollapsiblePaneEvent ev(p, p->GetId(), p->IsCollapsed());
106         p->HandleWindowEvent(ev);
107 
108         // the user asked to explicitly handle the resizing itself...
109         return;
110     }
111 
112     wxTopLevelWindow *
113         top = wxDynamicCast(wxGetTopLevelParent(p), wxTopLevelWindow);
114     if ( top && top->GetSizer() )
115     {
116         // 2) recalculate minimal size of the top window
117         sz = top->GetSizer()->CalcMin();
118 
119         if (top->m_mainWidget)
120         {
121             // 3) MAGIC HACK: if you ever used GtkExpander in a GTK+ program
122             //    you know that this magic call is required to make it possible
123             //    to shrink the top level window in the expanded->collapsed
124             //    transition.  This may be sometimes undesired but *is*
125             //    necessary and if you look carefully, all GTK+ programs using
126             //    GtkExpander perform this trick (e.g. the standard "open file"
127             //    dialog of GTK+>=2.4 is not resizable when the expander is
128             //    collapsed!)
129             gtk_window_set_resizable (GTK_WINDOW (top->m_widget), p->IsExpanded());
130 
131             // 4) set size hints
132             top->SetMinClientSize(sz);
133 
134             // 5) set size
135             top->SetClientSize(sz);
136         }
137     }
138 
139     if ( p->m_bIgnoreNextChange )
140     {
141         // change generated programmatically - do not send an event!
142         p->m_bIgnoreNextChange = false;
143         return;
144     }
145 
146     // fire an event
147     wxCollapsiblePaneEvent ev(p, p->GetId(), p->IsCollapsed());
148     p->HandleWindowEvent(ev);
149 }
150 }
151 
AddChildGTK(wxWindowGTK * child)152 void wxCollapsiblePane::AddChildGTK(wxWindowGTK* child)
153 {
154     // should be used only once to insert the "pane" into the
155     // GtkExpander widget. wxGenericCollapsiblePane::DoAddChild() will check if
156     // it has been called only once (and in any case we would get a warning
157     // from the following call as GtkExpander is a GtkBin and can contain only
158     // a single child!).
159     gtk_container_add(GTK_CONTAINER(m_widget), child->m_widget);
160 }
161 
162 //-----------------------------------------------------------------------------
163 // wxCollapsiblePane
164 //-----------------------------------------------------------------------------
165 
IMPLEMENT_DYNAMIC_CLASS(wxCollapsiblePane,wxControl)166 IMPLEMENT_DYNAMIC_CLASS(wxCollapsiblePane, wxControl)
167 
168 BEGIN_EVENT_TABLE(wxCollapsiblePane, wxCollapsiblePaneBase)
169     EVT_SIZE(wxCollapsiblePane::OnSize)
170 END_EVENT_TABLE()
171 
172 bool wxCollapsiblePane::Create(wxWindow *parent,
173                                wxWindowID id,
174                                const wxString& label,
175                                const wxPoint& pos,
176                                const wxSize& size,
177                                long style,
178                                const wxValidator& val,
179                                const wxString& name)
180 {
181     m_bIgnoreNextChange = false;
182 
183     if ( !PreCreation( parent, pos, size ) ||
184           !wxControl::CreateBase(parent, id, pos, size, style, val, name) )
185     {
186         wxFAIL_MSG( wxT("wxCollapsiblePane creation failed") );
187         return false;
188     }
189 
190     m_widget =
191         gtk_expander_new_with_mnemonic(wxGTK_CONV(GTKConvertMnemonics(label)));
192     g_object_ref(m_widget);
193 
194     // see the gtk_collapsiblepane_expanded_callback comments to understand why
195     // we connect to the "notify::expanded" signal instead of the more common
196     // "activate" one
197     g_signal_connect(m_widget, "notify::expanded",
198                      G_CALLBACK(gtk_collapsiblepane_expanded_callback), this);
199 
200     // this the real "pane"
201     m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
202                           wxTAB_TRAVERSAL|wxNO_BORDER, wxS("wxCollapsiblePanePane"));
203 
204     m_parent->DoAddChild( this );
205 
206     PostCreation(size);
207 
208     // we should blend into our parent background
209     const wxColour bg = parent->GetBackgroundColour();
210     SetBackgroundColour(bg);
211     m_pPane->SetBackgroundColour(bg);
212 
213     // remember the size of this control when it's collapsed
214     m_szCollapsed = GetBestSize();
215 
216     return true;
217 }
218 
DoGetBestSize() const219 wxSize wxCollapsiblePane::DoGetBestSize() const
220 {
221     wxASSERT_MSG( m_widget, wxT("DoGetBestSize called before creation") );
222 
223     GtkRequisition req;
224 #ifdef __WXGTK3__
225     gtk_widget_get_preferred_size(m_widget, NULL, &req);
226 #else
227     GTK_WIDGET_GET_CLASS(m_widget)->size_request(m_widget, &req);
228 #endif
229 
230     // notice that we do not cache our best size here as it changes
231     // all times the user expands/hide our pane
232     return wxSize(req.width, req.height);
233 }
234 
Collapse(bool collapse)235 void wxCollapsiblePane::Collapse(bool collapse)
236 {
237     // optimization
238     if (IsCollapsed() == collapse)
239         return;
240 
241     // do not send event in next signal handler call
242     m_bIgnoreNextChange = true;
243     gtk_expander_set_expanded(GTK_EXPANDER(m_widget), !collapse);
244 }
245 
IsCollapsed() const246 bool wxCollapsiblePane::IsCollapsed() const
247 {
248     return !gtk_expander_get_expanded(GTK_EXPANDER(m_widget));
249 }
250 
SetLabel(const wxString & str)251 void wxCollapsiblePane::SetLabel(const wxString &str)
252 {
253     gtk_expander_set_label(GTK_EXPANDER(m_widget),
254                            wxGTK_CONV(GTKConvertMnemonics(str)));
255 
256     // FIXME: we need to update our collapsed width in some way but using GetBestSize()
257     // we may get the size of the control with the pane size summed up if we are expanded!
258     //m_szCollapsed.x = GetBestSize().x;
259 }
260 
OnSize(wxSizeEvent & ev)261 void wxCollapsiblePane::OnSize(wxSizeEvent &ev)
262 {
263 #if 0       // for debug only
264     wxClientDC dc(this);
265     dc.SetPen(*wxBLACK_PEN);
266     dc.SetBrush(*wxTRANSPARENT_BRUSH);
267     dc.DrawRectangle(wxPoint(0,0), GetSize());
268     dc.SetPen(*wxRED_PEN);
269     dc.DrawRectangle(wxPoint(0,0), GetBestSize());
270 #endif
271 
272     // here we need to resize the pane window otherwise, even if the GtkExpander container
273     // is expanded or shrunk, the pane window won't be updated!
274     int h = ev.GetSize().y - m_szCollapsed.y;
275     if (h < 0) h = 0;
276     m_pPane->SetSize(ev.GetSize().x, h);
277 
278     // we need to explicitly call m_pPane->Layout() or else it won't correctly relayout
279     // (even if SetAutoLayout(true) has been called on it!)
280     m_pPane->Layout();
281 }
282 
283 
GTKGetWindow(wxArrayGdkWindows & windows) const284 GdkWindow *wxCollapsiblePane::GTKGetWindow(wxArrayGdkWindows& windows) const
285 {
286     GtkWidget *label = gtk_expander_get_label_widget( GTK_EXPANDER(m_widget) );
287     windows.Add(gtk_widget_get_window(label));
288     windows.Add(gtk_widget_get_window(m_widget));
289 
290     return NULL;
291 }
292 
293 #endif // wxUSE_COLLPANE && !defined(__WXUNIVERSAL__)
294 
295