aboutsummaryrefslogtreecommitdiff
path: root/pd/tcl/pd.tcl
blob: 0418dcd85c693b9a93a73da56b7c06355b7b8919 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
#!/bin/sh
# This line continues for Tcl, but is a single line for 'sh' \
    exec wish "$0" -- ${1+"$@"}
# For information on usage and redistribution, and for a DISCLAIMER OF ALL
# WARRANTIES, see the file, "LICENSE.txt," in this distribution.
# Copyright (c) 1997-2009 Miller Puckette.

# puts -------------------------------pd.tcl-----------------------------------

package require Tcl 8.3
package require Tk
if {[tk windowingsystem] ne "win32"} {package require msgcat}

# Pd's packages are stored in the same directory as the main script (pd.tcl)
set auto_path [linsert $auto_path 0 [file dirname [info script]]]
package require pd_connect
package require pd_menus
package require pd_bindings
package require dialog_font
package require dialog_gatom
package require dialog_iemgui
package require pdtk_array
package require pdtk_canvas
package require pdtk_text
# TODO eliminate this kludge:
package require wheredoesthisgo

# import into the global namespace for backwards compatibility
namespace import ::pd_connect::pdsend
namespace import ::dialog_font::pdtk_canvas_dofont
namespace import ::dialog_gatom::pdtk_gatom_dialog
namespace import ::dialog_iemgui::pdtk_iemgui_dialog

#------------------------------------------------------------------------------#
# global variables

# for testing which platform we are running on ("aqua", "win32", or "x11")
set windowingsystem ""

# canvas font, received from pd in pdtk_pd_startup, set in s_main.c
set font_family "Courier"
set font_weight "bold"
# sizes of chars for each of the Pd fixed font sizes:
#  fontsize  width(pixels)  height(pixels)
set font_fixed_metrics {
    8 5 10
    9 6 11
    10 6 13
    12 7 15
    14 8 17
    16 10 20
    18 11 22
    24 14 30
    30 18 37
    36 22 45
}

# store list of parent windows for Window menu
set menu_windowlist {}

#------------------------------------------------------------------------------#
# coding style
#
# these are preliminary ideas, we'll change them as we work things out:
# - when possible use "" doublequotes to delimit messages
# - use '$::myvar' instead of 'global myvar' 
# - for the sake of clarity, there should not be any inline code, everything 
#   should be in a proc that is ultimately triggered from main()
# - if a menu_* proc opens a panel, that proc is called menu_*_panel
# - use "eq/ne" for string comparison, NOT "==/!="
#
## Names for Common Variables
#----------------------------
#
# variables named after the Tk widgets they represent
#   $mytoplevel = 'toplevel'
#   $mymenubar = the 'menu' attached to the 'toplevel'
#   $mymenu = 'menu' attached to the menubar 'menu'
#   $menuitem = 'menu' item
#   $mycanvas = 'canvas'
#   $canvasitem = 'canvas' item
#
#
## Prefix Names for procs
#----------------------------
# pdtk      pd -> pd-gui API (i.e. called from 'pd')
# pdsend    pd-gui -> pd API (sends a message to 'pd' using pdsend)
# canvas    manipulates a canvas
# text      manipulates a Tk 'text' widget

# ------------------------------------------------------------------------------
# init functions

proc init {} {
    # we are not using Tk scaling, so fix it to 1 on all platforms.  This
    # guarantees that patches will be pixel-exact on every platform
    tk scaling 1

    # TODO Tcl/Tk 8.3 doesn't have [tk windowingsystem]
    set ::windowingsystem [tk windowingsystem]
    # get the versions for later testing
    regexp {([0-9])\.([0-9])\.([0-9]+)} [info patchlevel] \
        wholematch ::tcl_major ::tcl_minor ::tcl_patch
    switch -- $::windowingsystem {
        "x11" {
            # add control to show/hide hidden files in the open panel (load
            # the tk_getOpenFile dialog once, otherwise it will not work)
            catch {tk_getOpenFile -with-invalid-argument} 
            set ::tk::dialog::file::showHiddenBtn 1
            set ::tk::dialog::file::showHiddenVar 0
            # set file types that open/save recognize
            set ::filetypes {
                {{pd files}         {.pd}  }
                {{max patch files}  {.pat} }
                {{max text files}   {.mxt} }
            }
        }
        "aqua" {
            # set file types that open/save recognize
            set ::filetypes {
                {{Pd Files}               {.pd}  }
                {{Max Patch Files (.pat)} {.pat} }
                {{Max Text Files (.mxt)}  {.mxt} }
            }
        }
        "win32" {
            font create menufont -family Tahoma -size -11
            # set file types that open/save recognize
            set ::filetypes {
                {{Pd Files}         {.pd}  }
                {{Max Patch Files}  {.pat} }
                {{Max Text Files}   {.mxt} }
            }
        }
    }
}

# official GNU gettext msgcat shortcut
if {[tk windowingsystem] ne "win32"} {
    proc _ {s} {return [::msgcat::mc $s]}
} else {
    proc _ {s} {return $s}
}

proc load_locale {} {
    ::msgcat::mcload [file join [file dirname [info script]] locale]

    # for Windows
    #set locale "en"  ;# Use whatever is right for your app
    #if {[catch {package require registry}]} {
    #       tk_messageBox -icon error -message "Could not get locale from registry"
    #} else {
    #   set locale [string tolower \
    #       [string range \
    #       [registry get {HKEY_CURRENT_USER\Control Panel\International} sLanguage] 0 1] ]
    #}

    ##--moo: force default system and stdio encoding to UTF-8
    encoding system utf-8
    fconfigure stderr -encoding utf-8
    fconfigure stdout -encoding utf-8
    ##--/moo
}

# ------------------------------------------------------------------------------
# font handling

# this proc gets the internal font name associated with each size
proc get_font_for_size {size} {
    return "pd_font_${size}"
}

proc set_base_font {family weight} {
    if {[lsearch -exact [font families] $family] > -1} {
        set ::font_family $family
    } else {
        puts stderr "Error: Font family \"$family\" not found, using default: $::font_family"
    }
    if {[lsearch -exact {bold normal} $weight] > -1} {
        set ::font_weight $weight
        set using_defaults 0
    } else {
        puts stderr "Error: Font weight \"$weight\" not found, using default: $::font_weight"
    }
    puts stderr "Using FONT $::font_family $::font_weight"
}

# creates all the base fonts (i.e. pd_font_8 thru pd_font_36) so that they fit
# into the metrics given by $::font_fixed_metrics for any given font/weight
proc fit_font_into_metrics {} {
# TODO the fonts picked seem too small, probably on fixed width
    foreach {size width height} $::font_fixed_metrics {
    set myfont [get_font_for_size $size]
    font create $myfont -family $::font_family -weight $::font_weight \
        -size [expr {-$height}]
    set height2 $height
    set giveup 0
    while {[font measure $myfont M] > $width} {
        incr height2 -1
        font configure $myfont -size [expr {-$height2}]
        if {$height2 * 2 <= $height} {
        set giveup 1
        puts "error: [lindex [info level 0] 0] failed to find a font of size $size fitting into a $width x $height cell! this system sucks"
        break
        }
    }
    if {$giveup} {continue}
    }
}


# ------------------------------------------------------------------------------
# procs called directly by pd

proc pdtk_pd_startup {version {args ""}} {
    # pdtk_post "pdtk_pd_startup $version $args"
    # pdtk_post "\tversion: $version"
    # pdtk_post "\targs: $args"
    set oldtclversion 0
    pdsend "pd init [enquote_path [pwd]] $oldtclversion $::font_fixed_metrics"
    set_base_font [lindex $args 2] [lindex $args 3]
    fit_font_into_metrics
    # TODO what else is needed from the original?
}

##### routine to ask user if OK and, if so, send a message on to Pd ######
proc pdtk_check {ignoredarg message reply_to_pd default} {
    # TODO this should use -parent and -title, but the hard part is figuring
    # out how to get the values for those without changing g_editor.c
    set answer [tk_messageBox -type yesno -icon question \
                    -default $default -message $message]
    if {$answer eq "yes"} {
        pdsend $reply_to_pd
    }
}

proc pdtk_fixwindowmenu {} {
    #TODO figure out how to do this cleanly
    puts stderr "Running pdtk_fixwindowmenu"
}

# ------------------------------------------------------------------------------
# procs called directly by pd

proc check_for_running_instances {} {
## http://tcl.tk/man/tcl8.4/TkCmd/send.htm
## This script fragment can be used to make an application that only  
## runs once on a particular display.
#
#if {[tk appname FoobarApp] ne "FoobarApp"} {
#   send -async FoobarApp RemoteStart $argv
#   exit
#}
## The command that will be called remotely, which raises
## the application main window and opens the requested files
#proc RemoteStart args {
#   raise .
#   foreach filename $args {
#       OpenFile $filename
#   }
#}
}

proc load_startup {} {
    global errorInfo
    set pd_guidir "[pwd]/../startup"
    # puts stderr "load_startup $pd_guidir"
    if { ! [file isdirectory $pd_guidir]} { return }
    foreach filename [glob -directory $pd_guidir -nocomplain -types {f} -- *.tcl] {
        puts "Loading $filename"
        set tclfile [open $filename]
        set tclcode [read $tclfile]
        close $tclfile
        if {[catch {uplevel #0 $tclcode} errorname]} {
            puts stderr "------------------------------------------------------"
            puts stderr "UNHANDLED ERROR: $errorInfo"
            puts stderr "FAILED TO LOAD $filename"
            puts stderr "------------------------------------------------------"
        }
    }
}

# ------------------------------------------------------------------------------
# main
proc main {argc argv} {
    catch {console show} ;# Not all platforms have the console command
    post_tclinfo
    pdtk_post "Starting pd.tcl with main($argc $argv)"
    check_for_running_instances
    if {[tk windowingsystem] ne "win32"} {load_locale}
    init

    # TODO check args for -stderr and set pdtk_post accordingly
    if { $argc == 1 && [string is int [lindex $argv 0]]} {
    # 'pd' started first and launched us, so get the port to connect to
        ::pd_connect::to_pd [lindex $argv 0]
    } else {
        # the GUI is starting first, so create socket and exec 'pd'
        set portnumber [::pd_connect::create_socket]
        set pd_exec [file join [file dirname [info script]] ../bin/pd]
        exec -- $pd_exec -guiport $portnumber &
        #TODO add vwait so that pd-gui will exit if pd never shows up
    }
    ::pd_bindings::class_bindings
    create_pdwindow
    load_startup
}

main $::argc $::argv