;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: C32; Base: 10 -*- ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; The Garnet User Interface Development Environment. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; This code was written as part of the Garnet project at ;;; ;;; Carnegie Mellon University, and has been placed in the public ;;; ;;; domain. If you are using this code or any part of Garnet, ;;; ;;; please contact garnet@cs.cmu.edu to be put on the mailing list. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; C32 is a spreadsheet interface for Garnet constraints ;;; ;;; This file contains the code to deal with formula windows. ;;; Designed and implemented by Brad Myers #| ============================================================ Change log: 07/22/93 Dario Giuse - added Lisp Mode to formula window 07/15/93 Andrew Mickish - #+lcl3.0 ---> #+lucid 07/09/93 Andrew Mickish - Condition type is undefined for Lucid 03/11/93 Dario Giuse - fixed C32-Careful-String-Eval to work properly when *current-formula-obj* is nil. This happens, for example, when editing a new slot name in a panel. 03/02/93 Andrew Mickish - Removed superfluous quote from :items list in Form-Win-Header. 02/02/93 Andrew Mickish - opal:set-strings ---> opal:set-text 12/16/92 Giuse/Mickish - Careful-Read-From-String --> C32-Careful-String-Eval 10/01/92 Andrew Mickish - Only call Slot-Value if #+(or allegro-v4.0 allegro-v4.1) 9/01/92 Andrew Mickish - Moved defparameter of *c32-package* to c32.lisp; for CMUCL, ext:ignore-errors ---> ignore-errors; 9/11/90 Brad Myers - Started ============================================================ |# (in-package "C32") (defparameter Formula-Win-Width 440) (defparameter Formula-Win-Height 300) (defparameter Formula-Wins-Available NIL) (defparameter formula-win-offset 0 "Offset for formula windows, so they are not exactly on top of one another") (create-instance 'My-Text-Button-Panel garnet-gadgets:text-button-panel (:shadow-offset 4) (:text-offset 2) (:final-feedback-p NIL) (:direction :horizontal) (:fixed-width-p NIL) (:gray-width 3)) (create-instance 'Form-Win-Header opal:aggregadget (:left 2) (:top 2) (:slot NIL) (:obj NIL) (:parts `((:label ,opal:text (:string ,(o-formula (mk-string (gvl :parent :obj)))) (:left 6) (:top 4)) (:slot-label ,opal:text (:string ,(o-formula (concatenate 'simple-string "slot " (Mk-Colon-Str-And-Clip (gvl :parent :slot) 99)))) (:left 6) (:top 18)) (:OK ,My-Text-Button-Panel (:left ,(o-formula (- (gvl :window :width) 90;;(gvl :width) 5) 100)) (:top 2) (:items (("OK" Do-Form-Ok) ("Cancel" Do-Form-Cancel)))) (:buttons ,garnet-gadgets:text-button-panel (:left 6) (:top 38) (:final-feedback-p nil) (:rank-margin 1) (:shadow-offset 2) (:text-offset 2) (:gray-width 2) (:font ,opal:default-font) (:items (("Insert Function" InsertFunction) ("Insert From Spread" InsertRefFromSpread) ("Insert From Mouse" InsertRefFromMouse))))))) (defparameter Form-Header-Height (+ 3 (g-value Form-Win-Header :height))) (defparameter *Current-Formula-Win* NIL) (defparameter *current-formula-obj* nil) (defparameter *current-formula-slot* nil) (defun make-scroll-gadget (win1) (create-instance NIL garnet-gadgets:scrolling-window-with-bars (:left -2) (:top Form-Header-Height) ;; width and height overridden with formulas below (:width (+ 1 Formula-Win-Width)) (:height (- Formula-Win-Height Form-Header-Height 2)) (:parent-window win1) (:total-width (o-formula (+ 6 (gvl :inner-aggregate :width)))) (:total-height (o-formula (+ 6 (gvl :inner-aggregate :height)))))) #| (defun New-Formula-Win (left top) (let* ((agg (create-instance NIL opal:aggregate)) (str (create-instance NIL opal:multifont-text (:word-wrap-p T) (:auto-scroll-p T))) (win1 (create-instance NIL inter:interactor-window (:left left) (:top top) (:width Formula-Win-Width) (:height Formula-Win-Height) (:aggregate agg) (:title "C32 Formula Window"))) (scroll-gadget (make-scroll-gadget win1)) (header (create-instance NIL Form-Win-Header)) ;; Create a focus interactor so that keyboard events may be entered ;; into the text objects. (focus-inter (create-instance nil inter:focus-multifont-textinter (:window `(,win1 ,(g-value win1 :clip-window) ,(g-value win1 :inner-window))))) ;; Create a selection interactor to handle mouse events on the text ;; objects. (mouse-inter (create-instance nil inter:selection-interactor (:window `(,win1 ,(g-value win1 :clip-window) ,(g-value win1 :inner-window))) (:focus-interactor focus-inter) (:text-list `(,str)))) inter) (s-value mouse-inter :start-where `(:list-element-of ,mouse-inter :text-list :type ,opal:multifont-text)) (s-value str :text-width (o-formula (- (gvl :parent :window :parent :width) 10))) (opal:update win1) (opal:update scroll-gadget) (s-value scroll-gadget :width (formula `(+ 1 (gv ',win1 :width)))) (s-value scroll-gadget :height (formula `(- (gv ',win1 :height) Form-Header-Height 2))) #+COMMENT (setq inter (create-instance NIL inter:multifont-text-interactor (:start-where NIL) ;will be started explicitly (:start-event NIL) ;will be started explicitly (:word-wrap-p T) (:main-window win1) ;; window set below (:stop-event :zip) ;never stop (:abort-event NIL) (:running-action #'(lambda(inter obj ev) (setq *Current-Formula-Win* (g-value inter :main-window)) (call-prototype-method inter obj ev))) (:obj-to-change str))) (opal:add-component agg header) (opal:add-component (g-value scroll-gadget :inner-aggregate) str) (s-value win1 :header header) (s-value win1 :inter-windows (list (g-value scroll-gadget :inner-window) (g-value scroll-gadget :clip-window))) (s-value win1 :edit-string str) (s-value win1 :edit-interactor inter) (inter:set-focus focus-inter str) (opal:update win1) (push win1 *All-Windows*) ; so will be deleted on exit win1)) |# (defun New-Formula-Win (left top) ;; Create the main window. (let ((win (create-instance nil inter:interactor-window (:title "C32 Formula Window") (:left left) (:top top) (:width Formula-Win-Width) (:height Formula-Win-height))) (header (create-instance NIL Form-Win-Header)) (top (create-instance nil opal:aggregate)) text1 scroll-win1 focus-inter mouse-inter) (s-value win :aggregate top) (opal:add-component top header) (s-value win :header header) (opal:update win) ;; Create the internal multifont object (setf text1 (create-instance nil opal:multifont-text (:word-wrap-p t) (:auto-scroll-p T) (:lisp-mode-p T) (:match-parens-p T))) (s-value text1 :text-width (o-formula (gvl :scrolling-window :clip-window :width))) (setf scroll-win1 (create-instance nil garnet-gadgets:motif-scrolling-window-with-bars (:left 0) (:top Form-Header-Height) (:width (o-formula (- (gvl :parent-window :width) (* 2 (gvl :border-width))))) (:height (o-formula (- (- (gvl :parent-window :height) Form-Header-Height) (* 2 (gvl :border-width))))) (:total-width (o-formula (+ (gvl :text1 :width) (gvl :text1 :left)) 200)) (:total-height (o-formula (+ (gvl :text1 :top) (gvl :text1 :height)) 200)) (:h-scroll-bar-p nil) (:v-scroll-bar-p t))) (s-value text1 :scrolling-window scroll-win1) (s-value scroll-win1 :parent-window win) (s-value scroll-win1 :text1 text1) (opal:update scroll-win1) (opal:add-component (g-value scroll-win1 :inner-aggregate) text1) ;; Create a focus interactor so that keyboard events may be entered into ;; the text objects. (setf focus-inter (create-instance nil inter:focus-multifont-textinter (:window `(,win ,(g-value scroll-win1 :clip-window) ,(g-value scroll-win1 :inner-window))))) ;; Create a selection interactor to handle mouse events. (setf mouse-inter (create-instance nil inter:selection-interactor (:window `(,win ,(g-value scroll-win1 :clip-window) ,(g-value scroll-win1 :inner-window))) (:focus-interactor focus-inter) #+COMMENT (:text-list `(,text1)) (:start-where `(:in-box ,text1)))) (s-value win :edit-string text1) (s-value win :edit-interactor mouse-inter) (inter:set-focus focus-inter text1) (opal:update win) win)) (defun Do-Form-Cancel (gadget item) (if lapidary-p ;; Specialized version. (lapidary-Do-Form-Cancel gadget item) ;; Stand-alone version. (let ((win (g-value gadget :window))) (setq *Current-Formula-Win* NIL) (s-value win :visible NIL) (push win Formula-Wins-Available)))) (defun Do-Form-Ok (gadget item) (if lapidary-p ;; Specialized version. (lapidary-Do-Form-Ok gadget item) ;; Stand-alone version. (let* ((win (g-value gadget :window)) (obj (g-value win :c32-obj)) (slot (g-value win :c32-slot)) (item (g-value win :c32-item)) (valstr (opal:get-string (g-value win :edit-string))) (*current-formula-obj* obj) (*current-formula-slot* slot)) (setq *Current-Formula-Win* NIL) (multiple-value-bind (val ok-p) (Convert-Str-To-Formula valstr) (case ok-p ((t) (s-value obj slot val) ; formula ;; if formula added or removed, won't necessarily notice (kr:recompute-formula item :formula-p)) (:val (destroy-constraint obj slot) ; regular value (s-value obj slot val)) (:empty (destroy-constraint obj slot)) ; empty value, use old value ((NIL) (return-from Do-Form-Ok))) ; error already reported (s-value win :visible NIL) (push win Formula-Wins-Available))))) #| (defmacro ignore-error-give-message (form error-message &rest body) `(multiple-value-bind (val errorp) #+cmu (ignore-errors ,form) #+lucid (system::ignore-errors ,form) #+allegro (excl::ignore-errors ,form) #-(or cmu lucid allegro) ,form ;; Report error message, if possible. (if (and errorp (not (numberp errorp))) #+(or allegro-v4.0 allegro-v4.1) (C32error (format nil "~A~%~%~A" (format nil (slot-value errorp 'excl::format-control) (slot-value errorp 'excl::format-arguments)) ,error-message)) #-(or allegro-v4.0 allegro-v4.1) (C32error ,error-message)) ;; Now process the body. ,@body)) |# ;;; Returns a pair of values: new-val OK-P. If OK, then new-val is ;;; valid, otherwise it failed to be read. ;;; If , make sure the expression can be evaluated (as part ;;; of a formula). ;;; If is true, the first entry may be followed by a ;;; space and then a value. This can be used, for example, to read a slot ;;; name and value in one operation ;;; (defun C32-Careful-String-Eval (new-str check-formula-p read-value-too) (let ((*package* *c32-package*) (checked-p nil) (formula-message "Error while parsing formula, please re-edit string or press Cancel") final-value formula-value no-error had-value second-value) (multiple-value-bind (val errorp) (gg:Careful-Eval `(read-from-string ,new-str) error-gadget-object (if check-formula-p formula-message "Please enter a correct value")) (multiple-value-setq (final-value no-error) (cond ((not (numberp errorp)) ; invalid (values new-str NIL)) ((and (symbolp val) (boundp val)) ; if a symbol, eval it (values (eval val) T)) ((null val) (values NIL T)) ((and val (listp val) check-formula-p) ;; check and make sure there are no direct references. Only ;; continue if check-for-direct-ref returns nil (if (null (check-for-direct-ref val)) ;; Check and make sure this will work as a formula. (let ((new-formula (formula val))) ; make formula here. (multiple-value-bind (value errorp) (gg:Careful-Eval-Formula-Lambda val error-gadget-object formula-message *current-formula-obj* *current-formula-slot* new-formula T) (setf formula-value value) (setf checked-p T) (if #+(or allegro-V3.1 lucid) errorp #-(or allegro-V3.1 lucid) (typep errorp 'condition) (values val NIL) (values new-formula T)))) ;; else there were direct references that the user ;; wants to edit (values val nil))) ((listp val) (if (fboundp (car val)) ;; Evaluate this as a function application. (multiple-value-bind (value errorp) (gg:Careful-Eval val error-gadget-object (if check-formula-p formula-message "Please enter a correct value")) (if #+(or allegro-V3.1 lucid) (not errorp) #-(or allegro-V3.1 lucid) (not (typep errorp 'condition)) (values value T))) ;; Just return the values (values val T))) (t (values val T)))) (when (and no-error read-value-too (> (length new-str) errorp)) (setf new-str (subseq new-str errorp)) (setf had-value T) (setf second-value (C32-Careful-String-Eval new-str NIL NIL))) (if no-error (let ((result (if *current-formula-obj* (check-slot-type *current-formula-obj* *current-formula-slot* (if checked-p formula-value final-value) NIL) T))) (unless (eq result T) (c32error result) (setf no-error NIL)))) (if had-value (values final-value no-error second-value) (values final-value no-error))))) #| (defun Careful-Read-From-String (new-str check-formula-p read-value-too) (let ((checked-p nil) (formula-message "Error while parsing formula, please re-edit string or press Cancel") final-value formula-value no-error had-value second-value) (ignore-error-give-message (let ((*package* *c32-package*)) (read-from-string new-str)) (if check-formula-p formula-message "Please enter a correct value") (multiple-value-setq (final-value no-error) (cond ((not (numberp errorp)) ; invalid (values new-str NIL)) ((and (symbolp val) (boundp val)) ; if a symbol, eval it (values (eval val) T)) ((null val) (values NIL T)) ((and val (listp val) check-formula-p) ;; check and make sure there are no direct references. Only ;; continue if check-for-direct-ref returns nil (if (null (check-for-direct-ref val)) ;; Check and make sure this will work as a formula. (let ((value val) (new-formula (formula val))) ; make formula here. (ignore-error-give-message ;; Try out the formula without actually installing it. (let ((kr::*schema-self* *current-formula-obj*) (kr::*schema-slot* *current-formula-slot*) (kr::*current-formula* new-formula) (kr::*warning-on-null-link* T)) (setf formula-value (catch 'kr::no-link (eval value))) (setf checked-p T)) formula-message (setf val val) ; suppress compiler error message (if (or (null errorp) (numberp errorp)) (progn (setf checked-p T) (values new-formula T)) (values value NIL)))) ;; else there were direct references that the user ;; wants to edit (values val nil))) ((listp val) (if (fboundp (car val)) ;; Evaluate this as a function application. (ignore-error-give-message (eval val) (if check-formula-p formula-message "Please enter a correct value") (if errorp NIL (values val T))) ;; Just return the values (values val T))) (t (values val T)))) (when (and no-error read-value-too (> (length new-str) errorp)) (setf new-str (subseq new-str errorp)) (setf had-value T) (setf second-value (careful-read-from-string new-str NIL NIL))) (if no-error (unless (opal::legal-type-p *current-formula-obj* *current-formula-slot* (if checked-p formula-value final-value)) (setf no-error NIL))) (if had-value (values final-value no-error second-value) (values final-value no-error))))) |# ;; Returns two values. First is new value. ;; second is control, either T for Formula, :val for reg values, :empty ;; for empty string, or NIL for error. ;; (defun convert-str-to-formula (str) (if (zerop (length str)) ;; then remove the formula (values NIL :empty) ;; otherwise check if legal (multiple-value-bind (expr ok-p) (C32-Careful-String-Eval str T NIL) (if ok-p (if (formula-p expr) (values expr T) (values expr :val)) ;; else failed to read (values NIL NIL))))) (defparameter formula-y-offset 0 "Offset from one formula window to the next") ;;; ***** Main entry point for creating a new formula window ;;; (defun Assign-formula-win (obj slot left top c32item) ;; Do we already have a window displaying this formula? (dolist (w *all-windows*) (when (and (schema-p w) (g-value w :visible) (eq (g-local-value w :c32-obj) obj) (eq (g-local-value w :c32-slot) slot)) (opal:raise-window w) (return-from assign-formula-win w))) (let ((win (pop Formula-Wins-Available)) (orig-form (get-value (g-value c32item :obj) (g-value c32-item :slot))) str) (unless win (incf left formula-win-offset) (incf formula-y-offset 40) (setq win (New-Formula-Win left (+ top formula-y-offset))) (setf formula-win-offset (mod (+ formula-win-offset 40) 160))) (s-value win :c32-item c32item) (s-value win :visible T) (s-value win :c32-obj obj) (s-value win :c32-slot slot) #+COMMENT (let ((Inter (g-value win :edit-interactor)) (inter-windows (g-value win :inter-windows))) (s-value inter :window inter-windows) (inter:start-interactor inter)) (s-value (g-value win :header) :slot slot) (s-value (g-value win :header) :obj obj) (setq str (Get-Formula-String orig-form)) (let ((string (g-value win :edit-string))) (opal:set-text string str) (s-value string :width (g-value string :text-width)) (s-value string :cursor-index (length str))) (setq *Current-Formula-Win* win) (s-value win :copy-edit-string str) win)) ;;; Returns multiple values: (expr formula-p) ;;; (defun Get-Formula-Expr (form) (if (formula-p form) ;; A formula. (values (kr::get-lambda form) T) ;; else not a formula, just use the value (values form NIL))) (defun Get-Formula-String (form) (let ((kr::*print-as-structure* NIL)) (write-to-string (Get-Formula-Expr form) :pretty T))) (defun Insert-Obj-Ref-Into-Formula (to-obj to-slot) (let (ref) (when (and *current-formula-win* to-obj) (setq ref (get-reference-for to-obj to-slot (g-value *current-formula-win* :c32-obj) (g-value *current-formula-win* :c32-slot))) (let ((text-object (g-value *current-formula-win* :edit-string))) (opal:insert-string text-object ref) (opal:go-to-prev-char text-object) (opal:go-to-prev-char text-object))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; *** Top level Routines to add things into the current formula ;;; Called when middle button on a slot ;;; OBSOLETE - no longer called. ;;; (defun Secondary-select-item (inter item) (declare (ignore inter)) (let ((to-obj (g-value item :obj)) (to-slot (g-value item :slot))) (Insert-Obj-Ref-Into-Formula to-obj to-slot))) (defun InsertFunction (gadget sel) (declare (ignore sel)) ;; Make this window the current formula window. (setf *current-formula-win* (g-value gadget :parent :parent :window)) (if (and (boundp 'pop-up-functions) (g-value pop-up-functions :window) (g-value pop-up-functions :window :visible)) (let ((text-object (g-value *current-formula-win* :edit-string)) (val (concatenate 'simple-string "(" (car (g-value pop-up-functions :menu :value)) " ) "))) (opal:insert-string text-object val) (opal:go-to-prev-char text-object) (opal:go-to-prev-char text-object) #+COMMENT (s-value (g-value pop-up-functions :window) :visible NIL)) (gilt:show-in-window pop-up-functions))) ;; Does same thing as middle button, but uses the selected slot, rather than ;; the secondary selection slot. ;; (defun InsertRefFromSpread (gadget item) (declare (Ignore item)) ;; Make this window the current formula window. (setf *current-formula-win* (g-value gadget :parent :parent :window)) (let ((c32item (get-selected-item))) (if c32item (let ((to-obj (g-value c32item :obj)) (to-slot (g-value c32item :slot))) (Insert-Obj-Ref-Into-Formula to-obj to-slot)) ; else nothing selected (inter:beep)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; *** Top level routine to generate references for a slot to another object ;;; to-slot may be NIL if don't know what slot, or T if should use ;;; object itself (defun Get-Reference-For (to-obj to-slot from-obj from-slot) (declare (ignore from-slot)) (let ((list-from (get-all-aggregadgets from-obj)) (list-to (get-all-aggregadgets to-obj)) ref) (when (null to-slot) (setq to-slot "")) (if (setq ref (Check-aggregadget-ref from-obj to-obj list-from list-to)) (if (eq to-slot T) ; reference to the object itself (if (string= ref "") "(gv :SELF)" ; then return SELF (concatenate 'simple-string "(gvl" ref ") ")) ;; else use to-slot of object (concatenate 'simple-string "(gvl" ref " " (Mk-Colon-Str-And-Clip to-slot 999) ") ")) ;; else not in an aggregadget, use direct reference (if (eq to-slot T) ; reference to the object itself (Mk-String to-obj) ; just use object ;; else access the slot of the object (concatenate 'simple-string "(gv " (Mk-String to-obj) " " (Mk-Colon-Str-And-Clip to-slot 999) ") " ))))) ;; returns a list of obj and all :parents of obj (recursively) which are ;; aggregadgets, in reverse order (obj is last in the list) (defun Get-All-Aggregadgets (obj) (let ((p obj) (l (list obj))) (loop (setq p (g-value p :parent)) (if (and p (is-a-p p opal:aggregadget)) (push p l) (return))) ; else exit from loop l)) ;; return the first element that is in both list1 and list2. Unfortunately, ;; intersection is not guaranteed to return the list in order (defun first-same (list1 list2) (dolist (item list1) (when (member item list2) (return item)))) (defun Check-aggregadget-ref (from-obj to-obj list-from list-to) (let (l s1 s2) (cond ((member to-obj list-from) (generate-up-path to-obj list-from)) ((member from-obj list-to) (generate-down-path from-obj list-to to-obj)) ((and (and (> (length list-from) 1) (> (length list-to) 1)) ; otherwise, no aggregadgets (setq l (first-same list-from list-to)) (setq s1 (generate-up-path l list-from)) (setq s2 (generate-down-path l list-to to-obj))) ; may fail (concatenate 'simple-string s1 s2)) (t NIL)))) ; otherwise, not related ;; given the obj is a parent on the list l, generate the correct number of ;; :parents as a string (defun Generate-Up-Path (obj l) (let ((num (- (length l) (position obj l) 1)) (outs "")) (dotimes (i num) (setq outs (concatenate 'simple-string outs " :parent"))) outs)) ;; given that child is a child, and agg is an aggregadget in the list l, find ;; the slots of the aggregadgets that name the children (defun Generate-Down-Path (agg l child) (declare (ignore child)) (let* ((pos (position agg l)) (l (nthcdr (1+ pos) l)) (p agg) (outs "") known-as) (dolist (cur-agg l) (setq known-as (g-value cur-agg :known-as)) (if known-as (progn (unless (eq (g-value p known-as) cur-agg) (c32error (format NIL "object ~s has known-as slot ~s but parent ~s doesn't call it that" cur-agg known-as p)) (return-from generate-down-path NIL)) ; all ok here (setq outs (concatenate 'simple-string outs " :" (symbol-name known-as))) (setq p cur-agg)) ;; else no known-as, so exit (return-from generate-down-path NIL))) outs)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Guess reference slot from an object (defun Guess-Slot-For-Graphical-object (from-obj from-slot to-obj x y) (let* ((from-formula-so-far (g-value *current-formula-win* :edit-string)) (candidate-list (case from-slot (:left 0) (:top 1) ((:width :height :line-style :filling-style :foreground-color :visible :justification :fill-background-p :actual-heightp :draw-function :font :radius) (if (eq from-obj to-obj) ; if same object, error (progn (inter:beep) NIL) from-slot)) ((:x1 :x2) 0) ((:y1 :y2) 1) (:point-list (if (is-a-p to-obj opal:multipoint) :point-list (if (Check-Formula-For-List-even-odd from-formula-so-far) 0 1))) (:string (cond ((eq from-obj to-obj) ; if same object, error (progn (inter:beep) NIL)) ((is-a-p to-obj opal::window) :title) ; else use same slot ((is-a-p to-obj opal:text) :string) (T NIL))) ; otherwise, I don't know! ((:title :icon-title) (cond ((eq from-obj to-obj) ; if same object, use other slot (if (eq from-slot :title) :icon-title :title)) ((is-a-p to-obj opal::window) from-slot) ; else use same slot ((is-a-p to-obj opal:text) :string) (T NIL))) ; otherwise, I don't know! ((:start-where :running-where :feedback-obj :obj-to-change) T) ; for these slots, want object itself (T NIL)))) ; otherwise, don't know which slot to use! (if (numberp candidate-list) ;; have to pick one from the list (lefttop widthheight x1y1 x2y2) (Pick-Complicated-Slot from-obj from-slot to-obj x y candidate-list) ;; else just use the candidate-list candidate-list))) ;; search backwards in the current list in the string to find the ;; number of atoms, and return T if even and NIL if odd (defun Check-Formula-For-List-even-odd (string-obj) (let* ((str (g-value string-obj :string)) (pos (g-value string-obj :cursor-index)) (cnt (search-back-unbalanced-paren str pos))) (if cnt (evenp cnt) T))) (defun search-back-unbalanced-paren (str pos) (let ((paren-cnt 0) (atom-cnt 0) (in-item NIL) indx c) (dotimes (i pos) (setq indx (- pos i 1)) (setq c (elt str indx)) (case c (#\( (if (zerop paren-cnt) (progn (when in-item (incf atom-cnt)) (return-from search-back-unbalanced-paren atom-cnt)) (progn (decf paren-cnt) ))) (#\) (when (zerop paren-cnt) (setq in-item T)) (incf paren-cnt)) ((#\space #\newline) (when (zerop paren-cnt) (when in-item (incf atom-cnt) (setq in-item NIL)))) (T (setq in-item t)))) NIL)) ;;; x-p is 0 if x and 1 if y (defun Pick-Complicated-Slot (from-obj from-slot to-obj x y x-p) (declare (ignore from-obj from-slot)) ;maybe be smarter and use them someday (let ((xory (if (zerop x-p) x y))) (if (or (is-a-p to-obj opal:line) (g-value to-obj :line-p)) (Check-Which-closer xory to-obj (if (zerop x-p) :x1 :y1) (if (zerop x-p) :x2 :y2) NIL) ;; else see which side of object is closer to mouse (Check-Which-Closer xory to-obj (if (zerop x-p) :left :top) (if (zerop x-p) :width :height) T)))) (defmacro swap (a b) `(let ((tmp ,a)) (setq ,a ,b) (setq ,b tmp))) ;; IF wh-p then s2 is a width or height (so must be added to s1) ;; (defun Check-Which-closer (val obj s1 s2 wh-p) (let* ((val1 (g-value obj s1)) (val2 (g-value obj s2))) (when (and (numberp val1)(numberp val2)) (if wh-p (setq val2 (+ val1 val2)) (when (> val1 val2) (swap val1 val2)(swap s1 s2))) (let* ((p1 (floor (- val2 val1) 3)) (v1 (+ val1 p1)) (v2 (- val2 p1))) (cond ((< val v1) s1) ((> val v2) s2) (T NIL)))))) ;;; Point to an object not in the spreadsheet and put it into the formula (defun InsertRefFromMouse (gadget item) (declare (Ignore item)) ;; Make this window the current formula window. (setf *current-formula-win* (g-value gadget :parent :parent :window)) (if (schema-p *current-formula-win*) (Pop-Up-Request-Point-Object #'Insert-Obj-Ref-Into-Formula #'Guess-Slot-For-Graphical-object (g-value *current-formula-win* :c32-obj) (g-value *current-formula-win* :c32-slot)) (inter:beep))) ;; Lapidary interface version. ;; Lapidary tries to copy links as well as the formula; however, Lapidary ;; cannot assure that the links will be copied correctly. For example, ;; if a link has the formula (gvl :parent :frame), Lapidary will copy the ;; formula but it will only work if the object that the formula is copied ;; to has the appropriate structure ;; (defun Start-Copy-Formula (from-c32item to-c32item) (let* ((from-obj (g-value from-c32item :obj)) (from-slot (g-value from-c32item :slot)) (to-obj (g-value to-c32item :obj)) (to-slot (g-value to-c32item :slot)) (links (g-value from-obj :links)) (from-form (get-value from-obj from-slot)) (depends-on (kr::i-depend-on from-obj from-slot)) slot) (multiple-value-bind (expr formula-p) (Get-Formula-Expr from-form) (if formula-p (progn (multiple-value-bind (obj-list slot-list) (recursive-list-find-refs expr NIL NIL) (declare (ignore obj-list)) (multiple-value-bind (from-slots to-slots) (Map-Slots from-slot to-slot slot-list) (Pop-Up-Confirm-Copy-Formula from-obj from-slot to-obj to-slot from-slots to-slots expr to-c32item))) ;; copy any slots that are in from-obj but not in to-obj to ;; to-obj (dolist (obj-slot depends-on) (setf slot (cdr obj-slot)) (when (and (eq (car obj-slot) from-obj) (not (has-slot-p to-obj slot))) ;; copy the value of slot into to-obj (if (formula-p (get-value from-obj slot)) (s-value to-obj slot (copy-formula (get-value from-obj slot))) (s-value to-obj slot (g-value from-obj slot))) ;; if this slot is a link slot, push the link onto ;; to-obj's link list (when (or (member slot links) (member slot '(:left-over :top-over :width-over :height-over :x1-over :y1-over :x2-over :y2-over))) (push slot (g-value to-obj :links)))))) ;; else just copy the value (progn (destroy-constraint to-obj to-slot) ; in case used to be a formula (s-value to-obj to-slot expr) ;; * if formula added or removed, won't necessarily notice (kr:recompute-formula to-c32item :formula-p)))))) #| (defun Start-Copy-Formula (from-c32item to-c32item) (let* ((from-obj (g-value from-c32item :obj)) (from-slot (g-value from-c32item :slot)) (to-obj (g-value to-c32item :obj)) (to-slot (g-value to-c32item :slot)) (from-form (get-value from-obj from-slot))) (multiple-value-bind (expr formula-p) (Get-Formula-Expr from-form) (if formula-p (progn (multiple-value-bind (obj-list slot-list) (recursive-list-find-refs expr NIL NIL) (declare (ignore obj-list)) (multiple-value-bind (from-slots to-slots) (Map-Slots from-slot to-slot slot-list) (Pop-Up-Confirm-Copy-Formula from-obj from-slot to-obj to-slot from-slots to-slots expr to-c32item)))) ;; else just copy the value (progn (destroy-constraint to-obj to-slot) ; in case used to be a formula (s-value to-obj to-slot expr) ;; * if formula added or removed, won't necessarily notice (kr:recompute-formula to-c32item :formula-p)))))) |# (defconstant x-type-slots '(:left :width :x1 :x2)) (defconstant y-type-slots '(:top :height :y1 :y2)) ;; Maps slots for copy from x to y type or visa-versa (defun map-slots (from-slot to-slot slot-list) (if (or (and (member from-slot x-type-slots) (member to-slot y-type-slots)) (and (member to-slot x-type-slots) (member from-slot y-type-slots))) ;; then do mapping (let (from-slots to-slots pos) (dolist (s slot-list) (cond ((setq pos (position s x-type-slots)) (push s from-slots) (push (elt y-type-slots pos) to-slots)) ((setq pos (position s y-type-slots)) (push s from-slots) (push (elt x-type-slots pos) to-slots)) (T NIL))) (values from-slots to-slots)) ;; else return NILs (values NIL NIL))) ;; call this to actually copy the formula and insert it into the ;; object when the user hits OK in the dialog box (defun copy-and-set-formula (old-expr to-obj to-slot old-slot-list new-slot-list to-c32item) (let ((copy-old-expr (copy-tree old-expr))) (when old-slot-list (recursive-subst-obj-slot copy-old-expr NIL NIL old-slot-list new-slot-list NIL)) (set-formula-val copy-old-expr to-obj to-slot to-c32item))) ;;; set the value of the slot to the new expression, which should be ;;; put as a formula. May go into the formula window if there, or else into ;;; the object directly. ;;; (defun set-formula-val (expr obj slot c32-item) (if (and *current-formula-win* (g-value *current-formula-win* :visible) (eq obj (g-value *current-formula-win* :c32-obj)) (eq slot (g-value *current-formula-win* :c32-slot))) ;; then showing this formula (let ((obj (g-value *current-formula-win* :edit-string))) (opal:set-text obj (write-to-string expr :pretty T)) (s-value obj :cursor-index 0)) ;; else not showing it (let ((newform (formula expr))) (s-value obj slot newform) (kr:recompute-formula c32-item :formula-p)))) (defun recursive-list-find-refs (expr obj-list slot-list) (if (or (eq (car expr) 'gv)(eq (car expr) 'gvl)) (let ((objref (cdr (butlast expr)))) (when objref (pushnew objref obj-list :test #'equal) (pushnew (car (last expr)) slot-list)) (values obj-list slot-list)) ;; else look for lists in expr (progn (dolist (e expr) (when (listp e) (multiple-value-setq (obj-list slot-list) (recursive-list-find-refs e obj-list slot-list)))) (values obj-list slot-list)))) ;; substitutes objects in new-obj for the corresponding objects in old-obj ;; (objects in old-obj-list should be in a list: ( (:zup :zup) (obj) ) ;; new-obj-list is just a one-level list: (zupzup newobj) ;; similarly for slots, but both are one-level lists. ;; Modifies obj-ref-list-so-far to be the right way to reference the ;; objects. Should be a list of the same length as old-obj-list (defun recursive-subst-obj-slot (expr old-obj-list new-obj-list old-slot-list new-slot-list obj-ref-list-so-far) (if (or (eq (car expr) 'gv)(eq (car expr) 'gvl)) ; then check if need to substitute (progn ;; first do obj (when (and old-obj-list (> (length expr) 2)) (let* ((old-obj (cdr (butlast expr))) (indx (position old-obj old-obj-list :test #'equal)) new-obj) (when indx (when obj-ref-list-so-far (rplaca (nthcdr indx obj-ref-list-so-far) (gen-good-obj-ref expr))) (setq new-obj (nth indx new-obj-list)) (when (eq (car expr) 'gvl) ;; change to gv (rplaca expr 'gv)) (rplaca (cdr expr) new-obj) (rplacd (cdr expr) (last expr))))) ;; now test slot (when old-slot-list (let* ((old-slot (car (last expr))) (indx (position old-slot old-slot-list)) new-slot) (when indx (setq new-slot (nth indx new-slot-list)) (rplaca (last expr) new-slot))))) ;; otherwise, not a gv statement; look for lists in expr (progn (dolist (e expr) (when (listp e) (recursive-subst-obj-slot e old-obj-list new-obj-list old-slot-list new-slot-list obj-ref-list-so-far)))))) ;; returns a good reference for the object in the expression (defun gen-good-obj-ref (expr) (if (eq (car expr) 'gv) (if (> (length expr) 3) (butlast expr) (second expr)) (butlast expr))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun Insert-Func-Into-Form (gadget val) (if (and *current-formula-win* (g-value *current-formula-win* :visible)) (let ((text-object (g-value *current-formula-win* :edit-string))) (setq val (car (g-value gadget :value))) (setq val (concatenate 'simple-string "(" val " ) ")) (opal:insert-string text-object val) (opal:go-to-prev-char text-object) (opal:go-to-prev-char text-object)) ;; else no window (inter:beep)))