1/////////////////////////////////////////////////////////////////////////////
2// Name:        src/gtk/popupwin.cpp
3// Purpose:
4// Author:      Robert Roebling
5// Id:          $Id: popupwin.cpp 41045 2006-09-07 16:06:47Z PC $
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_POPUPWIN
14
15#include "wx/popupwin.h"
16
17#ifndef WX_PRECOMP
18    #include "wx/app.h"
19    #include "wx/frame.h"
20    #include "wx/cursor.h"
21#endif // WX_PRECOMP
22
23#include <gdk/gdk.h>
24#include <gtk/gtk.h>
25#include <gdk/gdkkeysyms.h>
26
27#include "wx/gtk/private.h" //for idle stuff
28#include "wx/gtk/win_gtk.h"
29
30//-----------------------------------------------------------------------------
31// "button_press"
32//-----------------------------------------------------------------------------
33
34extern "C" {
35static gint gtk_popup_button_press (GtkWidget *widget, GdkEvent *gdk_event, wxPopupWindow* win )
36{
37    GtkWidget *child = gtk_get_event_widget (gdk_event);
38
39    /* Ignore events sent out before we connected to the signal */
40    if (win->m_time >= ((GdkEventButton*)gdk_event)->time)
41        return FALSE;
42
43    /*  We don't ask for button press events on the grab widget, so
44     *  if an event is reported directly to the grab widget, it must
45     *  be on a window outside the application (and thus we remove
46     *  the popup window). Otherwise, we check if the widget is a child
47     *  of the grab widget, and only remove the popup window if it
48     *  is not. */
49    if (child != widget)
50    {
51        while (child)
52        {
53            if (child == widget)
54                return FALSE;
55            child = child->parent;
56        }
57    }
58
59    wxFocusEvent event( wxEVT_KILL_FOCUS, win->GetId() );
60    event.SetEventObject( win );
61
62    (void)win->GetEventHandler()->ProcessEvent( event );
63
64    return TRUE;
65}
66}
67
68//-----------------------------------------------------------------------------
69// "focus" from m_window
70//-----------------------------------------------------------------------------
71
72extern "C" {
73static gint gtk_dialog_focus_callback( GtkWidget *widget, GtkDirectionType WXUNUSED(d), wxWindow *WXUNUSED(win) )
74{
75    if (g_isIdle)
76        wxapp_install_idle_handler();
77
78    /* This disables GTK's tab traversal */
79    return TRUE;
80}
81}
82
83//-----------------------------------------------------------------------------
84// "delete_event"
85//-----------------------------------------------------------------------------
86
87extern "C" {
88bool gtk_dialog_delete_callback( GtkWidget *WXUNUSED(widget), GdkEvent *WXUNUSED(event), wxPopupWindow *win )
89{
90    if (g_isIdle)
91        wxapp_install_idle_handler();
92
93    if (win->IsEnabled())
94        win->Close();
95
96    return TRUE;
97}
98}
99
100//-----------------------------------------------------------------------------
101// "realize" from m_widget
102//-----------------------------------------------------------------------------
103
104/* we cannot MWM hints and icons before the widget has been realized,
105   so we do this directly after realization */
106
107extern "C" {
108static gint
109gtk_dialog_realized_callback( GtkWidget * WXUNUSED(widget), wxPopupWindow *win )
110{
111    if (g_isIdle)
112        wxapp_install_idle_handler();
113
114    /* all this is for Motif Window Manager "hints" and is supposed to be
115       recognized by other WM as well. not tested. */
116    long decor = (long) GDK_DECOR_BORDER;
117    long func = (long) GDK_FUNC_MOVE ;
118
119    gdk_window_set_decorations( win->m_widget->window, (GdkWMDecoration)decor);
120    gdk_window_set_functions( win->m_widget->window, (GdkWMFunction)func);
121
122    gtk_window_set_resizable(GTK_WINDOW(win->m_widget), FALSE);
123
124    return FALSE;
125}
126}
127
128//-----------------------------------------------------------------------------
129// InsertChild for wxPopupWindow
130//-----------------------------------------------------------------------------
131
132/* Callback for wxFrame. This very strange beast has to be used because
133 * C++ has no virtual methods in a constructor. We have to emulate a
134 * virtual function here as wxWidgets requires different ways to insert
135 * a child in container classes. */
136
137static void wxInsertChildInDialog( wxPopupWindow* parent, wxWindow* child )
138{
139    gtk_pizza_put( GTK_PIZZA(parent->m_wxwindow),
140                   GTK_WIDGET(child->m_widget),
141                   child->m_x,
142                   child->m_y,
143                   child->m_width,
144                   child->m_height );
145
146    if (parent->HasFlag(wxTAB_TRAVERSAL))
147    {
148        /* we now allow a window to get the focus as long as it
149           doesn't have any children. */
150        GTK_WIDGET_UNSET_FLAGS( parent->m_wxwindow, GTK_CAN_FOCUS );
151    }
152}
153
154//-----------------------------------------------------------------------------
155// wxPopupWindow
156//-----------------------------------------------------------------------------
157
158BEGIN_EVENT_TABLE(wxPopupWindow,wxPopupWindowBase)
159#ifdef __WXUNIVERSAL__
160    EVT_SIZE(wxPopupWindow::OnSize)
161#endif
162END_EVENT_TABLE()
163
164wxPopupWindow::~wxPopupWindow()
165{
166}
167
168bool wxPopupWindow::Create( wxWindow *parent, int style )
169{
170    m_needParent = false;
171
172    if (!PreCreation( parent, wxDefaultPosition, wxDefaultSize ) ||
173        !CreateBase( parent, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT("popup") ))
174    {
175        wxFAIL_MSG( wxT("wxPopupWindow creation failed") );
176        return false;
177    }
178
179    // Unlike windows, top level windows are created hidden by default.
180    m_isShown = false;
181
182    // All dialogs should really have this style
183    m_windowStyle |= wxTAB_TRAVERSAL;
184
185    m_insertCallback = (wxInsertChildFunction) wxInsertChildInDialog;
186
187    m_widget = gtk_window_new( GTK_WINDOW_POPUP );
188
189    if ((m_parent) && (GTK_IS_WINDOW(m_parent->m_widget)))
190        gtk_window_set_transient_for( GTK_WINDOW(m_widget), GTK_WINDOW(m_parent->m_widget) );
191
192    GTK_WIDGET_UNSET_FLAGS( m_widget, GTK_CAN_FOCUS );
193
194    g_signal_connect (m_widget, "delete_event",
195                      G_CALLBACK (gtk_dialog_delete_callback), this);
196
197    m_wxwindow = gtk_pizza_new();
198    gtk_widget_show( m_wxwindow );
199    GTK_WIDGET_UNSET_FLAGS( m_wxwindow, GTK_CAN_FOCUS );
200
201    gtk_container_add( GTK_CONTAINER(m_widget), m_wxwindow );
202
203    if (m_parent) m_parent->AddChild( this );
204
205    PostCreation();
206
207    /*  we cannot set MWM hints  before the widget has
208        been realized, so we do this directly after realization */
209    g_signal_connect (m_widget, "realize",
210                      G_CALLBACK (gtk_dialog_realized_callback), this);
211
212    // disable native tab traversal
213    g_signal_connect (m_widget, "focus",
214                      G_CALLBACK (gtk_dialog_focus_callback), this);
215
216    m_time = gtk_get_current_event_time();
217
218    g_signal_connect (m_widget, "button_press_event",
219                      G_CALLBACK (gtk_popup_button_press), this);
220
221    return true;
222}
223
224void wxPopupWindow::DoMoveWindow(int WXUNUSED(x), int WXUNUSED(y), int WXUNUSED(width), int WXUNUSED(height) )
225{
226    wxFAIL_MSG( wxT("DoMoveWindow called for wxPopupWindow") );
227}
228
229void wxPopupWindow::DoSetSize( int x, int y, int width, int height, int sizeFlags )
230{
231    wxASSERT_MSG( (m_widget != NULL), wxT("invalid dialog") );
232    wxASSERT_MSG( (m_wxwindow != NULL), wxT("invalid dialog") );
233
234    if (m_resizing) return; /* I don't like recursions */
235    m_resizing = true;
236
237    int old_x = m_x;
238    int old_y = m_y;
239
240    int old_width = m_width;
241    int old_height = m_height;
242
243    if ((sizeFlags & wxSIZE_ALLOW_MINUS_ONE) == 0)
244    {
245        if (x != -1) m_x = x;
246        if (y != -1) m_y = y;
247        if (width != -1) m_width = width;
248        if (height != -1) m_height = height;
249    }
250    else
251    {
252        m_x = x;
253        m_y = y;
254        m_width = width;
255        m_height = height;
256    }
257
258/*
259    if ((sizeFlags & wxSIZE_AUTO_WIDTH) == wxSIZE_AUTO_WIDTH)
260    {
261        if (width == -1) m_width = 80;
262    }
263
264    if ((sizeFlags & wxSIZE_AUTO_HEIGHT) == wxSIZE_AUTO_HEIGHT)
265    {
266       if (height == -1) m_height = 26;
267    }
268*/
269
270    int minWidth = GetMinWidth(),
271        minHeight = GetMinHeight(),
272        maxWidth = GetMaxWidth(),
273        maxHeight = GetMaxHeight();
274
275    if ((minWidth != -1) && (m_width < minWidth)) m_width = minWidth;
276    if ((minHeight != -1) && (m_height < minHeight)) m_height = minHeight;
277    if ((maxWidth != -1) && (m_width > maxWidth)) m_width = maxWidth;
278    if ((maxHeight != -1) && (m_height > maxHeight)) m_height = maxHeight;
279
280    if ((m_x != -1) || (m_y != -1))
281    {
282        if ((m_x != old_x) || (m_y != old_y))
283        {
284            /* we set the position here and when showing the dialog
285               for the first time in idle time */
286            // Where does that happen in idle time? I do not see it anywhere - MR
287            gtk_window_move( GTK_WINDOW(m_widget), m_x, m_y );
288        }
289    }
290
291    if ((m_width != old_width) || (m_height != old_height))
292    {
293        gtk_widget_set_size_request( m_widget, m_width, m_height );
294
295        /* actual resizing is deferred to GtkOnSize in idle time and
296           when showing the dialog */
297        m_sizeSet = false;
298
299    }
300
301    m_resizing = false;
302}
303
304void wxPopupWindow::GtkOnSize()
305{
306    if (m_sizeSet) return;
307    if (!m_wxwindow) return;
308
309    /* FIXME: is this a hack? */
310    /* Since for some reason GTK will revert to using maximum size ever set
311       for this window, we have to set geometry hints maxsize to match size
312       given. Also set the to that minsize since resizing isn't possible
313       anyway. */
314
315    /* set size hints */
316    gint flag = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE; // GDK_HINT_POS;
317    GdkGeometry geom;
318    geom.min_width = m_width;
319    geom.min_height = m_height;
320    geom.max_width = m_width;
321    geom.max_height = m_height;
322    gtk_window_set_geometry_hints( GTK_WINDOW(m_widget),
323                                   (GtkWidget*) NULL,
324                                   &geom,
325                                   (GdkWindowHints) flag );
326
327
328    m_sizeSet = true;
329
330    wxSizeEvent event( wxSize(m_width,m_height), GetId() );
331    event.SetEventObject( this );
332    GetEventHandler()->ProcessEvent( event );
333}
334
335void wxPopupWindow::OnInternalIdle()
336{
337    if (!m_sizeSet && GTK_WIDGET_REALIZED(m_wxwindow))
338        GtkOnSize();
339
340    wxWindow::OnInternalIdle();
341}
342
343bool wxPopupWindow::Show( bool show )
344{
345    if (show && !m_sizeSet)
346    {
347        /* by calling GtkOnSize here, we don't have to call
348           either after showing the frame, which would entail
349           much ugly flicker nor from within the size_allocate
350           handler, because GTK 1.1.X forbids that. */
351
352        GtkOnSize();
353    }
354
355    bool ret = wxWindow::Show( show );
356
357    return ret;
358}
359
360#endif // wxUSE_POPUPWIN
361