1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/gtk/minifram.cpp
3 // Purpose:
4 // Author:      Robert Roebling
5 // Copyright:   (c) 1998 Robert Roebling
6 // Licence:     wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx.h".
10 #include "wx/wxprec.h"
11 
12 #if wxUSE_MINIFRAME
13 
14 #include "wx/minifram.h"
15 
16 #ifndef WX_PRECOMP
17     #include "wx/settings.h"
18     #include "wx/dcclient.h"
19 #endif
20 
21 #ifdef __WXGTK3__
22 #include "wx/gtk/dc.h"
23 #else
24 #include "wx/gtk/dcclient.h"
25 #endif
26 
27 #include <gtk/gtk.h>
28 #include "wx/gtk/private/gtk2-compat.h"
29 #include "wx/gtk/private/gtk3-compat.h"
30 
31 //-----------------------------------------------------------------------------
32 // data
33 //-----------------------------------------------------------------------------
34 
35 extern bool        g_blockEventsOnDrag;
36 extern bool        g_blockEventsOnScroll;
37 
38 //-----------------------------------------------------------------------------
39 // "expose_event" of m_mainWidget
40 //-----------------------------------------------------------------------------
41 
42 extern "C" {
43 #ifdef __WXGTK3__
draw(GtkWidget * widget,cairo_t * cr,wxMiniFrame * win)44 static gboolean draw(GtkWidget* widget, cairo_t* cr, wxMiniFrame* win)
45 #else
46 static gboolean expose_event(GtkWidget* widget, GdkEventExpose* gdk_event, wxMiniFrame* win)
47 #endif
48 {
49 #ifdef __WXGTK3__
50     if (!gtk_cairo_should_draw_window(cr, gtk_widget_get_window(widget)))
51         return false;
52 
53     GtkStyleContext* sc = gtk_widget_get_style_context(widget);
54     gtk_style_context_save(sc);
55     gtk_style_context_add_class(sc, GTK_STYLE_CLASS_BUTTON);
56     gtk_render_frame(sc, cr, 0, 0, win->m_width, win->m_height);
57     gtk_style_context_restore(sc);
58 
59     wxGTKCairoDC dc(cr);
60 #else
61     if (gdk_event->count > 0 ||
62         gdk_event->window != gtk_widget_get_window(widget))
63     {
64         return false;
65     }
66 
67     gtk_paint_shadow (gtk_widget_get_style(widget),
68                       gtk_widget_get_window(widget),
69                       GTK_STATE_NORMAL,
70                       GTK_SHADOW_OUT,
71                       NULL, NULL, NULL, // FIXME: No clipping?
72                       0, 0,
73                       win->m_width, win->m_height);
74 
75     wxClientDC dc(win);
76 
77     wxDCImpl *impl = dc.GetImpl();
78     wxClientDCImpl *gtk_impl = wxDynamicCast( impl, wxClientDCImpl );
79     gtk_impl->m_gdkwindow = gtk_widget_get_window(widget); // Hack alert
80 #endif
81 
82     int style = win->GetWindowStyle();
83 
84     if (style & wxRESIZE_BORDER)
85     {
86         dc.SetBrush( *wxGREY_BRUSH );
87         dc.SetPen( *wxTRANSPARENT_PEN );
88         dc.DrawRectangle(win->m_width - 14, win->m_height - win->m_miniEdge, 14, win->m_miniEdge);
89         dc.DrawRectangle(win->m_width - win->m_miniEdge, win->m_height - 14, win->m_miniEdge, 14);
90     }
91 
92     if (win->m_miniTitle && !win->GetTitle().empty())
93     {
94         dc.SetFont( *wxSMALL_FONT );
95 
96         wxBrush brush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
97         dc.SetBrush( brush );
98         dc.SetPen( *wxTRANSPARENT_PEN );
99         dc.DrawRectangle( win->m_miniEdge-1,
100                           win->m_miniEdge-1,
101                           win->m_width - (2*(win->m_miniEdge-1)),
102                           15  );
103 
104         const wxColour textColor = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
105         dc.SetTextForeground(textColor);
106         dc.DrawText( win->GetTitle(), 6, 4 );
107 
108         if (style & wxCLOSE_BOX)
109         {
110             dc.SetTextBackground(textColor);
111             dc.DrawBitmap( win->m_closeButton, win->m_width-18, 3, true );
112         }
113     }
114 
115     return false;
116 }
117 }
118 
119 //-----------------------------------------------------------------------------
120 // "button_press_event" of m_mainWidget
121 //-----------------------------------------------------------------------------
122 
123 extern "C" {
124 static gboolean
gtk_window_button_press_callback(GtkWidget * widget,GdkEventButton * gdk_event,wxMiniFrame * win)125 gtk_window_button_press_callback(GtkWidget* widget, GdkEventButton* gdk_event, wxMiniFrame* win)
126 {
127     if (gdk_event->window != gtk_widget_get_window(widget))
128         return false;
129     if (g_blockEventsOnDrag) return TRUE;
130     if (g_blockEventsOnScroll) return TRUE;
131 
132     if (win->m_isDragging) return TRUE;
133 
134     int style = win->GetWindowStyle();
135 
136     int y = (int)gdk_event->y;
137     int x = (int)gdk_event->x;
138 
139     if ((style & wxRESIZE_BORDER) &&
140         (x > win->m_width-14) && (y > win->m_height-14))
141     {
142         GtkWidget *ancestor = gtk_widget_get_toplevel( widget );
143 
144         GdkWindow *source = gtk_widget_get_window(widget);
145 
146         int org_x = 0;
147         int org_y = 0;
148         gdk_window_get_origin( source, &org_x, &org_y );
149 
150         gtk_window_begin_resize_drag (GTK_WINDOW (ancestor),
151                                   GDK_WINDOW_EDGE_SOUTH_EAST,
152                                   1,
153                                   org_x + x,
154                                   org_y + y,
155                                   0);
156 
157         return TRUE;
158     }
159 
160     if (win->m_miniTitle && (style & wxCLOSE_BOX))
161     {
162         if ((y > 3) && (y < 19) && (x > win->m_width-19) && (x < win->m_width-3))
163         {
164             win->Close();
165             return TRUE;
166         }
167     }
168 
169     if (y >= win->m_miniEdge + win->m_miniTitle)
170         return true;
171 
172     gdk_window_raise(gtk_widget_get_window(win->m_widget));
173 
174     const GdkEventMask mask = GdkEventMask(
175         GDK_BUTTON_PRESS_MASK |
176         GDK_BUTTON_RELEASE_MASK |
177         GDK_POINTER_MOTION_MASK |
178         GDK_POINTER_MOTION_HINT_MASK |
179         GDK_BUTTON_MOTION_MASK |
180         GDK_BUTTON1_MOTION_MASK);
181 #ifdef __WXGTK3__
182     gdk_device_grab(
183         gdk_event->device, gdk_event->window, GDK_OWNERSHIP_NONE,
184         false, mask, NULL, gdk_event->time);
185 #else
186     gdk_pointer_grab(gdk_event->window, false, mask, NULL, NULL, gdk_event->time);
187 #endif
188 
189     win->m_diffX = x;
190     win->m_diffY = y;
191     win->m_oldX = 0;
192     win->m_oldY = 0;
193 
194     win->m_isDragging = true;
195 
196     return TRUE;
197 }
198 }
199 
200 //-----------------------------------------------------------------------------
201 // "button_release_event" of m_mainWidget
202 //-----------------------------------------------------------------------------
203 
204 extern "C" {
205 static gboolean
gtk_window_button_release_callback(GtkWidget * widget,GdkEventButton * gdk_event,wxMiniFrame * win)206 gtk_window_button_release_callback(GtkWidget* widget, GdkEventButton* gdk_event, wxMiniFrame* win)
207 {
208     if (gdk_event->window != gtk_widget_get_window(widget))
209         return false;
210     if (g_blockEventsOnDrag) return TRUE;
211     if (g_blockEventsOnScroll) return TRUE;
212     if (!win->m_isDragging) return TRUE;
213 
214     win->m_isDragging = false;
215 
216     int x = (int)gdk_event->x;
217     int y = (int)gdk_event->y;
218 
219 #ifdef __WXGTK3__
220     gdk_device_ungrab(gdk_event->device, gdk_event->time);
221 #else
222     gdk_pointer_ungrab(gdk_event->time);
223 #endif
224     int org_x = 0;
225     int org_y = 0;
226     gdk_window_get_origin(gtk_widget_get_window(widget), &org_x, &org_y);
227     x += org_x - win->m_diffX;
228     y += org_y - win->m_diffY;
229     gtk_window_move( GTK_WINDOW(win->m_widget), x, y );
230 
231     return TRUE;
232 }
233 }
234 
235 //-----------------------------------------------------------------------------
236 // "leave_notify_event" of m_mainWidget
237 //-----------------------------------------------------------------------------
238 
239 extern "C" {
240 static gboolean
gtk_window_leave_callback(GtkWidget * widget,GdkEventCrossing * gdk_event,wxMiniFrame *)241 gtk_window_leave_callback(GtkWidget *widget,
242                           GdkEventCrossing* gdk_event,
243                           wxMiniFrame*)
244 {
245     if (g_blockEventsOnDrag) return FALSE;
246     if (gdk_event->window != gtk_widget_get_window(widget))
247         return false;
248 
249     gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
250 
251     return FALSE;
252 }
253 }
254 
255 //-----------------------------------------------------------------------------
256 // "motion_notify_event" of m_mainWidget
257 //-----------------------------------------------------------------------------
258 
259 extern "C" {
260 static gboolean
gtk_window_motion_notify_callback(GtkWidget * widget,GdkEventMotion * gdk_event,wxMiniFrame * win)261 gtk_window_motion_notify_callback( GtkWidget *widget, GdkEventMotion *gdk_event, wxMiniFrame *win )
262 {
263     if (gdk_event->window != gtk_widget_get_window(widget))
264         return false;
265     if (g_blockEventsOnDrag) return TRUE;
266     if (g_blockEventsOnScroll) return TRUE;
267 
268     int x = int(gdk_event->x);
269     int y = int(gdk_event->y);
270 
271     if (gdk_event->is_hint)
272     {
273 #ifdef __WXGTK3__
274        gdk_window_get_device_position(gdk_event->window, gdk_event->device, &x, &y, NULL);
275 #else
276        gdk_window_get_pointer(gdk_event->window, &x, &y, NULL);
277 #endif
278     }
279 
280     if (!win->m_isDragging)
281     {
282         if (win->GetWindowStyle() & wxRESIZE_BORDER)
283         {
284             GdkCursor* cursor = NULL;
285             GdkWindow* window = gtk_widget_get_window(widget);
286             if ((x > win->m_width-14) && (y > win->m_height-14))
287             {
288                 GdkDisplay* display = gdk_window_get_display(window);
289                 cursor = gdk_cursor_new_for_display(display, GDK_BOTTOM_RIGHT_CORNER);
290             }
291             gdk_window_set_cursor(window, cursor);
292             if (cursor)
293             {
294 #ifdef __WXGTK3__
295                 g_object_unref(cursor);
296 #else
297                 gdk_cursor_unref(cursor);
298 #endif
299             }
300         }
301         return TRUE;
302     }
303 
304     win->m_oldX = x - win->m_diffX;
305     win->m_oldY = y - win->m_diffY;
306 
307     int org_x = 0;
308     int org_y = 0;
309     gdk_window_get_origin(gtk_widget_get_window(widget), &org_x, &org_y);
310     x += org_x - win->m_diffX;
311     y += org_y - win->m_diffY;
312     gtk_window_move( GTK_WINDOW(win->m_widget), x, y );
313 
314     return TRUE;
315 }
316 }
317 
318 //-----------------------------------------------------------------------------
319 // wxMiniFrame
320 //-----------------------------------------------------------------------------
321 
322 static unsigned char close_bits[]={
323     0xff, 0xff, 0xff, 0xff, 0x07, 0xf0, 0xfb, 0xef, 0xdb, 0xed, 0x8b, 0xe8,
324     0x1b, 0xec, 0x3b, 0xee, 0x1b, 0xec, 0x8b, 0xe8, 0xdb, 0xed, 0xfb, 0xef,
325     0x07, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
326 
327 
IMPLEMENT_DYNAMIC_CLASS(wxMiniFrame,wxFrame)328 IMPLEMENT_DYNAMIC_CLASS(wxMiniFrame,wxFrame)
329 
330 wxMiniFrame::~wxMiniFrame()
331 {
332     if (m_widget)
333     {
334         GtkWidget* eventbox = gtk_bin_get_child(GTK_BIN(m_widget));
335         GTKDisconnect(eventbox);
336     }
337 }
338 
Create(wxWindow * parent,wxWindowID id,const wxString & title,const wxPoint & pos,const wxSize & size,long style,const wxString & name)339 bool wxMiniFrame::Create( wxWindow *parent, wxWindowID id, const wxString &title,
340       const wxPoint &pos, const wxSize &size,
341       long style, const wxString &name )
342 {
343     m_miniTitle = 0;
344     if (style & wxCAPTION)
345         m_miniTitle = 16;
346 
347     if (style & wxRESIZE_BORDER)
348         m_miniEdge = 4;
349     else
350         m_miniEdge = 3;
351     m_isDragging = false;
352     m_oldX = -1;
353     m_oldY = -1;
354     m_diffX = 0;
355     m_diffY = 0;
356 
357     // don't allow sizing smaller than decorations
358     int minWidth = 2 * m_miniEdge;
359     int minHeight = 2 * m_miniEdge + m_miniTitle;
360     if (m_minWidth < minWidth)
361         m_minWidth = minWidth;
362     if (m_minHeight < minHeight)
363         m_minHeight = minHeight;
364 
365     wxFrame::Create( parent, id, title, pos, size, style, name );
366 
367     // Use a GtkEventBox for the title and borders. Using m_widget for this
368     // almost works, except that setting the resize cursor has no effect.
369     GtkWidget* eventbox = gtk_event_box_new();
370     gtk_widget_add_events(eventbox,
371         GDK_POINTER_MOTION_MASK |
372         GDK_POINTER_MOTION_HINT_MASK);
373     gtk_widget_show(eventbox);
374 #ifdef __WXGTK3__
375     g_object_ref(m_mainWidget);
376     gtk_container_remove(GTK_CONTAINER(m_widget), m_mainWidget);
377     gtk_container_add(GTK_CONTAINER(eventbox), m_mainWidget);
378     g_object_unref(m_mainWidget);
379 
380     gtk_widget_set_margin_start(m_mainWidget, m_miniEdge);
381     gtk_widget_set_margin_end(m_mainWidget, m_miniEdge);
382     gtk_widget_set_margin_top(m_mainWidget, m_miniTitle + m_miniEdge);
383     gtk_widget_set_margin_bottom(m_mainWidget, m_miniEdge);
384 #else
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 #endif
394     gtk_container_add(GTK_CONTAINER(m_widget), eventbox);
395 
396     m_gdkDecor = 0;
397     gtk_window_set_decorated(GTK_WINDOW(m_widget), false);
398     m_gdkFunc = 0;
399     if (style & wxRESIZE_BORDER)
400        m_gdkFunc = GDK_FUNC_RESIZE;
401     gtk_window_set_default_size(GTK_WINDOW(m_widget), m_width, m_height);
402     memset(&m_decorSize, 0, sizeof(m_decorSize));
403     m_deferShow = false;
404 
405     if (m_parent && (GTK_IS_WINDOW(m_parent->m_widget)))
406     {
407         gtk_window_set_transient_for( GTK_WINDOW(m_widget), GTK_WINDOW(m_parent->m_widget) );
408     }
409 
410     if (m_miniTitle && (style & wxCLOSE_BOX))
411     {
412         m_closeButton = wxBitmap((const char*)close_bits, 16, 16);
413         m_closeButton.SetMask(new wxMask(m_closeButton));
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 
DoGetClientSize(int * width,int * height) const435 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
DoSetSizeHints(int minW,int minH,int maxW,int maxH,int incW,int incH)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 
SetTitle(const wxString & title)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
474