• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * This file is part of the popup menu implementation for <select> elements in WebCore.
3  *
4  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
5  * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
6  * Copyright (C) 2008 Collabora Ltd.
7  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8  * Copyright (C) 2010 Igalia S.L.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  *
25  */
26 
27 #include "config.h"
28 #include "PopupMenuGtk.h"
29 
30 #include "FrameView.h"
31 #include "GOwnPtr.h"
32 #include "GtkVersioning.h"
33 #include "HostWindow.h"
34 #include "PlatformString.h"
35 #include <gdk/gdk.h>
36 #include <gtk/gtk.h>
37 #include <wtf/text/CString.h>
38 
39 namespace WebCore {
40 
41 static const uint32_t gSearchTimeoutMs = 1000;
42 
PopupMenuGtk(PopupMenuClient * client)43 PopupMenuGtk::PopupMenuGtk(PopupMenuClient* client)
44     : m_popupClient(client)
45     , m_previousKeyEventCharacter(0)
46     , m_currentlySelectedMenuItem(0)
47 {
48 }
49 
~PopupMenuGtk()50 PopupMenuGtk::~PopupMenuGtk()
51 {
52     if (m_popup) {
53         g_signal_handlers_disconnect_matched(m_popup.get(), G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, this);
54         hide();
55     }
56 }
57 
show(const IntRect & rect,FrameView * view,int index)58 void PopupMenuGtk::show(const IntRect& rect, FrameView* view, int index)
59 {
60     ASSERT(client());
61 
62     if (!m_popup) {
63         m_popup = GTK_MENU(gtk_menu_new());
64         g_signal_connect(m_popup.get(), "unmap", G_CALLBACK(PopupMenuGtk::menuUnmapped), this);
65         g_signal_connect(m_popup.get(), "key-press-event", G_CALLBACK(PopupMenuGtk::keyPressEventCallback), this);
66     } else
67         gtk_container_foreach(GTK_CONTAINER(m_popup.get()), reinterpret_cast<GtkCallback>(menuRemoveItem), this);
68 
69     int x = 0;
70     int y = 0;
71     GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(view->hostWindow()->platformPageClient()));
72     if (window)
73         gdk_window_get_origin(window, &x, &y);
74     m_menuPosition = view->contentsToWindow(rect.location());
75     m_menuPosition = IntPoint(m_menuPosition.x() + x, m_menuPosition.y() + y + rect.height());
76     m_indexMap.clear();
77 
78     const int size = client()->listSize();
79     for (int i = 0; i < size; ++i) {
80         GtkWidget* item;
81         if (client()->itemIsSeparator(i))
82             item = gtk_separator_menu_item_new();
83         else
84             item = gtk_menu_item_new_with_label(client()->itemText(i).utf8().data());
85 
86         m_indexMap.add(item, i);
87         g_signal_connect(item, "activate", G_CALLBACK(PopupMenuGtk::menuItemActivated), this);
88         g_signal_connect(item, "select", G_CALLBACK(PopupMenuGtk::selectItemCallback), this);
89 
90         // FIXME: Apply the PopupMenuStyle from client()->itemStyle(i)
91         gtk_widget_set_sensitive(item, client()->itemIsEnabled(i));
92         gtk_menu_shell_append(GTK_MENU_SHELL(m_popup.get()), item);
93         gtk_widget_show(item);
94     }
95 
96     gtk_menu_set_active(m_popup.get(), index);
97 
98 
99     // The size calls are directly copied from gtkcombobox.c which is LGPL
100     GtkRequisition requisition;
101     gtk_widget_set_size_request(GTK_WIDGET(m_popup.get()), -1, -1);
102 #ifdef GTK_API_VERSION_2
103     gtk_widget_size_request(GTK_WIDGET(m_popup.get()), &requisition);
104 #else
105     gtk_widget_get_preferred_size(GTK_WIDGET(m_popup.get()), &requisition, 0);
106 #endif
107 
108     gtk_widget_set_size_request(GTK_WIDGET(m_popup.get()), std::max(rect.width(), requisition.width), -1);
109 
110     GList* children = gtk_container_get_children(GTK_CONTAINER(m_popup.get()));
111     GList* p = children;
112     if (size) {
113         for (int i = 0; i < size; i++) {
114             if (i > index)
115               break;
116 
117             GtkWidget* item = reinterpret_cast<GtkWidget*>(p->data);
118             GtkRequisition itemRequisition;
119 #ifdef GTK_API_VERSION_2
120             gtk_widget_get_child_requisition(item, &itemRequisition);
121 #else
122             gtk_widget_get_preferred_size(item, &itemRequisition, 0);
123 #endif
124             m_menuPosition.setY(m_menuPosition.y() - itemRequisition.height);
125 
126             p = g_list_next(p);
127         }
128     } else {
129         // Center vertically the empty popup in the combo box area
130         m_menuPosition.setY(m_menuPosition.y() - rect.height() / 2);
131     }
132 
133     g_list_free(children);
134     gtk_menu_popup(m_popup.get(), 0, 0, reinterpret_cast<GtkMenuPositionFunc>(menuPositionFunction), this, 0, gtk_get_current_event_time());
135 }
136 
hide()137 void PopupMenuGtk::hide()
138 {
139     ASSERT(m_popup);
140     gtk_menu_popdown(m_popup.get());
141 }
142 
updateFromElement()143 void PopupMenuGtk::updateFromElement()
144 {
145     client()->setTextFromItem(client()->selectedIndex());
146 }
147 
disconnectClient()148 void PopupMenuGtk::disconnectClient()
149 {
150     m_popupClient = 0;
151 }
152 
typeAheadFind(GdkEventKey * event)153 bool PopupMenuGtk::typeAheadFind(GdkEventKey* event)
154 {
155     // If we were given a non-printable character just skip it.
156     gunichar unicodeCharacter = gdk_keyval_to_unicode(event->keyval);
157     if (!unicodeCharacter) {
158         resetTypeAheadFindState();
159         return false;
160     }
161 
162     glong charactersWritten;
163     GOwnPtr<gunichar2> utf16String(g_ucs4_to_utf16(&unicodeCharacter, 1, 0, &charactersWritten, 0));
164     if (!utf16String) {
165         resetTypeAheadFindState();
166         return false;
167     }
168 
169     // If the character is the same as the last character, the user is probably trying to
170     // cycle through the menulist entries. This matches the WebCore behavior for collapsed
171     // menulists.
172     bool repeatingCharacter = unicodeCharacter != m_previousKeyEventCharacter;
173     if (event->time - m_previousKeyEventTimestamp > gSearchTimeoutMs)
174         m_currentSearchString = String(static_cast<UChar*>(utf16String.get()), charactersWritten);
175     else if (repeatingCharacter)
176         m_currentSearchString.append(String(static_cast<UChar*>(utf16String.get()), charactersWritten));
177 
178     m_previousKeyEventTimestamp = event->time;
179     m_previousKeyEventCharacter = unicodeCharacter;
180 
181     // Like the Chromium port, we case fold before searching, because
182     // strncmp does not handle non-ASCII characters.
183     GOwnPtr<gchar> searchStringWithCaseFolded(g_utf8_casefold(m_currentSearchString.utf8().data(), -1));
184     size_t prefixLength = strlen(searchStringWithCaseFolded.get());
185 
186     GList* children = gtk_container_get_children(GTK_CONTAINER(m_popup.get()));
187     if (!children)
188         return true;
189 
190     // If a menu item has already been selected, start searching from the current
191     // item down the list. This will make multiple key presses of the same character
192     // advance the selection.
193     GList* currentChild = children;
194     if (m_currentlySelectedMenuItem) {
195         currentChild = g_list_find(children, m_currentlySelectedMenuItem);
196         if (!currentChild) {
197             m_currentlySelectedMenuItem = 0;
198             currentChild = children;
199         }
200 
201         // Repeating characters should iterate.
202         if (repeatingCharacter) {
203             if (GList* nextChild = g_list_next(currentChild))
204                 currentChild = nextChild;
205         }
206     }
207 
208     GList* firstChild = currentChild;
209     do {
210         currentChild = g_list_next(currentChild);
211         if (!currentChild)
212             currentChild = children;
213 
214         GOwnPtr<gchar> itemText(g_utf8_casefold(gtk_menu_item_get_label(GTK_MENU_ITEM(currentChild->data)), -1));
215         if (!strncmp(searchStringWithCaseFolded.get(), itemText.get(), prefixLength)) {
216             gtk_menu_shell_select_item(GTK_MENU_SHELL(m_popup.get()), GTK_WIDGET(currentChild->data));
217             return true;
218         }
219     } while (currentChild != firstChild);
220 
221     return true;
222 }
223 
menuItemActivated(GtkMenuItem * item,PopupMenuGtk * that)224 void PopupMenuGtk::menuItemActivated(GtkMenuItem* item, PopupMenuGtk* that)
225 {
226     ASSERT(that->client());
227     ASSERT(that->m_indexMap.contains(GTK_WIDGET(item)));
228     that->client()->valueChanged(that->m_indexMap.get(GTK_WIDGET(item)));
229 }
230 
menuUnmapped(GtkWidget *,PopupMenuGtk * that)231 void PopupMenuGtk::menuUnmapped(GtkWidget*, PopupMenuGtk* that)
232 {
233     ASSERT(that->client());
234     that->resetTypeAheadFindState();
235     that->client()->popupDidHide();
236 }
237 
menuPositionFunction(GtkMenu *,gint * x,gint * y,gboolean * pushIn,PopupMenuGtk * that)238 void PopupMenuGtk::menuPositionFunction(GtkMenu*, gint* x, gint* y, gboolean* pushIn, PopupMenuGtk* that)
239 {
240     *x = that->m_menuPosition.x();
241     *y = that->m_menuPosition.y();
242     *pushIn = true;
243 }
244 
resetTypeAheadFindState()245 void PopupMenuGtk::resetTypeAheadFindState()
246 {
247     m_currentlySelectedMenuItem = 0;
248     m_previousKeyEventCharacter = 0;
249     m_currentSearchString = "";
250 }
251 
menuRemoveItem(GtkWidget * widget,PopupMenuGtk * that)252 void PopupMenuGtk::menuRemoveItem(GtkWidget* widget, PopupMenuGtk* that)
253 {
254     ASSERT(that->m_popup);
255     gtk_container_remove(GTK_CONTAINER(that->m_popup.get()), widget);
256 }
257 
selectItemCallback(GtkMenuItem * item,PopupMenuGtk * that)258 int PopupMenuGtk::selectItemCallback(GtkMenuItem* item, PopupMenuGtk* that)
259 {
260     that->m_currentlySelectedMenuItem = GTK_WIDGET(item);
261     return FALSE;
262 }
263 
keyPressEventCallback(GtkWidget * widget,GdkEventKey * event,PopupMenuGtk * that)264 int PopupMenuGtk::keyPressEventCallback(GtkWidget* widget, GdkEventKey* event, PopupMenuGtk* that)
265 {
266     return that->typeAheadFind(event);
267 }
268 
269 }
270 
271