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