Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

merge TkExtras with Tk

  • Loading branch information...
commit e090724fb17fd8694410dac7f1c45c796adf9994 1 parent 9c2c28f
@jverzani jverzani authored
View
724 examples/README.md
@@ -0,0 +1,724 @@
+## The Tk Pacakge
+
+This package provides an interface to the Tcl/Tk libraries, useful for creating graphical user interfaces. The basic functionality is provided by the `tcl_eval` function, which is used to pass Tcl commands. The `Canvas` widget is used to create a device for plotting of `julia`'s graphics. In particular, the `Winston` and `Images` package can render to such a device.
+
+In addition, there are convenience methods for working with most of
+the widgets provided by `Tk`. The `tcl` function is a wrapper for
+`tcl_eval` which provides translations from `julia` objects into `Tcl`
+constructs. This function is similar to the one found in `R`'s `tcltk`
+package. Following that package, we provides several of the basic
+function to simplify the interface for working with Tk.
+
+
+
+### Constructors
+
+We provide constructors for the following widgets
+
+* `Toplevel`: for toplevel windows
+* `Frame`, `Labelframe`, `Notebook`, `Panedwindow`: for the basic containers
+* `Label`, `Button`, `Menu`: basic elements
+* `Checkbutton`, `Radio`, `Combobox`, `Slider`, `Spinbox`: selection widgets
+* `Entry`, `Text`: text widgets
+* `Treeview`: for trees, but also listboxes and grids
+* `Sizegrip`, `Separator`, `Progressbar`, `Image`, `Scrollbar`: various widgets
+
+Actually, scrollbars do not work (atleast on my Mac) and this also means comboboxes don't work.
+
+### Methods
+
+In addition to providing more constructors, there are some convenience methods defined.
+
+* The usual `pack`, `pack_configure`, `forget`, `grid`,
+ `grid_configure`, `grid_forget`, ... for containers, but also
+ `formlayout`.
+
+* The conveniences `get_value` and `set_value` to get and set the
+ primary value for a control
+
+* The conveniences `get_items` and `set_items` to get and set the
+ item(s) to select from for selection widgets.
+
+* The conveniences `get_width`, `get_height`, `get_size`, `set_width`,
+ `set_height`, `set_size` for adjusting sizes. (Resizing a window
+ with the mouse does strange things on a Mac ...)
+
+* The conveniences `get_enabled` and `set_enabled` to specify if a
+ widget accepts user input
+
+
+### Some basic examples
+
+A simple "Hello world" example, which shows off many of the styles is given by:
+
+```
+w = Toplevel() ## A top level window
+f = Frame(w, {:padding => [3,3,2,2], :relief=>"groove"}) ## A Frame with some options set
+pack(f, {:expand => true, :fill => "both"}) # using pack to manage the layout of f
+#
+callback(path) = Messagebox(w, "A message", "Hello World") ## A callback to open a message
+b = Button(f, "Click for a message", callback ) ## Button constructor has convenience interface
+grid(b, 1, 1) ## use grid to pack in b. 1,1 specifies location
+``
+
+
+
+For the R package `tctlk` there are numerous examples at http://bioinf.wehi.edu.au/~wettenhall/RTclTkExamples/
+
+We borrow a few of these to illustrate the `Tk` package for `julia`.
+
+The `Tk` package provides some convenience constructors. We
+follow Tk's convention of capitalizing the first letter, so
+`ttk::checkbutton` is `Checkbutton`. As well, we have the function
+`tcl` as a more convenient alternative to `Tk.tcl_eval` and some
+helpers like `tk_configure` and `tk_cget`. In addition, the package
+provides a few non-standard methods for interacting with the widgets
+in a generic manner: `get_value`, `set_value`, ...
+
+`Tk` commands are combined strings followed by options. Something
+like: `.button configure -text {button text}` is called as
+`tk_configure(button, {:text => "button text})`. key-value options are
+specified with a Dict, which converts to the underlying Tcl
+object. Similarly, path names are also translated.
+
+
+## Pack widgets into a themed widget for better appearance
+
+`Toplevel` is the command to create a new top-level
+window. (`Tk.Window` is similar, but we give `Toplevel` a unique type
+allowing us to add methods to it.) Toplevel windows play a special
+role, as they start the widget hierarchy needed when constructing
+child components.
+
+A toplevel window is not a themed widget. Immediately packing in a
+`Frame` instance is good practice, as otherwise the background of the
+window may show through:
+
+```
+w = Toplevel()
+f = Frame(w)
+pack(f, {:expand=>true, :fill=>"both"})
+```
+
+### Notes:
+
+* Sometimes the frame is configured with padding so that the sizegrip
+ shows, e.g. `frame(w,{:padding => [3,3,2,2]})`.
+
+* The above will get the size from the frame -- which has no
+ request. This means the window will disappear. You may want to force
+ the size to come from the toplevel window. You can use `tcl("pack",
+ "propagate", w, false)` (aliased to `pack_stop_propagate` to get
+ this:)
+
+```
+w = Toplevel("title", 400, 300) ## title, width, height
+pack_stop_propagate(w)
+f = Frame(w)
+pack(f, {:expand=>true, :fill=>"both"})
+```
+
+* resizing toplevel windows can leave visual artifacts, at least on a
+ Mac. This is not optimal!
+
+<img src="munged-window.png"></img>
+
+## Message Box
+
+The `Messagebox` constructor makes a modal message box.
+
+```
+Messagebox("title", "message")
+```
+
+An optional `parent` argument can be specified to locate the box near
+the parent, as seen in the examples.
+
+## Checkbuttons
+
+<img src="checkbutton.png" > </img>
+
+Check boxes are constructed with `Checkbutton`:
+
+```
+w = Toplevel()
+cb = Checkbutton(w, "I like Julia")
+pack(cb)
+
+function callback(path) ## callbacks have at least one argument
+ value = get_value(cb)
+ msg = value ? "Glad to hear that" : "Sorry to hear that"
+ Messagebox(w, "Thanks for the feedback", msg)
+end
+
+tk_bind(cb, "command", callback) ## bind to command option
+```
+
+The `set_items` method can be used to change the label.
+
+
+## Radio buttons
+
+<img src="radio.png" > </img>
+
+```
+w = Toplevel()
+f = Frame(w)
+pack(f, {:expand=>true, :fill=>"both"})
+
+l = Label(f, "Which do you prefer?")
+rb = Radio(f, ["apples", "oranges"])
+b = Button(f, "ok")
+map(u -> pack(u, {:anchor => "w"}), (l, rb, b)) ## pack in left to right
+
+
+function callback(path)
+ msg = (get_value(rb) == "apples") ? "Good choice! An apple a day keeps the doctor away!" :
+ "Good choice! Oranges are full of Vitamin C!"
+ Messagebox(w, "Title:", msg)
+end
+
+tk_bind(b, "command", callback)
+```
+
+The individual buttons can be accessed via `[`, as in `rb[1]`. This
+allows one to edit the labels, as in
+
+```
+set_items(rb[1], "Honeycrisp Apples")
+```
+
+(The `set_items` method is used to set the items for a selection
+widget, in this case the lone item is the name, or label of the
+button.)
+
+## Menus
+
+Menu bars for toplevel windows are easily created with the `menu_add`
+method. One can add actions items (pass a callback function), check
+buttons, radio buttons, or separators.
+
+
+```
+w = Toplevel()
+tcl("pack", "propagate", w, false) ## or pack_stop_propagate(w)
+
+mb = Menu(w) ## makes menu, adds to top-level window
+fmenu = menu_add(mb, "File")
+omenu = menu_add(mb, "Options")
+
+menu_add(fmenu, "Open file...", (path) -> println("Open file dialog, ..."))
+menu_add(fmenu, Separator(w)) ## second argument is Tk_Separator instance
+menu_add(fmenu, "Close window", (path) -> destroy(w))
+
+cb = Checkbutton(w, "Something visible")
+set_value(cb, true) ## initialize
+menu_add(omenu, cb) ## second argument is Tk_Checkbutton instance
+
+menu_add(omenu, Separator(w)) ## put in a separator
+
+rb = Radio(w, ["option 1", "option 2"])
+set_value(rb, "option 1") ## initialize
+menu_add(omenu, rb) ## second argument is Tk_Radio instance
+
+b = Button(w, "print selected options")
+pack(b, {:expand=>true, :fill=>"both"})
+
+function callback(path)
+ vals = map(get_value, (cb, rb))
+ println(vals)
+end
+
+callback_add(b, callback) ## generic way to add callback for most common event
+)
+```
+
+
+## Entry widget
+
+<img src="entry.png"></img>
+
+The entry widget can be used to collect data from the user.
+
+```
+w = Toplevel()
+f = Frame(w); pack(f, {:expand=>true, :fill=>"both"})
+
+e = Entry(f)
+b = Button(f, "Ok")
+
+formlayout(e, "First name:")
+formlayout(b, nothing)
+focus(e) ## put keyboard focus on widget
+
+function callback(path)
+ val = get_value(e)
+ msg = "You have a nice name $val"
+ Messagebox(w, "Title", msg)
+end
+
+tk_bind(b, "command", callback)
+tk_bind(b, "<Return>", callback)
+tk_bind(e, "<Return>", callback) ## bind to a certain key press event
+```
+
+## Listboxes
+
+<img src="listbox.png"></img>
+
+There is no `Listbox` constructor, rather we replicate this with
+`Treeview` simply by passing a vector of strings. Here we use a
+scrollbar too:
+
+```
+fruits = ["Apple", "Orange", "Banana", "Pear"]
+w = Toplevel("Favorite fruit?")
+tcl("pack", "propagate", w, false)
+f = Frame(w)
+pack(f, {:expand=>true, :fill=>"both"})
+
+f1 = Frame(f) ## need internal frame for use with scrollbars
+lb = Treeview(f1, fruits)
+scrollbars_add(f1, lb)
+pack(f1, {:expand=>true, :fill=>"both"})
+
+b = Button(f, "Ok")
+pack(b)
+
+function callback(path)
+ fruit_choice = get_value(lb)
+ msg = (fruit_choice == nothing) ? "What, no choice?" : "Good choice! $(fruit_choice[1])" * "s are delicious!"
+ Messagebox(w, "title", msg)
+end
+tk_bind(b, "command", callback)
+```
+
+The value returned by `get_value` is an array or `nothing`. Returning
+`nothing` may not be the best choice, perhaps a 0-length array is
+better?
+
+One can configure the `selectmode`. E.g. `tk_configure(lb,
+{:selectmode => "extended"})` with either `extended` (multiple
+selection possible, `browse` (single selection), or `none` (no
+selection).)
+
+The `Treeview` widget can also display a matrix of strings in a grid
+in addition to tree-like data.
+
+An editable grid could be done, but requires some additional Tk
+libraries.
+
+## Combo boxes
+
+Selection from a list of choices can be done with a combo box:
+
+<img src="combo.png" > </img>
+
+```
+fruits = ["Apple", "Orange", "Banana", "Pear"]
+
+w = Toplevel("Combo boxes", 300, 200)
+tcl("pack", "propagate", w, false)
+f = Frame(w); pack(f, {:expand=>true, :fill=>"both"})
+
+grid(Label(f, "Again, What is your favorite fruit?"), 1, 1)
+cb = Combobox(f, fruits)
+grid(cb, 2,1, {:sticky=>"ew"})
+
+b = Button(f, "Ok")
+grid(b, 3, 1)
+
+function callback(path)
+ fruit_choice = get_value(cb)
+ msg = (fruit_choice == nothing) ? "What, no choice?" :
+ "Good choice! $(fruit_choice)" * "s are delicious!"
+ Messagebox(w, "title", msg)
+end
+
+tk_bind(b, "command", callback)
+```
+
+
+Here no choice also returns `nothing`. Use this value with `set_value`
+to clear the selection, if desired.
+
+Editable combo boxes need to be configured by hand. (So combo isn't
+really what we have here :)
+
+## Text windows
+
+The basic multi-line text widget can be done through:
+
+```
+w = Toplevel()
+tcl("pack", "propagate", w, false)
+f = Frame(w)
+txt = Text(f)
+scrollbars_add(f, txt)
+pack(f, {:expand=>true, :fill => "both"})
+```
+
+Only a `get_value` and `set_value` is provided. One can configure
+other things (adding/inserting text, using tags, ...) directly with
+`tcl` or `tcl_eval`.
+
+## Events
+
+One can bind a callback to an event in tcltk. There are few things to know:
+
+* Callbacks have at least one argument (we use `path`). With
+ `tk_bind`, other arguments are matched by name to correspond to
+ tcltk's percent substitution. E.g. `f(path, x, y)` would get values
+ for x and y through `%x %y`.
+
+* We show how to bind to a widget event, but this can be more
+ general. E.g., toplevel events are for all children of the window a
+ style can match all object of that style.
+
+* many widgets have a standard `command` argument in addition to
+ window manager events they respond to. This can be passed to
+ `tk_bind` as the event.
+
+* The `tk_bind` method does most of the work. The `callback_add`
+ method binds to the most common event, mostly the `command`
+ one. This can be used to bind the same callback to multiple widgets
+ at once.
+
+
+
+## Sliders
+
+The `Slider` widget presents a slider for selection from a range of
+values. The convenience constructor allows one to specify the range of
+values through a Range object:
+
+```
+w = Toplevel()
+sc = Slider(w, 1:100)
+pack(sc)
+tk_bind(sc, "command", path -> println("The value is $(get_value(sc))"))
+```
+
+
+## Sharing a variable between widgets
+
+<img src="scale-label.png"></img>
+
+Some widgets have a `textvariable` option. These can be shared to have
+automatic synchronization. For example, the scale widget does not have
+any indication as to the value, we remedy this with a label.
+
+```
+w = Toplevel("Slider and label", 300, 200)
+pack_stop_propagate(w)
+f = Frame(w); pack(f, {:expand => true, :fill => "both"})
+
+sc = Slider(f, 1:20)
+l = Label(f)
+tk_configure(l, {:textvariable => tk_cget(sc, "variable") })
+
+pack(sc, {:side=>"left", :expand=>true, :fill=>"x", :anchor=>"w"})
+pack(l, {:side=>"left", :anchor=>"w"})
+```
+
+This combination above is not ideal, as the length of the label is not
+fixed. It would be better to format the value and use `set_value` in a
+callback.
+
+## Spinbox
+
+<img src="scale-spinbox.png"></img>
+
+The scale widget easily lets one pick a value, but it can be hard to
+select a precise one. The spinbox makes this easier. Here we link the
+two using a callback:
+
+```
+w = Toplevel("Slider/Spinbox")
+f = Frame(w); pack(f, {:expand => true, :fill => "both"})
+
+sc = Slider(f, 1:100)
+sp = Spinbox(f, 1:100)
+map(pack, (sc, sp))
+
+tk_bind(sc, "command", path -> set_value(sp, get_value(sc)))
+tk_bind(sp, "command", path -> set_value(sc, get_value(sp)))
+```
+## Images
+
+<img src="image.png"></img>
+
+The `Image` widget can be used to show `gif` files.
+
+```
+fname = Pkg.dir("Tk", "examples", "logo.gif")
+img = Image(fname)
+
+w = Toplevel("Image")
+l = Label(w, img)
+pack(l)
+```
+
+This example adds an image to a button.
+
+```
+fname = Pkg.dir("Tk", "examples", "weather-overcast.gif") ## https://code.google.com/p/ultimate-gnome/
+img = Image(fname)
+
+w = Toplevel("Icon in button")
+b = Button(w, "weather", img) ## or: b = Button(w, {:text=>"weather", :image=>img, :compound=>"left"})
+pack(b)
+```
+
+### Graphics
+
+The obvious desire to embed a graph into a GUI turns out to be not so
+simple. Why?
+
+* `Gadfly` makes SVG graphics. This is great for showing in a web
+ browser, but not so great with Tk, as there is not SVG viewer. The
+ example below can be easily modified.
+
+* `Winston` can render to a `Canvas` object. This should be great, *but* for
+ some reason trying to show the canvas object in anything but a
+ toplevel window fails. When this issue is fixed, that should work out
+ really well.
+
+* In the meantime, we can do the following:
+
+ - write a Winston graphic to a `png` file.
+
+ - convert this to `gif` format via imagemagick's `convert` function,
+ which is also used by the `Images` package
+
+ - use that `gif` file through an `Image` object.
+
+The only issue is the asynchronous nature of the `convert` command
+requires us to wait until the command is finished to update the
+label. The `spawn` command below with all its arguments does this.
+
+```
+using Tk
+using Winston
+using Images ## not used, but installs imagemagick?
+
+type WinstonLabel <: Tk.Tk_Widget
+ w
+ p
+ fname
+ WinstonLabel(w) = new(Tk.Label(w), nothing, nothing)
+ WinstonLabel(w, args::Dict) = new(Tk.Label(w, args), nothing, nothing)
+end
+
+function render(parent::WinstonLabel, p)
+ parent.p = p
+ if isa(parent.fname, Nothing)
+ parent.fname = nm = tempname()
+ ## tk_bind(parent, "<Destroy>", map(rm, ("$nm.png", "$nm.gif"))) ## clean up
+ end
+ nm = parent.fname
+ file(p, "$nm.png")
+ ## would use imwrite(imread("$nm.png"), "$nm.gif") but need to wait until done to update label
+ cmd = `convert $nm.png $nm.gif` # imagemagick convert from Images
+ spawn(false, cmd, (STDIN, STDOUT, STDERR), false, (self) -> begin ## spawn -- not run!
+ img = Tk.Image("$nm.gif")
+ Tk.tk_configure(parent.w, {:image=>img, :compound => "image"})
+ end)
+end
+
+
+## The interface
+w = Toplevel("demo", 800, 600)
+tcl("pack", "propagate", w, false)
+pg = Frame(w); pack(pg, {:expand=>true, :fill=>"both"})
+
+f = Labelframe(pg, "Controls"); pack(f, {:side=> "left", :anchor=>"nw"})
+img_area = WinstonLabel(pg)
+pack(img_area, {:expand => true, :fill=>"both"})
+
+sl = Slider(f, 1:20);
+formlayout(sl, "n")
+
+function plot_val(val)
+ x = linspace(0, 1.0, 200)
+ y = x.^val
+ p = FramedPlot()
+ add(p, Curve(x, y))
+
+ render(img_area, p)
+end
+
+tk_bind(sl, "command", (path) -> plot_val(get_value(sl)))
+plot_val(1) ## initial graph
+```
+
+If you try this you may find that it isn't great: sometimes the graphic is done; at times a black box
+flashes across the screen as the slider is moved.
+
+<img src="manipulate.png"></img>
+
+
+In the examples directory you can find an implementation of RStudio's
+`manipulate` function, which extends this example. This functions makes it very straightforward to define
+basic interactive GUIs for plotting with `Winston`.
+
+To try it, run
+
+```
+require(Pkg.dir("Tk", "examples", "manipulate.jl"))
+```
+
+The above graphic was produced with:
+
+```
+ex = quote
+ x = linspace( 0, n * pi, 100 )
+ c = cos(x)
+ s = sin(x)
+ p = FramedPlot()
+ setattr(p, "title", title)
+ if
+ fillbetween add(p, FillBetween(x, c, x, s) )
+ end
+ add(p, Curve(x, c, "color", color) )
+ add(p, Curve(x, s, "color", "blue") )
+ file(p, "example1.png")
+ p ## return a winston object
+end
+obj = manipulate(ex,
+ slider("n", "[0, n*pi]", 1:10)
+ ,entry("title", "Title", "title")
+ ,checkbox("fillbetween", "Fill between?", true)
+ ,picker("color", "Cos color", ["red", "green", "yellow"])
+ ,button("update")
+ )
+```
+
+
+## Frames
+
+The basic widget to hold child widgets is the Frame. As seen in the
+previous examples, it is simply constructed with `Frame`. The
+`padding` option can be used to give some breathing room.
+
+Laying out child components is done with a layout manager, one of
+`pack`, `grid`, or `place` in Tk.
+
+### pack
+
+For `pack` there are several configuration options that are used to
+indicate how packing of child components is to be done. The examples
+make use of
+
+* `side`: to indicate the side of the cavity that the child should be
+ packed against. Typically "top" to top to bottom packing or "left"
+ for left to right packing.
+
+* `anchor`: One of the compass points indicating what part of the
+ cavity the child should be attached to.
+
+* `expand`: should the child expand when packed
+
+* `fill`: how should an expanding child fill its space. We use
+ `{:expand=>true, :fill=>"both"}` to indicate the child should take
+ all the available space it can. Use `"x"` to stetch horizontally,
+ and `"y"` to stretch vertically.
+
+Unlike other toolkits (Gtk, Qt), one can pack both horizontally and
+vertically within a frame. So to pack horizontally, one must add the
+`side` option each time. It can be convenient to do this using a map
+by first creating the widgets, then managing them:
+
+```
+w = Toplevel("packing example")
+f = Frame(w); pack(f, {:expand=>true, :fill=>"both"})
+ok_b = Button(f, "Ok")
+cancel_b = Button(f, "Cancel")
+help_b = Button(f, "Help")
+map(u -> pack(u, {:side => "left"}), (ok_b, cancel_b, help_b))
+```
+
+### grid
+
+For `grid` the arguments are the row and column. We use integers or
+ranges. When a range, then the widget can span multiple rows or
+columns. Within a cell, the `sticky` argument replaces the `expand`,
+`fill`, and `anchor` arguments. This is a string with one or more
+directions to attach. A value of `news` is like `{:expand=>true,
+:fill=>"both"}`, as all four sides are attached to.
+
+We provide the `formlayout` method for conveniently laying out widgets
+in a form-like manner, with a label on the left. (Pass `nothing` to
+suppress this.)
+
+One thing to keep in mind: *in Tk a container can only employ one
+layout style for its immediate children* That is, you can't manage
+children both with `pack` and `grid`, though you can nest frames and
+mix and match layout managers.
+
+
+<img src="grid.png"></img>
+
+```
+w = Toplevel("Grid")
+f = Frame(w, {:padding => 10}); pack(f, {:expand=>true, :fill=>"both"})
+
+s1 = Slider(f, 1:10)
+s2 = Slider(f, 1:10, {:orient=>"vertical"})
+b3 = Button(f, "ew sticky")
+b4 = Button(f, "ns sticky")
+
+grid(s1, 1, 1:2, {:sticky=>"news"})
+grid(s2, 2:3, 2, {:sticky=>"news"})
+grid(b3, 2, 1)
+grid(b4, 3, 1, {:sticky=>"ns"}) ## breaks theme
+```
+
+## Notebooks
+
+A notebook container holds various pages and draws tabs to allow the
+user to switch between them. The `page_add` method makes this easy:
+
+```
+w = Toplevel()
+tcl("pack", "propagate", w, false)
+nb = Notebook(w)
+pack(nb, {:expand=>true, :fill=>"both"})
+
+page1 = Frame(nb)
+page_add(page1, "Tab 1")
+pack(Button(page1, "page 1"))
+
+page2 = Frame(nb)
+page_add(page2, "Tab 2")
+pack(Label(page2, "Some label"))
+
+set_value(nb, 2) ## position on page 2
+```
+
+
+## Panedwindows
+
+A paned window allows a user to allocate space between child
+components using their mouse. This is done by dragging a "sash". As
+with `Notebook` containers, children are added through `page_add`.
+
+```
+w = Toplevel("Panedwindow", 800, 300)
+tcl("pack", "propagate", w, false)
+f = Frame(w); pack(f, {:expand=>true, :fill=>"both"})
+
+pg = Panedwindow(f, "horizontal") ## orientation. Use "vertical" for up down.
+grid(pg, 1, 1, {:sticky => "news"})
+
+page_add(Button(pg, "button"))
+page_add(Label(pg, "label"))
+
+f = Frame(pg)
+formlayout(Entry(f), "Name:")
+formlayout(Entry(f), "Rank:")
+formlayout(Entry(f), "Serial Number:")
+page_add(f)
+
+tcl(pg, "sashpos", 0, 50) ## move first sash
+```
View
BIN  examples/checkbutton.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/combo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/entry.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/grid.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/image.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/listbox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/logo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
198 examples/manipulate.jl
@@ -0,0 +1,198 @@
+## This is an example mimicking RStudio's `manipulate` package (inspired by Mathematica's no doubt)
+## The manipulate function makes it easy to create "interactive" GUIs. In this case, we can
+## dynamically control parameters of a `Winston` graph.
+## To add a control is easy. There are just a few: slider, picker, checkbox, button, and entry
+
+using Winston
+
+
+
+type WinstonLabel <: Tk.Tk_Widget
+ w
+ p
+ fname
+ WinstonLabel(w) = new(Tk.Label(w), nothing, nothing)
+end
+
+function render(parent::WinstonLabel, p)
+ parent.p = p
+ if isa(parent.fname, Nothing)
+ parent.fname = tempname()
+ end
+ nm = parent.fname
+ file(p, "$nm.png")
+ ## would use imwrite(imread("$nm.png"), "$nm.gif") but need to wait until done to update label
+ cmd = `convert $nm.png $nm.gif` # imagemagick convert from Images
+ spawn(false, cmd, (STDIN, STDOUT, STDERR), false, (self) -> begin ## spawn -- not run!
+ img = Tk.Image("$nm.gif")
+ Tk.tk_configure(parent.w, {:image=>img, :compound => "image"})
+ end)
+end
+
+## do a manipulate type thing
+
+## context to store dynamic values
+module ManipulateContext
+using Winston
+end
+
+
+abstract ManipulateWidget
+get_label(widget::ManipulateWidget) = widget.label
+
+type SliderWidget <: ManipulateWidget
+ nm
+ label
+ initial
+ rng
+end
+function make_widget(parent, widget::SliderWidget)
+ sl = Slider(parent, widget.rng)
+ set_value(sl, widget.initial)
+ sl
+end
+
+slider(nm::String, label::String, rng::Range1, initial::Integer) = SliderWidget(nm, label, initial, rng)
+slider(nm::String, label::String, rng::Range1) = slider(nm, label, rng, min(rng))
+slider(nm::String, rng::Range1) = slider(nm, nm, rng, min(rng))
+
+
+type PickerWidget <: ManipulateWidget
+ nm
+ label
+ initial
+ vals
+end
+
+function make_widget(parent, widget::PickerWidget)
+ cb = Combobox(parent)
+ set_items(cb, widget.vals)
+ set_value(cb, widget.initial)
+ set_editable(cb, false)
+ cb
+end
+
+
+picker{T <: String}(nm::String, label::String, vals::Vector{T}, initial) = PickerWidget(nm, label, initial, vals)
+picker{T <: String}(nm::String, label::String, vals::Vector{T}) = picker(nm, label, vals, vals[1])
+picker{T <: String}(nm::String, vals::Vector{T}) = picker(nm, nm, vals)
+picker(nm::String, label::String, vals::Dict, initial) = PickerWidget(nm, label, vals, initial)
+picker(nm::String, label::String, vals::Dict) = PickerWidget(nm, label, vals, [string(k) for (k,v) in vals][1])
+picker(nm::String, vals::Dict) = picker(nm, nm, vals)
+
+type CheckboxWidget <: ManipulateWidget
+ nm
+ label
+ initial
+end
+function make_widget(parent, widget::CheckboxWidget)
+ w = Checkbutton(parent, widget.label)
+ set_value(w, widget.initial)
+ w
+end
+get_label(widget::CheckboxWidget) = nothing
+
+checkbox(nm::String, label::String, initial::Bool) = CheckboxWidget(nm, label, initial)
+checkbox(nm::String, label::String) = checkbox(nm, label, false)
+
+
+type ButtonWidget <: ManipulateWidget
+ label
+ nm
+end
+make_widget(parent, widget::ButtonWidget) = Button(parent, widget.label)
+get_label(widget::ButtonWidget) = nothing
+button(label::String) = ButtonWidget(label, nothing)
+
+
+## Add text widget to gather one-line of text
+type EntryWidget <: ManipulateWidget
+ nm
+ label
+ initial
+end
+make_widget(parent, widget::EntryWidget) = Entry(parent, widget.initial)
+entry(nm::String, label::String, initial::String) = EntryWidget(nm, label, initial)
+entry(nm::String, initial::String) = EntryWidget(nm, nm, initial)
+entry(nm::String) = EntryWidget(nm, nm, "{}")
+
+
+## Expression returns a plot object. Use names as values
+function manipulate(ex::Union(Symbol,Expr), controls...)
+ widgets = Array(Tk.Widget, 0)
+
+ w = Toplevel("Manipulate", 800, 500)
+ pack_stop_propagate(w)
+ pg = Panedwindow(w,"horizontal"); pack(pg, {:expand=>true, :fill=>"both"})
+ control_pane = Frame(pg); page_add(control_pane)
+ graph = WinstonLabel(pg); page_add(graph)
+ set_value(pg, 25) # heuristic
+
+ ## create, layout widgets
+ for i in controls
+ widget = make_widget(control_pane, i)
+ push!(widgets, widget)
+ formlayout(widget, get_label(i))
+ end
+
+ get_values() = [get_value(i) for i in widgets]
+ get_nms() = map(u -> u.nm, controls)
+ function get_vals()
+ d = Dict() # return Dict of values
+ vals = get_values(); keys = get_nms()
+ for i in 1:length(vals)
+ if !isa(keys[i], Nothing)
+ d[keys[i]] = vals[i]
+ end
+ end
+ d
+ end
+
+ function dict_to_module(d::Dict) ## stuff values into Manipulate Context
+ for (k,v) in d
+ eval(ManipulateContext, :($(symbol(k)) = $v))
+ end
+ end
+
+ function make_graphic(x...)
+ d = get_vals()
+ dict_to_module(d)
+ p = eval(ManipulateContext, ex)
+ render(graph, p)
+ end
+ map(u -> callback_add(u, make_graphic), widgets)
+ make_graphic()
+ widgets
+end
+
+
+
+
+## we need to make an expression
+## here we need to
+## * use semicolon (perhaps)
+## * return p, the FramedPlot object to draw
+
+ex = quote
+ x = linspace( 0, n * pi, 100 )
+ c = cos(x)
+ s = sin(x)
+ p = FramedPlot()
+ setattr(p, "title", title)
+ if
+ fillbetween add(p, FillBetween(x, c, x, s) )
+ end
+ add(p, Curve(x, c, "color", color) )
+ add(p, Curve(x, s, "color", "blue") )
+ file(p, "example1.png")
+ p
+end
+
+obj = manipulate(ex,
+ slider("n", "[0, n*pi]", 1:10)
+ ,entry("title", "Title", "title")
+ ,checkbox("fillbetween", "Fill between?", true)
+ ,picker("color", "Cos color", ["red", "green", "yellow"])
+ ,button("update")
+ )
+
View
BIN  examples/manipulate.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/munged-window.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
8 examples/process.jl
@@ -0,0 +1,8 @@
+f = "radio.png"
+
+function process_file(f)
+ a = readall(`base64 $f`) | chomp
+ "<!-- $f -->\n<img src='data:image/png;base64,$a'></img>"
+end
+
+process_file(f)
View
BIN  examples/radio.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/scale-label.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  examples/scale-spinbox.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
43 examples/test.jl
@@ -0,0 +1,43 @@
+## Example of widgets put into container with change handler assigned
+
+using TkExtras
+
+w = Toplevel("Test window", false)
+## pack in ttk frame for themed widgets
+f = Frame(w)
+tk_configure(f, {:padding => [3,3,2,2], :relief=>"groove"})
+pack(f, {:expand => true, :fill => "both"})
+
+## widgets
+b = Button(f, "one")
+cb = Checkbutton(f, "checkbutton")
+rg = Radio(f, ["one", "two", "trhee"], true)
+sc = Scale(f, 1:10)
+sl = Spinbox(f, 1:10)
+e = Entry(f, "starting text")
+widgets = (b, cb, rg, sc, sl, e)
+
+## oops, typo!
+set_items(rg[3], "three")
+
+## packing
+pack_style = ["pack", "grid", "formlayout"][3]
+
+if pack_style == "pack"
+ map(pack, widgets)
+ map(u -> pack_configure(u, {:anchor => "w"}), widgets)
+elseif pack_style == "grid"
+ for i in 1:length(widgets)
+ grid(widgets[i], i, 1)
+ grid_configure(widgets[i], {:sticky => "we"})
+ end
+else
+ map(u -> TkExtras.formlayout(u, "label"), widgets)
+end
+
+## bind a callback to each widget
+change_handler(path,xs...) = println(map(get_value, widgets))
+map(u -> callback_add(u, change_handler), widgets)
+
+set_visible(w, true)
+
View
BIN  examples/weather-overcast.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
341 src/Tk.jl
@@ -16,316 +16,53 @@ module Tk
using Base
using Cairo
-export Window, Button, TkCanvas, Canvas, pack, place, tcl_eval, TclError,
- cairo_surface_for, width, height, windowwidth, windowheight, reveal,
- cairo_context, cairo_surface,
- tcl_doevent, MouseHandler
-
-if OS_NAME == :Linux
- const libtcl = "libtcl8.5"
- const libtk = "libtk8.5"
-else
- const libtcl = "libtcl"
- const libtk = "libtk"
-end
-#const libX = "libX11"
-
-tcl_doevent() = tcl_doevent(0)
-function tcl_doevent(fd)
- while (ccall((:Tcl_DoOneEvent,libtcl), Int32, (Int32,), (1<<1))!=0)
- end
-end
-
-global timeout = nothing
-
-# fetch first word from struct
-tk_display(w) = pointer_to_array(convert(Ptr{Ptr{Void}},w), (1,), false)[1]
-
-function init()
- ccall((:Tcl_FindExecutable,libtcl), Void, (Ptr{Uint8},),
- joinpath(JULIA_HOME, "julia"))
- ccall((:g_type_init,"libgobject-2.0"),Void,())
- tcl_interp = ccall((:Tcl_CreateInterp,libtcl), Ptr{Void}, ())
- ccall((:Tcl_Init,libtcl), Int32, (Ptr{Void},), tcl_interp)
- ccall((:Tk_Init,libtk), Int32, (Ptr{Void},), tcl_interp)
- # TODO: for now cheat and use X-specific hack for events
- #mainwin = mainwindow(tcl_interp)
- #if mainwin == C_NULL
- # error("cannot initialize Tk: window system not available")
- #end
- #disp = tk_display(mainwin)
- #fd = ccall((:XConnectionNumber,libX), Int32, (Ptr{Void},), disp)
- #add_fd_handler(fd, tcl_doevent)
- global timeout
- timeout = Base.TimeoutAsyncWork(tcl_doevent)
- Base.start_timer(timeout,int64(100),int64(100))
- tcl_interp
-end
-
-mainwindow(interp) =
- ccall((:Tk_MainWindow,libtk), Ptr{Void}, (Ptr{Void},), interp)
-mainwindow() = mainwindow(tcl_interp)
-
-type TclError <: Exception
- msg::String
-end
-
-function tcl_result()
- bytestring(ccall((:Tcl_GetStringResult,libtcl),
- Ptr{Uint8}, (Ptr{Void},), tcl_interp))
-end
-
-function tcl_evalfile(name)
- if ccall((:Tcl_EvalFile,libtcl), Int32, (Ptr{Void}, Ptr{Uint8}),
- tcl_interp, name) != 0
- throw(TclError(tcl_result()))
- end
- nothing
-end
-
-function tcl_eval(cmd)
- code = ccall((:Tcl_Eval,libtcl), Int32, (Ptr{Void}, Ptr{Uint8}),
- tcl_interp, cmd)
- result = tcl_result()
- if code != 0
- throw(TclError(result))
- else
- result
- end
-end
-
-type TkWidget
- path::ByteString
- kind::ByteString
- parent::Union(TkWidget,Nothing)
-
- ID::Int = 0
- function TkWidget(parent::TkWidget, kind)
- path = "$(parent.path).jl_$(replace(kind, "::", "_"))$(ID)"; ID += 1
- new(path, kind, parent)
- end
- global Window
- function Window(title, w, h)
- wpath = ".jl_win$ID"; ID += 1
- tcl_eval("toplevel $wpath -width $w -height $h -background \"\"")
- tcl_eval("wm title $wpath \"$title\"")
- tcl_doevent()
- new(wpath, "toplevel", nothing)
- end
-end
-
-Window(title) = Window(title, 200, 200)
-
-Button(parent, text) = Button(parent, text, nothing)
-
-function Button(parent, text, command)
- b = TkWidget(parent, "ttk::button")
- cmd = "ttk::button $(b.path) -text \"$text\""
- if isa(command,Function)
- cmd = cmd * " -command $(tcl_callback(command))"
- end
- tcl_eval(cmd)
- b
-end
-
-function TkCanvas(parent, w, h)
- c = TkWidget(parent, "canvas")
- tcl_eval("canvas $(c.path) -width $w -height $h")
- c
-end
-pack(widget::TkWidget) = tcl_eval("pack $(widget.path)")
+import Base.string, Base.show, Base.getindex
-place(widget::TkWidget, x::Int, y::Int) = tcl_eval("place $(widget.path) -x $x -y $y")
+include("tkwidget.jl") # old Tk
+include("types.jl")
+include("core.jl")
+include("methods.jl")
+include("widgets.jl")
+include("containers.jl")
+include("dialogs.jl")
+include("menu.jl")
-function nametowindow(name)
- ccall((:Tk_NameToWindow,libtk), Ptr{Void},
- (Ptr{Void}, Ptr{Uint8}, Ptr{Void}),
- tcl_interp, name, mainwindow(tcl_interp))
-end
-const _callbacks = ObjectIdDict()
-
-const TCL_OK = int32(0)
-const TCL_ERROR = int32(1)
-const TCL_RETURN = int32(2)
-const TCL_BREAK = int32(3)
-const TCL_CONTINUE = int32(4)
-
-const TCL_VOLATILE = convert(Ptr{Void}, 1)
-const TCL_STATIC = convert(Ptr{Void}, 0)
-const TCL_DYNAMIC = convert(Ptr{Void}, 3)
-
-const empty_str = ""
-
-function jl_tcl_callback(f, interp, argc::Int32, argv::Ptr{Ptr{Uint8}})
- args = { bytestring(unsafe_ref(argv,i)) for i=1:argc }
- local result
- try
- result = f(args...)
- catch
- return TCL_ERROR
- end
- if isa(result,ByteString)
- ccall((:Tcl_SetResult,libtcl), Void, (Ptr{Void}, Ptr{Uint8}, Int32),
- interp, result, TCL_VOLATILE)
- else
- ccall((:Tcl_SetResult,libtcl), Void, (Ptr{Void}, Ptr{Uint8}, Int32),
- interp, empty_str, TCL_STATIC)
- end
- return TCL_OK
-end
-
-jl_tcl_callback_ptr = cfunction(jl_tcl_callback,
- Int32, (Function, Ptr{Void}, Int32, Ptr{Ptr{Uint8}}))
-
-function tcl_callback(f)
- cname = string("jl_cb", repr(uint32(object_id(f))))
- # TODO: use Tcl_CreateObjCommand instead
- ccall((:Tcl_CreateCommand,libtcl), Ptr{Void},
- (Ptr{Void}, Ptr{Uint8}, Ptr{Void}, Any, Ptr{Void}),
- tcl_interp, cname, jl_tcl_callback_ptr, f, C_NULL)
- # TODO: use a delete proc (last arg) to remove this
- _callbacks[f] = true
- cname
-end
-
-width(w::TkWidget) = int(tcl_eval("$(w.path) cget -width"))
-height(w::TkWidget) = int(tcl_eval("$(w.path) cget -height"))
-windowwidth(w::TkWidget) = int(tcl_eval("winfo width $(w.path)"))
-windowheight(w::TkWidget) = int(tcl_eval("winfo height $(w.path)"))
-
-# NOTE: This has to be ported to each window environment.
-# But, this should be the only such function needed.
-function cairo_surface_for(w::TkWidget)
- win = nametowindow(w.path)
- if OS_NAME == :Linux
- disp = ccall((:jl_tkwin_display,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
- win)
- d = ccall((:jl_tkwin_id,:libtk_wrapper), Int32, (Ptr{Void},), win)
- vis = ccall((:jl_tkwin_visual,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
- win)
- if disp==C_NULL || d==0 || vis==C_NULL
- error("invalid window")
- end
- return CairoXlibSurface(disp, d, vis, width(w), height(w))
- elseif OS_NAME == :Darwin
- context = ccall((:getView,:libtk_wrapper), Ptr{Void},
- (Ptr{Void},Int32), win, height(w))
- return CairoQuartzSurface(context, width(w), height(w))
- elseif OS_NAME == :Windows
- disp = ccall((:jl_tkwin_display,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
- win)
- hdc = ccall((:jl_tkwin_hdc,:libtk_wrapper), Ptr{Void}, (Ptr{Void},Ptr{Void}),
- win,disp)
- return CairoWin32Surface(hdc, width(w), height(w))
- else
- error("Unsupported Operating System")
- end
-end
-
-const default_mouse_cb = (w, x, y)->nothing
-
-type MouseHandler
- button1press
- button1release
- button2press
- button2release
- button3press
- button3release
- motion
- button1motion
-
- MouseHandler() = new(default_mouse_cb, default_mouse_cb, default_mouse_cb,
- default_mouse_cb, default_mouse_cb, default_mouse_cb,
- default_mouse_cb, default_mouse_cb)
-end
-
-# TkCanvas is the plain Tk canvas widget. This one is double-buffered
-# and built on Cairo.
-type Canvas
- c::TkWidget
- front::CairoSurface # surface for window
- back::CairoSurface # backing store
- frontcc::CairoContext
- backcc::CairoContext
- mouse::MouseHandler
-
- Canvas(parent) = Canvas(parent, -1, -1)
- function Canvas(parent, w, h)
- c = TkWidget(parent, "ttk::frame")
- # frame supports empty background, allowing us to control drawing
- if w < 0
- w = width(parent)
- end
- if h < 0
- h = height(parent)
- end
- tcl_eval("frame $(c.path) -width $w -height $h -background \"\"")
- new(c)
- end
-end
-
-# some canvas init steps require the widget to fully exist
-function init_canvas(c::Canvas)
- tcl_doevent() # make sure window resources are assigned
- c.front = cairo_surface_for(c.c)
- w = width(c.c)
- h = height(c.c)
- c.frontcc = CairoContext(c.front)
- if Base.is_unix(OS_NAME)
- c.back = surface_create_similar(c.front, w, h)
- else
- c.back = CairoRGBSurface(w, h)
- end
- c.backcc = CairoContext(c.back)
- c.mouse = MouseHandler()
- cb = tcl_callback((x...)->reveal(c))
- tcl_eval("bind $(c.c.path) <Expose> $(cb)")
- bp1cb = tcl_callback((path,x,y)->(c.mouse.button1press(c,int(x),int(y))))
- br1cb = tcl_callback((path,x,y)->(c.mouse.button1release(c,int(x),int(y))))
- bp2cb = tcl_callback((path,x,y)->(c.mouse.button2press(c,int(x),int(y))))
- br2cb = tcl_callback((path,x,y)->(c.mouse.button2release(c,int(x),int(y))))
- bp3cb = tcl_callback((path,x,y)->(c.mouse.button3press(c,int(x),int(y))))
- br3cb = tcl_callback((path,x,y)->(c.mouse.button3release(c,int(x),int(y))))
- motcb = tcl_callback((path,x,y)->(c.mouse.motion(c,int(x),int(y))))
- b1mcb = tcl_callback((path,x,y)->(c.mouse.button1motion(c,int(x),int(y))))
- tcl_eval("bind $(c.c.path) <ButtonPress-1> {$bp1cb %x %y}")
- tcl_eval("bind $(c.c.path) <ButtonRelease-1> {$br1cb %x %y}")
- tcl_eval("bind $(c.c.path) <ButtonPress-2> {$bp2cb %x %y}")
- tcl_eval("bind $(c.c.path) <ButtonRelease-2> {$br2cb %x %y}")
- tcl_eval("bind $(c.c.path) <ButtonPress-3> {$bp3cb %x %y}")
- tcl_eval("bind $(c.c.path) <ButtonRelease-3> {$br3cb %x %y}")
- tcl_eval("bind $(c.c.path) <Motion> {$motcb %x %y}")
- tcl_eval("bind $(c.c.path) <Button1-Motion> {$b1mcb %x %y}")
- c
-end
-
-function pack(c::Canvas)
- pack(c.c)
- init_canvas(c)
-end
-
-function place(c::Canvas, x::Int, y::Int)
- place(c.c, x, y)
- init_canvas(c)
-end
+export Window, TkCanvas, Canvas, pack, place, tcl_eval, TclError,
+ cairo_surface_for, width, height, windowwidth, windowheight, reveal,
+ cairo_context, cairo_surface,
+ tcl_doevent, MouseHandler
-function reveal(c::Canvas)
- set_source_surface(c.frontcc, c.back, 0, 0)
- paint(c.frontcc)
- tcl_doevent()
-end
+export tcl, tclvar, tk_configure, tk_cget, tk_identify, tk_state, tk_instate, tk_winfo, tk_wm, tk_bind, callback_add
+export Tk_Widget, TTk_Widget, Tk_Container
+export Toplevel, Frame, Labelframe, Notebook, Panedwindow
+export Label, Button
+export Checkbutton, Radio, Combobox
+export Slider, Spinbox
+export Entry, Text
+export Treeview, selected_nodes, node_insert, node_move, node_delete, node_open
+export tree_headers, tree_column_widths, tree_key_header, tree_key_width
+export Kanvas
+export Sizegrip, Separator, Progressbar, Image, Scrollbar
+export Menu, menu_add
+export GetOpenFile, GetSaveFile, ChooseDirectory, Messagebox
+export pack, pack_configure, forget, pack_stop_propagate
+export grid, grid_configure, grid_rowconfigure, grid_columnconfigure, grid_forget, grid_stop_propagate
+export page_add, page_insert
+export formlayout, scrollbars_add
+export get_value, set_value,
+ get_items, set_items,
+ get_width, set_width,
+ get_height, set_height,
+ get_size, set_size,
+ get_enabled, set_enabled,
+ get_editable, set_editable,
+ get_visible, set_visible,
+ raise, focus, update, destroy
-function update()
- tcl_eval("update")
-end
-cairo_context(c::Canvas) = c.backcc
-cairo_surface(c::Canvas) = c.back
-tcl_interp = init()
-tcl_eval("wm withdraw .")
end # module
View
174 src/containers.jl
@@ -0,0 +1,174 @@
+## Types
+type Tk_Toplevel <: TTk_Container w::TkWidget end
+type Tk_Frame <: TTk_Container w::TkWidget end
+type Tk_Labelframe <: TTk_Container w::TkWidget end
+type Tk_Notebook <: TTk_Container w::TkWidget end
+type Tk_Panedwindow <: TTk_Container w::TkWidget end
+
+show(io::IO, widget::TkWidget) = println(typeof(widget))
+
+## Toplevel window
+function Toplevel(title::String, width::Integer, height::Integer, visible::Bool)
+ w = Window(title, width, height)
+ w = Tk_Toplevel(w)
+ set_visible(w, visible)
+ w
+end
+Toplevel(title::String, width::Integer, height::Integer) = Toplevel(title, width, height, true)
+Toplevel(title::String, visible::Bool) = Toplevel(title, 200, 200, visible)
+Toplevel(title::String) = Toplevel(title, 200, 200)
+Toplevel() = Toplevel("Default window")
+
+
+get_value(widget::Tk_Toplevel) = tk_wm(widget, "title")
+set_value(widget::Tk_Toplevel, value::String) = tk_wm(widget, "title", value)
+
+function set_visible(widget::Tk_Toplevel, value::Bool)
+ value = value ? "normal" : "withdrawn"
+ tk_wm(widget, "state", value)
+end
+
+set_size(widget::Tk_Toplevel, width::Integer, height::Integer) = tcl(I"wm minsize", widget, width, height)
+update(widget::Tk_Toplevel) = tk_wm(widget, "geometry")
+destroy(widget::Tk_Toplevel) = tcl("destroy", widget)
+
+## Frame
+## nothing to add...
+
+## Labelframe
+Labelframe(parent::Widget, text::String) = Labelframe(parent, {:text=>text})
+set_value(widget::Tk_Labelframe, text::String) = tk_configure(f, {:text=> text})
+
+
+## Notebook
+function page_add(child::Widget, label::String)
+ parent = tk_winfo(child, "parent")
+ tcl(parent, "add", child, {:text => label})
+end
+
+
+function page_insert(child::Widget, index::Integer, label::String)
+ parent = tk_winfo(child, "parent")
+ tcl(parent, "insert", index, child, {:text => label})
+end
+
+get_value(widget::Tk_Notebook) = 1 + int(tcl(widget, I"index current"))
+set_value(widget::Tk_Notebook, index::Integer) = tcl(widget, "select", index - 1)
+
+
+
+## Panedwindow
+## orient in "horizontal" or "vertical"
+Panedwindow(widget::Widget, orient::String) = Panedwindow(widget, {:orient => orient})
+
+function page_add(child::Widget, weight::Integer)
+ parent = tk_winfo(child, "parent")
+ tcl(parent, "add", child, {:weight => weight})
+end
+
+## value is sash position as percentage of first pane
+function get_value(widget::Tk_Panedwindow)
+ sz = (tk_cget(widget, "orient") == "horizontal") ? get_width(widget) : get_height(widget)
+ pos = tcl(widget, "sashpos", 0) | int
+ floor(pos/sz*100)
+end
+function set_value(widget::Tk_Panedwindow, value::Integer)
+ sz = (tk_cget(widget, "orient") == "horizontal") ? get_width(widget) : get_height(widget)
+ pos = int(value * sz/100)
+ tcl(widget, "sashpos", 0, pos)
+end
+page_add(child::Widget) = page_add(child, 1)
+
+
+## Container methods
+
+## pack(widget, {:expand => true, :anchor => "w"})
+pack(widget::Widget, args::Dict) = tcl("pack", widget, args)
+pack(widget::Widget) = pack(widget, Dict())
+
+pack_configure(widget::Widget, args::Dict) = tcl(I"pack configure", widget, args)
+pack_stop_propagate(widget::Widget) = tcl(I"pack propagate", widget, false)
+
+## remove a page from display
+forget(widget::Widget) = tcl(widget, "forget")
+forget(parent::Widget, child::Widget) = tcl(widget, "forget", child)
+
+## grid ...
+IntOrRange = Union(Integer, Range1)
+function grid(child::Widget, row::IntOrRange, column::IntOrRange, args::Dict)
+ path = get_path(child)
+ args[:row] = min(row) - 1
+ args[:column] = min(column) - 1
+ if isa(row, Range1) args[:rowspan] = 1 + max(row) - min(row) end
+ if isa(column, Range1) args[:columnspan] = 1 + max(column) - min(column) end
+ grid_configure(child, args)
+end
+grid(child::Widget, row::IntOrRange, column::IntOrRange) = grid(child, row, column, Dict())
+
+grid_configure(child::Widget, args::Dict) = tcl("grid", "configure", child, args)
+grid_rowconfigure(parent::Widget, row::Integer, args::Dict) = tcl(I"grid rowconfigure", parent, row-1, args)
+grid_columnconfigure(parent::Widget, column::Integer, args::Dict) = tcl(I"grid columnconfigure", parent, column-1, args)
+grid_stop_propagate(parent::Widget) = tcl(I"grid propagate", parent, false)
+grid_forget(child::Widget) = tcl(I"grid forget", child)
+
+## Helper to layout two column with label using grid
+##
+## w = Toplevel()
+## f = Frame(w); pack(f)
+## sc = Slider(f, 1:10)
+## e = Entry(f, "")
+## b = Button(f, "click me", W -> println(map(get_value, (sc, e))))
+## formlayout(sc, "Slider")
+## formlayout(e, "Entry")
+## formlayout(Separator(f), nothing)
+## formlayout(b, nothing)
+##
+function formlayout(child::Tk_Widget, label::MaybeString)
+ master = tk_winfo(child, "parent")
+ sz = int(split(tcl_eval("grid size $master"), " ")) ## columns, rows
+ nrows = sz[2]
+
+ if isa(label, String)
+ l = Label(child.w.parent, label)
+ grid(l, nrows + 1, 1)
+ grid_configure(l, {:sticky => "e"})
+ end
+ grid(child, nrows + 1, 2)
+ grid_configure(child, {:sticky => "we", :padx=>5, :pady=>2})
+ grid_columnconfigure(master, 1, {:weight => 1})
+end
+
+
+## Wrap child in frame, return frame to pack (or grid) into parent of child
+##
+## w = Toplevel()
+## f = Frame(w); pack(f) ## f shouldn't have any layout management
+## t = Text(f)
+## scrollbars_add(f,t)
+##
+function scrollbars_add(parent::Tk_Frame, child::Tk_Widget)
+
+ ## we use tcl commands for the scrollbar, not Scrollbar. Can't get the callbacks to work properly
+ fpath = parent.w.path
+ wpath = child.w.path
+ xscr = "$fpath.xscr"
+ yscr = "$fpath.yscr"
+
+ tcl_eval("ttk::scrollbar $xscr -orient horizontal -command \"$wpath xview\"")
+ tcl_eval("ttk::scrollbar $yscr -orient vertical -command \"$wpath yview\"")
+
+ tk_configure(child, {:xscrollcommand => "$xscr set",
+ :yscrollcommand => "$yscr set"})
+
+ grid(child, 1, 1)
+ grid(yscr, 1, 2)
+ grid(xscr, 2, 1)
+ grid_configure(child, {:sticky => "news"})
+ grid_configure(yscr, {:sticky => "ns"})
+ grid_configure(xscr, {:sticky => "ew"})
+ grid_rowconfigure(parent, 1, {:weight => 1})
+ grid_columnconfigure(parent, 1, {:weight => 1})
+
+ tcl(I"grid propagate", parent, false) ## size request comes from parent, not from child.
+end
+
View
188 src/core.jl
@@ -0,0 +1,188 @@
+tcl_eval("set auto_path")
+tcl_add_path(path::String) = tcl_eval("lappend auto_path $path")
+tcl_add_path(Pkg.dir("Tk", "tcl"))
+tcl_require(pkg::String) = tcl_eval("package require $pkg")
+
+## tk_configure helpers
+
+## helper to get path
+## assumes Tk_Widgets have w property storing TkWidget
+get_path(path::String) = path
+get_path(widget::Tk_Widget) = get_path(widget.w)
+get_path(widget::TkWidget) = get_path(widget.path)
+get_path(widget::Canvas) = get_path(widget.c) # Tk.Canvas object
+
+## Coversion of julia objects into tcl strings for inclusion via tcl() call
+to_tcl(x) = string(x)
+to_tcl(x::Nothing) = ""
+has_space = r" "
+to_tcl(x::String) = ismatch(has_space, x) ? "{$x}" : x
+type I x::MaybeString end # avoid wrapping in {} and ismatch call.
+macro I_str(s) I(s) end
+to_tcl(x::I) = x.x == nothing ? "" : x.x
+to_tcl{T <: Number}(x::Vector{T}) = "\"" * string(join(x, " ")) * "\""
+function to_tcl{T <: String}(x::Vector{T})
+ tmp = join(["{$i}" for i in x], " ")
+ "[list $tmp ]"
+end
+to_tcl(x::Widget) = get_path(x)
+function to_tcl(x::Dict)
+ out = Dict()
+ for (k,v) in x
+ if v!=nothing out[k] = v end
+ end
+ join([" -$(string(k)) $(to_tcl(v))" for (k, v) in out], "")
+end
+to_tcl(x::Function) = Tk.tcl_callback(x)
+
+
+## Function to simplify call to tcl_eval, ala R's tcl() function
+function tcl(xs...)
+ cmd = join([" $(to_tcl(x))" for x in xs], "")
+ ## println(cmd)
+ tcl_eval(cmd)
+end
+
+## Work with a text variable. Stores values as strings. Must coerce!
+## getter -- THIS FAILS IN A CALLBACK!!!
+#function tclvar(nm::String)
+# cmd = "return \$::" * nm
+# tcl(cmd)
+#end
+
+## setter
+function tclvar(nm::String, value)
+ tcl("set", nm, value)
+end
+
+## create new variable with random name
+function tclvar()
+ var = "tcl" * join(int(10*rand(20)), "")
+ tclvar(var, "null")
+ var
+end
+
+## main configuration interface
+function tk_configure(widget::Widget, args::Dict)
+ tcl(widget, "configure", args)
+end
+
+## Get values
+## cget
+function tk_cget(widget::Widget, prop::String, coerce::MaybeFunction)
+ out = tcl(widget, "cget", "-$prop")
+ isa(coerce, Function) ? coerce(out) : out
+end
+tk_cget(widget::Widget, prop::String) = tk_cget(widget, prop, nothing)
+
+## Identify
+tk_identify(widget::Widget, x::Integer, y::Integer) = tcl(widget, "identify", "%x", "%y")
+
+## tk_state(w, "!disabled")
+tk_state(widget::Widget, state::String) = tcl(widget, "state", state)
+tk_instate(widget::Widget, state::String) = tcl(widget, "instate", state) == "1" # return Bool
+
+## tkwinfo
+function tk_winfo(widget::Widget, prop::String, coerce::MaybeFunction)
+ out = tcl("winfo", prop, widget)
+ isa(coerce, Function) ? coerce(out) : out
+end
+tk_winfo(widget::Widget, prop::String) = tk_winfo(widget, prop, nothing)
+
+## wm. Is this okay? In many case args is wanted
+tk_wm(window::Widget, prop::String, args::MaybeString) = tcl("wm", prop, window, args)
+tk_wm(window::Widget, prop::String) = tk_wm(window, prop, nothing)
+
+## Take a function, get its args as array of symbols. There must be better way...
+## Helper functions for bind callback
+function get_args(li::LambdaStaticData)
+ e = li.ast
+ if !isa(e, Expr)
+ e = Base.uncompress_ast(li)
+ end
+ argnames = e.args[1]
+ ## return array of symbols -- not args
+ if isa(argnames[1], Expr)
+ argnames = map(u -> u.args[1], argnames)
+ end
+
+ argnames
+end
+
+function get_args(m::Method)
+ li = m.func.code
+ get_args(li)
+end
+
+function get_args(f::Function)
+ try
+ get_args(f.env.defs.func)
+ catch e
+ get_args(f.code)
+ end
+end
+
+
+## bind
+## Function callbacks have first argument path that is ignored
+## others match percent substitution
+## e.g. (W, x, y) -> x will have W, x and y available through %W %x %y bindings
+function tk_bind(widget::Widget, event::String, callback::Function)
+ if event == "command"
+ tk_configure(widget, {:command => callback})
+ else
+ path = get_path(widget)
+ ## Need to grab percent subs from signature of function
+ ccb = Tk.tcl_callback(callback)
+ args = get_args(callback)
+ if length(args) == 0
+ error("Callback should have first argument of \"path\".")
+ end
+ ## ala: "bind $path <ButtonPress-1> {$bp1cb %x %y}"
+ cmd = "bind $path $event {$ccb " * join(map(u -> "%$(string(u))", args[2:end]), " ") * "}"
+ tcl_eval(cmd)
+ end
+end
+## add most typical callback
+function callback_add(widget::Tk_Widget, callback::Function)
+ events ={:Tk_Window => "<Destroy>",
+ :Tk_Frame => nothing,
+ :Tk_Labelframe => nothing,
+ :Tk_Notebook => "<<NotebookTabChanged>>",
+ :Tk_Panedwindow => nothing,
+ ##
+ :Tk_Label => nothing,
+ :Tk_Button => "command",
+ :Tk_Checkbutton => "command",
+ :Tk_Radio => "command",
+ :Tk_Combobox => "<<ComboboxSelected>>",
+ :Tk_Scale => "command",
+ :Tk_Spinbox => "command",
+ :Tk_Entry => "<FocusOut>",
+ :Tk_Text => "<FocusOut>",
+ :Tk_Treeview => "<<TreeviewSelect>>"
+ }
+ key = (widget | typeof | string | Base.symbol)
+ if has(events, key)
+ event = events[key]
+ if event == nothing
+ return()
+ else
+ tk_bind(widget, event, callback)
+ end
+ end
+end
+
+
+## Need this pattern to make a widget
+## Parent is not a string, but TkWidget or Tk_Widget instance
+function make_widget(parent::Widget, str::String, args::Dict)
+ if isa(parent, Tk_Widget)
+ return(make_widget(parent.w, str, args))
+ end
+
+ w = TkWidget(parent, str)
+ tcl(str, w, args)
+ w
+end
+make_widget(parent::Widget, str::String) = make_widget(parent, str, Dict())
View
23 src/dialogs.jl
@@ -0,0 +1,23 @@
+## dialogs
+
+## can add arguments if desired. Don't like names or lack of arguments
+GetOpenFile() = tcl("tkGetOpenFile")
+GetSaveFile() = tcl("tkGetSaveFile")
+ChooseDirectory() = tcl("tk_chooseDirectory")
+
+## Message box
+function Messagebox(parent::MaybeWidget, title::MaybeString, message::MaybeString, detail::MaybeString)
+ args = Dict()
+ if !isa(parent, Nothing) args["parent"] = get_path(parent) end
+ args["title"] = title
+ args["message"] = message
+ args["detail"] = detail
+ args["type"] = "okcancel"
+
+ tcl("tk_messageBox", args)
+end
+
+Messagebox(parent::Widget, title::String, message::String) = Messagebox(parent, title, message, nothing)
+Messagebox(parent::Widget, message::String) = Messagebox(parent, nothing, message, nothing)
+Messagebox(title::String, message::String, detail::String) = Messagebox(nothing, title, message, detail)
+Messagebox(title::String, message::String) = Messagebox(nothing, title, message, nothing)
View
55 src/menu.jl
@@ -0,0 +1,55 @@
+## remove tearoff menus
+tcl_eval("option add *tearOff 0")
+
+
+## make a menubar
+## w = Toplevel()
+## mb = Menu(w) ## adds to toplevel too when w is Toplevel
+## file_menu = menu_add(mb, "File...") ## make top level entry
+## menu_add(file_menu, "title", w -> println("hi")) ## pass function to make action item
+## menu_add(file_menu, Separator(w)) ## a separator
+## menu_add(file_menu, Checkbutton(w, "label")) ## A checkbutton
+## menu_add(file_menu, Radio(w, ["one", "two"])) ## A checkbutton
+
+## create menu, add to window
+function Menu(widget::Tk_Toplevel)
+ m = Menu(widget.w) # dispatch down
+ tk_configure(widget, {:menu => m})
+ m
+end
+
+## add a submenu, return it
+function menu_add(widget::Tk_Menu, label::String)
+ m = Menu(widget)
+ tcl(widget, "add", "cascade", {:menu => m, :label => label})
+ m
+end
+
+## add menu item to menu
+function menu_add(widget::Tk_Menu, label::String, command::Function, img::Tk_Image)
+ tcl(widget, "add", "command", {:label => label, :command => command, :image => img, :compound => "left"})
+end
+function menu_add(widget::Tk_Menu, label::String, command::Function)
+ ccb = Tk.tcl_callback(command)
+ tcl(widget, "add", "command", {:label => label, :command => command})
+end
+
+function menu_add(widget::Tk_Menu, sep::Tk_Separator)
+ tcl(widget, "add", "separator")
+end
+
+function menu_add(widget::Tk_Menu, cb::Tk_Checkbutton)
+ ## no ! here, as state has changed by the time we get this
+ tcl(widget, "add", "checkbutton", {:label => get_items(cb), :variable => tk_cget(cb, "variable")})
+end
+
+
+function menu_add(widget::Tk_Menu, rb::Tk_Radio)
+ var = tk_cget(rb.buttons[1], "variable")
+ items = get_items(rb)
+ for i in 1:length(items)
+ tcl(widget, "add", "radiobutton", {:label => items[i], :value => items[i],
+ :variable => var})
+ end
+end
+
View
49 src/methods.jl
@@ -0,0 +1,49 @@
+## Additional methods to give simplified interface to the Tk widgets created in this package
+
+XXX() = error("No default method.")
+
+## Main value
+get_value(widget::Widget) = XXX()
+set_value(widget::Widget, value) = XXX()
+
+## items to select from
+get_items(widget::Widget) = XXX()
+set_items(widget::Widget, items) = XXX()
+
+## size
+get_width(widget::Widget) = tk_winfo(widget, "width") | int
+set_width(widget::Widget, value::Integer) = tk_configure(widget, {:width => value})
+
+
+get_height(widget::Widget) = tk_winfo(widget, "height") | int
+set_height(widget::Widget, value::Integer) = tk_configure(widget, {:height => value})
+
+get_size(widget::Widget) = [get_width(widget), get_height(widget)]
+function set_size(widget::Widget, width::Integer, height::Integer)
+ set_width(widget, width)
+ set_height(widget, height)
+end
+set_size(widget::Widget, value::Array{Integer}) = set_size(widget, value[1], value[2])
+
+## sensitive to user input
+get_enabled(widget::Widget) = XXX()
+get_enabled(widget::TTk_Widget) = tk_instate(widget, "!disabled")
+
+set_enabled(widget::Widget, value::Bool) = XXX()
+set_enabled(widget::TTk_Widget, value::Bool) = tk_configure(widget, {:state => value ? "!disabled" : "disabled"})
+
+
+## can be edited
+get_editable(widget::Widget) = XXX()
+get_editable(widget::TTk_Widget) = tk_instate(widget, "!readonly")
+
+set_editable(widget::Widget, value::Bool) = XXX()
+set_editable(widget::TTk_Widget, value::Bool) = tk_configure(widget, {:state => value ? "!readonly" : "readonly"})
+
+## hide/show widget
+get_visible(widget::Widget) = XXX()
+set_visible(widget::Widget, value::Bool) = XXX()
+
+## set focus
+focus(widget::Widget) = tcl("focus", widget)
+raise(widget::Widget) = tcl("raise", widget)
View
309 src/tkwidget.jl
@@ -0,0 +1,309 @@
+
+if OS_NAME == :Linux
+ const libtcl = "libtcl8.5"
+ const libtk = "libtk8.5"
+else
+ const libtcl = "libtcl"
+ const libtk = "libtk"
+end
+
+#const libX = "libX11"
+
+tcl_doevent() = tcl_doevent(0)
+function tcl_doevent(fd)
+ while (ccall((:Tcl_DoOneEvent,libtcl), Int32, (Int32,), (1<<1))!=0)
+ end
+end
+
+global timeout = nothing
+
+# fetch first word from struct
+tk_display(w) = pointer_to_array(convert(Ptr{Ptr{Void}},w), (1,), false)[1]
+
+function init()
+ ccall((:Tcl_FindExecutable,libtcl), Void, (Ptr{Uint8},),
+ joinpath(JULIA_HOME, "julia"))
+ ccall((:g_type_init,"libgobject-2.0"),Void,())
+ tcl_interp = ccall((:Tcl_CreateInterp,libtcl), Ptr{Void}, ())
+ ccall((:Tcl_Init,libtcl), Int32, (Ptr{Void},), tcl_interp)
+ ccall((:Tk_Init,libtk), Int32, (Ptr{Void},), tcl_interp)
+ # TODO: for now cheat and use X-specific hack for events
+ #mainwin = mainwindow(tcl_interp)
+ #if mainwin == C_NULL
+ # error("cannot initialize Tk: window system not available")
+ #end
+ #disp = tk_display(mainwin)
+ #fd = ccall((:XConnectionNumber,libX), Int32, (Ptr{Void},), disp)
+ #add_fd_handler(fd, tcl_doevent)
+ global timeout
+ timeout = Base.TimeoutAsyncWork(tcl_doevent)
+ Base.start_timer(timeout,int64(100),int64(100))
+ tcl_interp
+end
+
+mainwindow(interp) =
+ ccall((:Tk_MainWindow,libtk), Ptr{Void}, (Ptr{Void},), interp)
+mainwindow() = mainwindow(tcl_interp)
+
+type TclError <: Exception
+ msg::String
+end
+
+function tcl_result()
+ bytestring(ccall((:Tcl_GetStringResult,libtcl),
+ Ptr{Uint8}, (Ptr{Void},), tcl_interp))
+end
+
+function tcl_evalfile(name)
+ if ccall((:Tcl_EvalFile,libtcl), Int32, (Ptr{Void}, Ptr{Uint8}),
+ tcl_interp, name) != 0
+ throw(TclError(tcl_result()))
+ end
+ nothing
+end
+
+function tcl_eval(cmd)
+ code = ccall((:Tcl_Eval,libtcl), Int32, (Ptr{Void}, Ptr{Uint8}),
+ tcl_interp, cmd)
+ result = tcl_result()
+ if code != 0
+ throw(TclError(result))
+ else
+ result
+ end
+end
+
+type TkWidget
+ path::ByteString
+ kind::ByteString
+ parent::Union(TkWidget,Nothing)
+
+ ID::Int = 0
+ function TkWidget(parent::TkWidget, kind)
+ path = "$(parent.path).jl_$(replace(kind, "::", "_"))$(ID)"; ID += 1
+ new(path, kind, parent)
+ end
+ global Window
+ function Window(title, w, h)
+ wpath = ".jl_win$ID"; ID += 1
+ tcl_eval("toplevel $wpath -width $w -height $h -background \"\"")
+ tcl_eval("wm title $wpath \"$title\"")
+ tcl_doevent()
+ new(wpath, "toplevel", nothing)
+ end
+end
+
+Window(title) = Window(title, 200, 200)
+
+## Button(parent, text) = Button(parent, text, nothing)
+
+## function Button(parent, text, command)
+## b = TkWidget(parent, "ttk::button")
+## cmd = "ttk::button $(b.path) -text \"$text\""
+## if isa(command,Function)
+## cmd = cmd * " -command $(tcl_callback(command))"
+## end
+## tcl_eval(cmd)
+## b
+## end
+
+## function TkCanvas(parent, w, h)
+## c = TkWidget(parent, "canvas")
+## tcl_eval("canvas $(c.path) -width $w -height $h")
+## c
+## end
+
+## pack(widget::TkWidget) = tcl_eval("pack $(widget.path)")
+
+place(widget::TkWidget, x::Int, y::Int) = tcl_eval("place $(widget.path) -x $x -y $y")
+
+function nametowindow(name)
+ ccall((:Tk_NameToWindow,libtk), Ptr{Void},
+ (Ptr{Void}, Ptr{Uint8}, Ptr{Void}),
+ tcl_interp, name, mainwindow(tcl_interp))
+end
+
+const _callbacks = ObjectIdDict()
+
+const TCL_OK = int32(0)
+const TCL_ERROR = int32(1)
+const TCL_RETURN = int32(2)
+const TCL_BREAK = int32(3)
+const TCL_CONTINUE = int32(4)
+
+const TCL_VOLATILE = convert(Ptr{Void}, 1)
+const TCL_STATIC = convert(Ptr{Void}, 0)
+const TCL_DYNAMIC = convert(Ptr{Void}, 3)
+
+const empty_str = ""
+
+function jl_tcl_callback(f, interp, argc::Int32, argv::Ptr{Ptr{Uint8}})
+ args = { bytestring(unsafe_ref(argv,i)) for i=1:argc }
+ local result
+ try
+ result = f(args...)
+ catch
+ return TCL_ERROR
+ end
+ if isa(result,ByteString)
+ ccall((:Tcl_SetResult,libtcl), Void, (Ptr{Void}, Ptr{Uint8}, Int32),
+ interp, result, TCL_VOLATILE)
+ else
+ ccall((:Tcl_SetResult,libtcl), Void, (Ptr{Void}, Ptr{Uint8}, Int32),
+ interp, empty_str, TCL_STATIC)
+ end
+ return TCL_OK
+end
+
+jl_tcl_callback_ptr = cfunction(jl_tcl_callback,
+ Int32, (Function, Ptr{Void}, Int32, Ptr{Ptr{Uint8}}))
+
+function tcl_callback(f)
+ cname = string("jl_cb", repr(uint32(object_id(f))))
+ # TODO: use Tcl_CreateObjCommand instead
+ ccall((:Tcl_CreateCommand,libtcl), Ptr{Void},
+ (Ptr{Void}, Ptr{Uint8}, Ptr{Void}, Any, Ptr{Void}),
+ tcl_interp, cname, jl_tcl_callback_ptr, f, C_NULL)
+ # TODO: use a delete proc (last arg) to remove this
+ _callbacks[f] = true
+ cname
+end
+
+width(w::TkWidget) = int(tcl_eval("$(w.path) cget -width"))
+height(w::TkWidget) = int(tcl_eval("$(w.path) cget -height"))
+windowwidth(w::TkWidget) = int(tcl_eval("winfo width $(w.path)"))
+windowheight(w::TkWidget) = int(tcl_eval("winfo height $(w.path)"))
+
+# NOTE: This has to be ported to each window environment.
+# But, this should be the only such function needed.
+function cairo_surface_for(w::TkWidget)
+ win = nametowindow(w.path)
+ if OS_NAME == :Linux
+ disp = ccall((:jl_tkwin_display,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
+ win)
+ d = ccall((:jl_tkwin_id,:libtk_wrapper), Int32, (Ptr{Void},), win)
+ vis = ccall((:jl_tkwin_visual,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
+ win)
+ if disp==C_NULL || d==0 || vis==C_NULL
+ error("invalid window")
+ end
+ return CairoXlibSurface(disp, d, vis, width(w), height(w))
+ elseif OS_NAME == :Darwin
+ context = ccall((:getView,:libtk_wrapper), Ptr{Void},
+ (Ptr{Void},Int32), win, height(w))
+ return CairoQuartzSurface(context, width(w), height(w))
+ elseif OS_NAME == :Windows
+ disp = ccall((:jl_tkwin_display,:libtk_wrapper), Ptr{Void}, (Ptr{Void},),
+ win)
+ hdc = ccall((:jl_tkwin_hdc,:libtk_wrapper), Ptr{Void}, (Ptr{Void},Ptr{Void}),
+ win,disp)
+ return CairoWin32Surface(hdc, width(w), height(w))
+ else
+ error("Unsupported Operating System")
+ end
+end
+
+const default_mouse_cb = (w, x, y)->nothing
+
+type MouseHandler
+ button1press
+ button1release
+ button2press
+ button2release
+ button3press
+ button3release
+ motion
+ button1motion
+
+ MouseHandler() = new(default_mouse_cb, default_mouse_cb, default_mouse_cb,
+ default_mouse_cb, default_mouse_cb, default_mouse_cb,
+ default_mouse_cb, default_mouse_cb)
+end
+
+# TkCanvas is the plain Tk canvas widget. This one is double-buffered
+# and built on Cairo.
+type Canvas
+ c::TkWidget
+ front::CairoSurface # surface for window
+ back::CairoSurface # backing store
+ frontcc::CairoContext
+ backcc::CairoContext
+ mouse::MouseHandler
+
+ Canvas(parent) = Canvas(parent, -1, -1)
+ function Canvas(parent, w, h)
+ c = TkWidget(parent, "ttk::frame")
+ # frame supports empty background, allowing us to control drawing
+ if w < 0
+ w = width(parent)
+ end
+ if h < 0
+ h = height(parent)
+ end
+ tcl_eval("frame $(c.path) -width $w -height $h -background \"\"")
+ new(c)
+ end
+end
+
+# some canvas init steps require the widget to fully exist
+function init_canvas(c::Canvas)
+ tcl_doevent() # make sure window resources are assigned
+ c.front = cairo_surface_for(c.c)
+ w = width(c.c)
+ h = height(c.c)
+ c.frontcc = CairoContext(c.front)
+ if Base.is_unix(OS_NAME)
+ c.back = surface_create_similar(c.front, w, h)
+ else
+ c.back = CairoRGBSurface(w, h)
+ end
+ c.backcc = CairoContext(c.back)
+ c.mouse = MouseHandler()
+ cb = tcl_callback((x...)->reveal(c))
+ tcl_eval("bind $(c.c.path) <Expose> $(cb)")
+ bp1cb = tcl_callback((path,x,y)->(c.mouse.button1press(c,int(x),int(y))))
+ br1cb = tcl_callback((path,x,y)->(c.mouse.button1release(c,int(x),int(y))))
+ bp2cb = tcl_callback((path,x,y)->(c.mouse.button2press(c,int(x),int(y))))
+ br2cb = tcl_callback((path,x,y)->(c.mouse.button2release(c,int(x),int(y))))
+ bp3cb = tcl_callback((path,x,y)->(c.mouse.button3press(c,int(x),int(y))))
+ br3cb = tcl_callback((path,x,y)->(c.mouse.button3release(c,int(x),int(y))))
+ motcb = tcl_callback((path,x,y)->(c.mouse.motion(c,int(x),int(y))))
+ b1mcb = tcl_callback((path,x,y)->(c.mouse.button1motion(c,int(x),int(y))))
+ tcl_eval("bind $(c.c.path) <ButtonPress-1> {$bp1cb %x %y}")
+ tcl_eval("bind $(c.c.path) <ButtonRelease-1> {$br1cb %x %y}")
+ tcl_eval("bind $(c.c.path) <ButtonPress-2> {$bp2cb %x %y}")
+ tcl_eval("bind $(c.c.path) <ButtonRelease-2> {$br2cb %x %y}")
+ tcl_eval("bind $(c.c.path) <ButtonPress-3> {$bp3cb %x %y}")
+ tcl_eval("bind $(c.c.path) <ButtonRelease-3> {$br3cb %x %y}")
+ tcl_eval("bind $(c.c.path) <Motion> {$motcb %x %y}")
+ tcl_eval("bind $(c.c.path) <Button1-Motion> {$b1mcb %x %y}")
+ c
+end
+
+function pack(c::Canvas)
+ pack(c.c)
+ init_canvas(c)
+end
+
+function place(c::Canvas, x::Int, y::Int)
+ place(c.c, x, y)
+ init_canvas(c)
+end
+
+function reveal(c::Canvas)
+ set_source_surface(c.frontcc, c.back, 0, 0)
+ paint(c.frontcc)
+ tcl_doevent()
+end
+
+function update()
+ tcl_eval("update")
+end
+
+cairo_context(c::Canvas) = c.backcc
+cairo_surface(c::Canvas) = c.back
+
+
+tcl_interp = init()
+tcl_eval("wm withdraw .")
+
View
17 src/types.jl
@@ -0,0 +1,17 @@
+## Abstract types
+abstract Tk_Widget
+abstract TTk_Widget <: Tk_Widget ## for ttk::widgets
+abstract TTk_Container <: Tk_Widget ## for containers (frame, labelframe, ???)
+
+
+
+Widget = Union(TkWidget, Tk_Widget, Canvas, String)
+
+## Maybe -- can this be parameterized?
+## https://groups.google.com/forum/?fromgroups=#!topic/julia-dev/IbbWwplrqlc (takeaway -- this style if frowned on)
+MaybeFunction = Union(Function, Nothing)
+MaybeString = Union(String, Nothing)
+MaybeStringInteger = Union(String, Integer, Nothing) # for at in tree insert
+MaybeVector = Union(Vector, Nothing)
+MaybeWidget = Union(Widget, Nothing)
+MaybeBool = Union(Bool, Nothing)
View
713 src/widgets.jl
@@ -0,0 +1,713 @@
+## Create basic widgets here
+
+## Most types are simple, some have other properties
+type Tk_Label <: TTk_Widget w::TkWidget end
+type Tk_Button <: TTk_Widget w::TkWidget end
+type Tk_Checkbutton <: TTk_Widget w::TkWidget end
+##type Tk_Radio <: TTk_Widget w::TkWidget end
+##type Tk_Combobox <: TTk_Widget w::TkWidget end
+type Tk_Scale <: TTk_Widget w::TkWidget end
+#type Tk_Spinbox <: TTk_Widget w::TkWidget end
+type Tk_Entry <: TTk_Widget w::TkWidget end
+type Tk_Sizegrip <: TTk_Widget w::TkWidget end
+type Tk_Separator <: TTk_Widget w::TkWidget end
+type Tk_Progressbar <: TTk_Widget w::TkWidget end
+type Tk_Menu <: TTk_Widget w::TkWidget end
+type Tk_Menubutton <: TTk_Widget w::TkWidget end
+type Tk_Image <: TTk_Widget w::String end
+type Tk_Scrollbar <: TTk_Widget w::TkWidget end
+type Tk_Text <: Tk_Widget w::TkWidget end
+##type Tk_Treeview <: Tk_Widget w::TkWidget end
+type Tk_Canvas <: Tk_Widget w::TkWidget end
+
+for (k, k1, v) in ((:Label, :Tk_Label, "ttk::label"),
+ (:Button, :Tk_Button, "ttk::button"),
+ (:Checkbutton, :Tk_Checkbutton, "ttk::checkbutton"),
+ (:Radiobutton, :Tk_Radiobutton, "ttk::radiobutton"),
+ (:Combobox, :Tk_Combobox, "ttk::combobox"),
+ (:Slider, :Tk_Scale, "ttk::scale"), # Scale conflicts with Gadfly.Scale
+ (:Spinbox, :Tk_Spinbox, "ttk::spinbox"),
+ (:Entry, :Tk_Entry, "ttk::entry"),
+ (:Sizegrip, :Tk_Sizegrip, "ttk::sizegrip"),
+ (:Separator, :Tk_Separator, "ttk::separator"),
+ (:Progressbar, :Tk_Progressbar, "ttk::progressbar"),
+ (:Menu, :Tk_Menu, "menu"),
+ (:Menubutton, :Tk_Menubutton, "ttk::menubutton"),
+ (:Scrollbar, :Tk_Scrollbar, "ttk::scrollbar"),
+ (:Text, :Tk_Text, "text"),
+ (:Treeview, :Tk_Treeview, "ttk::treeview"),
+ (:TkCanvas, :Tk_Canvas, "canvas"),
+ ##
+ (:Frame, :Tk_Frame, "ttk::frame"),
+ (:Labelframe, :Tk_Labelframe, "ttk::labelframe"),
+ (:Notebook, :Tk_Notebook, "ttk::notebook"),
+ (:Panedwindow, :Tk_Panedwindow, "ttk::panedwindow")
+ )
+ @eval begin
+ function $k(parent::Widget, args::Dict)
+ w = make_widget(parent, $v, args)
+ $k1(w)
+ end
+ $k(parent::Widget) = $k(parent, Dict())
+ end
+end
+
+
+## Now customize
+
+## Label constructors
+Label(parent::Widget, text::String, image::Tk_Image) = Label(parent, {:text => text, :image=>image, :compound => "left"})
+Label(parent::Widget, text::String) = Label(parent, {:text => text})
+Label(parent::Widget, image::Tk_Image) = Label(parent, {:image=>image, :compound => "image"})
+## cf. Button for get_value, set_value
+
+## Button constructors
+Button(parent::Widget, text::String, command::Function, image::Tk_Image) =
+ Button(parent, {:text => text, :command=>command, :image=>image, :compound=>"left"})
+Button(parent::Widget, text::String, image::Tk_Image) =
+ Button(parent, {:text => text, :image=>image, :compound=>"left"})
+Button(parent::Widget, text::String, command::Function) = Button(parent, {:text=>text, :command=>command})
+Button(parent::Widget, text::String) = Button(parent, {:text=>text})
+Button(parent::Widget, command::Function, image::Tk_Image) =
+ Button(parent, {:command=>command, :image=>image, :compound=>"image"})
+Button(parent::Widget, image::Tk_Image) =
+ Button(parent, {:image=>image, :compound=>"image"})
+
+get_value(widget::Union(Tk_Button, Tk_Label)) = tk_cget(widget, "text")
+function set_value(widget::Union(Tk_Button, Tk_Label), value::String)
+ variable = tk_cget(widget, "variable")
+ (variable == "") ? tk_configure(widget, {:text => value}) : tclvar(variable, value)
+end
+
+## checkbox
+function Checkbutton(parent::Widget, label::String)
+ cb = Checkbutton(parent)
+ set_items(cb, label)
+ tk_configure(cb, {:variable => tclvar()})
+ set_value(cb, false)
+ cb
+end
+
+function get_value(widget::Tk_Checkbutton)
+ ## var = tk_cget(widget, "variable")
+ ## tclvar(var) == "1"
+ tk_instate(widget, "selected")
+end
+function set_value(widget::Tk_Checkbutton, value::Bool)
+ var = tk_cget(widget, "variable")
+ tclvar(var, value ? 1 : 0)
+end
+get_items(widget::Tk_Checkbutton) = tk_cget(widget, "text")
+set_items(widget::Tk_Checkbutton, value::String) = tk_configure(widget, {:text => value})
+
+## RadioButton
+type Tk_Radiobutton <: TTk_Widget
+ w::TkWidget
+end
+MaybeTkRadioButton = Union(Nothing, Tk_Radiobutton)
+
+function Radiobutton(parent::Widget, group::MaybeTkRadioButton, label::String)
+
+ rb = Radiobutton(parent)
+
+ var = isa(group, Tk_Radiobutton) ? tk_cget(group, "variable") : tclvar()
+ tk_configure(rb, {:variable => var, :text=>label, :value=>label})
+ rb
+end
+Radiobutton(parent::Widget, label::String) = Radiobutton(parent, nothing, label)
+
+get_value(widget::Tk_Radiobutton) = tk_instate(widget, "selected")
+set_value(widget::Tk_Radiobutton, value::Bool) = tk_state(value ? "selected" : "!selected")
+get_items(widget::Tk_Radiobutton) = tk_cget(widget, "text")
+set_items(widget::Tk_Radiobutton, value::String) = tk_configure(widget, {:text => value})
+
+
+## Radio Button Group
+type Tk_Radio <: TTk_Widget
+ w::TkWidget
+ buttons::Vector
+ Tk_Radio(w::TkWidget) = new(w, [])
+end
+
+function Radio(parent::Widget, labels::Vector, horizontal::Bool)
+ rbs = Array(Tk_Radiobutton, length(labels))
+ frame = Frame(parent)
+ rbs[1] = Radiobutton(frame, labels[1])
+ if length(labels) > 1
+ for j in 2:length(labels)
+ rbs[j] = Radiobutton(frame, rbs[1], labels[j])
+ end
+ end
+ path = tk_cget(rbs[1], "variable")
+ value = labels[1]
+ tcl("set", path, value)
+
+
+ rb = Tk_Radio(frame.w)
+ rb.buttons = rbs
+ map(u -> pack(u, {:side => horizontal ? "left" : "top",
+ :anchor => horizontal ? "n