initial draft of wxCollapsiblePane (patch 1577412 by Francesco)
[wxWidgets.git] / src / gtk / collpane.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/collpane.cpp
3 // Purpose: wxCollapsiblePane
4 // Author: Francesco Montorsi
5 // Modified By:
6 // Created: 8/10/2006
7 // Id: $Id$
8 // Copyright: (c) Francesco Montorsi
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12
13 // ----------------------------------------------------------------------------
14 // headers
15 // ----------------------------------------------------------------------------
16
17 // For compilers that support precompilation, includes "wx.h".
18 #include "wx/wxprec.h"
19
20 #ifdef __WXGTK24__
21
22 #include "wx/collpane.h"
23 #include <gtk/gtkexpander.h>
24 #include <gtk/gtk.h>
25
26 // ============================================================================
27 // implementation
28 // ============================================================================
29
30
31 /* DEBUG TIMER */
32 class myTimer : public wxTimer
33 {
34 wxCollapsiblePane *p;
35
36 public:
37 myTimer(wxCollapsiblePane *pp) {p=pp;}
38
39 void Notify()
40 {
41 //wxLogDebug(wxT("%d"), p->IsCollapsed());
42
43 wxSize sz = p->GetBestSize();
44 wxLogDebug(wxT("our best size is now: %d-%d"), sz.GetWidth(), sz.GetHeight());
45 }
46 };
47
48
49
50 const wxChar wxCollapsiblePaneNameStr[] = wxT("CollapsiblePane");
51
52 //-----------------------------------------------------------------------------
53 // "notify::expanded" signal
54 //-----------------------------------------------------------------------------
55
56 extern "C" {
57
58 static void gtk_collapsiblepane_expanded_callback (GObject *object,
59 GParamSpec *param_spec,
60 wxCollapsiblePane *p)
61 {
62 // NB: unlike for the "activate" signal, when this callback is called, if we try to
63 // query the "collapsed" status through p->IsCollapsed(), we get the right value.
64 // I.e. here p->IsCollapsed() will return false if this callback has been called
65 // at the end of a collapsed->expanded transition and viceversa.
66 // Inside the "activate" signal callback p->IsCollapsed() would return the wrong value!
67
68 wxLogDebug("gtk_collapsiblepane_expanded_callback - IsCollapsed says %d", p->IsCollapsed());
69
70 wxSize sz;
71 if (!p->IsCollapsed())
72 {
73 // unfortunately there's no clean way to retrieve the minimal size of the expanded pane
74 // in this handler or in other handlers for the signals generated by user clicks on the
75 // GtkExpander button: p->GetBestSize() or p->GetMinSize() would still return the size for
76 // the collapsed expander even if the collapsed->expanded transition has already been
77 // completed (this because GTK+ queues some resize calls which still must be processed).
78 // So, the only solution to correctly set the size hints for this window is to calculate
79 // the expanded size ourselves, without relying on p->Get[Best|Min]Size:
80 sz = p->GetMinSize();
81 sz.SetWidth( wxMax(sz.GetWidth(), p->GetPane()->GetMinSize().GetWidth()) );
82 sz.SetHeight( sz.GetHeight() + p->GetPane()->GetMinSize().GetHeight() + 10 );
83 }
84 else
85 {
86 // same problem described above: using p->Get[Best|Min]Size() here we would get the size
87 // of the control when it is expanded even if the expanded->collapsed transition should be
88 // complete now...
89 // So, we use the size cached at control-creation time...
90 sz = p->m_szCollapsed;
91 }
92
93 wxLogDebug(wxT("gtk_collapsiblepane_expanded_callback - my min size is now: %d-%d"),
94 sz.GetWidth(), sz.GetHeight());
95
96 // minimal size has priority over the best size so set here our min size
97 p->SetMinSize(sz);
98 p->SetSize(sz);
99
100 wxWindow *top = p->GetTopLevelParent();
101 if (top)
102 {
103 // we've changed our size, thus our top level parent needs to relayout itself
104 top->Layout();
105
106 // FIXME: this makes wxGenericCollapsiblePane behave as the user expect but
107 // maybe there are cases where this is unwanted!
108 if (top->GetSizer())
109 #ifdef __WXGTK__
110 // FIXME: the SetSizeHints() call would be required also for GTK+ for the
111 // expanded->collapsed transition.
112 // Unfortunately if we enable this line, then the GTK+ top window
113 // won't always be resized by the SetClientSize() call below!
114 // As a side effect of this dirty fix, the minimal size for the
115 // pane window is not set in GTK+ and the user can hide it shrinking
116 // the "top" window...
117 if (p->IsCollapsed())
118 #endif
119 top->GetSizer()->SetSizeHints(top);
120
121 if (p->IsCollapsed())
122 {
123 // NB: we need to use SetClientSize() and not SetSize() otherwise the size for
124 // windows like e.g. wxFrames with wxMenubars won't be correctly set
125 top->SetClientSize(sz);
126 }
127 else
128 {
129 // force our parent to "fit", i.e. expand so that it can honour
130 // our minimal size
131 top->Fit();
132 }
133 }
134
135 if (p->m_bIgnoreNextChange)
136 {
137 // change generated programmatically - do not send an event!
138 p->m_bIgnoreNextChange = false;
139 return;
140 }
141
142 // fire an event
143 wxCollapsiblePaneEvent ev(p, p->GetId(), p->IsCollapsed());
144 p->GetEventHandler()->ProcessEvent(ev);
145 }
146 }
147
148 static void gtk_collapsiblepane_insert_callback( wxWindowGTK* parent, wxWindowGTK* child )
149 {
150 // this callback should be used only once to insert the "pane" into
151 // the GtkExpander widget. wxGenericCollapsiblePane::DoAddChild() will check
152 // if it has been called only once (and in any case we would get a warning
153 // from the following call as GtkExpander is a GtkBin and can contain only a
154 // single child!).
155 gtk_container_add (GTK_CONTAINER (parent->m_widget), child->m_widget);
156 }
157
158
159 //-----------------------------------------------------------------------------
160 // wxCollapsiblePane
161 //-----------------------------------------------------------------------------
162
163 IMPLEMENT_DYNAMIC_CLASS(wxCollapsiblePane, wxGenericCollapsiblePane)
164
165 BEGIN_EVENT_TABLE(wxCollapsiblePane, wxGenericCollapsiblePane)
166 EVT_SIZE(wxCollapsiblePane::OnSize)
167 END_EVENT_TABLE()
168
169 bool wxCollapsiblePane::Create( wxWindow *parent, wxWindowID id,
170 const wxString& label,
171 const wxPoint& pos,
172 const wxSize& size,
173 long style,
174 const wxValidator& val,
175 const wxString& name)
176 {
177 if (gtk_check_version(2,4,0)) {wxASSERT(0);
178 return wxGenericCollapsiblePane::Create(parent, id, label, pos, size, style, val, name);}
179
180 m_needParent = true;
181 m_acceptsFocus = true;
182 m_bIgnoreNextChange = false;
183
184 if (!PreCreation( parent, pos, size ) ||
185 !wxControl::CreateBase(parent, id, pos, size, style, val, name))
186 {
187 wxFAIL_MSG( wxT("wxCollapsiblePane creation failed") );
188 return false;
189 }
190
191 m_widget = gtk_expander_new(label.c_str());
192
193 // see the gtk_collapsiblepane_expanded_callback comments to understand why we connect
194 // to the "notify::expanded" signal instead of the more common "activate" one
195 g_signal_connect(m_widget, "notify::expanded",
196 G_CALLBACK(gtk_collapsiblepane_expanded_callback), this);
197
198 // before creating m_pPane, we need to makesure our own insert callback will be used
199 m_insertCallback = gtk_collapsiblepane_insert_callback;
200
201 // this the real "pane"
202 m_pPane = new wxWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
203
204 gtk_widget_show( GTK_WIDGET(m_widget) );
205 m_parent->DoAddChild( this );
206
207 PostCreation(size);
208 //SetBestSize(size);
209
210 // remember the size of this control when it's collapsed
211 m_szCollapsed = GetBestSize();
212
213 /*
214 myTimer *t = new myTimer(this);
215 t->Start(2000);
216 */
217
218 return true;
219 }
220
221 wxSize wxCollapsiblePane::DoGetBestSize() const
222 {
223 if (!gtk_check_version(2,4,0))
224 {
225 //return wxControl::DoGetBestSize(); // need not to cache the best size!
226
227 wxASSERT_MSG( m_widget, wxT("DoGetBestSize called before creation") );
228
229 GtkRequisition req;
230 req.width = 2;
231 req.height = 2;
232 (* GTK_WIDGET_CLASS( GTK_OBJECT_GET_CLASS(m_widget) )->size_request )
233 (m_widget, &req );
234
235 wxSize best(req.width, req.height);
236 //CacheBestSize(best);
237 return best;
238 }
239
240 return wxGenericCollapsiblePane::DoGetBestSize();
241 }
242
243 void wxCollapsiblePane::Collapse(bool collapse)
244 {
245 if (!gtk_check_version(2,4,0))
246 {
247 // optimization
248 if (IsCollapsed() == collapse)
249 return;
250
251 // do not send event in next signal handler call
252 m_bIgnoreNextChange = true;
253 gtk_expander_set_expanded(GTK_EXPANDER(m_widget), !collapse);
254 }
255 else
256 wxGenericCollapsiblePane::Collapse(collapse);
257 }
258
259 bool wxCollapsiblePane::IsCollapsed() const
260 {
261 if (!gtk_check_version(2,4,0))
262 return !gtk_expander_get_expanded(GTK_EXPANDER(m_widget));
263
264 return wxGenericCollapsiblePane::IsCollapsed();
265 }
266
267 void wxCollapsiblePane::SetLabel(const wxString &str)
268 {
269 if (!gtk_check_version(2,4,0))
270 gtk_expander_set_label(GTK_EXPANDER(m_widget), str.c_str());
271 else
272 wxGenericCollapsiblePane::SetLabel(str);
273 }
274
275 void wxCollapsiblePane::OnSize(wxSizeEvent &ev)
276 {
277 #if 0 // for debug only
278 wxClientDC dc(this);
279 dc.SetPen(*wxBLACK_PEN);
280 dc.SetBrush(*wxTRANSPARENT_BRUSH);
281 dc.DrawRectangle(wxPoint(0,0), GetSize());
282 dc.SetPen(*wxRED_PEN);
283 dc.DrawRectangle(wxPoint(0,0), GetBestSize());
284 #endif
285
286 //wxLogDebug(wxT("wxCollapsiblePane::OnSize"));
287
288 // here we need to resize the pane window otherwise, even if the GtkExpander container
289 // is expanded or shrinked, the pane window won't be updated!
290 m_pPane->SetSize(ev.GetSize());
291
292 // we need to explicitely call m_pPane->Layout() or else it won't correctly relayout
293 // (even if SetAutoLayout(true) has been called on it!)
294 m_pPane->Layout();
295 }
296
297 #endif // __WXGTK24__
298