# ------------------------------------------------------------------------------
# optional documents directory, implemented as a GUI plugin
#
# this feature provides a beginner-friendly location for patches & externals and
# is created by a dialog when first running Pd
#
# a subfolder for externals is automatically added to the user search paths and
# file dialogs will open in the docs path by default
#
# Dan Wilcox <danomatika.com> 2017
#
# The minimum version of TCL that allows the plugin to run
package require Tcl 8.4 9
package require pdwindow 0.1
package require pd_guiprefs 0.1
package require wheredoesthisgo
namespace eval ::pd_docsdir:: {
variable docspath
namespace export init
namespace export get_default_path
namespace export get_disabled_path
namespace export create_path
namespace export set_path
namespace export path_is_valid
namespace export get_default_externals_path
namespace export get_externals_path
namespace export externals_path_is_valid
}
# if empty, the user is prompted about creating this
# if set to "DISABLED", the docs dir functionality is disabled
set ::pd_docsdir::docspath ""
# check the Pd documents directory path & prompt user to create if it's empty,
# otherwise ignore all of this if the user cancelled it
# set reset to true to force the welcome prompt
proc ::pd_docsdir::init {{reset false}} {
if {$reset} {
set ::pd_docsdir::docspath ""
} else {
set ::pd_docsdir::docspath [::pd_guiprefs::read docspath]
}
set docspath $::pd_docsdir::docspath
if { "$docspath" eq "DISABLED" } {
# respect prev decision
return
} elseif { "$docspath" eq "" } {
# sanity check, Pd might be running on a non-writable install
if {![file exists $::env(HOME)] || ![file writable $::env(HOME)]} {
return
}
# default location: Documents dir if it exists, otherwise use home
set docspath_default [file join $::env(HOME) "Documents"]
if {![file exists $docspath_default]} {
set docspath_default $::env(HOME)
}
if {![file writable $docspath_default]} {
# sanity check
set docspath "DISABLED"
return
}
set docspath_default [file join $docspath_default "Pd"]
# prompt
if {[file isdir ${docspath_default}]} {
set msg [_ "Do you want Pd to use the existing documents directory for patches and external libraries?\n\nLocation: %s\n\nYou can change or disable this later in the Path preferences." ]
} {
set msg [_ "Do you want Pd to create a documents directory for patches and external libraries?\n\nLocation: %s\n\nYou can change or disable this later in the Path preferences." ]
}
set _args "-message \"[format $msg $docspath_default]\" -type yesnocancel -default yes -icon question -parent .pdwindow"
switch -- [eval tk_messageBox ${_args}] {
yes {
set docspath $docspath_default
# set the new docs path
if {![::pd_docsdir::create_path $docspath]} {
# didn't work
return
}
}
no {
# bug off
set docspath "DISABLED"
}
cancel {
# defer
return
}
}
::pd_docsdir::set_path $docspath
if {[::pd_docsdir::create_externals_path]} {
::pd_docsdir::add_externals_path
if {[namespace exists ::deken]} {
# clear first so set_installpath doesn't pick up prev value from guiprefs
set ::deken::installpath ""
:::deken::set_installpath [::pd_docsdir::get_externals_path]
}
}
focus .pdwindow
} else {
# check saved setting
set fullpath [file normalize "$docspath"]
if {![file exists $fullpath]} {
set msg [_ "Pd documents directory cannot be found:\n\n%s\n\nChoose a new location?" ]
set _args "-message \"[format $msg $::pd_docsdir::docspath]\" -type yesno -default yes -icon warning -parent .pdwindow"
switch -- [eval tk_messageBox ${_args}] {
yes {
# set the new docs path
set newpath [tk_chooseDirectory -title [_ "Choose Pd documents directory:"] \
-initialdir $::env(HOME)]
if {$newpath ne ""} {
if {![::pd_docsdir::update_path $newpath]} {
# didn't work
return
}
}
}
no {
# defer
return
}
}
focus .pdwindow
}
}
# open dialogs in docs dir if GUI was started first
if {[::pd_docsdir::path_is_valid]} {
if {"$::filenewdir" eq "$::env(HOME)"} {
set ::filenewdir $::pd_docsdir::docspath
}
if {"$::fileopendir" eq "$::env(HOME)"} {
set ::fileopendir $::pd_docsdir::docspath
}
}
::pdwindow::verbose 0 "Pd documents directory: $::pd_docsdir::docspath\n"
}
# try to find a default docs path, returns an empty string if not possible
proc ::pd_docsdir::get_default_path {} {
# sanity check, Pd might be running on a non-writable install
if {![file exists $::env(HOME)] || ![file writable $::env(HOME)]} {
return ""
}
# default location: Documents dir if it exists, otherwise use home
set path [file join $::env(HOME) "Documents"]
if {![file exists $path]} {
set path $::env(HOME)
}
if {[file writable $path]} {
return [file join $path "Pd"]
}
return ""
}
# get the default disabled path value
proc ::pd_docsdir::get_disabled_path {} {
return "DISABLED"
}
# update the Pd documents directory path & externals path, creates if needed
# higher level complement to create_path & path_set
# checks ::deken::installpath value
# returns 1 if the update was successful
proc ::pd_docsdir::update_path {path} {
if {"$path" eq "" || "$path" eq "DISABLED"} {
::pd_docsdir::set_path "$path"
} else {
if {![::pd_docsdir::create_path "$path"]} {return 0}
::pd_docsdir::set_path "$path"
# only try creating the externals path if it is the deken installpath,
# this keeps the installpath from changing arbitrarily
if {![namespace exists ::deken] || [::pd_docsdir::is_externals_path_deken_installpath]} {
if {[::pd_docsdir::create_externals_path]} {
::pd_docsdir::add_externals_path
}
}
}
return 1
}
# create the Pd documents directory path and it's "externals" subdir,
# set externalsdir to false to create the given path only
# returns true if the path already exists
proc ::pd_docsdir::create_path {path} {
if {"$path" eq "" || "$path" eq "DISABLED"} {return 0}
if {[file mkdir [file normalize "$path"]] eq ""} {
return 1
}
set msg [_ "couldn't create Pd documents directory: %s" $path]
::pdwindow::error "${msg}\n"
return 0
}
# set the Pd documents directory path
proc ::pd_docsdir::set_path {path} {
set ::pd_docsdir::docspath $path
::pd_guiprefs::write docspath "$path"
}
# returns 1 if the docspath is set, not disabled, & a directory
proc ::pd_docsdir::path_is_valid {} {
if {"$::pd_docsdir::docspath" eq "" ||
"$::pd_docsdir::docspath" eq "DISABLED" ||
![file isdirectory "$::pd_docsdir::docspath"]} {
return 0
}
return 1
}
# create the externals subdir within the current docs path or a given path,
# returns 1 on success
proc ::pd_docsdir::create_externals_path {{path ""}} {
if {"$path" eq ""} {
if {"$::pd_docsdir::docspath" eq "" ||
"$::pd_docsdir::docspath" eq "DISABLED"} {
return 0
}
set path $::pd_docsdir::docspath
}
set newpath [file join [file normalize "$path"] "externals"]
if {[file mkdir "$newpath" ] eq ""} {
return 1
}
set msg [_ "couldn't create \"externals\" directory in: %s" $path]
::pdwindow::error "${msg}\n"
return 0
}
# get the externals subdir within the current docs path or a given path,
# returns an empty string if docs path is not valid
proc ::pd_docsdir::get_externals_path {{path ""}} {
if {"$path" eq ""} {
if {[::pd_docsdir::path_is_valid]} {
return [file join $::pd_docsdir::docspath "externals"]
} else {
return ""
}
}
return [file join "$path" "externals"]
}
# returns 1 if the docspath externals subdir exists
proc ::pd_docsdir::externals_path_is_valid {} {
set path [::pd_docsdir::get_externals_path]
if {"$path" ne "" && [file writable [file normalize "$path"]]} {
return 1
}
return 0
}
# add the externals subdir within the current docs path or a given path to the
# the user search paths
proc ::pd_docsdir::add_externals_path {{path ""}} {
set externalspath [::pd_docsdir::get_externals_path $path]
if {$externalspath ne ""} {
add_to_searchpaths $externalspath
}
}
# is the deken installpath the externals subdir within the current docs path?
proc ::pd_docsdir::is_externals_path_deken_installpath {} {
if {![namespace exists ::deken]} {return 0}
if {[file normalize $::deken::installpath] eq
[file normalize [::pd_docsdir::get_externals_path]]} {
return 1
}
return 0
}
# self-init after loading
::pd_docsdir::init