Home | History | Annotate | Download | only in gtk
      1 /*
      2  * Copyright (C) 2010 Martin Robinson <mrobinson (at) 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 String gMarkupPrefix;
     42 
     43 static void removeMarkupPrefix(String& markup)
     44 {
     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     gMarkupPrefix = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">";
     66 }
     67 
     68 PasteboardHelper::PasteboardHelper()
     69     : m_targetList(gtk_target_list_new(0, 0))
     70 {
     71     initGdkAtoms();
     72 }
     73 
     74 PasteboardHelper::~PasteboardHelper()
     75 {
     76     gtk_target_list_unref(m_targetList);
     77 }
     78 
     79 void PasteboardHelper::initializeTargetList()
     80 {
     81     gtk_target_list_add_text_targets(m_targetList, getIdForTargetType(TargetTypeText));
     82     gtk_target_list_add(m_targetList, markupAtom, 0, getIdForTargetType(TargetTypeMarkup));
     83     gtk_target_list_add_uri_targets(m_targetList, getIdForTargetType(TargetTypeURIList));
     84     gtk_target_list_add(m_targetList, netscapeURLAtom, 0, getIdForTargetType(TargetTypeNetscapeURL));
     85     gtk_target_list_add_image_targets(m_targetList, getIdForTargetType(TargetTypeImage), TRUE);
     86 }
     87 
     88 static inline GtkWidget* widgetFromFrame(Frame* frame)
     89 {
     90     ASSERT(frame);
     91     Page* page = frame->page();
     92     ASSERT(page);
     93     Chrome* chrome = page->chrome();
     94     ASSERT(chrome);
     95     PlatformPageClient client = chrome->platformPageClient();
     96     ASSERT(client);
     97     return client;
     98 }
     99 
    100 GtkClipboard* PasteboardHelper::getCurrentClipboard(Frame* frame)
    101 {
    102     if (usePrimarySelectionClipboard(widgetFromFrame(frame)))
    103         return getPrimarySelectionClipboard(frame);
    104     return getClipboard(frame);
    105 }
    106 
    107 GtkClipboard* PasteboardHelper::getClipboard(Frame* frame) const
    108 {
    109     return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_CLIPBOARD);
    110 }
    111 
    112 GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const
    113 {
    114     return gtk_widget_get_clipboard(widgetFromFrame(frame), GDK_SELECTION_PRIMARY);
    115 }
    116 
    117 GtkTargetList* PasteboardHelper::targetList() const
    118 {
    119     return m_targetList;
    120 }
    121 
    122 static String selectionDataToUTF8String(GtkSelectionData* data)
    123 {
    124     // g_strndup guards against selection data that is not null-terminated.
    125     GOwnPtr<gchar> markupString(g_strndup(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)), gtk_selection_data_get_length(data)));
    126     return String::fromUTF8(markupString.get());
    127 }
    128 
    129 void PasteboardHelper::getClipboardContents(GtkClipboard* clipboard)
    130 {
    131     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
    132     ASSERT(dataObject);
    133 
    134     if (gtk_clipboard_wait_is_text_available(clipboard)) {
    135         GOwnPtr<gchar> textData(gtk_clipboard_wait_for_text(clipboard));
    136         if (textData)
    137             dataObject->setText(String::fromUTF8(textData.get()));
    138     }
    139 
    140     if (gtk_clipboard_wait_is_target_available(clipboard, markupAtom)) {
    141         if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, markupAtom)) {
    142             String markup(selectionDataToUTF8String(data));
    143             removeMarkupPrefix(markup);
    144             dataObject->setMarkup(markup);
    145             gtk_selection_data_free(data);
    146         }
    147     }
    148 
    149     if (gtk_clipboard_wait_is_target_available(clipboard, uriListAtom)) {
    150         if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, uriListAtom)) {
    151             dataObject->setURIList(selectionDataToUTF8String(data));
    152             gtk_selection_data_free(data);
    153         }
    154     }
    155 }
    156 
    157 void PasteboardHelper::fillSelectionData(GtkSelectionData* selectionData, guint info, DataObjectGtk* dataObject)
    158 {
    159     if (info == getIdForTargetType(TargetTypeText))
    160         gtk_selection_data_set_text(selectionData, dataObject->text().utf8().data(), -1);
    161 
    162     else if (info == getIdForTargetType(TargetTypeMarkup)) {
    163         // Some Linux applications refuse to accept pasted markup unless it is
    164         // prefixed by a content-type meta tag.
    165         CString markup = (gMarkupPrefix + dataObject->markup()).utf8();
    166         gtk_selection_data_set(selectionData, markupAtom, 8,
    167             reinterpret_cast<const guchar*>(markup.data()), markup.length() + 1);
    168 
    169     } else if (info == getIdForTargetType(TargetTypeURIList)) {
    170         CString uriList = dataObject->uriList().utf8();
    171         gtk_selection_data_set(selectionData, uriListAtom, 8,
    172             reinterpret_cast<const guchar*>(uriList.data()), uriList.length() + 1);
    173 
    174     } else if (info == getIdForTargetType(TargetTypeNetscapeURL) && dataObject->hasURL()) {
    175         String url(dataObject->url());
    176         String result(url);
    177         result.append("\n");
    178 
    179         if (dataObject->hasText())
    180             result.append(dataObject->text());
    181         else
    182             result.append(url);
    183 
    184         GOwnPtr<gchar> resultData(g_strdup(result.utf8().data()));
    185         gtk_selection_data_set(selectionData, netscapeURLAtom, 8,
    186             reinterpret_cast<const guchar*>(resultData.get()), strlen(resultData.get()) + 1);
    187 
    188     } else if (info == getIdForTargetType(TargetTypeImage))
    189         gtk_selection_data_set_pixbuf(selectionData, dataObject->image());
    190 }
    191 
    192 GtkTargetList* PasteboardHelper::targetListForDataObject(DataObjectGtk* dataObject)
    193 {
    194     GtkTargetList* list = gtk_target_list_new(0, 0);
    195 
    196     if (dataObject->hasText())
    197         gtk_target_list_add_text_targets(list, getIdForTargetType(TargetTypeText));
    198 
    199     if (dataObject->hasMarkup())
    200         gtk_target_list_add(list, markupAtom, 0, getIdForTargetType(TargetTypeMarkup));
    201 
    202     if (dataObject->hasURIList()) {
    203         gtk_target_list_add_uri_targets(list, getIdForTargetType(TargetTypeURIList));
    204         gtk_target_list_add(list, netscapeURLAtom, 0, getIdForTargetType(TargetTypeNetscapeURL));
    205     }
    206 
    207     if (dataObject->hasImage())
    208         gtk_target_list_add_image_targets(list, getIdForTargetType(TargetTypeImage), TRUE);
    209 
    210     return list;
    211 }
    212 
    213 void PasteboardHelper::fillDataObjectFromDropData(GtkSelectionData* data, guint info, DataObjectGtk* dataObject)
    214 {
    215     if (!gtk_selection_data_get_data(data))
    216         return;
    217 
    218     GdkAtom target = gtk_selection_data_get_target(data);
    219     if (target == textPlainAtom)
    220         dataObject->setText(selectionDataToUTF8String(data));
    221     else if (target == markupAtom) {
    222         String markup(selectionDataToUTF8String(data));
    223         removeMarkupPrefix(markup);
    224         dataObject->setMarkup(markup);
    225     } else if (target == uriListAtom) {
    226         dataObject->setURIList(selectionDataToUTF8String(data));
    227     } else if (target == netscapeURLAtom) {
    228         String urlWithLabel(selectionDataToUTF8String(data));
    229         Vector<String> pieces;
    230         urlWithLabel.split("\n", pieces);
    231 
    232         // Give preference to text/uri-list here, as it can hold more
    233         // than one URI but still take  the label if there is one.
    234         if (!dataObject->hasURIList())
    235             dataObject->setURIList(pieces[0]);
    236         if (pieces.size() > 1)
    237             dataObject->setText(pieces[1]);
    238     }
    239 }
    240 
    241 Vector<GdkAtom> PasteboardHelper::dropAtomsForContext(GtkWidget* widget, GdkDragContext* context)
    242 {
    243     // Always search for these common atoms.
    244     Vector<GdkAtom> dropAtoms;
    245     dropAtoms.append(textPlainAtom);
    246     dropAtoms.append(markupAtom);
    247     dropAtoms.append(uriListAtom);
    248     dropAtoms.append(netscapeURLAtom);
    249 
    250     // For images, try to find the most applicable image type.
    251     GRefPtr<GtkTargetList> list(gtk_target_list_new(0, 0));
    252     gtk_target_list_add_image_targets(list.get(), getIdForTargetType(TargetTypeImage), TRUE);
    253     GdkAtom atom = gtk_drag_dest_find_target(widget, context, list.get());
    254     if (atom != GDK_NONE)
    255         dropAtoms.append(atom);
    256 
    257     return dropAtoms;
    258 }
    259 
    260 static DataObjectGtk* settingClipboardDataObject = 0;
    261 
    262 static void getClipboardContentsCallback(GtkClipboard* clipboard, GtkSelectionData *selectionData, guint info, gpointer data)
    263 {
    264     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
    265     ASSERT(dataObject);
    266     Pasteboard::generalPasteboard()->helper()->fillSelectionData(selectionData, info, dataObject);
    267 }
    268 
    269 static void clearClipboardContentsCallback(GtkClipboard* clipboard, gpointer data)
    270 {
    271     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
    272     ASSERT(dataObject);
    273 
    274     // Only clear the DataObject for this clipboard if we are not currently setting it.
    275     if (dataObject != settingClipboardDataObject)
    276         dataObject->clear();
    277 
    278     if (!data)
    279         return;
    280 
    281     GClosure* callback = static_cast<GClosure*>(data);
    282     GValue firstArgument = {0, {{0}}};
    283     g_value_init(&firstArgument, G_TYPE_POINTER);
    284     g_value_set_pointer(&firstArgument, clipboard);
    285     g_closure_invoke(callback, 0, 1, &firstArgument, 0);
    286     g_closure_unref(callback);
    287 }
    288 
    289 void PasteboardHelper::writeClipboardContents(GtkClipboard* clipboard, GClosure* callback)
    290 {
    291     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
    292     GtkTargetList* list = targetListForDataObject(dataObject);
    293 
    294     int numberOfTargets;
    295     GtkTargetEntry* table = gtk_target_table_new_from_list(list, &numberOfTargets);
    296 
    297     if (numberOfTargets > 0 && table) {
    298         settingClipboardDataObject = dataObject;
    299 
    300         gtk_clipboard_set_with_data(clipboard, table, numberOfTargets,
    301             getClipboardContentsCallback, clearClipboardContentsCallback, callback);
    302         gtk_clipboard_set_can_store(clipboard, 0, 0);
    303 
    304         settingClipboardDataObject = 0;
    305 
    306     } else
    307         gtk_clipboard_clear(clipboard);
    308 
    309     if (table)
    310         gtk_target_table_free(table, numberOfTargets);
    311     gtk_target_list_unref(list);
    312 }
    313 
    314 }
    315 
    316