You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

295 lines
9.2 KiB

#include "ClipboardHandler.h"
#include "Control.h"
#include "../util/pixbuf-utils.h"
#include <cairo-svg.h>
#include <config.h>
#include "../util/ObjectStream.h"
ClipboardHandler::ClipboardHandler(ClipboardListener * listener, GtkWidget * widget) {
this->listener = listener;
this->clipboard = gtk_widget_get_clipboard(widget, GDK_SELECTION_CLIPBOARD);
this->containsText = false;
this->containsXournal = false;
this->selection = NULL;
this->hanlderId = g_signal_connect(this->clipboard, "owner-change", G_CALLBACK(&ownerChangedCallback), this);
this->listener->clipboardCutCopyEnabled(false);
GdkDisplay * display = gtk_clipboard_get_display(clipboard);
if (gdk_display_supports_selection_notification(display)) {
gtk_clipboard_request_contents(clipboard, gdk_atom_intern_static_string("TARGETS"),
(GtkClipboardReceivedFunc) receivedClipboardContents, this);
} else {
// XFIXES extension not available, make Paste always sensitive
this->listener->clipboardPasteEnabled(true);
}
}
ClipboardHandler::~ClipboardHandler() {
g_signal_handler_disconnect(this->clipboard, this->hanlderId);
}
static GdkAtom atomXournal = gdk_atom_intern_static_string("application/xournal");
void ClipboardHandler::paste() {
if (this->containsXournal) {
gtk_clipboard_request_contents(clipboard, atomXournal, (GtkClipboardReceivedFunc) pasteClipboardContents, this);
} else if (this->containsText) {
gtk_clipboard_request_contents(clipboard, gdk_atom_intern_static_string("UTF8_STRING"),
(GtkClipboardReceivedFunc) pasteClipboardContents, this);
}
}
void ClipboardHandler::cut() {
this->copy();
}
gint ElementCompareFunc(Element * a, Element * b) {
if (a->getY() == b->getY()) {
return a->getX() - b->getX();
}
return a->getY() - b->getY();
}
static GdkAtom atomSvg1 = gdk_atom_intern_static_string("image/svg");
static GdkAtom atomSvg2 = gdk_atom_intern_static_string("image/svg+xml");
// The contents of the clipboard
class ClipboardContents {
public:
ClipboardContents(String text, GdkPixbuf * image, String svg, GString * str) {
this->text = text;
this->image = image;
this->svg = svg;
this->str = str;
}
~ClipboardContents() {
gdk_pixbuf_unref(this->image);
g_string_free(this->str, true);
}
public:
static void getFunction(GtkClipboard *clipboard, GtkSelectionData *selection, guint info,
ClipboardContents * contents) {
if (selection->target == gdk_atom_intern_static_string("UTF8_STRING")) {
gtk_selection_data_set_text(selection, contents->text.c_str(), -1);
} else if (selection->target == gdk_atom_intern_static_string("image/png") || selection->target
== gdk_atom_intern_static_string("image/jpeg") || selection->target == gdk_atom_intern_static_string(
"image/gif")) {
gtk_selection_data_set_pixbuf(selection, contents->image);
} else if (atomSvg1 == selection->target || atomSvg2 == selection->target) {
gtk_selection_data_set(selection, selection->target, 8, (guchar *) contents->svg.c_str(),
contents->svg.size());
} else if (atomXournal == selection->target) {
gtk_selection_data_set(selection, selection->target, 8, (guchar *) contents->str->str, contents->str->len);
}
}
static void clearFunction(GtkClipboard *clipboard, ClipboardContents * contents) {
delete contents;
}
private:
String text;
GdkPixbuf * image;
String svg;
GString * str;
};
static cairo_status_t svgWriteFunction(GString * string, const unsigned char *data, unsigned int length) {
g_string_append_len(string, (const gchar *) data, length);
return CAIRO_STATUS_SUCCESS;
}
void ClipboardHandler::copy() {
if (!this->selection) {
return;
}
/////////////////////////////////////////////////////////////////
// prepare xournal contents
/////////////////////////////////////////////////////////////////
ObjectOutputStream out;
out.writeString(PACKAGE_STRING);
int count = g_list_length(this->selection->getElements());
out.writeObject("Selection");
out.writeDouble(this->selection->getX());
out.writeDouble(this->selection->getY());
out.writeDouble(this->selection->getWidth());
out.writeDouble(this->selection->getHeight());
out.writeInt(count);
out.endObject();
for (GList * l = this->selection->getElements(); l != NULL; l = l->next) {
Element * e = (Element *) l->data;
out << e;
}
/////////////////////////////////////////////////////////////////
// prepare text contents
/////////////////////////////////////////////////////////////////
GList * textElements = NULL;
for (GList * l = this->selection->getElements(); l != NULL; l = l->next) {
Element * e = (Element *) l->data;
if (e->getType() == ELEMENT_TEXT) {
textElements = g_list_insert_sorted(textElements, e, (GCompareFunc) ElementCompareFunc);
}
}
String text = "";
for (GList * l = textElements; l != NULL; l = l->next) {
Text * e = (Text *) l->data;
if (text != "") {
text += "\n";
}
text += e->getText();
}
g_list_free(textElements);
/////////////////////////////////////////////////////////////////
// prepare image contents: PNG
/////////////////////////////////////////////////////////////////
DocumentView view;
double dpiFactor = 1.0 / 72.0 * 300.0;
cairo_surface_t * surfacePng = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, selection->getWidth() * dpiFactor,
selection->getHeight() * dpiFactor);
cairo_t * crPng = cairo_create(surfacePng);
cairo_scale(crPng, dpiFactor, dpiFactor);
cairo_translate(crPng, -selection->getX(), -selection->getY());
view.drawSelection(crPng, this->selection);
cairo_destroy(crPng);
GdkPixbuf * image = f_pixbuf_from_cairo_surface(surfacePng);
cairo_surface_destroy(surfacePng);
/////////////////////////////////////////////////////////////////
// prepare image contents: SVG
/////////////////////////////////////////////////////////////////
GString * svgString = g_string_sized_new(1048576); // 1MB
cairo_surface_t * surfaceSVG = cairo_svg_surface_create_for_stream((cairo_write_func_t) svgWriteFunction,
svgString, selection->getWidth(), selection->getHeight());
cairo_t * crSVG = cairo_create(surfaceSVG);
view.drawSelection(crSVG, this->selection);
cairo_surface_destroy(surfaceSVG);
cairo_destroy(crSVG);
/////////////////////////////////////////////////////////////////
// copy to clipboard
/////////////////////////////////////////////////////////////////
GtkTargetList * list = gtk_target_list_new(NULL, 0);
GtkTargetEntry *targets;
int n_targets;
// if we have text elements...
if (!text.isEmpty()) {
gtk_target_list_add_text_targets(list, 0);
}
// we always copy an image to clipboard
gtk_target_list_add_image_targets(list, 0, TRUE);
gtk_target_list_add(list, atomSvg1, 0, 0);
gtk_target_list_add(list, atomSvg2, 0, 0);
gtk_target_list_add(list, atomXournal, 0, 0);
targets = gtk_target_table_new_from_list(list, &n_targets);
ClipboardContents * contents = new ClipboardContents(text, image, svgString->str, out.getStr());
gtk_clipboard_set_with_data(clipboard, targets, n_targets, (GtkClipboardGetFunc) ClipboardContents::getFunction,
(GtkClipboardClearFunc) ClipboardContents::clearFunction, contents);
gtk_clipboard_set_can_store(clipboard, NULL, 0);
gtk_target_table_free(targets, n_targets);
gtk_target_list_unref(list);
g_string_free(svgString, true);
}
void ClipboardHandler::setSelection(EditSelection * selection) {
this->selection = selection;
this->listener->clipboardCutCopyEnabled(selection != NULL);
}
void ClipboardHandler::setCopyPasteEnabled(bool enabled) {
if (enabled) {
listener->clipboardCutCopyEnabled(true);
} else if (!selection) {
listener->clipboardCutCopyEnabled(false);
}
}
void ClipboardHandler::ownerChangedCallback(GtkClipboard *clip, GdkEvent *event, ClipboardHandler * handler) {
if (event->type == GDK_OWNER_CHANGE) {
handler->clipboardUpdated(event->owner_change.selection);
}
}
void ClipboardHandler::clipboardUpdated(GdkAtom atom) {
gtk_clipboard_request_contents(clipboard, gdk_atom_intern_static_string("TARGETS"),
(GtkClipboardReceivedFunc) receivedClipboardContents, this);
}
void ClipboardHandler::pasteClipboardContents(GtkClipboard *clipboard, GtkSelectionData *selectionData,
ClipboardHandler * handler) {
if (atomXournal == selectionData->target) {
ObjectInputStream in;
if(in.read((const char *)selectionData->data, selectionData->length)) {
handler->listener->clipboardPasteXournal(in);
}
} else {
guchar * text = gtk_selection_data_get_text(selectionData);
if (text != NULL) {
handler->listener->clipboardPasteText((const char *) text);
g_free(text);
}
}
}
gboolean gtk_selection_data_targets_include_xournal(GtkSelectionData *selection_data) {
GdkAtom *targets;
gint n_targets;
gboolean result = FALSE;
if (gtk_selection_data_get_targets(selection_data, &targets, &n_targets)) {
for (int i = 0; i < n_targets; i++) {
if (targets[i] == atomXournal) {
result = true;
break;
}
}
g_free(targets);
}
return result;
}
void ClipboardHandler::receivedClipboardContents(GtkClipboard *clipboard, GtkSelectionData * selectionData,
ClipboardHandler * handler) {
handler->containsText = gtk_selection_data_targets_include_text(selectionData);
handler->containsXournal = gtk_selection_data_targets_include_xournal(selectionData);
handler->listener->clipboardPasteEnabled(handler->containsText || handler->containsXournal);
}