//[of]:license //[c] Code Browser - a folding text editor for programmers //[c] Copyright (C) 2003-07 Marc Kerbiquet //[c] //[c] This program is free software; you can redistribute it and/or modify //[c] it under the terms of the GNU General Public License as published by //[c] the Free Software Foundation; either version 2 of the License, or //[c] (at your option) any later version. //[c] //[c] This program is distributed in the hope that it will be useful, //[c] but WITHOUT ANY WARRANTY; without even the implied warranty of //[c] MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //[c] GNU General Public License for more details. //[c] //[c] You should have received a copy of the GNU General Public License //[c] along with this program; if not, write to the Free Software //[c] Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //[cf] //[of]:imports import "base/types" import "base/memory-allocator" import "base/memory" import "text/string" import "text/string-buffer" import "net/uri" import "collection/vector" import "collection/collection" import "swift" //[c] import "utilities/file-utils" import "editor/text-model" import "editor/text-view" import "editor/tree-view" //[c] import "application/strings-en" import "application/code-browser-application" import "application/text-file" //[c] import "code-browser-view" //[cf] //[c] //[of]:multi pane window //[of]:description //[c]A single window object //[c] //[c]A multi pane window wraps a group of text views that can be //[c]organized using different layouts, it adds to it the notion of //[c]stacked views. It can display one or more panes. //[c] //[c]It handles the navigation between folders and link. //[c]It has access to the file-database, it is able to open files when //[c]requesting the traversal of an hyperlink. //[cf] //[of]:definition //[c] private equ max panes = 5 //[c] public struct multi pane window : local left list box private // the application // to get language and file databases application : application // the list of panes panes : [max panes] code browser pane // temporary lock // this lock prevent update of pane 2 when changing pane 1 // since it's going to be updated do not touch panes : int // the paned box paned : paned box // the tree view tree view: tree view // Open interface for the tree view // The tree view opens folder and links using this object open interface: local open interface text file: text file text file listener: local text file listener // temporary locks synchronizing tree view: bool synchronizing from tree view: bool end public struct multi pane window class : local left list box class end //[c] //[c]Internal aliases: //[c] private typedef pane = code browser pane private typedef window = multi pane window private typedef application = code browser application //[cf] //[c] //[of]:instance creation //[of]:new multi pane window (parent box, application) public func new multi pane window (parent: box, application: application) def m = allocate memory (sizeof local multi pane window) : window initialize (m, parent, application) return m end //[cf] //[of]:new multi pane window (parent box, application, src window) //[c]Creates a new window by cloning an existing window: //[c]- same layout //[c]- same content in panes (including stack) //[c] public func new multi pane window (parent: box, application: application, src: window) def m = allocate memory (sizeof local multi pane window) : window initialize (m, parent, application, src) return m end //[cf] //[cf] //[of]:opening //[of]:open (text file, folder) //[c]Opens the window on an existing text file //[c] //[c]This method can be used for the initial opening and also invoked later //[c]to recycle its content. //[c] public func open (m: window, text file: text file, folder: text object) clear panes (m, first pane (m), nil) // clear all panes show folder (m, folder) set text file (m, text file) setup tree (m) end //[cf] //[cf] //[of]:notifying //[of]:settings changed //[c]The user preferences have changed, requiring to update //[c]formats //[c] public func settings changed ( m: window, old settings: code browser settings, new settings: code browser settings) def folder = folder (tree view (m)) def style = find tree view style (application (m), folder) set style (tree view (m), style) each pane (m) ? pane settings changed (pane, old settings, new settings) end end //[cf] //[cf] //[of]:browsing //[of]:show item (reverse) //[c]Selects the next or previous item //[c] public func show item (m: window, reverse: bool) def pane = active pane (m) def l: local stack location pane (l) = pane stack (l) = stack (pane) initialize (configs (l)) if next (m, l, reverse) def location := target location (application (m), parent (l), line (l)) def folder = folder (location) def line number = line number (location) if not nil (folder) line number = find similar line (m, pane, folder, line number) add (configs (l), new pane configuration (folder, line number)) show config (m, configs (l), pane (l)) end end delete pane configurations (configs (l)) end //[c] struct stack location pane: code browser pane stack: stacked view configs: local collection parent: text object line: text line end //[c] //[c]Sub-functions: //[of]:next (m, stack location, reverse) //[c] func next ( m: window, l: stack location, reverse: bool) : bool if ~ move to parent (m, l) return false end def parent = parent (l) def parent line = line (l) def line = next item (parent line, reverse) if not nil (line) parent (l) = parent line (l) = line add (configs (l), new pane configuration (parent, line number (line))) return true end if ~ next (m, l, reverse) return false end def location := target location (application (m), parent (l), line (l)) parent = folder (location) if is nil (parent) return false end def first = first item (parent, reverse) if not nil (first) parent (l) = parent line (l) = first add (configs (l), new pane configuration (parent, line number (first))) end return true end //[cf] //[of]:move to parent (m, stack location) //[c] func move to parent (m: window, l: stack location) def pane = pane (l) def stack = stack (l) if is nil (stack) pane = preview of (m, pane) if is nil (pane) return false end pane (l) = pane stack (l) = stack (pane) def view = view (pane) parent (l) = text (view) line (l) = text line (cursor (view)) else stack (l) = back (stack) parent (l) = text (stack) line (l) = text line (cursor (stack)) end return true end //[cf] //[cf] //[of]:show folder (folder) //[c]Make a folder visible at the best position //[c] public func show folder (m: window, folder: text object) show folder (m, folder, -1) end //[cf] //[c] //[of]:enter //[c]Enters the current line //[c] public func enter (m: window) def active pane = active pane (m) def loc := open target location (active pane) def pane = enter (m, active pane, folder (loc), line number (loc), false) if not nil (pane) set focus (paned (m), pane) end end //[cf] //[of]:leave //[c]Leaves the current folder //[c] public func leave (m: window) def active pane = active pane (m) // If the stack is not empty, pop element from stack if not stack empty (active pane) // The item must be removed from stack before changing // folder in order to have a correct depth when invoking // 'set folder' (to display a correct page number). def i = remove last from stack (active pane) // scroll lock preview (m) def pane = last visible pane (m) while pane <> active pane def previous = previous pane (m, pane) set folder (pane, previous) pane = previous end unlock preview (m) // pop set folder (pane, text (i), page (i), cursor (i), cursor (i)) delete (i) sync tree view (m) else def previous pane = preview of (m, active pane) if not nil (previous pane) set focus (paned (m), previous pane) end end end //[cf] //[c] //[of]:goto page (n) //[c]Goto local line //[c] //[c] Note: line number starts to 0. //[c] public func goto page (m: window, n: int) def pane = last visible pane (m) while is void (pane) pane = previous pane (m, pane) end def page number = page number (pane) // the page number is not accessible if n > page number return end // start from the last visible pane set focus (paned (m), pane) while n < page number leave (m) pane = active pane (m) page number = page number (pane) end end //[cf] //[of]:goto local line (line number) //[c]Goto local line //[c] //[c] Note: line number starts to 0. //[c] public func goto local line (m: window, line number: int) set focus (m, paned (m)) cursor to (active view (m), line number, 0, false) end //[cf] //[of]:goto absolute line (line number) //[c]Goto absolute line //[c] //[c] Note: line number starts to 0. //[c] public func goto absolute line (m: window, line number: int) set focus (m, paned (m)) def pos := line location (text (current text file (m)), line number) def folder = folder (pos) if not nil (folder) show folder (m, folder, line number (pos)) end end //[cf] //[cf] //[of]:editing //[of]:cancel (redo) //[c]Cancel last operation //[c] //[c] This is the common method for undo and redo //[c] public func cancel (m: window, redo: bool) // Note: since the last line is unknown by the text view, // 'state after limit' cannot be set, the replace event will // invalidate everything below the replaced area. def text file = current text file (m) if ~ can cancel (text file, redo) return end // check the folder of the next item // if it differs from the current folder, enter into it before // performing the action otherwise the cursor won't move // to the location of the change. show folder (m, cancel folder (text file, redo)) def view = active view (m) set append mode (view) cancel (text file, redo) set ignore mode (view) end //[cf] //[cf] //[of]:accessing //[of]:current text file //[c]Returns the active text file of the window //[c]i.e. the text file of the folder currently edited by this window. //[c] public func current text file (m: window) return find text file (active pane (m)) end //[cf] //[of]:active folder //[c]Returns the current edited folder //[c] public func active folder (m: window) return folder (active pane (m)) end //[cf] //[of]:active view //[c]Returns the view of the active pane //[c] public func active view (m: window) return view (active pane (m)) end //[cf] //[of]:active line //[c]Returns the view of the active pane //[c] public func active line (m: window, p: [] text object) def folder = nil : text object def line = nil : text line def tree = tree view (m) if focused child (m) == tree def selection = selection (tree) if not nil (selection) folder = text object (selection) line = text line (selection) end else def view = active view (m) folder = text (view) line = text line (cursor (view)) end if not nil (p) p [] = folder end return line end //[cf] //[c] //[of]:set layout (mode) //[c]Changes the layout //[c] //[c]The mode is the one defined by the 'set mode' method of the //[c]paned box object. //[c] //[c]Two path are built: //[c]- A path from the first folder opened in this window to the folder //[c]displayed in the active path //[c]- A path, called 'extent', from the successor of the active folder //[c]to the last displayed in the window. //[c] //[c]The first path is restored according to the new layout, the extent //[c]path is restored according to the remaining panes. //[c] public func set layout (m: window, mode: paned box mode) def tree view = false if mode == pbm tree mode = pbm single tree view = true end if is visible (tree view (m)) <> tree view show list (m, tree view) setup tree (m) end // get old and new number of panes before changing mode def c1 = number of pages (paned (m)) def c2 = number of pages (paned (m), mode) set mode (paned (m), mode) // layout unchanged or text not set yet if c1 == c2 || is nil (folder (first pane (m))) return end def active pane = active pane (m) def pane = last non void pane (m) // Build a list of config // path: the path of folders from start to active // extent: the path of folders after the active (will be displayed if enough room) def path := collection def extent := collection def configs = extent while not nil (pane) // switch from extent to main path if pane == active pane configs = path end add to config (pane, configs) pane = preview of (m, pane) end show config (m, path, extent, nil) clear hidden panes (m) // free the list of configs delete pane configurations (path) delete pane configurations (extent) end //[cf] //[of]:activate tree public func activate tree (m: window) set focus (m, tree view (m)) end //[cf] //[of]:last visible page number //[c]Returns the page number of the last visible pane //[c] public func last visible page number (m: window) def pane = last visible pane (m) return page number (pane) end //[cf] //[cf] //[of]:testing //[of]:can activate tree //[c]Returns true if 'activate tree' action is valid //[c] public func can activate tree (m: window) def tv = tree view (m) return ~ has focus (tv) && is visible (tv) end //[cf] //[of]:can enter //[c]Returns true if enter action is valid //[c] public func can enter (m: window) if has focus (tree view (m)) return false end return can enter (m, active pane (m)) end //[cf] //[of]:can leave //[c] public func can leave (m: window) if has focus (tree view (m)) return false end return can leave (m, active pane (m)) end //[cf] //[c] //[of]:has preview public func has preview (m: window) return layout (m) <> pbm single end //[cf] //[of]:has first item //[c]Tests if there is a first item //[c] public func has first item (m: window) def pane = active pane (m) return has first headline (view (pane)) end //[cf] //[of]:has next item //[c]Tests if there is a next item //[c] public func has next item (m: window) return has item (m, false) end //[cf] //[of]:has prev item //[c]Tests if there is a next item //[c] public func has prev item (m: window) // the method is defined in the private area return has item (m, true) end //[cf] //[c] //[of]:selection matches (find data) //[c]Returns true if the current selection in the active pane matches with the //[c]searched string (find data). //[c] public func selection matches (m: window, data: find data) def view = active view (m) def pos = min (cursor (view), block (view)) def folder = active folder (m) def text line = text line (pos) def buf = buf (text line) def col = column (pos) def limit = search limit (data, size (text line)) return col < limit && (match (data, buf, col) > 0) end //[cf] //[cf] //[c] //[of]:restricted //[of]:class accessing //[of]:multi pane window class public func multi pane window class def c = the multi pane window class if ~ initialized initialized = true copy (c, left list box class) mem size (c) = sizeof local multi pane window class release (c) = ^actual release (window) on child event (c) = ^handle child event (window, box, event) accept focus (c) = ^yes (box) end return c end private def initialized = false private def the multi pane window class : local multi pane window class //[cf] //[cf] //[of]:initializing //[of]:initialize (parent box, application) public func initialize ( m: window, parent: box, application: application) initialize (super (m), parent) class (m) = multi pane window class application (m) = application do not touch panes (m) = 0 synchronizing tree view (m) = false synchronizing from tree view (m) = false text file (m) = nil // create paned box tree view (m) = new tree view (m) set image list (tree view (m), image list (application)) paned (m) = new paned box (m, pbm single) show list (m, false) // creates all pages def i = 0 def pane = nil : pane while i < max panes def border = new border box (paned (m)) pane = new code browser pane (border, application, pane) panes (m) [i] = pane i += 1 end // call backs for the tree model def oi = open interface (m) data (oi) = m open (oi) = ^open target (window, text object, text line) append title (oi) = ^append title (window, string buffer) // detect filename change (for the tree view) def fl = text file listener (m) recipient (fl) = m file changed (fl) = ^ignore file changed (object, text file, bool) read only changed (fl) = ^ignore read only changed (object, text file, bool) filename changed (fl) = ^handle filename changed (window, text file) end //[c] func open target (m: window, text object: text object, text line: text line) def location := target location (application (m), text object, text line) return folder (location) end //[c] func append title (m: window, s: string buffer) append base name (s, display filename (text file (m))) end //[cf] //[of]:initialize (parent box, application, src window) public func initialize ( m: window, parent: box, application: application, src: window) initialize (m, parent, application) // copy mode set mode (paned (m), mode (paned (src))) show list (m, is visible (tree view (src))) lock preview (m) def i = 0 def src active pane = active pane (src) def active pane = first pane (m) each pane (src) ? src pane def pane = panes (m) [i] copy pane (pane, src pane) // activate this pane if src pane == src active pane active pane = pane end ++i end unlock preview (m) set text file (m, text file (src)) setup tree (m) // activate the pane set focus (paned (m), active pane) if has focus (tree view (src)) set focus (m, tree view (m)) end end //[cf] //[cf] //[of]:box interface implementation //[of]:actual release public func actual release (m: window) // force unregister text file set text file (m, nil) actual release (super (m)) end //[cf] //[of]:handle child event public func handle child event (m: window, b: box, e: event) def t = type (e) if t == enter event type enter (m) return true elsif t == leave event type leave (m) return true elsif t == selection changed event type if b == tree view (m) sync from tree view (m) else def pane = find pane (m, b) if not nil (pane) update preview (m, pane) end end elsif t == folder changed event type // Prevent to have the focus on a void pane // It can occurs when a change occur in another window def first pane = first pane (m) def pane = b : pane if is void (pane) && pane == active pane (m) && pane <> first pane set focus (paned (m), first pane) end elsif t == property changed event type // the pane has sent a notification: properties of an headline // have change, it could be the path of the link of the current // selection => update preview update preview (m, b : pane) elsif t == tree item changed event type equ event = e : tree item changed event if tree item (event) == selection (tree view (m)) sync from tree view (m) end elsif t == folder destroyed event type def p = b : pane def o = folder (p) // If the folder being deleted was displayed in the first pane, // close the window. def pane = first pane (m) if o == text (view (pane)) // re-emit the event notify parent (m, e) return true end // If any folder is impacted, update all following previews def next = next non void pane (m, pane) while not nil (next) if o == text (view (next)) update preview (m, pane) break end pane = next next = next non void pane (m, pane) end // Re-assign the focus pane = active pane (m) while not nil (pane) if not void (pane) set focus (paned (m), pane) break end pane = previous pane (m, pane) end // do not process default handling: this event must // not be forwarded as is to the parent (wrong source) return true elsif t == key down event type equ evt = e: key event if key (evt) == VK RETURN && status (evt) == 0 set focus (m, paned (m)) return true end end // default processing return handle child event (super (m), b, e) end //[c] //[cf] //[cf] //[of]:observing text file //[of]:handle filename changed //[c]Handles the filename change event. //[c] //[c] The filename in the tree view must be updated. //[c] func handle filename changed (m: window, t: text file) notify title changed (tree view (m)) end //[cf] //[cf] //[cf] //[of]:private //[of]:browsing //[of]:show folder (folder, line number) //[c]Make a folder visible at the best position according to the current state. //[c] //[c]ARGUMENTS //[c] folder //[c] the folder to be shown //[c] line number //[c] if this value is different from -1, the cursor is moved to this line //[c] //[c]The algorithm is quite complex: the method first attemtps to find the //[c]first ancestor of folder that is displayed (or stacked) in a pane of the //[c]window. Once found, a serie of 'enter' operations are simulated starting //[c]from the pane that contains the ancestor. //[c] private func show folder (m: window, folder: text object, line number: int) def configs := collection def f = folder def l = line number def pane = nil : pane while not nil (f) def config = new pane configuration (f, l) add first (configs, config) // First attempt: find the last pane containing f pane = last pane containing (m, f) if not nil (pane) break end // Second chance: scan for a folder pointing to f pane = last visible pane (m) while not nil (pane) def lc = folder (pane) if not nil (lc) def ln = find link to (application (m), lc, f) def s = stack (pane) while not nil (s) && ln <> -1 lc = text (s) ln = find link to (application (m), lc, f) s = back (s) end if ln <> -1 add first (configs, new pane configuration (lc, ln)) break end end pane = preview of (m, pane) end if not nil (pane) break end l = line number (parent line (f)) f = parent folder (f) end show config (m, configs, pane) // free the list of configs delete pane configurations (configs) end //[c] //[c]Sub-functions: //[of]:delete pane configurations (collection) //[c] func delete pane configurations (m: collection) def i = first (m) while not nil (i) def next = next sibling (i) delete (i : pane configuration) i = next end end //[cf] //[of]:last pane containing (m, folder) //[c] func last pane containing (m: window, folder: text object) def pane = last visible pane (m) while not nil (pane) if contains (pane, folder) return pane end pane = preview of (m, pane) end return nil end //[cf] //[cf] //[of]:show config (config, pane) //[c]Attempt to show a sequence of folders as best as possible starting //[c]from 'pane'. //[c] //[c]If there is not enough available panes, folders are stacked. //[c] private func show config ( m: window, configs: collection, pane: code browser pane) show config (m, configs, nil, pane) end //[cf] //[of]:show config (path, extent, pane) //[c]Attempt to show a sequence of folders as best as possible starting //[c]from 'pane'. //[c] //[c]ARGUMENTS //[c] configs //[c] the path to build/restore. The pane displaying the last folder //[c] of this path will becomes the active one. //[c] extent //[c] an optional path. If this path is not nil, it will be displayed //[c] after the active pane, but only if there is enough space left. //[c] pane //[c] the pane to start showing the config. //[c] The pane that should contain the starting folder, otherwise //[c] its stack is entirely cleared. //[c] If this value is nil, first pane is used. //[c] private func show config ( m: window, configs: collection, extent: collection, pane: pane) def has focus = has focus (m) // no pane found: use the first one if is nil (pane) pane = first pane (m) end lock preview (m) // Bring the first folder to front def e = first (configs) : pane configuration remove to folder from stack (pane, folder (e)) set folder (m, pane, folder (e), line number (e)) e = next sibling (e) : pane configuration while not nil (e) pane = enter (m, pane, folder (e), line number (e), false) e = next sibling (e) : pane configuration end def xpane = pane if not nil (extent) e = first (extent) : pane configuration while not nil (e) && not nil (xpane) def n = enter (m, xpane, folder (e), line number (e), true) if is nil (n) break end xpane = n e = next sibling (e) : pane configuration end end unlock preview (m) // the preview must be updated since it was locked update preview (m, xpane) // activate the pane set focus (paned (m), pane) if ~ has focus (m) && has focus set focus (m) end sync tree view (m) end //[cf] //[c] //[of]:enter (pane, folder, line, extent) //[c]Initializes a pane to show a folder following an existing pane. //[c]A scrolling (or stacking) is performed if there is no available pane. //[c] //[c]ARGUMENTS //[c] active pane //[c] the ancestor pane (i.e. the source of the link/folder) //[c] folder //[c] the folder to be displayed //[c] line number //[c] the line number to go //[c] extent //[c] if this flag is set, the enter operation is performed ONLY //[c] if there is a preview pane available: this option protects //[c] the given pane to be modified. //[c] //[c]RETURN VALUE //[c] The new pane where the folder is displayed. //[c] The pane can be the same as the given one. //[c] Returns nil if the operation failed. Possible reasons are: //[c] - extent==true and no room //[c] - folder is nil //[c] public func enter ( m: window, pane: pane, folder: text object, line number: int, extent: bool) if is nil (folder) return nil end // Retrieve the preview pane or nil def preview pane = find preview pane (m, pane, folder) // If there is a preview, just activate it if is nil (preview pane) if extent return nil end preview pane = pane prepare pane (m, pane) else remove all from stack (preview pane) end // clear all intermediary pages clear panes (m, next pane (m, pane), preview pane) set folder (m, preview pane, folder, line number) sync tree view (m) return preview pane end //[cf] //[of]:prepare pane (pane) //[c]Makes room for a folder in a pane //[c]A scrolling or stacking is performed if there is no available pane. //[c] //[c]ARGUMENTS //[c] active pane //[c] the ancestor pane (i.e. the source of the link/folder) //[c] public func prepare pane (m: window, active pane: pane) // pane to start scrolling def pane: pane if ~ is browser (paned (m)) pane = first pane (m) elsif is list (folder (active pane)) && is stack empty (active pane) // if there is an intermediary free pane: use it pane = first pane (m) while pane <> active pane if is void (pane) set folder (pane, active pane) return end pane = next pane (m, pane) end pane = first pane (m) else pane = active pane end // 1. Push on first pane stack add last to stack (pane) // 2. Scroll intermediary panes lock preview (m) while pane <> active pane def next = next pane (m, pane) set folder (pane, next) pane = next end unlock preview (m) end //[cf] //[c] //[of]:update preview (pane) //[c]Updates the preview associated to the given pane. //[c] //[c]This method is usually invoked when the selection //[c]of the text inside the pane has changed. //[c] func update preview (m: window, pane: pane) if do not touch panes (m) <> 0 return end if ~ has potential preview (m, pane) return end // Retrieve the preview folder or nil def location := open target location (pane) def folder = folder (location) def line = line number (location) // Retrieve the preview pane or nil // The preview pane depends on the content of the folder // to be displayed, that's why it is retrieved first. def preview = find preview pane (m, pane, folder) // Clear all intermediary panes clear panes (m, next pane (m, pane), preview) if not nil (folder) && not nil (preview) line = find similar line (m, preview, folder, line) remove all from stack (preview) set folder (m, preview, folder, line) end end //[cf] //[of]:set folder (pane, folder, line number) //[c]Sets the current edited folder //[c] private func set folder ( m: window, pane: pane, folder: text object, line: int) : void lock preview (m) set folder (pane, folder) // Keep cursor column and block unchanged when possible. def view = view (pane) if line >=0 && line (cursor (view)) <> line move cursor (view, line, 0, false) center page (view) end unlock preview (m) update preview (m, pane) end //[cf] //[of]:clear hidden panes //[c] func clear hidden panes (m: window) def first hidden = next pane (m, last visible pane (m)) if not nil (first hidden) clear panes (m, first hidden, nil) end end //[cf] //[of]:clear panes (first pane, last pane) //[c]Clears the range of panes [ first, last [. //[c]If last is nil, all panes are cleared starting from 'first'. //[c] func clear panes (m: window, first: pane, last: pane) // Preview must be locked to prevent any reentrant calls lock preview (m) def p = first while not nil (p) && p <> last clear pane (p) p = next pane (m, p) end unlock preview (m) end //[cf] //[cf] //[of]:locking //[of]:lock preview //[c]Prevents to update preview when the selection of a pane //[c]has changed //[c] //[c]Several calls can be nested. //[c] equ lock preview (m: window) = ++ do not touch panes (m) //[cf] //[of]:unlock preview //[c]Allows to update preview when the selection of a pane //[c]has changed //[c] //[c]It takes effect only when all locks are removed. //[c] equ unlock preview (m: window) = -- do not touch panes (m) //[cf] //[cf] //[of]:finding //[of]:find pane (view) //[c]Returns the pane containing the given view or nil //[c] private func find pane (m: window, view: box) each pane (m) ? pane if view == view (pane) return pane end end return nil end //[cf] //[of]:find preview pane (pane, folder) //[c]Returns the preview pane for 'pane' to show 'folder'. //[c]Returns nil if there is no valid preview pane for this folder. //[c] //[c]With a regular layout (1 and 2 visible panes), the method searches the //[c]visible successor of given pane. //[c] //[c]With a browser type layout, the method is identical to the regular layout's if the //[c]given folder is a list, otherwise it returns the last visible pane (the bottom or //[c]right pane). //[c] //[c]Note: checking if a folder is a list or not consumes CPU, that's why the method is //[c]optimized to invoke 'is list' only when necessary. //[c] func find preview pane (m: window, pane: pane, folder: text object) // no preview pane if there is no valid folder // to display if is nil (folder) return nil end switch mode (paned (m)) case pbm horizontal, pbm vertical if pane <> panes (m) [1] return panes (m) [1] end case pbm browser 3, pbm browser 4, pbm browser 5, pbm vbrowser 3, pbm vbrowser 4, pbm vbrowser 5 def last visible pane = last visible pane (m) if pane <> last visible pane if is list (folder) return next pane (m, pane) else return last visible pane end end end return nil end //[cf] //[of]:find similar line (pane, folder, line) //[c]Returns a line similar to the current one in given pane. //[c] //[c]This only work if the give line number (line) is not set (-1), otherwise //[c]this line number is returned instead. //[c] private func find similar line ( m: window, pane: pane, folder: text object, line: int) if not void (pane) && line == -1 def ln = find similar line (view (pane), folder) if ln <> -1 line = ln end end return line end //[cf] //[cf] //[of]:enumerating //[of]:each pane //[c]Enumerates all panes (including hidden ones) //[c] private equ each pane (m: window) def i = 0 def n = max panes while i < n def pane = panes (m) [i] yield (pane) ++ i end end //[cf] //[cf] //[of]:accessing //[of]:preview of (pane) func preview of (m: window, p: pane) p = previous pane (m, p) while not nil (p) if not void (p) break end p = previous pane (m, p) end return p end //[cf] //[of]:next pane (pane) //[c]Returns the next pane (visible or hidden) //[c] func next pane (m: window, p: pane) def i = 0 def n = max panes - 1 while i < n if p == panes (m) [i] return panes (m) [i+1] end ++ i end return nil end //[cf] //[of]:next non void pane (pane) //[c]Returns the next pane (visible or hidden) having a non void content //[c] func next non void pane (m: window, p: pane) def i = 0 def n = max panes - 1 while i < n if p == panes (m) [i] while i < n ++ i def pane = panes (m) [i] if not void (pane) return pane end end return nil end ++ i end return nil end //[cf] //[of]:previous pane (pane) //[c]Returns the previous pane (visible or hidden) //[c] func previous pane (m: window, p: pane) def i = max panes - 1 while i > 0 if p == panes (m) [i] return panes (m) [i-1] end -- i end return nil end //[cf] //[c] //[of]:active pane func active pane (m: window) if has focus (tree view (m)) return first pane (m) else def border = focused child (paned (m)) if is nil (border) return nil end return first child (border) : pane end end //[cf] //[of]:first pane equ first pane (m: window) = panes (m) [0] //[cf] //[of]:last pane equ last pane (m: window) = panes (m) [4] //[cf] //[of]:last visible pane func last visible pane (m: window) switch mode (paned (m)) case pbm single return panes (m) [0] case pbm horizontal, pbm vertical return panes (m) [1] case pbm browser 3, pbm vbrowser 3 return panes (m) [2] case pbm browser 4, pbm vbrowser 4 return panes (m) [3] case pbm browser 5, pbm vbrowser 5 return panes (m) [4] end // should never occur return nil end //[cf] //[of]:last non void pane //[c]Returns the last non-void pane or nil //[c] private func last non void pane (m: window) def pane = last pane (m) while not nil (pane) && is void (pane) pane = previous pane (m, pane) end return pane end //[cf] //[c] //[of]:set text file (text file) private func set text file (m: window, text file: text file) def old text file = text file (m) if not nil (old text file) remove listener (old text file, text file listener (m)) end text file (m) = text file if not nil (text file) add listener (text file, text file listener (m)) end end //[cf] //[of]:file database private func file database (m: window) return file database (application (m)) end //[cf] //[of]:layout public equ layout (m: window) = mode (paned (m)) //[cf] //[cf] //[of]:testing //[of]:has item (m, reverse) //[c] func has item (m: window, reverse: bool) if has focus (tree view (m)) return false end def l: local stack location pane (l) = active pane (m) stack (l) = stack (pane (l)) return has next (m, l, reverse) end //[c] //[c]Sub-functions: //[of]: has next (m, stack location, reverse) //[c] func has next ( m: window, l: stack location, reverse: bool) : bool if ~ move to parent (m, l) return false end def line = next item (line (l), reverse) if not nil (line) return true end return has next (m, l, reverse) end //[cf] //[cf] //[of]:can enter (m, pane) //[c]Returns true if enter action is valid //[c] public func can enter (m: window, pane: pane) def view = view (pane) if is nil (text (view)) return false end def line = text line (cursor (view)) return is headline (line) end //[cf] //[of]:can leave (m, pane) //[c] public func can leave (m: window, pane: pane) return pane <> first pane (m) || not stack empty (pane) end //[cf] //[of]:has potential preview (m, pane) //[c]Returns true if the page has a potential preview //[c] public func has potential preview (m: window, pane: pane) return pane <> last visible pane (m) end //[cf] //[cf] //[of]:tree //[of]:set tree (m, folder) //[c]Setup the tree view with a new folder //[c] private func setup tree (m: window) if is nil (text file (m)) return end if is visible (tree view (m)) set tree folder (m, root folder (text file (m))) sync tree view (m) else set tree folder (m, nil) end end //[c] public func set tree folder (m: window, folder: text object) synchronizing tree view (m) = true set folder (tree view (m), folder, open interface (m)) set style (tree view (m), find tree view style (application (m), folder)) synchronizing tree view (m) = false end //[cf] //[of]:sync tree view (m) //[c]Synchronizes the tree view from the first pane //[c] func sync tree view (m: window) if synchronizing from tree view (m) return end //if ~ is tree visible (m) if is nil (tree (tree view (m))) return end synchronizing tree view (m) = true def item = find item (m, stack (first pane (m))) set selection (tree view (m), item) //close ancestors (item) synchronizing tree view (m) = false end //[c] //[c]Sub-functions: //[of]:find item (m) //[c] func find item (m: window, s: stacked view) : text tree item if is nil (s) return root (tree (tree view (m))) : text tree item else def parent = find item (m, back (s)) if is nil (parent) return root (tree (tree view (m))) : text tree item end def text line = text line (cursor (s)) open (parent) def child = first child (parent) : text tree item while not nil (child) if text line (child) == text line return child end child = next sibling (child) : text tree item end return child end end //[cf] //[of]:close ancestors (item) //[c] func close ancestors (item: tree item) if is nil (item) return end def parent = parent (item) while not nil (parent) close (parent) parent = parent (parent) end end //[cf] //[cf] //[of]:sync from tree view (m) //[c]Synchronize the panes from the tree view //[c] func sync from tree view (m: window) if synchronizing tree view (m) return end def item = selection (tree view (m)) if is nil (item) return end synchronizing from tree view (m) = true def text object = text object (item) def text line = text line (item) def line number = 0 if not nil (text line) def location := target location (application (m), text object, text line) text object = folder (location) line number = line number (location) end def configs := collection if not nil (text object) add first (configs, new pane configuration (text object, line number)) end while not nil (text line (item)) text object = text object (item) text line = text line (item) line number = line number (text line) add first (configs, new pane configuration (text object, line number)) item = parent (item) : text tree item end show config (m, configs, first pane (m)) // free the list of configs delete pane configurations (configs) synchronizing from tree view (m) = false end //[cf] //[of]:is tree visible (m) //[c] equ is tree visible (m: window) = is visible (tree view (m)) //[cf] //[cf] //[cf] //[cf] //[c] //[c]private: //[of]:code browser pane //[of]:description //[c]The code browser pane is a code browser view with a stack. //[c] //[c]In theory, only the first pane of a group can have a non empty stack. //[c]- In browser modes, there is two groups: a group for lists (at the //[c] top or at the left) and a group for text having only one pane. //[c]- In other modes, there is only one group. //[cf] //[of]:definition private struct code browser pane : local code browser view application: application stack : stacked view // Previous pane or nil // The previous pane is required to compute the page number. previous pane: pane // Listen to the displayed folder and its parent (if any) // // The listener updates titles when title of folder changes, // notifies parent when properties change or close the window // when the folder is destroyed. text listener : local text listener // Listen to any file in the database // // The listener updates all title bars when a filename changes // since a title display the filename as title for a root folder. file listener : local text file listener // Listen to any folder from the back stack // // The listener maintains pointers when content changes, // or clean up the stacks when a folder is destroyed. // // Having only one listener for all items of the stack versus // one listener per stack item allows more lightweight objects // (reduces memory usage and intensive event dispatching). stack listener : local text listener // all watched folders, i.e. folders which I'm listening to. watched folders : local vector end public struct code browser pane class : local code browser view class end //[cf] //[c] //[of]:instance creation //[of]:new code browser pane public func new code browser pane (parent: box, application: application, previous: pane) def m = allocate memory (sizeof local code browser pane) : pane initialize (m, parent, application, previous) return m end //[cf] //[cf] //[of]:copying //[of]:copy stack //[c]Copy the stack from one pane to another //[c] //[c]The source pane can be from a different window. //[c] public func copy stack (m: pane, src: pane) remove all from stack (m) def last = nil : stacked view def t = stack (src) while not nil (t) def back = new stacked view (nil, text (t), page (t), cursor (t)) if is nil (last) stack (m) = back else back (last) = back end last = back t = back (t) end update stack listeners (m) end //[cf] //[cf] //[of]:adding - removing //[of]:remove all from stack //[c]Removes all entries from the back stack //[c] //[c]Warning: the list of listener is not refreshed //[c] public func remove all from stack (m: pane) def s = stack (m) while not nil (s) def back = back (s) delete (s) s = back end stack (m) = nil update stack listeners (m) end //[cf] //[of]:remove last from stack //[c]Pop an item from the stack //[c] //[c]Note: //[c]- The stack must not be empty //[c]- It is the responsibility of the caller to delete the removed item //[c] public func remove last from stack (m: pane) def stack = stack (m) stack (m) = back (stack) update stack listeners (m) return stack end //[cf] //[of]:remove to folder from stack //[c]Removes all items from the top of the stack to the given folder //[c]- the item with the folder is also removed //[c]- if there is no stack item with folder, the stack is emptied //[c] public func remove to folder from stack (m: pane, folder: text object) if folder (m) <> folder def s = stack (m) while not nil (s) def next = back (s) def text = text (s) stack (m) = next delete (s) if text == folder break end s = next end end update stack listeners (m) end //[cf] //[of]:add last to stack //[c]Pushes the current view to the stack //[c] public func add last to stack (m: pane) def view = view (m) stack (m) = new stacked view ( stack (m), text (view), page (view), cursor (view)) update stack listeners (m) end //[cf] //[cf] //[of]:finding //[of]:find text file //[c]Returns the text file edited by the pane //[c] public func find text file (m: pane) def folder = folder (m) if is nil (folder) return nil else return find text file (file database (application (m)), folder) end end //[cf] //[cf] //[of]:accessing //[of]:copy pane (src) //[c]Copy the configuration from one pane to another //[c] //[c]The source pane can be from a different window. //[c] public func copy pane (m: pane, src: pane) copy stack (m, src) set folder (m, src) end //[cf] //[of]:clear pane //[c]Empties the pane //[c] public func clear pane (m: pane) remove all from stack (m) set folder (m, dummy text (m)) end //[cf] //[c] //[of]:set folder (folder) //[c]Sets the current edited folder //[c] public func set folder (m: pane, folder: text object) def corner := top left position (folder) def page := top left page position (folder) set folder (m, folder, page, corner, corner) end //[cf] //[of]:set folder (pane) public func set folder (m: pane, pane: pane) def v = view (pane) set folder (m, text (v), page (v), cursor (v), block (v)) end //[cf] //[of]:set folder (folder, top, cursor, block) //[c]Sets the current edited folder //[c] public func set folder ( m: pane, folder: text object, top: page position, cursor: text position, block: text position) def view = view (m) // same folder if text (view) == folder return end unregister folder listener (m) def text file = find text file (file database (application (m)), folder) def ro = not nil (text file) && is read only (text file) // update the page set format (m, find format (application (m), folder)) set view visible (m, folder <> dummy text (m) || is nil (previous pane (m))) set folder (view, folder, top, cursor, block) set read only (view, ro) update title bar (m) register folder listener (m) // notify folder changed //on folder changed (m) notify parent (m, event (folder changed event type)) end //[cf] //[c] //[of]:add to config (config) //[c]Add the stack content + the current view to the path //[c] public func add to config (m: pane, path: collection) def view = view (m) def f = text (view) def l = line number (cursor (view)) add first (path, new pane configuration (f, l)) def s = stack (m) while not nil (s) f = text (s) l = line number (cursor (s)) add first (path, new pane configuration (f, l)) s = back (s) end end //[cf] //[of]:open target location //[c]Returns the target location (i.e. the folder + line) //[c] public func open target location (m: pane, return location: target location) def view = view (m) def text line = text line (cursor (view)) def text object = text (view) target location (application (m), text object, text line, location) end //[cf] //[of]:settings changed //[c]The user preferences have changed, requiring to update formats //[c] //[c]This event is explicitely called by parent //[c] public func settings changed ( m: pane, old settings: code browser settings, new settings: code browser settings) def folder = folder (m) def format = find format (application (m), folder) set title font (m, title bar font (new settings)) reset format (m, format) end //[cf] //[cf] //[of]:testing //[of]:contains //[c]Returns true if the pane edits the given folder of if it is in its stack. //[c] public func contains (m: pane, folder: text object) if folder (m) == folder return true end def s = stack (m) while not nil (s) if text (s) == folder return true end s = back (s) end return false end //[cf] //[of]:is void //[c]Returns true if the pane is displaying a blank page //[c] public func is void (m: pane) return folder (m) == dummy text (m) end //[cf] //[of]:not void //[c]Returns true if the pane is not displaying a blank page //[c] public func not void (m: pane) return folder (m) <> dummy text (m) end //[cf] //[of]:is stack empty public equ is stack empty (m: pane) = is nil (stack (m)) //[cf] //[of]:not stack empty public equ not stack empty (m: pane) = not nil (stack (m)) //[cf] //[cf] //[c] //[c]restricted: //[of]:class accessing //[of]:code browser pane class private def code browser pane initialized = false private def the code browser pane class : local code browser pane class //[c] public func code browser pane class def c = the code browser pane class if ~ code browser pane initialized code browser pane initialized = true copy (c, code browser view class) release (c) = ^actual release (pane) end return c end //[cf] //[cf] //[of]:initialize - release //[of]:initialize public func initialize ( m: pane, parent: box, application: application, previous: pane) def settings = settings (application) def font = title bar font (settings) def cf = cf structured text (application) def sb = use horizontal scrollbar (settings) def vw = virtual width (settings) initialize (super (m), parent, font, cf, sb, vw) class (m) = code browser pane class application (m) = application previous pane (m) = previous stack (m) = nil initialize (watched folders (m), 8) def tl = text listener (m) recipient (tl) = m text changed (tl) = ^ignore replace event (object, text object, replace text event) prop changed (tl) = ^handle prop changed (pane, text object, replace prop event) group changed (tl) = ^ignore group event (object, text object, bool) destroyed (tl) = ^handle destroyed (pane, text object) marker added (tl) = ^ignore marker (object, text object, text marker) marker removed (tl) = ^ignore marker (object, text object, text marker) def sl = stack listener (m) recipient (sl) = m text changed (sl) = ^replace on stack (pane, text object, replace text event) prop changed (sl) = ^ignore prop changed (object , text object, replace prop event) group changed (sl) = ^ignore group event (object, text object, bool) destroyed (sl) = ^destroy on stack (pane, text object) marker added (sl) = ^ignore marker (object, text object, text marker) marker removed (sl) = ^ignore marker (object, text object, text marker) def fl = file listener (m) recipient (fl) = m file changed (fl) = ^ignore file changed (object, text file, bool) read only changed (fl) = ^handle read only changed (pane, text file, bool) filename changed (fl) = ^handle filename changed (pane, text file) add listener (file database (application (m)), fl) end //[cf] //[cf] //[of]:box interface implementation //[of]:actual release public func actual release (m: pane) remove listener (file database (application (m)), file listener (m)) remove all from stack (m) unregister folder listener (m) release (watched folders (m)) actual release (super (m)) end //[cf] //[cf] //[c] //[c]private: //[of]:observing text objects //[of]:handle prop changed //[c]Properties of an headline have changed //[c]- update affected title bars //[c]- update affected previews //[c] func handle prop changed (m: pane, o: text object, e: replace prop event) def line = line object (e) if line == parent line (folder (m)) update title bar (m) end if line == text line (cursor (view (m))) // force updating preview notify parent (m, event (property changed event type)) end end //[cf] //[of]:handle destroyed //[c] func handle destroyed (m: pane, o: text object) // it is the displayed folder, we are no longer able to display // something: notify parent if o == folder (m) notify parent (m, event (folder destroyed event type)) end end //[cf] //[of]:replace on stack //[c]A folder in stack has been modified //[c] func replace on stack (m: pane, o: text object, e: replace text event) def stack = stack (m) while not nil (stack) if text (stack) == o // correct positions in the back stack fix position (page (stack), e) fix position (cursor (stack), e) end stack = back (stack) end end //[cf] //[of]:destroy on stack //[c]A folder on stack has been modified //[c] //[c]The stack may contain views to a folder that is being destroyed, //[c]this method cleans the stack. //[c] func destroy on stack (m: pane, o: text object) def folder = o def previous = nil : stacked view def view = stack (m) while not nil (view) def back = back (view) if text (view) == folder delete (view) if is nil (previous) stack (m) = back else back (previous) = back end else previous = view end view = back end update stack listeners (m) end //[cf] //[cf] //[of]:observing text files //[of]:handle filename changed //[c]Handles the filename change event from the file database. //[c] //[c]The filename can be displayed in the title bar, then the title bar //[c]must be updated. //[c] func handle filename changed (m: pane, t: text file) // Note: the renamed file may not be the one displayed in the // title bar. Renaming a file will result a refresh of all title bars. update title bar (m) end //[cf] //[of]:handle read only changed //[c]Handles the filename change event from the file database. //[c] //[c]The filename can be displayed in the title bar, then the title bar //[c]must be updated. //[c] func handle read only changed (m: pane, t: text file, ro: bool) def text file = find text file (m) if text file == t set read only (view (m), is read only (t)) end end //[cf] //[cf] //[of]:updating //[of]:update title bar //[c] private func update title bar (m: pane) def folder = folder (m) def t := temp string buffer append title (m, folder, t) // display a page number only if there is a title def u := temp string buffer if size (t) > 1 u << page number (m) end set title (m, as string (t), as string (u)) release (u) release (t) end //[cf] //[of]:update stack listeners //[c]Register to events from text objects in stack //[c] //[c]Rebuilds the list of text object to listen according to //[c]what is displayed and the content of stacks. //[c] private func update stack listeners (m: pane) def list = watched folders (m) // unregister previous items each (list) ? f remove listener (f : text object, stack listener (m)) end remove all (list) // build the list of folders to watch def e = stack (m) while not nil (e) def folder = text (e) if ~ contains (list, folder) add (list, folder) end e = back (e) end // register all new listeners each (list) ? f add listener (f : text object, stack listener (m)) end end //[cf] //[of]:register folder listener //[c]Register to events from the current edited folder and its parent //[c] //[c]The parent folder is also registered in order to detect when the title //[c]change. //[c] private func register folder listener (m: pane) def folder = folder (m) if is nil (folder) return end add listener (folder, text listener (m)) folder = parent folder (folder) if is nil (folder) return end add listener (folder, text listener (m)) end //[cf] //[of]:unregister folder listener //[c]Unregister to events from the current edited folder and its parent //[c] private func unregister folder listener (m: pane) def folder = folder (m) if is nil (folder) return end remove listener (folder, text listener (m)) folder = parent folder (folder) if is nil (folder) return end remove listener (folder, text listener (m)) end //[cf] //[cf] //[of]:accessing //[of]:dummy text private equ dummy text (m: pane) = dummy text (application (m)) //[cf] //[of]:append title private func append title (m: pane, folder: text object, t: string buffer) if is nil (folder) // empty elsif not empty (title (folder)) append description (folder, t) else def text file = find text file (file database (application (m)), folder) if not nil (text file) append base name (t, display filename (text file)) end end end //[cf] //[of]:page number //[c]Returns the page number of this pane //[c] func page number (m: pane) def page number = 0 def p = m while not nil (p) if not void (p) ++ page number def stack = stack (p) while not nil (stack) stack = back (stack) ++ page number end end p = previous pane (p) end return page number end //[cf] //[cf] //[cf] //[of]:stacked view //[of]:description //[c]The stacked view is a structure that stores a snapshot of the //[c]current view in order to be restored later. //[cf] //[of]:definition //[c] private struct stacked view // pointer to chain stacked views back: stacked view // the folder text: text object // the position of the top of the page page: local page position // the position of the cursor cursor: local text position end //[cf] //[c] //[of]:instance creation //[of]:new stacked view (back, folder, page, cursor) //[c] private func new stacked view ( back: stacked view, text: text object, page: page position, cursor: text position) def m = allocate memory (sizeof local stacked view): stacked view back (m) = back text (m) = text set (page (m), page) set (cursor (m), cursor) return m end //[cf] //[of]:delete (m) //[c] private func delete (m: stacked view) free memory (m) end //[cf] //[cf] //[cf] //[of]:pane configuration //[of]:description //[c]A pane configuration stores a 'snapshot' of a pane. //[c] //[c]Pane configurations are linked in an ordered collection //[c]to store/restore a configuration of a complete window. //[cf] //[of]:definition //[c] struct pane configuration : local element folder : text object line number : int end //[cf] //[c] //[of]:instance creation func new pane configuration (f: text object, l: int) def m = allocate memory (sizeof local pane configuration) : pane configuration folder (m) = f line number (m) = l return m end //[c] equ delete (m: pane configuration) = free memory (m) //[c] //[cf] //[cf] //[l]:code browser view:code-browser-view.zc