diff --git a/src/g_editor.c b/src/g_editor.c index 27188090e5..51d201a69f 100644 --- a/src/g_editor.c +++ b/src/g_editor.c @@ -3,6 +3,7 @@ * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ #include +#include #include "m_pd.h" #include "m_imp.h" #include "s_stuff.h" @@ -3718,6 +3719,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) @@ -4123,6 +4135,44 @@ static void canvas_paste(t_canvas *x) } } +static char* clipboard_patch_text = NULL; +static size_t clipboard_patch_len = 0; + +void canvas_got_clipboard_contents(t_canvas *x, t_symbol*s, int argc, t_atom*argv) { + t_symbol *reset = gensym("reset"); + t_symbol *submit = gensym("submit"); + t_symbol *addbytes = gensym("addbytes"); + t_symbol*sflag = (argc>0)?atom_getsymbol(argv):0; + int flag = 0; + int i; + if(sflag == reset || sflag == submit) { + if (clipboard_patch_text) { + if(sflag == submit) { + t_binbuf *temp_bb = binbuf_new(); + binbuf_text(temp_bb, clipboard_patch_text, clipboard_patch_len); + canvas_dopaste(x, temp_bb); + } + freebytes(clipboard_patch_text, clipboard_patch_len); + } + clipboard_patch_text = NULL; + clipboard_patch_len = 0; + } else if(sflag == addbytes) { + if (clipboard_patch_text) { + clipboard_patch_text = resizebytes(clipboard_patch_text, clipboard_patch_len, clipboard_patch_len + argc); + } else { + clipboard_patch_text = getbytes(argc); + clipboard_patch_len = 0; + } + for(i=1; igl_editor) @@ -4916,7 +4966,8 @@ 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_GIMME, A_NULL); /* ------------------------ menu actions ---------------------------- */ class_addmethod(canvas_class, (t_method)canvas_menuclose, gensym("menuclose"), A_DEFFLOAT, 0); @@ -4924,6 +4975,8 @@ void g_editor_setup(void) 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, diff --git a/tcl/pd-gui.tcl b/tcl/pd-gui.tcl index e56046d274..0247e4bc82 100755 --- a/tcl/pd-gui.tcl +++ b/tcl/pd-gui.tcl @@ -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 @@ -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) @@ -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+ @@ -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 diff --git a/tcl/pd_bindings.tcl b/tcl/pd_bindings.tcl index de8f4b614c..12a04b9143 100644 --- a/tcl/pd_bindings.tcl +++ b/tcl/pd_bindings.tcl @@ -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 {::pd_bindings::sendkey %W 1 %K "" 1 %k} @@ -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 diff --git a/tcl/pd_menus.tcl b/tcl/pd_menus.tcl index e08eb5a13c..5bb8f314f6 100644 --- a/tcl/pd_menus.tcl +++ b/tcl/pd_menus.tcl @@ -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 @@ -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 @@ -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 @@ -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" ] \ diff --git a/tcl/pdtk_canvas.tcl b/tcl/pdtk_canvas.tcl index 8039592653..7aa8ebff00 100644 --- a/tcl/pdtk_canvas.tcl +++ b/tcl/pdtk_canvas.tcl @@ -250,6 +250,84 @@ proc pdtk_canvas_clickpaste {tkcanvas x y b} { } } +proc ::pdtk_canvas::pdtk_get_clipboard_text {tkcanvas} { + # TODO: max size is now limited by binbuf_text MAXPDSTRING (1000) + # pdsend max size is 65536 bytes (I think), so could maybe optimize this by + # sending larger chunks and processing smaller chunks in C side + set MAX_CHUNK_SIZE 1000 + set total_bytes 0 + set toplevel [winfo toplevel $tkcanvas] + set clipboard_data [clipboard get] + if {[string length $clipboard_data] == 0} { + ::pdwindow::post "Clipboard is empty.\n" + return + } + # 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 "$toplevel got-clipboard-contents reset" + set output {} + foreach char [split $clipboard_data ""] { + set char_bytes [string bytelength $char] + if { $total_bytes + $char_bytes > $MAX_CHUNK_SIZE } { + pdsend "$toplevel got-clipboard-contents addbytes $output" + set output {} + set total_bytes 0 + } + lappend output [scan $char %c] + incr total_bytes $char_bytes + } + if { [llength $output] > 0 } { + pdsend "$toplevel got-clipboard-contents addbytes $output" + } + pdsend "$toplevel got-clipboard-contents submit" +} + +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] + set atom_line [regsub -all -- {\$} $atom_line {\\$}] + append atom_line ";" + } elseif {$atom == ";"} { + append atom_line "\\; " + } 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