Name all the GLib timeout sources
[WebKit-https.git] / Source / WebCore / platform / gtk / PasteboardHelper.cpp
1 /*
2  * Copyright (C) 2010 Martin Robinson <mrobinson@webkit.org>
3  * Copyright (C) Igalia S.L.
4  * All rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  *
21  */
22 #include "config.h"
23 #include "PasteboardHelper.h"
24
25 #include "Chrome.h"
26 #include "DataObjectGtk.h"
27 #include "Frame.h"
28 #include "GtkVersioning.h"
29 #include "Page.h"
30 #include "Pasteboard.h"
31 #include "TextResourceDecoder.h"
32 #include <gtk/gtk.h>
33 #include <wtf/gobject/GOwnPtr.h>
34
35 namespace WebCore {
36
37 static GdkAtom textPlainAtom;
38 static GdkAtom markupAtom;
39 static GdkAtom netscapeURLAtom;
40 static GdkAtom uriListAtom;
41 static GdkAtom smartPasteAtom;
42 static String gMarkupPrefix;
43
44 static void removeMarkupPrefix(String& markup)
45 {
46     // The markup prefix is not harmful, but we remove it from the string anyway, so that
47     // we can have consistent results with other ports during the layout tests.
48     if (markup.startsWith(gMarkupPrefix))
49         markup.remove(0, gMarkupPrefix.length());
50 }
51
52 static void initGdkAtoms()
53 {
54     static gboolean initialized = FALSE;
55
56     if (initialized)
57         return;
58
59     initialized = TRUE;
60
61     textPlainAtom = gdk_atom_intern("text/plain;charset=utf-8", FALSE);
62     markupAtom = gdk_atom_intern("text/html", FALSE);
63     netscapeURLAtom = gdk_atom_intern("_NETSCAPE_URL", FALSE);
64     uriListAtom = gdk_atom_intern("text/uri-list", FALSE);
65     smartPasteAtom = gdk_atom_intern("application/vnd.webkitgtk.smartpaste", FALSE);
66     gMarkupPrefix = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">";
67 }
68
69 PasteboardHelper* PasteboardHelper::defaultPasteboardHelper()
70 {
71     DEFINE_STATIC_LOCAL(PasteboardHelper, defaultHelper, ());
72     return &defaultHelper;
73 }
74
75 PasteboardHelper::PasteboardHelper()
76     : m_targetList(gtk_target_list_new(0, 0))
77 {
78     initGdkAtoms();
79
80     gtk_target_list_add_text_targets(m_targetList, PasteboardHelper::TargetTypeText);
81     gtk_target_list_add(m_targetList, markupAtom, 0, PasteboardHelper::TargetTypeMarkup);
82     gtk_target_list_add_uri_targets(m_targetList, PasteboardHelper::TargetTypeURIList);
83     gtk_target_list_add(m_targetList, netscapeURLAtom, 0, PasteboardHelper::TargetTypeNetscapeURL);
84     gtk_target_list_add_image_targets(m_targetList, PasteboardHelper::TargetTypeImage, TRUE);
85 }
86
87 PasteboardHelper::~PasteboardHelper()
88 {
89     gtk_target_list_unref(m_targetList);
90 }
91
92 static inline GdkDisplay* displayFromFrame(Frame* frame)
93 {
94     ASSERT(frame);
95     Page* page = frame->page();
96     ASSERT(page);
97     PlatformPageClient client = page->chrome().platformPageClient();
98     return client ? gtk_widget_get_display(client) : gdk_display_get_default();
99 }
100
101 GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const
102 {
103     return gtk_clipboard_get_for_display(displayFromFrame(frame), GDK_SELECTION_PRIMARY);
104 }
105
106 GtkTargetList* PasteboardHelper::targetList() const
107 {
108     return m_targetList;
109 }
110
111 static String selectionDataToUTF8String(GtkSelectionData* data)
112 {
113     // g_strndup guards against selection data that is not null-terminated.
114     GOwnPtr<gchar> markupString(g_strndup(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)), gtk_selection_data_get_length(data)));
115     return String::fromUTF8(markupString.get());
116 }
117
118 void PasteboardHelper::getClipboardContents(GtkClipboard* clipboard)
119 {
120     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
121     ASSERT(dataObject);
122
123     if (gtk_clipboard_wait_is_text_available(clipboard)) {
124         GOwnPtr<gchar> textData(gtk_clipboard_wait_for_text(clipboard));
125         if (textData)
126             dataObject->setText(String::fromUTF8(textData.get()));
127     }
128
129     if (gtk_clipboard_wait_is_target_available(clipboard, markupAtom)) {
130         if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, markupAtom)) {
131             String markup(selectionDataToUTF8String(data));
132             removeMarkupPrefix(markup);
133             dataObject->setMarkup(markup);
134             gtk_selection_data_free(data);
135         }
136     }
137
138     if (gtk_clipboard_wait_is_target_available(clipboard, uriListAtom)) {
139         if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, uriListAtom)) {
140             dataObject->setURIList(selectionDataToUTF8String(data));
141             gtk_selection_data_free(data);
142         }
143     }
144 }
145
146 void PasteboardHelper::fillSelectionData(GtkSelectionData* selectionData, guint info, DataObjectGtk* dataObject)
147 {
148     if (info == TargetTypeText)
149         gtk_selection_data_set_text(selectionData, dataObject->text().utf8().data(), -1);
150
151     else if (info == TargetTypeMarkup) {
152         // Some Linux applications refuse to accept pasted markup unless it is
153         // prefixed by a content-type meta tag.
154         CString markup = String(gMarkupPrefix + dataObject->markup()).utf8();
155         gtk_selection_data_set(selectionData, markupAtom, 8,
156             reinterpret_cast<const guchar*>(markup.data()), markup.length());
157
158     } else if (info == TargetTypeURIList) {
159         CString uriList = dataObject->uriList().utf8();
160         gtk_selection_data_set(selectionData, uriListAtom, 8,
161             reinterpret_cast<const guchar*>(uriList.data()), uriList.length());
162
163     } else if (info == TargetTypeNetscapeURL && dataObject->hasURL()) {
164         String url(dataObject->url());
165         String result(url);
166         result.append("\n");
167
168         if (dataObject->hasText())
169             result.append(dataObject->text());
170         else
171             result.append(url);
172
173         GOwnPtr<gchar> resultData(g_strdup(result.utf8().data()));
174         gtk_selection_data_set(selectionData, netscapeURLAtom, 8,
175             reinterpret_cast<const guchar*>(resultData.get()), strlen(resultData.get()));
176
177     } else if (info == TargetTypeImage)
178         gtk_selection_data_set_pixbuf(selectionData, dataObject->image());
179
180     else if (info == TargetTypeSmartPaste)
181         gtk_selection_data_set_text(selectionData, "", -1);
182 }
183
184 GtkTargetList* PasteboardHelper::targetListForDataObject(DataObjectGtk* dataObject, SmartPasteInclusion shouldInludeSmartPaste)
185 {
186     GtkTargetList* list = gtk_target_list_new(0, 0);
187
188     if (dataObject->hasText())
189         gtk_target_list_add_text_targets(list, TargetTypeText);
190
191     if (dataObject->hasMarkup())
192         gtk_target_list_add(list, markupAtom, 0, TargetTypeMarkup);
193
194     if (dataObject->hasURIList()) {
195         gtk_target_list_add_uri_targets(list, TargetTypeURIList);
196         gtk_target_list_add(list, netscapeURLAtom, 0, TargetTypeNetscapeURL);
197     }
198
199     if (dataObject->hasImage())
200         gtk_target_list_add_image_targets(list, TargetTypeImage, TRUE);
201
202     if (shouldInludeSmartPaste == IncludeSmartPaste)
203         gtk_target_list_add(list, smartPasteAtom, 0, TargetTypeSmartPaste);
204
205     return list;
206 }
207
208 void PasteboardHelper::fillDataObjectFromDropData(GtkSelectionData* data, guint info, DataObjectGtk* dataObject)
209 {
210     if (!gtk_selection_data_get_data(data))
211         return;
212
213     GdkAtom target = gtk_selection_data_get_target(data);
214     if (target == textPlainAtom)
215         dataObject->setText(selectionDataToUTF8String(data));
216     else if (target == markupAtom) {
217         String markup(selectionDataToUTF8String(data));
218         removeMarkupPrefix(markup);
219         dataObject->setMarkup(markup);
220     } else if (target == uriListAtom) {
221         dataObject->setURIList(selectionDataToUTF8String(data));
222     } else if (target == netscapeURLAtom) {
223         String urlWithLabel(selectionDataToUTF8String(data));
224         Vector<String> pieces;
225         urlWithLabel.split("\n", pieces);
226
227         // Give preference to text/uri-list here, as it can hold more
228         // than one URI but still take  the label if there is one.
229         if (!dataObject->hasURIList())
230             dataObject->setURIList(pieces[0]);
231         if (pieces.size() > 1)
232             dataObject->setText(pieces[1]);
233     }
234 }
235
236 Vector<GdkAtom> PasteboardHelper::dropAtomsForContext(GtkWidget* widget, GdkDragContext* context)
237 {
238     // Always search for these common atoms.
239     Vector<GdkAtom> dropAtoms;
240     dropAtoms.append(textPlainAtom);
241     dropAtoms.append(markupAtom);
242     dropAtoms.append(uriListAtom);
243     dropAtoms.append(netscapeURLAtom);
244
245     // For images, try to find the most applicable image type.
246     GRefPtr<GtkTargetList> list = adoptGRef(gtk_target_list_new(0, 0));
247     gtk_target_list_add_image_targets(list.get(), TargetTypeImage, TRUE);
248     GdkAtom atom = gtk_drag_dest_find_target(widget, context, list.get());
249     if (atom != GDK_NONE)
250         dropAtoms.append(atom);
251
252     return dropAtoms;
253 }
254
255 static DataObjectGtk* settingClipboardDataObject = 0;
256
257 static void getClipboardContentsCallback(GtkClipboard* clipboard, GtkSelectionData *selectionData, guint info, gpointer data)
258 {
259     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
260     ASSERT(dataObject);
261     PasteboardHelper::defaultPasteboardHelper()->fillSelectionData(selectionData, info, dataObject);
262 }
263
264 static void clearClipboardContentsCallback(GtkClipboard* clipboard, gpointer data)
265 {
266     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
267     ASSERT(dataObject);
268
269     // Only clear the DataObject for this clipboard if we are not currently setting it.
270     if (dataObject != settingClipboardDataObject)
271         dataObject->clearAll();
272
273     if (!data)
274         return;
275
276     GClosure* callback = static_cast<GClosure*>(data);
277     GValue firstArgument = {0, {{0}}};
278     g_value_init(&firstArgument, G_TYPE_POINTER);
279     g_value_set_pointer(&firstArgument, clipboard);
280     g_closure_invoke(callback, 0, 1, &firstArgument, 0);
281     g_closure_unref(callback);
282 }
283
284 void PasteboardHelper::writeClipboardContents(GtkClipboard* clipboard, SmartPasteInclusion includeSmartPaste, GClosure* callback)
285 {
286     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
287     GtkTargetList* list = targetListForDataObject(dataObject, includeSmartPaste);
288
289     int numberOfTargets;
290     GtkTargetEntry* table = gtk_target_table_new_from_list(list, &numberOfTargets);
291
292     if (numberOfTargets > 0 && table) {
293         settingClipboardDataObject = dataObject;
294
295         gtk_clipboard_set_with_data(clipboard, table, numberOfTargets,
296             getClipboardContentsCallback, clearClipboardContentsCallback, callback);
297         gtk_clipboard_set_can_store(clipboard, 0, 0);
298
299         settingClipboardDataObject = 0;
300
301     } else
302         gtk_clipboard_clear(clipboard);
303
304     if (table)
305         gtk_target_table_free(table, numberOfTargets);
306     gtk_target_list_unref(list);
307 }
308
309 bool PasteboardHelper::clipboardContentSupportsSmartReplace(GtkClipboard* clipboard)
310 {
311     return gtk_clipboard_wait_is_target_available(clipboard, smartPasteAtom);
312 }
313
314 }
315