//[of]:description //[c]A single window object //[c] //[c]A zoom edit 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]: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 "text/string" import "text/string-buffer" import "swift" import "utilities/file-utils" import "editor/text-model" import "editor/text-view" //[c] import "application/strings-en" import "application/code-browser-application" import "application/text-file" //[cf] //[of]:private classes //[of]:floating page //[of]:description //[c]Display a text view inside a page. //[cf] //[of]:definition //[c] public struct floating page : local container box application: application text view: text view //vertical scrollbar: scrollbar title font: font title height: int title: string row: int column: int frame index: int frame number: int initial position: local rectangle final position: local rectangle current position: local rectangle // This flag notifies the update geometry method // that the final position must be re-computed invalid final: bool // The current movement of the page // Cleared immediately after read direction: int // The line number of the cursor // Updated immediately after read line number: int // This flag prevent reentrant notif // when the page changes //private ignore scroll event : bool // 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 end public struct floating page class : local container box class end //[cf] //[of]:contants equ fp margin between title and page = 8 equ fp border size = 1 equ fp margin size = 6 equ fp shadow size = 2 //[cf] //[c] //[of]:instance creation //[of]:new floating page (parent box, row, column) //[c] public func new floating page (parent: box, application: application, row: int, column: int) def m = allocate memory (sizeof local floating page) : floating page initialize (m, parent, application, row, column) return m end //[cf] //[of]:new floating page (parent box, page) //[c] public func new floating page (parent: box, src: floating page) def m = allocate memory (sizeof local floating page) : floating page initialize (m, parent, src) return m end //[cf] //[cf] //[of]:accessing //[of]:folder //[c]Returns the current edited folder //[c] public func folder (m: floating page) return text (text view (m)) end //[cf] //[of]:view //[c]Returns the text view of the page //[c] public equ view (m: floating page) = text view (m) //[cf] //[of]:format //[c]Returns the text view of the page //[c] public func format (m: floating page) return format (text view (m)) end //[cf] //[c] //[of]:set folder (folder) //[c]Sets the current edited folder //[c] public func set folder (m: floating page, 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 (page) //[c]Clone an exisiting view //[c] public func set folder (m: floating page, src: floating page) def v = view (src) 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: floating page, folder: text object, top: page position, cursor: text position, block: text position) def view = text 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 def format = find format (application (m), folder) set format (view, format) set folder (view, folder, top, cursor, block) set read only (view, ro) update title (m) update decoration (m) register folder listener (m) // notify folder changed //on folder changed (m) notify parent (m, event (folder changed event type)) end //[cf] //[of]:move cursor to line (line number) func move cursor to line (m: floating page, line: int) def view = view (m) if line >= 0 && line (cursor (view)) <> line move cursor (view, line, 0, false) center page (view) end end //[cf] //[c] //[of]:settings changed (old, new) //[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: floating page, 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 (text view (m), format) update decoration (m) end //[cf] //[of]:open target location (m) //[c]Returns the target location (i.e. the folder + line) //[c] public func open target location (m: floating page, 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] //[cf] //[of]:finding //[of]:find text file //[c]Returns the text file edited by the pane //[c] public func find text file (m: floating page) def folder = folder (m) if is nil (folder) return nil else return find text file (file database (application (m)), folder) end end //[cf] //[cf] //[c] //[of]:restricted //[of]:floating page class public func floating page class def c = the floating page class if ~ floating page initialized floating page initialized = true copy (c, container box class) mem size (c) = sizeof local floating page class release (c) = ^actual release (floating page) on size (c) = ^handle size (floating page) on paint (c) = ^handle paint (floating page, canvas, rectangle) //on child event (c) = ^handle child event (floating page, box, event) end return c end private def floating page initialized = false private def the floating page class : local floating page class //[cf] //[c] //[of]:initialize (m, parent box, application) //[c] public func initialize (m: floating page, parent: box, application: application, row: int, column: int) initialize (super (m), parent, true) class (m) = floating page class application (m) = application text view (m) = new text view (m) //vertical scrollbar (m) = new scrollbar (m, sb vertical) title (m) = nil set title font (m, title bar font (settings (application))) // initialize view def cf = cf structured text (application) set structured text clipboard format (text view (m), cf) row (m) = row column (m) = column frame index (m) = 0 frame number (m) = 0 line number (m) = 0 invalid final (m) = true direction (m) = 0 //ignore scroll event (m) = false 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 (floating page, text object, replace prop event) group changed (tl) = ^ignore group event (object, text object, bool) destroyed (tl) = ^handle destroyed (floating page, text object) marker added (tl) = ^ignore marker (object, text object, text marker) marker removed (tl) = ^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 (floating page, text file, bool) filename changed (fl) = ^handle filename changed (floating page, text file) add listener (file database (application (m)), fl) end //[c] //[cf] //[of]:initialize (m, src) //[c]Initialize by cloning another page //[c] public func initialize (m: floating page, parent: box, s: floating page) initialize (m, parent, application (s), row (s), column (s)) frame index (m) = frame index (s) frame number (m) = frame number (s) line number (m) = line number (s) set (initial position (m), initial position (s)) set (final position (m), final position (s)) set (current position (m), current position (s)) invalid final (m) = invalid final (s) direction (m) = direction (s) set folder (m, s) set visible (m, is visible (s)) end //[c] //[cf] //[c] //[of]:actual release //[c] public func actual release (m: floating page) delete (title (m)) remove listener (file database (application (m)), file listener (m)) unregister folder listener (m) actual release (super (m)) end //[cf] //[of]:actual compute min size //[c] /*public func actual compute min size (m: list box) def a: local area def canvas: local canvas initialize (canvas) set font (canvas, edit font (style (m))) text extent (canvas, "X", 1, a) def lines = 0 if not nil (list of strings (m)) each (list of strings (m)) ? s def b: local area text extent (canvas, s, size (s), b) w (a) = max (w (a), w (b)) ++ lines end end release (canvas) // the min size is never less than 5 lines // the min size is never more than 10 lines equ min lines = 5 equ max lines = 10 if lines < min lines lines = min lines elsif lines > max lines lines = max lines end def rect : local LPRECT left (rect) = 0 top (rect) = 0 right (rect) = w (a) bottom (rect) = h (a) * lines AdjustWindowRectEx (rect, get style (m), 0, get ex style (m)) equ extra width = 16 // for scrollbar equ extra height = 0 def w = width (rect) + extra width def h = bottom (rect) - top (rect) + extra height set min size(m, w, h) end*/ //[cf] //[of]:handle size //[c]The grid has been resized //[c] public func handle size (m: floating page) def c = client rect (m) def x = c.x + fp border size + fp margin size def y = c.y + fp border size * 2 + fp margin size * 3 + title height (m) def w = c.w - (fp border size + fp margin size) * 2 - fp shadow size def h = c.h - fp border size * 3 - fp margin size * 4 - fp shadow size - title height (m) if w < 0 w = 0 end if h < 0 h = 0 end /*def sb = vertical scrollbar (m) def scrollbar width = min width (sb) w -= scrollbar width def r:= rectangle(x, y, w, h) move (text view (m), r) x (r) += w w (r) = scrollbar width move (sb, r)*/ move (text view (m), rectangle(x, y, w, h)) invalidate (m) //update scrollbar (m) end //[cf] //[of]:handle paint (canvas, clip) //[c] public func handle paint (m: floating page, c: canvas, clip: rectangle) def client rect = client rect (m) def r : local rectangle equ black = 0:d // Draw page def color = comment back color (format (m)) x (r) = x (client rect) + fp border size y (r) = y (client rect) + fp border size w (r) = w (client rect) - fp shadow size - fp border size * 2 h (r) = h (client rect) - fp shadow size - fp border size * 2 fill rectangle (c, r, color) // Draw borders color = black x (r) = x (client rect) y (r) = y (client rect) w (r) = w (client rect) - fp shadow size h (r) = fp border size fill rectangle (c, r, color) y (r) = h (client rect) - fp shadow size - fp border size fill rectangle (c, r, color) y (r) = y (client rect) + fp border size + fp margin size * 2 + title height (m) fill rectangle (c, r, color) x (r) = x (client rect) y (r) = y (client rect) + fp border size w (r) = fp border size h (r) = h (client rect) - fp shadow size - fp border size * 2 fill rectangle (c, r, color) x (r) = w (client rect) - fp shadow size - fp border size fill rectangle (c, r, color) // Draw shadow color = black x (r) = x (client rect) + w (client rect) - fp shadow size y (r) = y (client rect) + fp shadow size w (r) = fp shadow size h (r) = h (client rect) - fp shadow size fill rectangle (c, r, color) x (r) = x (client rect) + fp shadow size y (r) = y (client rect) + h (client rect) - fp shadow size w (r) = w (client rect) - fp shadow size h (r) = fp shadow size fill rectangle (c, r, color) color = workspace color (settings (application (m))) x (r) = x (client rect) + w (client rect) - fp shadow size y (r) = y (client rect) w (r) = fp shadow size h (r) = fp shadow size fill rectangle (c, r, color) x (r) = x (client rect) y (r) = y (client rect) + h (client rect) - fp shadow size w (r) = fp shadow size h (r) = fp shadow size fill rectangle (c, r, color) // Print title set font (c, title font (m)) title rectangle (m, r) color = comment back color (format (m)) set back color (c, color) set text color (c, comment fore color (format (m))) def x = x (r) def w = w (r) // Print page number def u := temp string buffer u << column (m) + 1 def wr = text width (c, base (u), size (u)) def xr = max (0, x + w - wr) text out (c, xr, y (r), wr, h (r), base (u), size (u)) release (u) // Print margin wr = fp margin between title and page xr = max (0, xr - wr) def margin rect := rectangle (xr, y (r), wr, h (r)) fill rectangle (c, margin rect, color) // Print title w = max (0, xr - x (r)) text out (c, x(r), y(r), w, h(r), title (m), size (title (m))) end //[cf] //[of]:handle child event (src, event) //[c] /*public func handle child event (m: floating page, src: box, e: event) def t = type (e) if t == value changed event type || t == page changed event type update scrollbar (m) elsif t == scroll event type if ~ ignore scroll event (m) def pos = pos (e : scroll event) def view = view (m) move page (view, add (text view position (view, 0, 0), pos)) set focus (view) end return true end return handle child event (super (m), src, e) end*/ //[cf] //[cf] //[of]:private //[of]:observing text objects //[of]:handle prop changed (folder, event) //[c]Properties of an headline have changed //[c]- update affected title bars //[c]- update affected previews //[c] func handle prop changed (m: floating page, o: text object, e: replace prop event) def line = line object (e) if line == parent line (folder (m)) update title (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 (folder) //[c] func handle destroyed (m: floating page, 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] //[cf] //[of]:observing text files //[of]:handle filename changed (text file) //[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: floating page, 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 (m) end //[cf] //[of]:handle read only changed (text file) //[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: floating page, 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 //[c] private func update title (m: floating page) def folder = folder (m) def t := temp string buffer append title (m, folder, t) set title (m, as string (t)) release (t) end //[c] //[c]Sub-functions: //[of]:append title (folder, string buffer) //[c] private func append title (m: floating page, 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] //[cf] //[of]:update decoration //[c]Update the decoration //[c] //[c] Invoked when there is a new format //[c] public func update decoration (m: floating page) invalidate (m) 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]changes. //[c] private func register folder listener (m: floating page) 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: floating page) 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] //[of]:update scrollbar //[c] /*private func update scrollbar (m: floating page) def view = view (m) def text = text (view) if not nil (text) ignore scroll event (m) = true def page = page (view) def line = convert line to logical line (view, line number (page), subline (page)) set scroll info ( vertical scrollbar (m), 0, number of sublines (view) - 1, page height (view), line) ignore scroll event (m) = false end end*/ //[cf] //[cf] //[of]:accessing //[of]:set title font (font) //[c] func set title font (m: floating page, font: font) title font (m) = font def canvas := canvas (m) set font (canvas, title font (m)) title height (m) = height (canvas) release (canvas) end //[cf] //[of]:set title (title) //[c]Changes the title //[c] public func set title (m: floating page, title: string) delete (title (m)) title (m) = new string (title) invalidate (m, title rectangle (m)) end //[cf] //[of]:title rectangle //[c] func title rectangle (m: floating page, return r: rectangle) def c = client rect (m) x (r) = c.x + fp border size + fp margin size y (r) = c.y + fp border size + fp margin size w (r) = c.w - (fp border size + fp margin size) * 2 - fp shadow size h (r) = title height (m) end //[cf] //[cf] //[cf] //[cf] //[of]:floating page grid //[of]:description //[c]A grid object where each cell store one page //[cf] //[of]:constants //[c] equ grid size = column number * row number //[cf] //[of]:definition struct floating page grid array : [grid size] floating page end //[cf] //[c] //[of]:initialize func initialize (m: floating page grid) def p = array (m) def limit = p + grid size while p < limit p++[] = nil end end //[cf] //[of]:release equ release (m: floating page grid) // empty end //[cf] //[c] //[of]:[] equ @at (m: floating page grid, row: int, column: int) = array (m) [column * row number + row + 1] //[cf] //[of]:put (page, row, column) func put (m: floating page grid, page: floating page, row: int, column: int) def old = m [row, column] if not nil (old) delete (old) end m [row, column] = page end //[cf] //[cf] //[of]:floating workspace //[of]:description //[c]The workspace is a container for floating pages. //[c] //[c]It just fill its background with app workspace color. //[cf] //[of]:constants //[c] equ column number = 128 equ row number = 3 //[c] equ columns = 3 equ h margin = 4 equ v margin = 16 equ min page width = 32 equ min page height = 32 //[cf] //[of]:definition //[c] public struct floating workspace : local container box application : application // Grid // grid: local floating page grid grid offset: int // the last page having the focus active page : floating page // true if the active window is zoomed or not zoomed : bool // Temporary lock // This lock forces ignoring the selection change events // while reorganzing pages do not touch panes : int // Remember if a children has the focus // This flag is used to softly set the focus, i.e. the focus is // changed only if the workspace already has the focus, // otherwise we just remember which control to activate // later (active page). has focus : bool // Timer to animate pages timer: local timer // client rect on last on-size event save rect : local rectangle end //[c] public struct floating workspace class : local container box class end //[cf] //[c] //[of]:instance creation //[of]:new floating workspace (parent box) //[c] public func new floating workspace (parent: box, application: application) def m = allocate memory (sizeof local floating workspace) : floating workspace initialize (m, parent, application) return m end //[cf] //[of]:new floating workspace (parent box, src) //[c] public func new floating workspace (parent: box, src: floating workspace) def m = allocate memory (sizeof local floating workspace) : floating workspace initialize (m, parent, src) return m end //[cf] //[cf] //[of]:browsing //[of]:enter public func enter (m: floating workspace) def page = active page (m) update preview (m, page, 1) def forward column = column (page) + 1 def forward page = page (m, 0, forward column) if not nil (forward page) def restore focus = has focus (m) set active page (m, forward page) update preview (m, forward page) update layout (m, restore focus, false) end end //[cf] //[of]:leave public func leave (m: floating workspace) def page = active page (m) def column = column (page) if column <> 0 def restore focus = has focus (m) def backward page = page (m, 0, column - 1) set active page (m, backward page) update layout (m, restore focus, false) end end //[cf] //[c] //[of]:show item (reverse) //[c]Selects the next or previous item //[c] public func show item (m: floating workspace, reverse: bool) def restore focus = has focus (m) lock preview (m) def column = column (active page (m)) def location: local target location def page = next (m, column, reverse, location) unlock preview (m) if not nil (page) set active page (m, page) update preview (m, page) update layout (m, restore focus, false) end end //[c] //[c]SUB-FUNCTIONS //[of]: next (stack location, reverse) //[c] func next ( m: floating workspace, column: int, reverse: bool, location: target location) : floating page if column == 0 folder (location) = nil return nil end def backward column = column - 1 def page = page (m, 0, backward column) def view = view (page) def folder = text (view) def line = text line (cursor (view)) line = next item (line, reverse) if is nil (line) page = next (m, backward column, reverse, location) if is nil (page) return nil end folder = folder (location) if is nil (folder) return page end line = first item (folder, reverse) if is nil (line) return page end end move cursor to line (page, line number (line)) target location (application (m), folder, line, location) folder = folder (location) if is nil (folder) return page end return update forward (m, page, folder, line number (location)) end //[cf] //[cf] //[of]:show folder (folder) //[c]Makes a folder visible at the best position //[c] public private func show folder (m: floating workspace, folder: text object) show folder (m, folder, -1) end //[cf] //[of]:show folder (folder, line number) //[c]Makes 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] public func show folder ( m: floating workspace, folder: text object, line number: int) // try the current page first def page = active page (m) if folder (page) == folder move cursor to line (page, line number) return end def restore focus = has focus (m) lock preview (m) page = find or create page (m, folder, line number) unlock preview (m) set active page (m, page) update preview (m, page) update layout (m, restore focus, false) end //[c] //[c]SUB-FUNCTIONS //[of]: find or create page (folder, line) func find or create page ( m: floating workspace, folder: text object, line: int) : floating page def center = false def page = find last page (m, folder) if is nil (page) def parent folder = parent folder (folder) if is nil (parent folder ) page = page (m, 0, 0) set folder (page, folder) else def parent line = line number (parent line (folder)) def parent page = find or create page (m, parent folder, parent line) return update forward (m, parent page, folder, line) end end move cursor to line (page, line) return page end //[c] //[c]SUB-FUNCTIONS //[of]: find last page (folder) //[c]Searches the last page editing a folder //[c] func find last page (m: floating workspace, folder: text object) def page : floating page def column = 1 repeat page = page (m, 0, column) if is nil (page) break end column += 1 end while column > 0 column -= 1 page = page (m, 0, column) if folder (page) == folder return page end end return nil end //[cf] //[cf] //[cf] //[c] //[of]:goto page (n) //[c]Goto column //[c] //[c] First page is page number 1. //[c] public func goto page (m: floating workspace, n: int) def page = page (m, 0, n-1) if is nil (page) return end def restore focus = has focus (m) set active page (m, page) update preview (m, page) update layout (m, restore focus, false) end //[cf] //[of]:last visible page number //[c]Returns the page number of the last visible page //[c] public func last visible page number (m: floating workspace) def column = 1 repeat def page = page (m, 0, column) if is nil (page) break end column += 1 end // The last column is 'column - 1'. // The page number is the last column + 1 return column - 1 + 1 end //[cf] //[cf] //[of]:zoom //[of]:invert zoom public func invert zoom (m: floating workspace) set zoomed (m, ~ zoomed (m)) end //[cf] //[of]:set zoomed (zoomed) func set zoomed (m: floating workspace, zoomed: bool) if zoomed (m) == zoomed return end zoomed (m) = zoomed // fixup grid offset // the grid offset must be the one with the // active page if zoomed grid offset (m) = column (active page (m)) end def restore focus = has focus (m) update preview (m, active page (m)) update layout (m, restore focus, true) end //[cf] //[cf] //[of]:settings //[of]:change settings (old , new) //[c]The user preferences have changed, requiring to update //[c]formats //[c] public func change settings ( m: floating workspace, old settings: code browser settings, new settings: code browser settings) each page (m) ? page settings changed (page, old settings, new settings) end // the fisheye view option may have changed update geometry (m, true) end //[cf] //[cf] //[of]:testing //[of]:can enter //[c]Returns true if enter action is valid //[c] //[c] The method is designed to be fast but not exact, it just check that //[c] the cursor of the active page is on a headline. //[c] public func can enter (m: floating workspace) def active page = active page (m) def cursor = cursor (view (active page)) def line = text line (cursor) return is headline (line) end //[cf] //[of]:can leave //[c]Returns true if there is a parent page //[c] public func can leave (m: floating workspace) def active page = active page (m) def column = column (active page) return column <> 0 end //[cf] //[of]:has item (reverse) //[c] public func has item (m: floating workspace, reverse: bool) def column = column (active page (m)) return has next (m, column, reverse) end //[c] //[c]SUB-FUNCTIONS //[of]: has next (column, reverse) //[c] func has next ( m: floating workspace, column: int, reverse: bool) : bool if column == 0 return false end def backward column = column - 1 def page = page (m, 0, backward column) def view = view (page) def folder = text (view) def line = text line (cursor (view)) line = next item (line, reverse) if is nil (line) return has next (m, backward column, reverse) end return true end //[cf] //[cf] //[of]:has preview public func has preview (m: floating workspace) return ~ zoomed (m) end //[cf] //[cf] //[c] //[of]:restricted //[of]:class accessing //[of]:floating workspace class public func floating workspace class def c = the floating workspace class if ~ floating workspace initialized floating workspace initialized = true copy (c, container box class) mem size (c) = sizeof local floating workspace class release (c) = ^actual release (floating workspace) adjust (c) = ^actual adjust (floating workspace) accept focus (c) = ^yes (box) on paint (c) = ^handle paint (floating workspace, canvas, rectangle) on size (c) = ^handle size (floating workspace) on focus (c) = ^handle focus (floating workspace, event) on child event (c) = ^handle child event (floating workspace, box, event) end return c end private def floating workspace initialized = false private def the floating workspace class : local floating workspace class //[cf] //[cf] //[of]:initializing //[of]:initialize (m, parent box, application) //[c] public func initialize (m: floating workspace, parent: box, application: application) initialize (super (m), parent, true) class (m) = floating workspace class application (m) = application initialize (grid (m)) grid offset (m) = 0 do not touch panes (m) = 0 has focus (m) = false zoomed (m) = false empty (save rect (m)) active page (m) = new page (m, 0, 0) initialize (timer (m), 20, m, ^handle timer (floating workspace, timer)) start (timer (m)) end //[cf] //[of]:intialize (parent box, source) //[c]Initialize a workspace by cloning another one //[c] func initialize (m: floating workspace, parent: box, src: floating workspace) initialize (super (m), parent, true) class (m) = floating workspace class application (m) = application (src) initialize (grid (m)) grid offset (m) = grid offset (src) do not touch panes (m) = 0 has focus (m) = false zoomed (m) = zoomed (src) empty (save rect (m)) // copy all the page from the grid lock preview (m) def src active page = active page (src) def active page = nil : floating page def row = -(row number - 1) / 2 def column = 0 while row <= row number / 2 def src page = page (src, row, column) if not nil (src page) def page = new floating page (m, src page) put (m, row, column, page) // activate this pane if src page == src active page active page = page end end column +=1 if column == column number column = 0 row += 1 end end unlock preview (m) // activate the right pane active page (m) = active page initialize (timer (m), 20, m, ^handle timer (floating workspace, timer)) start (timer (m)) end //[cf] //[cf] //[of]:box events //[of]:actual release func actual release (m: floating workspace) release (timer (m)) actual release (super (m)) end //[cf] //[of]:actual adjust //[c]Invoked when the chlidren must be moved //[c] public func actual adjust (m: floating workspace) update geometry (m, true) end //[cf] //[of]:handle paint (canvas, clip) public func handle paint (m: floating workspace, c: canvas, clip: rectangle) def color = workspace color (settings (application (m))) fill rectangle (c, clip, color) end //[cf] //[of]:handle size func handle size (m: floating workspace) if ~ is equal (save rect (m), client rect (m)) set (save rect (m), client rect (m)) update geometry (m, true) end end //[cf] //[of]:handle focus (event) //[c]The workspace is recieving the focus //[c] public func handle focus (m: floating workspace, e: event) // Transfer the focus to active page // // If there is no active page it means that the page has been deleted // but it should be restored with 'fixup active page' or 'set active page' // before returning to the message loop. // def active page = active page (m) if not nil (active page) set focus (active page) end return true end //[cf] //[of]:handle child event (child box, event) public func handle child event (m: floating workspace, 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 do not touch panes (m) == 0 def restore focus = has focus (m) def view = b def page = find page (m, b) // ignore event from non center row if row (page) == 0 update preview (m, page) fixup active page (m) update layout (m, restore focus, false) end end elsif t == property changed event type // the page has sent a notification: properties of a headline // have change, it could be the path of the link of the current // selection => update preview def restore focus = has focus (m) update preview (m, b : floating page) fixup active page (m) update layout (m, restore focus, false) elsif t == folder destroyed event type def page = b : floating page def o = folder (page) // If the folder being deleted was displayed in the first page, // close the window (pass event to the parent). if column (page) <> 0 def restore focus = has focus (m) // Delete the page (and subsequent pages if on row 0) def row = row (page) def column = column (page) delete page (m, page) if row == 0 column += 1 repeat page = page (m, row, column) if is nil (page) break end delete page (m, page) end end fixup active page (m) update layout (m, restore focus, false) // do not process default handling: this event must // not be forwarded as is to the parent (wrong source) return true end elsif t == focus event type has focus (m) = true active page (m) = find page (m, b) elsif t == blur event type has focus (m) = false end // default processing return handle child event (super (m), b, e) end //[cf] //[cf] //[of]:timer events //[of]:handle timer (timer) //[c] private func handle timer (m: floating workspace, t: timer) // when zoomed is active, there is no animation if zoomed (m) return end each page (m) ? page if frame index (page) <> frame number (page) frame index (page) += 1 // move the page def n = frame number (page) def i = frame index (page) def r1 = initial position (page) def r2 = final position (page) def r : local rectangle x (r) = x (r1) + i * (x (r2) - x (r1)) / n y (r) = y (r1) + i * (y (r2) - y (r1)) / n w (r) = w (r1) + i * (w (r2) - w (r1)) / n h (r) = h (r1) + i * (h (r2) - h (r1)) / n set (current position (page), r) move (page, r) if i == n // final is now the initial set (initial position (page), final position (page)) end end end end //[cf] //[cf] //[cf] //[of]:private //[of]:grid //[of]:fixup active page //[c]Find a new page to activate if there is no more active page //[c] //[c] There can be no active page if the active page has just //[c] been destroyed. //[c] func fixup active page (m: floating workspace) if not nil (active page (m)) return end def page = page (m, 0, 0) def column = 0 repeat def next = page (m, 0, ++column) if is nil (next) break end page = next end set active page (m, page) end //[cf] //[of]:set active page (page) //[c]Changes the active page and adjust the grid offset if necessary //[c] func set active page (m: floating workspace, page: floating page) // The active page is unchanged ? if page == active page (m) return end // Update active page (m) = page // The current offset is unchanged ? if grid offset (m) == column (page) return end // Update grid offset (m) = column (page) // Invalidate all final positions each page (m) ? p invalid final (p) = true end end //[cf] //[c] //[of]:update preview (start page) //[c] equ update preview (m: floating workspace, start page: floating page) = update preview (m, start page, 0) //[c] func update preview (m: floating workspace, start page: floating page, look ahead: int) def page = start page if is nil (page) return end def column = column (page) + 1 lock preview (m) // Compute the last column to apply the preview // This limit is the last column plus an optional number of columns // (look ahead). def limit = ( zoomed (m) -> column (active page (m)), (grid offset (m) + columns / 2)) limit += look ahead while not nil (page) && column <= limit // Retrieve the preview folder or nil def location := open target location (page) def folder = folder (location) def line = line number (location) page = update forward (m, page, folder, line) column += 1 end // reset the direction of the last page if not nil (page) def cursor = cursor (view (page)) line number (page) = line number (cursor) direction (page) = 0 end unlock preview (m) end //[cf] //[of]:lock preview //[c]Prevents to update preview when the selection of a page changes //[c] //[c]Several calls can be nested. //[c] equ lock preview (m: floating workspace) = ++ do not touch panes (m) //[cf] //[of]:unlock preview //[c]Allows to update preview when the selection of a page changes //[c] //[c]It takes effect only when all locks are removed. //[c] equ unlock preview (m: floating workspace) = -- do not touch panes (m) //[cf] //[c] //[of]:update forward (parent page, folder, line number) //[c]Update the column following the paent page with given folder/line //[c] //[c]ARGUMENTS //[c] parent page //[c] The page on the left. //[c] folder //[c] The folder to show on the right of the parent page or nil. //[c] line number //[c] The line to select in the folder or -1. //[c] //[c]RETURN VALUE //[c] Returns the page created or recycled. //[c] func update forward ( m: floating workspace, parent page: floating page, folder: text object, line number: int) def forward column = column (parent page) + 1 // Is there is already a page on the same folder def old = page (m, 0, forward column) if not nil (old) && folder == folder (old) move cursor to line (old, line number) return old end def d row = update line number (m, parent page) // Eject any existing page def c = forward column repeat def p = page (m, 0, c) if is nil (p) break end if d row == 0 delete page (m, p) else move page (m, p, row (p) + d row) end c += 1 end if is nil (folder) return nil end def final row = 0 def initial row = final row - d row // Check if previous folder can be reused def page = page (m, initial row, forward column) if is nil (page) || folder <> folder (page) page = new page (m, initial row, forward column) set folder (page, folder) end move page (m, page, final row) // Move the cursor to the requested line move cursor to line (page, line number) return page end //[c] //[c]SUB-FUNCTIONS //[of]: update line number (page) func update line number (m: floating workspace, page: floating page) // Check if move up or down def cursor = cursor (view (page)) def line number = line number (cursor) def d row = 0 if direction (page) < 0 d row = -1 elsif direction (page) > 0 d row = 1 elsif line number > line number (page) d row = -1 elsif line number < line number (page) d row = 1 end line number (page) = line number direction (page) = 0 return d row end //[cf] //[of]: move page (page) //[c] func move page (m: floating workspace, page: floating page, row: int) def direction = 0 // Unchanged ? if row <> row (page) direction = (row > row (page) -> 1, -1) invalid final (page) = true // clear old slot clear (m, row (page), column (page)) // set new slot put (m, row, column (page), page) row (page) = row end direction (page) = direction end //[cf] //[cf] //[cf] //[of]:layout //[of]:update layout (focus) //[c]Forces recomputing the layout //[c] func update layout (m: floating workspace, focus: bool, immediate: bool) // Update the visibility of each page each page (m) ? page def visible = ~ zoomed (m) || page == active page (m) set visible (page, visible) end // Update the geometry of each page update geometry (m, immediate) // Restore the focus if focus set focus (active page (m)) end end //[cf] //[cf] //[of]:geometry //[of]:update geometry (immediate) //[c]Update the geometry from the current grid configuration //[c] func update geometry (m: floating workspace, immediate: bool) each page (m) ? page update geometry (m, page, immediate) end end //[cf] //[of]:update geometry (page, immediate) //[c]Update the geometry from the current grid configuration //[c] func update geometry (m: floating workspace, page: floating page, immediate: bool) def frame number = frame number (m) if frame number == 1 immediate = true end if zoomed (m) def r : local rectangle set (r, client rect (m)) x (r) -= fp border size y (r) -= fp border size w (r) += fp border size * 2 + fp shadow size h (r) += fp border size * 2 + fp shadow size move (page, r) elsif immediate position (m, row (page), column (page), initial position (page)) set (final position (page), initial position (page)) invalid final (page) = false frame index (page) = 0 frame number (page) = 0 set (current position (page), initial position (page)) move (page, final position (page)) elsif invalid final (page) frame index (page) = 0 frame number (page) = frame number // setup the initial position with the current position set (initial position (page), current position (page)) // setup the final position position (m, row (page), column (page), final position (page)) invalid final (page) = false end end //[c] //[c]SUB-FUNCTIONS //[of]: position (row, column) //[c]Returns the rectangle to move a page for a column and a row //[c] //[c]ARGUMENTS //[c] row //[c] The row number, 0 for center row, -1 for upper row and 1 //[c] for lower row. //[c] column //[c] The column number. 0 is the first column. //[c] //[c]RETURN VALUE //[c] The rectangle to move the page. //[c] func position (m: floating workspace, row: int, column: int, return r: rectangle) def settings = settings (application (m)) def fisheye view = fisheye view (settings) def client = client rect (m) def n = columns if fisheye view n += 1 end def i = column + (n - 1) / 2 - grid offset (m) if fisheye view && i > 1 i += 1 end // Stay in the near hidden area if i > n i = n elsif i < -1 i = -1 end x (r) = i * width (client) / n + h margin y (r) = v margin + row * height (client) // always the same to avoid resize if fisheye view && column == grid offset (m) w (r) = max (min page width, 2 * width (client) / n - 2 * h margin) else w (r) = max (min page width, width (client) / n - 2 * h margin) end h (r) = max (min page height, height (client) - 2 * v margin) //y (r) = v margin + (row + 1) * height (client) / 3 //h (r) = max (min page height, height (client)/3 - v margin * 2) end //[cf] //[of]: frame number func frame number (m: floating workspace) def settings = settings (application (m)) return scroll speed (settings) end //[cf] //[cf] //[cf] //[of]:pages //[of]:accessing //[of]:page (row, column) func page (m: floating workspace, row: int, column: int) return grid (m) [row, column] end //[cf] //[of]:put (page, row, column) func put (m: floating workspace, row: int, column: int, page: floating page) put (grid (m), page, row, column) end //[cf] //[of]:clear (row, column) func clear (m: floating workspace, row: int, column: int) grid (m) [row, column] = nil end //[cf] //[cf] //[of]:adding - removing //[of]:new page (row, column) func new page (m: floating workspace, row: int, column: int) def page = new floating page (m, application (m), row, column) if activated (m) activate (page) //validate size (page) end // update grid cell put (m, row, column, page) // immediate move to return a non empty size update geometry (m, page, true) return page end //[c] //[cf] //[of]:delete page (page) func delete page (m: floating workspace, page: floating page) if page == active page (m) active page (m) = nil // need fix end clear (m, row (page), column (page)) delete (page) end //[cf] //[cf] //[of]:enumerating //[of]:each page equ each page (m: floating workspace) def box = first child (m) while not nil (box) def next = next sibling (box) yield (box:floating page) box = next end end //[cf] //[cf] //[of]:finding //[of]:find page (view) //[c]Returns the pane containing the given view or nil //[c] private func find page (m: floating workspace, view: box) each page (m) ? page if view == view (page) return page end end return nil end //[cf] //[cf] //[cf] //[cf] //[cf] //[cf] //[of]:definition //[c] public struct zoom edit window : local border box private // the application // to get language and file databases application : application workspace : floating workspace text file : text file end public struct zoom edit window class : local container box class end //[c] //[c]Internal aliases: //[c] private typedef window = zoom edit window private typedef application = code browser application //[cf] //[c] //[of]:instance creation //[of]:new zoom edit window (parent box, application) //[c] public func new zoom edit window (parent: box, application: application) def m = allocate memory (sizeof local zoom edit window) : window initialize (m, parent, application) return m end //[cf] //[of]:new zoom edit 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 zoom edit window (parent: box, application: application, src: window) def m = allocate memory (sizeof local zoom edit 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) show folder (m, folder) set text file (m, text file) end //[cf] //[cf] //[of]:settings //[of]:settings changed (old, new) //[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) change settings (workspace (m), old settings, new settings) 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) show item (workspace (m), reverse) end //[c] //[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) enter (workspace (m)) end //[cf] //[of]:leave //[c]Leaves the current folder //[c] public func leave (m: window) leave (workspace (m)) end //[cf] //[c] //[of]:goto page (n) //[c]Goto page //[c] public func goto page (m: window, n: int) goto page (workspace (m), n) 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) 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) 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 page (m)) end //[cf] //[of]:active folder //[c]Returns the current edited folder //[c] public func active folder (m: window) return text (active view (m)) end //[cf] //[of]:active view //[c]Returns the view of the active pane //[c] public func active view (m: window) return view (active page (m)) end //[cf] //[of]:active line //[c]Returns the view of the active pane //[c] public func active line (m: window, folder: [] text object) def view = active view (m) if not nil (folder) folder [] = text (view) end return text line (cursor (view)) end //[cf] //[c] //[of]:zoom public func zoom (m: window) invert zoom (workspace (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) return last visible page number (workspace (m)) 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) return false end //[cf] //[of]:can enter //[c]Returns true if enter action is valid //[c] public func can enter (m: window) return can enter (workspace (m)) end //[cf] //[of]:can leave //[c] public func can leave (m: window) return can leave (workspace (m)) end //[cf] //[c] //[of]:has preview public func has preview (m: window) return has preview (workspace (m)) end //[cf] //[of]:has first item //[c]Tests if there is a first item //[c] public func has first item (m: window) return has first headline (active view (m)) end //[cf] //[of]:has next item //[c]Tests if there is a next item //[c] public func has next item (m: window) // the method is defined in the private area 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]:zoom edit window class public func zoom edit window class def c = the zoom edit window class if ~ initialized initialized = true copy (c, border box class) mem size (c) = sizeof local zoom edit window class release (c) = ^actual release (window) on child event (c) = ^handle child event (window, box, event) end return c end private def initialized = false private def the zoom edit window class : local zoom edit window class //[cf] //[cf] //[of]:initializing //[of]:initialize (parent box, application) //[c] public func initialize ( m: window, parent: box, application: application) initialize (super (m), parent) class (m) = zoom edit window class application (m) = application text file (m) = nil workspace (m) = new floating workspace (m, application) end //[cf] //[of]:initialize (parent box, application, src window) //[c] public func initialize ( m: window, parent: box, application: application, src: window) initialize (super (m), parent) class (m) = zoom edit window class application (m) = application text file (m) = text file (src) workspace (m) = new floating workspace (m, workspace (src)) end //[cf] //[cf] //[of]:box interface implementation //[of]:actual release public func actual release (m: window) // Force unregister text file // useless with crazy browser //set text file (m, nil) actual release (super (m)) end //[cf] //[of]:handle child event (child box, event) public func handle child event (m: window, b: box, e: event) if type (e) == folder destroyed event type // re-emit the event notify parent (m, e) return true end // default processing return handle child event (super (m), b, e) end //[c] //[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] public func show folder (m: window, folder: text object, line number: int) show folder (workspace (m), folder, line number) end //[cf] //[cf] //[of]:accessing //[of]:set text file (text file) private func set text file (m: window, text file: text file) text file (m) = text file end //[cf] //[of]:file database //[c] private func file database (m: window) return file database (application (m)) end //[cf] //[of]:active page func active page (m: window) return active page (workspace (m)) end //[cf] //[cf] //[of]:testing //[of]:has item (reverse) //[c] func has item (m: window, reverse: bool) return has item (workspace (m), reverse) end //[c] //[cf] //[cf] //[cf]