Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Copy-paste patch objects using system clipboard text buffer #2086

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
74 changes: 73 additions & 1 deletion src/g_editor.c
Expand Up @@ -3,6 +3,7 @@
* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */

#include <stdio.h>
#include <stdlib.h>
#include "m_pd.h"
#include "m_imp.h"
#include "s_stuff.h"
Expand Down Expand Up @@ -37,6 +38,10 @@ struct _instanceeditor

/* positional offset for duplicated items */
#define PASTE_OFFSET 10
#define CLIPBOARD_PATCH_TEXT_START 0
#define CLIPBOARD_PATCH_TEXT_LINE_END 1
#define CLIPBOARD_PATCH_TEXT_END 2
#define CLIPBOARD_PATCH_TEXT_LINE_PARTIAL 3

void glist_readfrombinbuf(t_glist *x, const t_binbuf *b, const char *filename,
int selectem);
Expand Down Expand Up @@ -3718,6 +3723,17 @@ static void canvas_copy(t_canvas *x)
}
}

static void canvas_copy_to_clipboard_as_text(t_canvas *x)
{
t_binbuf *bb = canvas_docopy(x);
if (!bb)
return;
t_atom *atoms = binbuf_getvec(bb);
int num_atoms = binbuf_getnatom(bb);
pdgui_vmess("pdtk_copy_to_clipboard_as_text", "ca", x, num_atoms, atoms);
binbuf_free(bb);
}

static void canvas_clearline(t_canvas *x)
{
if (x->gl_editor->e_selectedline)
Expand Down Expand Up @@ -4123,6 +4139,59 @@ static void canvas_paste(t_canvas *x)
}
}

static t_binbuf *clipboard_patch_bb = NULL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should go into (t_glist*)->gl_privatedata


void canvas_got_clipboard_contents(t_canvas *x, t_floatarg flagf, t_symbol *s) {
int flag = flagf;

switch (flag) {
case CLIPBOARD_PATCH_TEXT_START:
if (clipboard_patch_bb) {
binbuf_free(clipboard_patch_bb);
}
clipboard_patch_bb = binbuf_new();
break;

case CLIPBOARD_PATCH_TEXT_LINE_END:
if (clipboard_patch_bb) {
char* received_data = strdup(s->s_name);
t_binbuf *temp_bb = binbuf_new();
binbuf_text(temp_bb, received_data, strlen(received_data));
binbuf_addsemi(temp_bb);
binbuf_add(clipboard_patch_bb, binbuf_getnatom(temp_bb), binbuf_getvec(temp_bb));
binbuf_clear(temp_bb);
binbuf_free(temp_bb);
free(received_data);
}
break;

case CLIPBOARD_PATCH_TEXT_LINE_PARTIAL:
if (clipboard_patch_bb) {
char* received_data = strdup(s->s_name);
t_binbuf *temp_bb = binbuf_new();
binbuf_text(temp_bb, received_data, strlen(received_data));
binbuf_add(clipboard_patch_bb, binbuf_getnatom(temp_bb), binbuf_getvec(temp_bb));
binbuf_clear(temp_bb);
binbuf_free(temp_bb);
free(received_data);
}
break;

eeropic marked this conversation as resolved.
Show resolved Hide resolved
case CLIPBOARD_PATCH_TEXT_END:
if (clipboard_patch_bb) {
canvas_dopaste(x, clipboard_patch_bb);
binbuf_free(clipboard_patch_bb);
clipboard_patch_bb = NULL;
}
break;

default:
post("Invalid flag received in canvas_got_clipboard_contents.");
break;
}
}


static void canvas_duplicate(t_canvas *x)
{
if (!x->gl_editor)
Expand Down Expand Up @@ -4916,14 +4985,17 @@ void g_editor_setup(void)
A_GIMME, A_NULL);
class_addmethod(canvas_class, (t_method)canvas_motion, gensym("motion"),
A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);

class_addmethod(canvas_class, (t_method)canvas_got_clipboard_contents,
gensym("got-clipboard-contents"), A_FLOAT, A_SYMBOL, A_NULL);
eeropic marked this conversation as resolved.
Show resolved Hide resolved
/* ------------------------ menu actions ---------------------------- */
class_addmethod(canvas_class, (t_method)canvas_menuclose,
gensym("menuclose"), A_DEFFLOAT, 0);
class_addmethod(canvas_class, (t_method)canvas_cut,
gensym("cut"), A_NULL);
class_addmethod(canvas_class, (t_method)canvas_copy,
gensym("copy"), A_NULL);
class_addmethod(canvas_class, (t_method)canvas_copy_to_clipboard_as_text,
gensym("copy-to-clipboard-as-text"), A_NULL);
class_addmethod(canvas_class, (t_method)canvas_paste,
gensym("paste"), A_NULL);
class_addmethod(canvas_class, (t_method)canvas_paste_replace,
Expand Down
5 changes: 5 additions & 0 deletions tcl/pd-gui.tcl
Expand Up @@ -200,6 +200,8 @@ set recentfiles_list {}
set total_recentfiles 5
# modifier for key commands (Ctrl/Control on most platforms, Cmd/Mod1 on MacOSX)
set modifier ""
# on Mac OS X/Aqua, the Alt/Option key is called Option in Tcl
set alt ""
# current state of the Edit Mode menu item
set editmode_button 0

Expand Down Expand Up @@ -294,6 +296,7 @@ proc init_for_platform {} {
switch -- $::windowingsystem {
"x11" {
set ::modifier "Control"
set ::alt "Alt"
option add *PatchWindow*Canvas.background "white" startupFile
# add control to show/hide hidden files in the open panel (load
# the tk_getOpenFile dialog once, otherwise it will not work)
Expand Down Expand Up @@ -334,6 +337,7 @@ proc init_for_platform {} {
# from the commandline incorporates the special mac event handling
package require apple_events
set ::modifier "Mod1"
set ::alt "Option"
if {$::tcl_version < 8.5} {
# old default font for Tk 8.4 on macOS
# since font detection requires 8.5+
Expand Down Expand Up @@ -371,6 +375,7 @@ proc init_for_platform {} {
}
"win32" {
set ::modifier "Control"
set ::alt "Alt"
option add *PatchWindow*Canvas.background "white" startupFile
# fix menu font size on Windows with tk scaling = 1
font create menufont -family Tahoma -size -11
Expand Down
9 changes: 9 additions & 0 deletions tcl/pd_bindings.tcl
Expand Up @@ -120,6 +120,12 @@ proc ::pd_bindings::global_bindings {} {
bind_capslock all $::modifier-Key m {::pd_menucommands::scheduleAction menu_minimize %W}
bind all <$::modifier-quoteleft> {::pd_menucommands::scheduleAction menu_raisenextwindow}
}

# On mac, the copy/paste menu command bindings also execute the commands, results in paste executed twice
# however, if we don't set this binding, the regular copy and paste gets called even with alt pressed
bind_capslock all $::modifier-$::alt-Key c {}
bind_capslock all $::modifier-$::alt-Key v {}

# BackSpace/Delete report the wrong isos (unicode representations) on OSX,
# so we set them to the empty string and let ::pd_bindings::sendkey guess the correct values
bind all <KeyPress-BackSpace> {::pd_bindings::sendkey %W 1 %K "" 1 %k}
Expand All @@ -134,6 +140,9 @@ proc ::pd_bindings::global_bindings {} {
bind_capslock all $::modifier-Key q {::pd_connect::menu_quit}
bind_capslock all $::modifier-Key m {menu_minimize %W}

bind_capslock all $::modifier-$::alt-Key c {menu_send %W copy-to-clipboard-as-text}
bind_capslock all $::modifier-$::alt-Key v {::pdtk_canvas::pdtk_get_clipboard_text $::focused_window}

bind all <$::modifier-Next> {menu_raisenextwindow} ;# PgUp
bind all <$::modifier-Prior> {menu_raisepreviouswindow};# PageDown
# these can conflict with CMD+comma & CMD+period bindings in Tk Cococa
Expand Down
10 changes: 10 additions & 0 deletions tcl/pd_menus.tcl
Expand Up @@ -64,6 +64,8 @@ proc ::pd_menus::configure_for_pdwindow {} {
$menubar.file entryconfigure [_ "Print..."] -state disabled

# Edit menu
$menubar.edit entryconfigure [_ "Copy to clipboard (text)"] -state disabled
$menubar.edit entryconfigure [_ "Paste from clipboard (text)"] -state disabled
$menubar.edit entryconfigure [_ "Paste Replace"] -state disabled
$menubar.edit entryconfigure [_ "Duplicate"] -state disabled
$menubar.edit entryconfigure [_ "Font"] -state normal
Expand Down Expand Up @@ -96,6 +98,8 @@ proc ::pd_menus::configure_for_canvas {mytoplevel} {
$menubar.file entryconfigure [_ "Save As..."] -state normal
$menubar.file entryconfigure [_ "Print..."] -state normal
# Edit menu
$menubar.edit entryconfigure [_ "Copy to clipboard (text)"] -state normal
$menubar.edit entryconfigure [_ "Paste from clipboard (text)"] -state normal
$menubar.edit entryconfigure [_ "Paste Replace"] -state normal
$menubar.edit entryconfigure [_ "Duplicate"] -state normal
$menubar.edit entryconfigure [_ "Font"] -state normal
Expand Down Expand Up @@ -142,6 +146,8 @@ proc ::pd_menus::configure_for_dialog {mytoplevel} {

# Edit menu
$menubar.edit entryconfigure [_ "Font"] -state disabled
$menubar.edit entryconfigure [_ "Copy to clipboard (text)"] -state disabled
$menubar.edit entryconfigure [_ "Paste from clipboard (text)"] -state disabled
$menubar.edit entryconfigure [_ "Paste Replace"] -state disabled
$menubar.edit entryconfigure [_ "Duplicate"] -state disabled
$menubar.edit entryconfigure [_ "Zoom In"] -state disabled
Expand Down Expand Up @@ -196,8 +202,12 @@ proc ::pd_menus::build_edit_menu {mymenu} {
-command {::pd_menucommands::scheduleAction menu_send $::focused_window cut}
$mymenu add command -label [_ "Copy"] -accelerator "$accelerator+C" \
-command {::pd_menucommands::scheduleAction menu_send $::focused_window copy}
$mymenu add command -label [_ "Copy to clipboard (text)"] -accelerator "$accelerator+$::alt+C" \
-command {::pd_menucommands::scheduleAction menu_send $::focused_window copy-to-clipboard-as-text}
$mymenu add command -label [_ "Paste"] -accelerator "$accelerator+V" \
-command {::pd_menucommands::scheduleAction menu_send $::focused_window paste}
$mymenu add command -label [_ "Paste from clipboard (text)"] -accelerator "$accelerator+$::alt+V" \
-command {::pdtk_canvas::pdtk_get_clipboard_text $::focused_window}
$mymenu add command -label [_ "Duplicate"] -accelerator "$accelerator+D" \
-command {::pd_menucommands::scheduleAction menu_send $::focused_window duplicate}
$mymenu add command -label [_ "Paste Replace" ] \
Expand Down
81 changes: 81 additions & 0 deletions tcl/pdtk_canvas.tcl
Expand Up @@ -250,6 +250,87 @@ proc pdtk_canvas_clickpaste {tkcanvas x y b} {
}
}

proc ::pdtk_canvas::pdtk_get_clipboard_text {tkcanvas} {
set CLIPBOARD_PATCH_TEXT_START 0;
set CLIPBOARD_PATCH_TEXT_LINE_END 1;
set CLIPBOARD_PATCH_TEXT_END 2;
set CLIPBOARD_PATCH_TEXT_LINE_PARTIAL 3;
set MAX_CHUNK_SIZE 960;
set clipboard_data [clipboard get]
eeropic marked this conversation as resolved.
Show resolved Hide resolved

# TODO: better validation of PD patch clipboard data
if {[string index $clipboard_data 0] != "#"} {
::pdwindow::post "Warning: Clipboard content does not seem to be valid PD patch: \n"
::pdwindow::post $clipboard_data
return
}
pdsend "[winfo toplevel $tkcanvas] got-clipboard-contents $CLIPBOARD_PATCH_TEXT_START NONE"

foreach line [split $clipboard_data \n] {
if {[string length $line] > 0} {
set escaped_line [string range $line 0 end-1]
set escaped_line [string map {" " {\ } ";" {\\;} "," {\\,} "\$" "\\$"} $escaped_line]
# split each line into chunks, and make sure atoms don't get split
while {[string length $escaped_line] > $MAX_CHUNK_SIZE} {
set boundary [string last { } [string range $escaped_line 0 [expr $MAX_CHUNK_SIZE - 1]]]
set chunk [string range $escaped_line 0 $boundary]
pdsend "[winfo toplevel $tkcanvas] got-clipboard-contents $CLIPBOARD_PATCH_TEXT_LINE_PARTIAL $chunk"
set escaped_line [string range $escaped_line [expr $boundary + 1] end]
}
# FIXME: the atoms can have width property, i.e. , f 12 - this last comma is not escaped
# and causing lot of trouble, so discarding it for now
# fix by adding new flag and appending comma manually to binbuf
set escaped_line [regsub {\,\\\sf\\\s\d+$} $escaped_line ""]
pdsend "[winfo toplevel $tkcanvas] got-clipboard-contents $CLIPBOARD_PATCH_TEXT_LINE_END $escaped_line"
}
}
pdsend "[winfo toplevel $tkcanvas] got-clipboard-contents $CLIPBOARD_PATCH_TEXT_END NONE"
}


proc pdtk_copy_to_clipboard_as_text {tkcanvas args} {
clipboard clear
set clipboard_content ""
set atom_line ""
set obj_type ""
for {set i 0} {$i < [llength $args]} {incr i} {
set prev_atom [lindex $args [expr $i - 1]]
set atom [lindex $args $i]
set next_atom [lindex $args [expr $i + 1]]
set next_next_atom [lindex $args [expr $i + 2]]
# Check for beginning of new line (#) but discard hex colors
if {[string first "#" $atom] == 0 && ![regexp {^#[0-9a-fA-F]{6}$} $atom]} {
append clipboard_content [string trim $atom_line]
append clipboard_content "\n"
set atom_line "$atom "
set obj_type $next_atom
} else {
if {$atom == ";" && ([string first "#" $next_atom] == 0 || $next_atom == "")} {
set atom_line [string trimright $atom_line]
append atom_line ";"
} elseif {$atom == ";"} {
append atom_line "\\; "
} elseif {[string first "\$" $atom] == 0} {
append atom_line "\\$" [string range $atom 1 end] " "
} elseif {$atom == ","} {
# text items can have unescaped comma delimiting the width attribute
if {$obj_type == "text"} {
append atom_line [expr {$next_atom != "f" ? "\\, " : ", "}]
} else {
append atom_line "\\, "
}
} else {
set delimiter [expr {$obj_type == "text" && $next_next_atom == "f" ? "" : " "}]
append atom_line $atom $delimiter
}
}
}
append clipboard_content "$atom_line\n"
set processed_content $clipboard_content
clipboard append [string trimleft $processed_content]
}


#------------------------------------------------------------------------------#
# canvas popup menu

Expand Down