# # $Id: tree.icn,v 1.7 2004/11/13 20:49:23 rparlett Exp $ # # This file is in the public domain. # # Author: Robert Parlett (parlett@dial.pipex.com) # package gui link graphics $include "guih.icn" # # This class represents a tree object. A TREE_NODE_EXPANSION_EVENT occurs when # a node is expanded/contracted. Also, the events generated by the parent class # SelectableScrollArea are inherited. # class Tree : SelectableScrollArea( root_node, default_bmps, col_w, bmp_w, bmp_h, plus, minus, little_h, little_w, last_expanded, show_root, show_root_handles ) method get_line_height() res:= WAttrib(self.cwin, "fheight") # # Ensure the line height is slightly greater than the # bitmap height # res <:= \bmp_h + 4 return res end method get_subject_width() mw := 0 every s := !self.contents do mw <:= col_w * s.depth + col_w + TextWidth(self.cwin, s.label) return mw + 2 * HIGHLIGHT_TEXT_SPACING end # # Expand all the nodes in the {Tree}. # method expand() if \root_node then { root_node.expand() tree_structure_changed() } end # # Set the default bitmaps for each {Node} in the {Tree}. The parameter should be # a list of three bitmaps; see {Node} above for an explanation. # @param l The list of bitmaps. # method set_default_bmps(l) return default_bmps := l end # # Set the root node of the {Tree}. # method set_root_node(r) root_node := r if /r then set_contents([]) else { root_node.is_expanded_flag := 1 compute_bmp_wh() set_contents(flatten()) set_selections([]) } return root_node end # # Get the root node of the tree # method get_root_node() return root_node end method tree_structure_changed() local sels if /root_node then return sels := object_get_selections() set_contents(flatten()) object_set_selections(sels) end # # Set the component so that root handles are shown (the default). # method set_show_root_handles() self.show_root_handles := 1 self.tree_structure_changed() end # # Set the component so that root handles are not shown. # method clear_show_root_handles() self.show_root_handles := &null self.tree_structure_changed() end # # Set the tree so that the root is shown (the default). # method set_show_root() self.show_root := 1 self.tree_structure_changed() end # # Set the component such that the root node is not shown. # method clear_show_root() self.show_root := &null self.tree_structure_changed() end method compute_bmp_wh() bmp_w := bmp_h := 0 every n := root_node.generate_all_preorder() do { bmps := \n.bmps | default_bmps bmp_w <:= img_width(bmps[1]) bmp_w <:= img_width(bmps[2]) bmp_w <:= img_width(bmps[3]) bmp_h <:= img_height(bmps[1]) bmp_h <:= img_height(bmps[2]) bmp_h <:= img_height(bmps[3]) } # # The column width is slightly wider than the bitmap width # col_w := bmp_w + 4 end # # This method returns a flat list of all the {Nodes} in the tree that are # currently displayed. # @return A list of nodes. # method flatten() l := [] if \self.show_root then { if \self.show_root_handles then flatten2(l, root_node, "n") else flatten2(l, root_node, "") } else { if *root_node.subnodes = 1 then { flatten2(l, root_node.subnodes[1], "n") } else { flatten2(l, root_node.subnodes[1], "d") every sub := root_node.subnodes[2 to *root_node.subnodes - 1] do flatten2(l, sub, "f") flatten2(l, root_node.subnodes[-1], "u") } } return l end method flatten2(l, n, dl) n.draw_line := dl n.depth := *dl put(l, n) if n.is_expanded() then { every sub := n.subnodes[1 to *n.subnodes - 1] do flatten2(l, sub, dl || "f") flatten2(l, n.subnodes[-1], dl || "u") } end method handle_press(e) if (self.view.x <= &x < self.view.x + self.view.w) & (self.view.y <= &y < self.view.y + self.view.h) then { lno := (&y - self.view.y) / self.line_height l := lno + self.get_first_line() if l <= self.get_last_line() then { N := self.contents[l] if (N.depth > 0) & (*N.subnodes > 0) | \N.always_expandable_flag then { # # Check for click on little +/- icon. # yp := self.view.y + self.line_height / 2 + self.line_height * lno - little_h / 2 xp := self.get_left_pos() + (N.depth - 1) * col_w + little_w / 2 if (xp <= &x < xp + little_w) & (yp <= &y < yp + little_h) then { # # Clicking on the little icon ends the sequence, and sets the selection # to the given node. # start_handle(e) N.toggle_expanded() self.last_expanded := N tree_structure_changed() is_held := &null selchange := 1 fire(TREE_NODE_EXPANSION_EVENT, e) end_handle(e) return } } } } self.SelectableScrollArea.handle_press(e) end method get_last_expanded() return self.last_expanded end method draw_line(xp, yp, i, selection_cw, cursor_cw, highlight_cw) local N, dashed, lp, j N := contents[i] dashed := Clone(self.cbwin, "pattern=gray", "fillstyle=textured") lp := xp every j := 1 to N.depth - 1 do { if N.draw_line[j] == ("f"|"d") then DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp + self.line_height / 2) lp +:= col_w } if N.depth > 0 then { if N.draw_line[N.depth] == "d" then DrawLine(dashed, lp + col_w / 2, yp, lp + col_w / 2, yp + self.line_height / 2) else if N.draw_line[N.depth] == "f" then DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp + self.line_height / 2) else if N.draw_line[N.depth] == "u" then DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp) DrawLine(dashed, lp + col_w / 2, yp, lp + col_w + col_w / 2, yp) lp +:= col_w } bmps := \N.bmps | default_bmps if (*N.subnodes = 0) & /N.always_expandable_flag then img := bmps[3] else { if N.is_expanded() then { img := bmps[2] little := minus if *N.subnodes > 0 then DrawLine(dashed, lp + col_w / 2, yp, lp + col_w / 2, yp + self.line_height / 2) } else { img := bmps[1] little := plus } EraseArea(self.cbwin, lp - col_w / 2 - little_w / 2, yp - little_h / 2, little_w, little_h) DrawImageEx(self.cbwin, lp - col_w / 2 - little_w / 2, yp - little_h / 2, little) } DrawImageEx(self.cbwin, lp + col_w / 2 - bmp_w / 2, yp - bmp_h / 2, img) left_string(self.cbwin, lp + col_w + HIGHLIGHT_TEXT_SPACING, yp, N.label) if \selection_cw then FillRectangle(selection_cw, lp + col_w, yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height) if \cursor_cw then { Rectangle(cursor_cw, lp + col_w, yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height) } if \highlight_cw then { Rectangle(highlight_cw, lp + col_w, yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height) } Uncouple(dashed) end method set_one(attr, val) case attr of { "show_root" : if test_flag(attr, val) then set_show_root() else clear_show_root() "show_root_handles" : if test_flag(attr, val) then set_show_root_handles() else clear_show_root_handles() default: self.SelectableScrollArea.set_one(attr, val) } end initially(a[]) self.SelectableScrollArea.initially() plus := img_style("plus") minus := img_style("minus") little_w := img_width(plus) little_h := img_height(plus) default_bmps := [img_style("closed_folder"), img_style("open_folder"), img_style("file")] show_root := show_root_handles := 1 set_fields(a) end