package provide pd_menucommands 0.1
namespace eval ::pd_menucommands:: {
variable untitled_number "1"
namespace export menu_*
}
# ------------------------------------------------------------------------------
# functions called from File menu
proc ::pd_menucommands::menu_new {} {
variable untitled_number
set untitled_name $::pdtk_canvas::untitled_name
if { ! [file isdirectory $::filenewdir]} {set ::filenewdir $::env(HOME)}
pdsend "pd menunew $untitled_name-$untitled_number [enquote_path $::filenewdir]"
incr untitled_number
}
proc ::pd_menucommands::menu_open {} {
if { ! [file isdirectory $::fileopendir]} {set ::fileopendir $::env(HOME)}
set files [tk_getOpenFile -defaultextension .pd \
-multiple true \
-filetypes $::filetypes \
-initialdir $::fileopendir]
if {$files ne ""} {
foreach filename $files {
open_file $filename
}
set ::fileopendir [file dirname $filename]
}
}
# TODO set the current font family & size via the -fontmap option:
# http://wiki.tcl.tk/41871
proc ::pd_menucommands::menu_print {mytoplevel} {
set initialfile "[file rootname [lookup_windowname $mytoplevel]].ps"
set filename [tk_getSaveFile -initialfile $initialfile \
-title [_ "Print..." ] \
-defaultextension .ps \
-filetypes { {{Postscript} {.ps}} }]
if {$filename ne ""} {
set tkcanvas [tkcanvas_name $mytoplevel]
# set $fontfind & $fontsub if font name needs to be fixed
if {$::font_family eq "DejaVu Sans Mono"} {
# capitalize V
set fontfind "DejavuSansMono"
set fontsub "DejaVuSansMono"
} elseif {$::font_family eq "Menlo"} {
# add -Regular suffix, -Bold is added automatically
if {$::font_weight eq "normal"} {
set fontfind "Menlo"
set fontsub "Menlo-Regular"
}
}
if {[info exists fontfind]} {
# FIXME hack to fix incorrect PS font naming,
# this could be removed in the future
set ps [$tkcanvas postscript]
regsub -all $fontfind $ps $fontsub ps
set f [open $filename w]
puts $f $ps
close $f
} else {
$tkcanvas postscript -file $filename
}
}
}
# ------------------------------------------------------------------------------
# functions called from Edit menu
proc ::pd_menucommands::menu_undo {} {
if { $::focused_window ne ".pdwindow" } {
pdsend "$::focused_window undo"
}
}
proc ::pd_menucommands::menu_redo {} {
if { $::focused_window ne ".pdwindow" } {
pdsend "$::focused_window redo"
}
}
proc ::pd_menucommands::menu_editmode {state} {
if {[winfo class $::focused_window] ne "PatchWindow"} {return}
set ::editmode_button $state
# this shouldn't be necessary because 'pd' will reply with pdtk_canvas_editmode
# set ::editmode($::focused_window) $state
pdsend "$::focused_window editmode $state"
}
proc ::pd_menucommands::menu_toggle_editmode {} {
menu_editmode [expr {! $::editmode_button}]
}
# ------------------------------------------------------------------------------
# generic procs for sending menu events
# send a message to a pd canvas receiver
proc ::pd_menucommands::menu_send {window message} {
if { [catch {set mytoplevel [winfo toplevel $window]} ] } {
::pdwindow::logpost {} 4 "menu_send: skipping unknown window '$window'\n"
return
}
if {[winfo class $mytoplevel] eq "PatchWindow"} {
pdsend "$mytoplevel $message"
} elseif {$mytoplevel eq ".pdwindow"} {
if {$message eq "copy"} {
tk_textCopy .pdwindow.text
} elseif {$message eq "selectall"} {
.pdwindow.text tag add sel 1.0 end
} elseif {$message eq "menusaveas"} {
::pdwindow::save_logbuffer_to_file
}
}
}
# send a message to a pd canvas receiver with a float arg
proc ::pd_menucommands::menu_send_float {window message float} {
set mytoplevel [winfo toplevel $window]
if {[winfo class $mytoplevel] eq "PatchWindow"} {
pdsend "$mytoplevel $message $float"
}
}
# ------------------------------------------------------------------------------
# open the dialog panels
proc ::pd_menucommands::menu_message_dialog {} {
::dialog_message::open_message_dialog $::focused_window
}
proc ::pd_menucommands::menu_find_dialog {} {
::dialog_find::open_find_dialog $::focused_window
}
proc ::pd_menucommands::menu_font_dialog {} {
if {[winfo exists .font]} {
raise .font
focus .font
} elseif {$::focused_window eq ".pdwindow"} {
pdtk_canvas_dofont .pdwindow [lindex [.pdwindow.text cget -font] 1]
} else {
pdsend "$::focused_window menufont"
}
}
proc ::pd_menucommands::menu_path_dialog {} {
if {[winfo exists .path]} {
raise .path
focus .path
} else {
pdsend "pd start-path-dialog"
}
}
proc ::pd_menucommands::menu_startup_dialog {} {
if {[winfo exists .startup]} {
raise .startup
focus .startup
} else {
pdsend "pd start-startup-dialog"
}
}
proc ::pd_menucommands::menu_preference_dialog {} {
pdsend "pd start-preference-dialog"
}
proc ::pd_menucommands::menu_manual {} {
::pd_menucommands::menu_doc_open doc/1.manual index.htm
}
proc ::pd_menucommands::menu_helpbrowser {} {
::helpbrowser::open_helpbrowser
}
# ------------------------------------------------------------------------------
# window management functions
proc ::pd_menucommands::menu_minimize {window} {
wm iconify [winfo toplevel $window]
}
proc ::pd_menucommands::menu_maximize {window} {
wm state [winfo toplevel $window] zoomed
}
proc ::pd_menucommands::menu_raise_pdwindow {} {
# explicitly raise/lower & focus relative to the current window stack for Tk Cocoa
if {$::focused_window eq ".pdwindow" && [winfo viewable .pdwindow]} {
lower .pdwindow [lindex [wm stackorder .] 0]
focus [lindex [wm stackorder .] end]
} else {
wm deiconify .pdwindow
raise .pdwindow [lindex [wm stackorder .] end]
focus .pdwindow
}
}
# used for cycling thru windows of an app
proc ::pd_menucommands::menu_raisepreviouswindow {} {
set mytoplevel [lindex [wm stackorder .] end]
lower $mytoplevel [lindex [wm stackorder .] 0]
focus $mytoplevel
}
# used for cycling thru windows of an app the other direction
proc ::pd_menucommands::menu_raisenextwindow {} {
set mytoplevel [lindex [wm stackorder .] 0]
raise $mytoplevel [lindex [wm stackorder .] end]
focus $mytoplevel
}
# ------------------------------------------------------------------------------
# Pd window functions
proc menu_clear_console {} {
::pdwindow::clear_console
}
# ------------------------------------------------------------------------------
# manage the saving of the directories for the new commands
# this gets the dir from the path of a window's title
proc ::pd_menucommands::set_filenewdir {mytoplevel} {
# TODO add Aqua specifics once g_canvas.c has [wm attributes -titlepath]
if {$mytoplevel eq ".pdwindow"} {
set ::filenewdir $::fileopendir
} else {
regexp -- ".+ - (.+)" [wm title $mytoplevel] ignored ::filenewdir
}
}
# parse the textfile for the About Pd page
proc ::pd_menucommands::menu_aboutpd {} {
set versionstring "Pd $::PD_MAJOR_VERSION.$::PD_MINOR_VERSION.$::PD_BUGFIX_VERSION$::PD_TEST_VERSION"
set filename [file join $::sys_guidir about.txt]
if {![file exists $filename]} {
::pdwindow::error [_ "ignoring '%s': doesn't exist" $filename]
::pdwindow::error "\n"
#return
}
if {[winfo exists .aboutpd]} {
wm deiconify .aboutpd
raise .aboutpd
focus .aboutpd
} else {
toplevel .aboutpd -class TextWindow
wm title .aboutpd [_ "About Pd"]
wm group .aboutpd .
.aboutpd configure -menu $::dialog_menubar
text .aboutpd.text -relief flat -borderwidth 0 -highlightthickness 0 \
-yscrollcommand ".aboutpd.scroll set" -background white
scrollbar .aboutpd.scroll -command ".aboutpd.text yview"
pack .aboutpd.scroll -side right -fill y
pack .aboutpd.text -side left -fill both -expand 1
bind .aboutpd <$::modifier-Key-w> "destroy .aboutpd"
if { [catch {
set textfile [open $filename]
while {![eof $textfile]} {
set bigstring [read $textfile 1000]
regsub -all PD_BASEDIR $bigstring $::sys_libdir bigstring2
regsub -all PD_VERSION $bigstring2 $versionstring bigstring3
.aboutpd.text insert end $bigstring3
}
close $textfile
} stderr ] } {
::pdwindow::error [_ "couldn't read \"%s\" document" [_ "About Pd" ] ]
::pdwindow::error "\n\t$stderr\n"
destroy .aboutpd
}
}
}
# ------------------------------------------------------------------------------
# opening docs as menu items (like the Test Audio and MIDI patch and the manual)
proc ::pd_menucommands::menu_doc_open {dir basename} {
if {[file pathtype $dir] eq "relative"} {
set dirname "$::sys_libdir/$dir"
} else {
set dirname $dir
}
set textextension "[string tolower [file extension $basename]]"
if {[lsearch -exact [lindex $::filetypes 0 1] $textextension] > -1} {
set fullpath [file normalize [file join $dirname $basename]]
set dirname [file dirname $fullpath]
set basename [file tail $fullpath]
pdsend "pd open [enquote_path $basename] [enquote_path $dirname] 1"
} else {
::pd_menucommands::menu_openfile "$dirname/$basename"
}
}
# open HTML docs from the menu using the OS-default HTML viewer
proc ::pd_menucommands::menu_openfile {filename} {
if {$::tcl_platform(os) eq "Darwin"} {
exec sh -c [format "open '%s'" $filename]
} elseif {$::tcl_platform(platform) eq "windows"} {
exec rundll32 url.dll,FileProtocolHandler [format "%s" $filename] &
} else {
foreach candidate { gnome-open xdg-open sensible-browser iceweasel firefox \
mozilla galeon konqueror netscape lynx } {
set browser [lindex [auto_execok $candidate] 0]
if {[string length $browser] != 0} {
exec -- sh -c [format "%s '%s'" $browser $filename] &
break
}
}
}
}
# ------------------------------------------------------------------------------
# open the help-intro.pd patch which provides a list of core objects
proc ::pd_menucommands::menu_objectlist {} {
pdsend "pd help-intro"
}
# ------------------------------------------------------------------------------
# Mac OS X specific functions
proc ::pd_menucommands::menu_bringalltofront {} {
# use [winfo children .] here to include windows that are minimized
foreach item [winfo children .] {
# get all toplevel windows, exclude menubar windows
if { [string equal [winfo toplevel $item] $item] && \
[catch {$item cget -tearoff}]} {
wm deiconify $item
}
}
wm deiconify .
}
# this is needed because on macOS the Menu-Accelerators are actually used
# (rather than just displayed)
# so this proc simply gobbles the commands to suppress duplicates:
# only the first $script until the next idle-period is run (the rest is discarded)
# see https://stackoverflow.com/a/69900053/1169096
proc ::pd_menucommands::scheduleAction {args} {
if {$::pd_menucommands::currentAction eq ""} {
# Prepend a command to clear the variable
set act "set ::pd_menucommands::currentAction {};$args"
set ::pd_menucommands::currentAction [after idle $act]
}
}
set ::pd_menucommands::currentAction {}