Use client size determined by size-allocate when possible
[wxWidgets.git] / src / gtk / minifram.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/minifram.cpp
3 // Purpose:
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #if wxUSE_MINIFRAME
14
15 #include "wx/minifram.h"
16
17 #ifndef WX_PRECOMP
18 #include "wx/settings.h"
19 #include "wx/dcclient.h"
20 #include "wx/image.h"
21 #endif
22
23 #ifdef __WXGTK3__
24 #include "wx/gtk/dc.h"
25 #else
26 #include "wx/gtk/dcclient.h"
27 #endif
28
29 #include <gtk/gtk.h>
30 #include "wx/gtk/private/gtk2-compat.h"
31
32 //-----------------------------------------------------------------------------
33 // data
34 //-----------------------------------------------------------------------------
35
36 extern bool g_blockEventsOnDrag;
37 extern bool g_blockEventsOnScroll;
38
39 //-----------------------------------------------------------------------------
40 // "expose_event" of m_mainWidget
41 //-----------------------------------------------------------------------------
42
43 // StepColour() it a utility function that simply darkens
44 // or lightens a color, based on the specified percentage
45 static wxColor StepColour(const wxColor& c, int percent)
46 {
47 int r = c.Red(), g = c.Green(), b = c.Blue();
48 return wxColour((unsigned char)wxMin((r*percent)/100,255),
49 (unsigned char)wxMin((g*percent)/100,255),
50 (unsigned char)wxMin((b*percent)/100,255));
51 }
52
53 static wxColor LightContrastColour(const wxColour& c)
54 {
55 int amount = 120;
56
57 // if the color is especially dark, then
58 // make the contrast even lighter
59 if (c.Red() < 128 && c.Green() < 128 && c.Blue() < 128)
60 amount = 160;
61
62 return StepColour(c, amount);
63 }
64
65 extern "C" {
66 #ifdef __WXGTK3__
67 static gboolean draw(GtkWidget* widget, cairo_t* cr, wxMiniFrame* win)
68 #else
69 static gboolean expose_event(GtkWidget* widget, GdkEventExpose* gdk_event, wxMiniFrame* win)
70 #endif
71 {
72 #ifdef __WXGTK3__
73 if (!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
74 return false;
75
76 GtkStyleContext* sc = gtk_widget_get_style_context(widget);
77 gtk_style_context_save(sc);
78 gtk_style_context_add_class(sc, GTK_STYLE_CLASS_BUTTON);
79 gtk_render_frame(sc, cr, 0, 0, win->m_width, win->m_height);
80 gtk_style_context_restore(sc);
81
82 wxGTKCairoDC dc(cr);
83 #else
84 if (gdk_event->count > 0 ||
85 gdk_event->window != gtk_widget_get_window(widget))
86 {
87 return false;
88 }
89
90 gtk_paint_shadow (gtk_widget_get_style(widget),
91 gtk_widget_get_window(widget),
92 GTK_STATE_NORMAL,
93 GTK_SHADOW_OUT,
94 NULL, NULL, NULL, // FIXME: No clipping?
95 0, 0,
96 win->m_width, win->m_height);
97
98 wxClientDC dc(win);
99
100 wxDCImpl *impl = dc.GetImpl();
101 wxClientDCImpl *gtk_impl = wxDynamicCast( impl, wxClientDCImpl );
102 gtk_impl->m_gdkwindow = gtk_widget_get_window(widget); // Hack alert
103 #endif
104
105 int style = win->GetWindowStyle();
106
107 if (style & wxRESIZE_BORDER)
108 {
109 dc.SetBrush( *wxGREY_BRUSH );
110 dc.SetPen( *wxTRANSPARENT_PEN );
111 dc.DrawRectangle( win->m_width - 14, win->m_height-14, 14, 14 );
112 }
113
114 if (win->m_miniTitle && !win->GetTitle().empty())
115 {
116 dc.SetFont( *wxSMALL_FONT );
117
118 wxBrush brush( LightContrastColour( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT) ) );
119 dc.SetBrush( brush );
120 dc.SetPen( *wxTRANSPARENT_PEN );
121 dc.DrawRectangle( win->m_miniEdge-1,
122 win->m_miniEdge-1,
123 win->m_width - (2*(win->m_miniEdge-1)),
124 15 );
125
126 dc.SetTextForeground( *wxWHITE );
127 dc.DrawText( win->GetTitle(), 6, 4 );
128
129 if (style & wxCLOSE_BOX)
130 dc.DrawBitmap( win->m_closeButton, win->m_width-18, 3, true );
131 }
132
133 return false;
134 }
135 }
136
137 //-----------------------------------------------------------------------------
138 // "button_press_event" of m_mainWidget
139 //-----------------------------------------------------------------------------
140
141 extern "C" {
142 static gboolean
143 gtk_window_button_press_callback(GtkWidget* widget, GdkEventButton* gdk_event, wxMiniFrame* win)
144 {
145 if (gdk_event->window != gtk_widget_get_window(widget))
146 return false;
147 if (g_blockEventsOnDrag) return TRUE;
148 if (g_blockEventsOnScroll) return TRUE;
149
150 if (win->m_isDragging) return TRUE;
151
152 int style = win->GetWindowStyle();
153
154 int y = (int)gdk_event->y;
155 int x = (int)gdk_event->x;
156
157 if ((style & wxRESIZE_BORDER) &&
158 (x > win->m_width-14) && (y > win->m_height-14))
159 {
160 GtkWidget *ancestor = gtk_widget_get_toplevel( widget );
161
162 GdkWindow *source = gtk_widget_get_window(widget);
163
164 int org_x = 0;
165 int org_y = 0;
166 gdk_window_get_origin( source, &org_x, &org_y );
167
168 gtk_window_begin_resize_drag (GTK_WINDOW (ancestor),
169 GDK_WINDOW_EDGE_SOUTH_EAST,
170 1,
171 org_x + x,
172 org_y + y,
173 0);
174
175 return TRUE;
176 }
177
178 if (win->m_miniTitle && (style & wxCLOSE_BOX))
179 {
180 if ((y > 3) && (y < 19) && (x > win->m_width-19) && (x < win->m_width-3))
181 {
182 win->Close();
183 return TRUE;
184 }
185 }
186
187 if (y >= win->m_miniEdge + win->m_miniTitle)
188 return true;
189
190 gdk_window_raise(gtk_widget_get_window(win->m_widget));
191
192 const GdkEventMask mask = GdkEventMask(
193 GDK_BUTTON_PRESS_MASK |
194 GDK_BUTTON_RELEASE_MASK |
195 GDK_POINTER_MOTION_MASK |
196 GDK_POINTER_MOTION_HINT_MASK |
197 GDK_BUTTON_MOTION_MASK |
198 GDK_BUTTON1_MOTION_MASK);
199 #ifdef __WXGTK3__
200 gdk_device_grab(
201 gdk_event->device, gdk_event->window, GDK_OWNERSHIP_NONE,
202 false, mask, NULL, gdk_event->time);
203 #else
204 gdk_pointer_grab(gdk_event->window, false, mask, NULL, NULL, gdk_event->time);
205 #endif
206
207 win->m_diffX = x;
208 win->m_diffY = y;
209 win->m_oldX = 0;
210 win->m_oldY = 0;
211
212 win->m_isDragging = true;
213
214 return TRUE;
215 }
216 }
217
218 //-----------------------------------------------------------------------------
219 // "button_release_event" of m_mainWidget
220 //-----------------------------------------------------------------------------
221
222 extern "C" {
223 static gboolean
224 gtk_window_button_release_callback(GtkWidget* widget, GdkEventButton* gdk_event, wxMiniFrame* win)
225 {
226 if (gdk_event->window != gtk_widget_get_window(widget))
227 return false;
228 if (g_blockEventsOnDrag) return TRUE;
229 if (g_blockEventsOnScroll) return TRUE;
230 if (!win->m_isDragging) return TRUE;
231
232 win->m_isDragging = false;
233
234 int x = (int)gdk_event->x;
235 int y = (int)gdk_event->y;
236
237 #ifdef __WXGTK3__
238 gdk_device_ungrab(gdk_event->device, gdk_event->time);
239 #else
240 gdk_pointer_ungrab(gdk_event->time);
241 #endif
242 int org_x = 0;
243 int org_y = 0;
244 gdk_window_get_origin(gtk_widget_get_window(widget), &org_x, &org_y);
245 x += org_x - win->m_diffX;
246 y += org_y - win->m_diffY;
247 win->m_x = x;
248 win->m_y = y;
249 gtk_window_move( GTK_WINDOW(win->m_widget), x, y );
250
251 return TRUE;
252 }
253 }
254
255 //-----------------------------------------------------------------------------
256 // "leave_notify_event" of m_mainWidget
257 //-----------------------------------------------------------------------------
258
259 extern "C" {
260 static gboolean
261 gtk_window_leave_callback(GtkWidget *widget,
262 GdkEventCrossing* gdk_event,
263 wxMiniFrame*)
264 {
265 if (g_blockEventsOnDrag) return FALSE;
266 if (gdk_event->window != gtk_widget_get_window(widget))
267 return false;
268
269 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
270
271 return FALSE;
272 }
273 }
274
275 //-----------------------------------------------------------------------------
276 // "motion_notify_event" of m_mainWidget
277 //-----------------------------------------------------------------------------
278
279 extern "C" {
280 static gboolean
281 gtk_window_motion_notify_callback( GtkWidget *widget, GdkEventMotion *gdk_event, wxMiniFrame *win )
282 {
283 if (gdk_event->window != gtk_widget_get_window(widget))
284 return false;
285 if (g_blockEventsOnDrag) return TRUE;
286 if (g_blockEventsOnScroll) return TRUE;
287
288 int x = int(gdk_event->x);
289 int y = int(gdk_event->y);
290
291 if (gdk_event->is_hint)
292 {
293 #ifdef __WXGTK3__
294 gdk_window_get_device_position(gdk_event->window, gdk_event->device, &x, &y, NULL);
295 #else
296 gdk_window_get_pointer(gdk_event->window, &x, &y, NULL);
297 #endif
298 }
299
300 if (!win->m_isDragging)
301 {
302 if (win->GetWindowStyle() & wxRESIZE_BORDER)
303 {
304 if ((x > win->m_width-14) && (y > win->m_height-14))
305 gdk_window_set_cursor(gtk_widget_get_window(widget), gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER));
306 else
307 gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
308 win->GTKUpdateCursor(false);
309 }
310 return TRUE;
311 }
312
313 win->m_oldX = x - win->m_diffX;
314 win->m_oldY = y - win->m_diffY;
315
316 int org_x = 0;
317 int org_y = 0;
318 gdk_window_get_origin(gtk_widget_get_window(widget), &org_x, &org_y);
319 x += org_x - win->m_diffX;
320 y += org_y - win->m_diffY;
321 win->m_x = x;
322 win->m_y = y;
323 gtk_window_move( GTK_WINDOW(win->m_widget), x, y );
324
325 return TRUE;
326 }
327 }
328
329 //-----------------------------------------------------------------------------
330 // wxMiniFrame
331 //-----------------------------------------------------------------------------
332
333 static unsigned char close_bits[]={
334 0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xfb, 0xef, 0xdb, 0xed, 0x8b, 0xe8,
335 0x1b, 0xec, 0x3b, 0xee, 0x1b, 0xec, 0x8b, 0xe8, 0xdb, 0xed, 0xfb, 0xef,
336 0x07, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
337
338
339 IMPLEMENT_DYNAMIC_CLASS(wxMiniFrame,wxFrame)
340
341 wxMiniFrame::~wxMiniFrame()
342 {
343 if (m_widget)
344 {
345 GtkWidget* eventbox = gtk_bin_get_child(GTK_BIN(m_widget));
346 GTKDisconnect(eventbox);
347 }
348 }
349
350 bool wxMiniFrame::Create( wxWindow *parent, wxWindowID id, const wxString &title,
351 const wxPoint &pos, const wxSize &size,
352 long style, const wxString &name )
353 {
354 m_miniTitle = 0;
355 if (style & wxCAPTION)
356 m_miniTitle = 16;
357
358 if (style & wxRESIZE_BORDER)
359 m_miniEdge = 4;
360 else
361 m_miniEdge = 3;
362 m_isDragging = false;
363 m_oldX = -1;
364 m_oldY = -1;
365 m_diffX = 0;
366 m_diffY = 0;
367
368 // don't allow sizing smaller than decorations
369 int minWidth = 2 * m_miniEdge;
370 int minHeight = 2 * m_miniEdge + m_miniTitle;
371 if (m_minWidth < minWidth)
372 m_minWidth = minWidth;
373 if (m_minHeight < minHeight)
374 m_minHeight = minHeight;
375
376 wxFrame::Create( parent, id, title, pos, size, style, name );
377
378 // Use a GtkEventBox for the title and borders. Using m_widget for this
379 // almost works, except that setting the resize cursor has no effect.
380 GtkWidget* eventbox = gtk_event_box_new();
381 gtk_widget_add_events(eventbox,
382 GDK_POINTER_MOTION_MASK |
383 GDK_POINTER_MOTION_HINT_MASK);
384 gtk_widget_show(eventbox);
385 // Use a GtkAlignment to position m_mainWidget inside the decorations
386 GtkWidget* alignment = gtk_alignment_new(0, 0, 1, 1);
387 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment),
388 m_miniTitle + m_miniEdge, m_miniEdge, m_miniEdge, m_miniEdge);
389 gtk_widget_show(alignment);
390 // The GtkEventBox and GtkAlignment go between m_widget and m_mainWidget
391 gtk_widget_reparent(m_mainWidget, alignment);
392 gtk_container_add(GTK_CONTAINER(eventbox), alignment);
393 gtk_container_add(GTK_CONTAINER(m_widget), eventbox);
394
395 m_gdkDecor = 0;
396 m_gdkFunc = 0;
397 if (style & wxRESIZE_BORDER)
398 m_gdkFunc = GDK_FUNC_RESIZE;
399 gtk_window_set_default_size(GTK_WINDOW(m_widget), m_width, m_height);
400 m_decorSize.Set(0, 0);
401 m_deferShow = false;
402
403 if (m_parent && (GTK_IS_WINDOW(m_parent->m_widget)))
404 {
405 gtk_window_set_transient_for( GTK_WINDOW(m_widget), GTK_WINDOW(m_parent->m_widget) );
406 }
407
408 if (m_miniTitle && (style & wxCLOSE_BOX))
409 {
410 wxImage img = wxBitmap((const char*)close_bits, 16, 16).ConvertToImage();
411 img.Replace(0,0,0,123,123,123);
412 img.SetMaskColour(123,123,123);
413 m_closeButton = wxBitmap( img );
414 }
415
416 /* these are called when the borders are drawn */
417 #ifdef __WXGTK3__
418 g_signal_connect_after(eventbox, "draw", G_CALLBACK(draw), this);
419 #else
420 g_signal_connect_after(eventbox, "expose_event", G_CALLBACK(expose_event), this);
421 #endif
422
423 /* these are required for dragging the mini frame around */
424 g_signal_connect (eventbox, "button_press_event",
425 G_CALLBACK (gtk_window_button_press_callback), this);
426 g_signal_connect (eventbox, "button_release_event",
427 G_CALLBACK (gtk_window_button_release_callback), this);
428 g_signal_connect (eventbox, "motion_notify_event",
429 G_CALLBACK (gtk_window_motion_notify_callback), this);
430 g_signal_connect (eventbox, "leave_notify_event",
431 G_CALLBACK (gtk_window_leave_callback), this);
432 return true;
433 }
434
435 void wxMiniFrame::DoGetClientSize(int* width, int* height) const
436 {
437 wxFrame::DoGetClientSize(width, height);
438
439 if (m_useCachedClientSize)
440 return;
441
442 if (width)
443 {
444 *width -= 2 * m_miniEdge;
445 if (*width < 0) *width = 0;
446 }
447 if (height)
448 {
449 *height -= m_miniTitle + 2 * m_miniEdge;
450 if (*height < 0) *height = 0;
451 }
452 }
453
454 // Keep min size at least as large as decorations
455 void wxMiniFrame::DoSetSizeHints(int minW, int minH, int maxW, int maxH, int incW, int incH)
456 {
457 const int w = 2 * m_miniEdge;
458 const int h = 2 * m_miniEdge + m_miniTitle;
459 if (minW < w) minW = w;
460 if (minH < h) minH = h;
461 wxFrame::DoSetSizeHints(minW, minH, maxW, maxH, incW, incH);
462 }
463
464 void wxMiniFrame::SetTitle( const wxString &title )
465 {
466 wxFrame::SetTitle( title );
467
468 GdkWindow* window = gtk_widget_get_window(gtk_bin_get_child(GTK_BIN(m_widget)));
469 if (window)
470 gdk_window_invalidate_rect(window, NULL, false);
471 }
472
473 #endif // wxUSE_MINIFRAME