REBOL

 

Form Designer - Source Code

Design and run simple forms with a WYSIWYG editor.
Author: Ashley
File size: 22K
Return to index

 

REBOL [
    Title:      "RebForms"
    Author:     "Ashley Truter <ashley@dobeash.com>"
    Purpose:    "REBOL/View Form Designer."
    Date:       26-Mar-2004
    Version:    1.0
    Copyright:  "©2004 Dobeash Investments Pty Ltd"
    Licence:    "Free for both commercial and non-commercial use."
]

;   ----------------------------------------
;   VID Patches
;   ----------------------------------------

;   colour of text-list/picked
;   http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlNJXK

svvc/field-select: sky

;   [view] changing window title. -- vid 1.3 --
;   http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlQGLQ

set-title: func [
    "Sets title bar of window"
    face [object!] "Window dialog face"
    title [string!] "Window bar title"
][
    face/text: title
    face/changes: 'text
    show face
]

;   Area bug?
;   http://www.rebol.org/cgi-bin/cgiwrap/rebol/ml-display-thread.r?m=rmlMZJJ

stylize/master [
    area:   area wrap with [insert tail init [para: make para []]]
    field:  field middle with [insert tail init [para: make para []]]
    info:   info middle with [feel: none]
    label:  label middle with [feel: none]
    text:   text middle with [feel: none]
]

;   simple dialog wrapper for view, center-face, layout

dialog: function [
    "Displays a window face with a pane built from style description dialect."
    title [string!] "Window bar title"
    specs [block!] "Dialect block of styles, attributes, and layouts"
][
    face
][
    either empty? system/view/screen-face/pane [
        view/new/title center-face layout specs title
    ][
        face: last system/view/screen-face/pane
        view/new/title/options/offset layout specs title compose [
            parent (first system/view/screen-face/pane)
        ] face/offset + 0x28
    ]
]

;   ----------------------------------------
;   Styles
;   ----------------------------------------

svsf: system/view/screen-face

to-width: func [x [integer!]] [
    GUI/element/x + GUI/spacing/x * x - GUI/spacing/x
]

to-size: func [size [pair!]] [
    as-pair GUI/element/x + GUI/spacing/x * size/x - GUI/spacing/x GUI/element/y + GUI/spacing/y * size/y - GUI/spacing/y
]

stylize/master [

    field:      field       edge [size: 1x1 color: black effect: none] [trim face/text show face]
    label:      label       black font [shadow: none]
    title:      txt         font [valign: 'middle align: 'center]

    check:      face with [
        size: 24x24
        color: white
        font: [name: "Wingdings" align: 'center valign: 'bottom style: 'bold shadow: none colors: reduce [leaf leaf]]
        image: effects: para: none
        edge: [size: 1x1 color: black effect: none]
        feel: make svvf/toggle [
            redraw: func [face act pos] [
                face/text: either face/state [face/texts/2] [face/texts/1]
            ]
        ]
        flags: [toggle]
        init: [
            edge: make edge []
            font: make font []
            font/size: to integer! size/y * .75
            font/color: first font/colors
            texts: copy ["" "ü"]
        ]
    ]

    ;   file: %dropdown.r
    ;   date: 20-Feb-2004
    ;   title: "VID Dropdown"
    ;   author: "Ammon Johnson"

    drop-down: face with [
        size: 100x24
        color: white
        get-selected: lay-options: options: unview-options: none
        words: [
            data [new/data: first next args next args]
        ]
        resize: func [new /arrow arr-size] [
            size: new
            if arrow [pane/2/size/x: arr-size]
            pane/1/size: new - as-pair pane/2/size/x 0
            pane/2/size/y: new/y
            pane/2/offset: new - pane/2/size
            options/size/x: size/x
            options/sub-area/size/x: pane/1/size/x
            options/sld/size/x: pane/2/size/x + pane/2/edge/size/x
            options/sld/offset/x: pane/1/size/x
            lay-options/size/x: size/x
            options/data: data ; to work with VID 1.3
        ]
        init: [
            lay-options: layout [
                origin 0x0
                options: text-list #"^(ESC)" (size + 0x150 )[
                    if not empty? options/picked [
                        pane/1/text: copy first options/picked ; added ESC and copy
                    ]
                    show pane/1
                    unview/only lay-options
                    remove-event-func :unview-options
                    action pane/1 pane/1/text
                ]
            ]
            lay-options/options: reduce ['no-border 'no-title 'parent self]
            unview-options: func [f "face" e "event"] [
                if all [e/type = 'inactive e/face = lay-options] [ 
                    unview/only lay-options
                    remove-event-func :unview-options
                ]
                e
            ]
            get-selected: does[
                options/lines: data
                options/update
                lay-options/offset: (screen-offset? pane/1) + as-pair 0 size/y
                insert-event-func :unview-options
                view/new lay-options
            ]
            pane: reduce [
                ;   added color support
                make-face/spec 'field compose/deep [
                    color: (color)
                    colors: [(color) (color)]
                    edge: none
                ]
                make-face/spec 'arrow [
                    size: 16x16
                    data: 'down
                    action: [get-selected]
                ]
            ]
            if text [pane/1/text: text]
            if none? data [data: texts]
            if none? data [data: copy []]
;           if not found? find data text [insert data text text: none]
            resize size
        ]
    ]
]

set-styles: does [
    ;   Element sizes
    stylize/master [
        ;   Standard
        btn:            btn         to-size 1x1 font-size to-integer GUI/fontsize
        sbtn:           btn         GUI/element / 2x1 font-size to-integer GUI/fontsize
        bar:            bar         to-width 1
        field:          field       to-size 1x1 font-size GUI/fontsize GUI/Backcolor GUI/Backcolor
        info:           info        to-size 1x1 font-size GUI/fontsize
        label:          label       to-size 1x1 font-size GUI/fontsize
        text:           text        to-size 1x1 font-size GUI/fontsize
        title:          title       to-size 1x1 font-size GUI/fontsize navy pewter bold
        tog:            tog         to-size 1x1 font-size to-integer GUI/fontsize
        stog:           tog         GUI/element / 2x1 font-size to-integer GUI/fontsize
        ;   Base action buttons
        btn-yes:        btn         "Yes"
        btn-no:         btn         "No"        [unview/only face/parent-face/self]
        btn-ok:         btn         "OK"
        btn-cancel:     btn         "Cancel"    [unview/only face/parent-face/self]
        btn-close:      btn         "Close"     [unview/only face/parent-face/self]
        btn-exit:       btn         "Exit"      [quit]
        ;   Special
        area:           area        to-size 2x2 font-size GUI/fontsize GUI/Backcolor GUI/Backcolor
        check-line:     check-line  to-size 1x1
        radio-line:     radio-line  to-size 1x1
        check:          check       as-pair GUI/element/y GUI/element/y
        radio:          radio       as-pair GUI/element/y GUI/element/y
        txt:            txt         font-size GUI/fontsize
    ]
]

GUI: context [

    backcolor:      white
    fontsize:       none
    element:        none
    spacing:        8x8
    margin:         20x20
    imagesize:      none

    x1: x2: x3: x4: x5: x6: x7: x8: none

    get-text-size: has [t][
        layout [t: txt font-size fontsize "MMMMMMMM"]
        size-text t
    ]

    set-fontsize: func [size [integer!]][
        if size <> fontsize [
            if odd? size [size: size - 1]
            fontsize:       max 12 min size 36
            ;   Set element size to an even pair
            element:        8x4 + get-text-size
            element/y:      to-integer element/y * 1.2  ;   20% vertical margin
            element/y:      max element/y 22            ;   minimum y of 22 (btn bitmap height)
            if odd? element/x [element/x: element/x + 1]
            if odd? element/y [element/y: element/y + 1]
            ;   set rest
            spacing: as-pair fontsize / 2 fontsize / 2
            margin: as-pair fontsize fontsize
            x1: to-size 1x1
            x2: to-size 2x1
            x3: to-size 3x1
            x4: to-size 4x1
            x5: to-size 5x1
            x6: to-size 6x1
            x7: to-size 7x1
            x8: to-size 8x1
            set-styles
        ]
    ]
]

GUI/set-fontsize 12 ;to integer! svsf/size/x / 64

;   ----------------------------------------
;   Main Program Code
;   ----------------------------------------

designer: context [
    main-out: none
    ;   align
    radio-align: 'lm
    ;   cell size
    cell-size: 16x16
    ;   pointer to current object
    nub-obj: none
    ;   active object
    nub-face: make face [
        offset: 0x0 size: 0x0
        edge: make edge [color: 255.0.0 effect: 'nubs size: 4x4]
        text: color: font: para: data: none
        feel: make feel [
            engage: func [f a e] [
                if all [empty? p/move-list data][data/feel/engage data a e]
            ]
        ]
    ]
    ;   update after move / resize
    update-nubs: func [f] [
        nub-face/offset: f/offset ; - 3x3
        nub-face/size: f/size ;+ 6x6
    ]
    ;   default feel for designer objects
    nub-feel: [
        engage: func [f a e][
            if find [over away] a [
                either f/data [
                    f/offset: f/offset + e/offset - f/data
                    f/offset: min max 0x0 f/offset / cell-size * cell-size cell-size * 23x31
                    f/offset: confine f/offset f/size 0x0 cell-size * 24x32
                ][
                    f/size: (cell-size / 2) + e/offset / cell-size * cell-size
                    if f/size/x < cell-size/x [f/size/x: cell-size/x]
                    if f/size/y < cell-size/y [f/size/y: cell-size/y]
                    if (f/offset/x + f/size/x) > (cell-size/x * 24) [f/size/x: (cell-size/x * 24) - f/offset/x]
                    if (f/offset/y + f/size/y) > (cell-size/y * 32) [f/size/y: (cell-size/y * 32) - f/offset/y]
                ]
                update-nubs f
                show [f nub-face]
            ]
            if find [down alt-down] a [
                nub-obj: f
                f/data: e/offset - (cell-size / 2)
                update-nubs f
                nub-face/data: f
                show f/parent-face
                ;   set properties
                cell-type/text: form f/var
                cell-name/text: form f/text
                cell-link/text: form f/text
                cell-edge/data: either zero? f/edge/size [false][true]
                cell-color/data: either f/color = silver [true][false]
                hide b
                switch/default f/var [
                    Field [
                        hide [cell-name cell-edge cell-color lt ct rt lm cm rm lb cb rb]
                        show [cell-type cell-link cell-edge cell-color]
                    ]
                    Check [
                        hide [cell-link cell-name cell-edge cell-color lt ct rt lm cm rm lb cb rb]
                        show cell-type
                    ]
                    Diagram [
                        hide [cell-name cell-edge cell-color lt ct rt lm cm rm lb cb rb]
                        show [cell-type cell-link]
                    ]
                ][
                    ;   hide previous radio selection
                    designer/:radio-align/data: false
                    ;   show current radio selection
                    radio-align: to-word join first form f/font/align first form f/font/valign
                    designer/:radio-align/data: true
                    ;   Heading / Label
                    hide cell-link
                    show [cell-type cell-name cell-edge cell-color lt ct rt lm cm rm lb cb rb]
                ]
                ;   don't allow resize on check / diagram
                if all [a = 'alt-down not find [Check Diagram] f/var][f/data: none]
            ]
        ]
    ]

    get-offset: has [offset][
        offset: negate cell-size/y
        foreach obj next p/pane [
            offset: max offset obj/offset/y
        ]
        offset: min offset 30 * cell-size/y
        as-pair 0 (offset + cell-size/y) / cell-size/y
    ]

    make-heading: func [/load blk [block!]][
        if not load [
            blk: copy []
            insert blk reduce [
                get-offset
                4x1
                "Heading"
                silver
                0x0
                'left
                'middle
            ]
        ]
        make face [
            offset: blk/1 * cell-size
            size:   blk/2 * cell-size
            text:   blk/3
            color:  blk/4
            edge:   make edge [size: blk/5 color: black]
            font:   make font [align: blk/6 valign: blk/7 size: cell-size/x - 4 style: 'bold]
            para:   none
            feel:   make feel nub-feel
            var:    'Heading
        ]
    ]

    make-label: func [/load blk [block!]][
        if not load [
            blk: copy []
            insert blk reduce [
                get-offset
                3x1
                "Label"
                white
                1x1
                'left
                'middle
            ]
        ]
        make face [
            offset: blk/1 * cell-size
            size:   blk/2 * cell-size
            text:   blk/3
            color:  blk/4
            edge:   make edge [size: blk/5 color: black]
            font:   make font [align: blk/6 valign: blk/7 size: cell-size/x - 6]
            para:   none
            feel:   make feel nub-feel
            var:    'Label
        ]
    ]

    make-field: func [/load blk [block!]][
        if not load [
            blk: copy []
            insert blk reduce [
                get-offset
                3x1
                ""
                white
                1x1
                'center
                'middle
            ]
        ]
        make face [
            offset: blk/1 * cell-size
            size:   blk/2 * cell-size
            text:   blk/3
            color:  blk/4
            edge:   make edge [size: blk/5 color: black]
            font:   make font [align: blk/6 valign: blk/7 size: 10 color: blue style: 'underline]
            para:   none
            feel:   make feel nub-feel
            var:    'Field
        ]
    ]

    make-check: func [/load blk [block!]][
        if not load [
            blk: copy []
            insert blk reduce [
                get-offset
                1x1
                "ü"
                white
                1x1
                'center
                'bottom
            ]
        ]
        make face [
            offset: blk/1 * cell-size
            size:   blk/2 * cell-size
            text:   blk/3
            color:  blk/4
            edge:   make edge [size: blk/5 color: black]
            font:   make font [align: blk/6 valign: blk/7 size: cell-size/x - 4 color: leaf name: "Wingdings" style: 'bold]
            para:   none
            feel:   make feel nub-feel
            var:    'Check
        ]
    ]

    make-diagram: func [/load blk [block!]][
        if not load [
            blk: copy []
            insert blk reduce [
                get-offset
                2x1
                ""
                white
                0x0
                'center
                'middle
            ]
        ]
        make face [
            offset: blk/1 * cell-size
            size:   blk/2 * cell-size
            text:   blk/3
            color:  blk/4
            edge:   make edge [size: blk/5 color: black]
            font:   make font [align: blk/6 valign: blk/7 size: 10 color: blue style: 'underline]
            para:   none
            feel:   make feel nub-feel
            var:    'Diagram
            image:  btn-up.png
            effect: compose [extend 22 colorize 255.205.40]
        ]
    ]

    lt: ct: rt: lm: cm: rm: lb: cb: rb: none
    b: p: cell-type: cell-name: cell-link: cell-color: cell-edge: none

    hide-nub: does [
        nub-face/offset: nub-face/size: 0x0
        show [b p]
    ]

    show-request-file: function [type [string!]][file][
        if file: switch type [
            "Open"  [request-file/title/filter/keep/only "Open Form" "" "*.frm"]
            "New"   [request-file/title/filter/keep/only/file "New Form" "" "*.frm" "new.frm"]
            "Save"  [request-file/title/filter/keep/only/file/save "Save Form" "" "*.frm" "new.frm"]
        ][
            if %.frm <> suffix? file [file: join file %.frm]
            set-title main-out rejoin ["Designer [" to-local-file file "]"]
        ]
        file
    ]

    run-form: function [file][to-size page type offset size text color edge align valign][
        page: copy [origin 16 space 0 backcolor white]
        to-size: func [size][
            8x8 + cell-size * size - 4x4
        ]
        foreach blk load/all file [
            set [type offset size text color edge align valign] blk
            insert tail page compose [at (8x8 + cell-size * offset + 16x16)]
            insert tail page switch type [
                Heading [compose/deep [label (to-size size) (text) black (color) (align) (valign) edge [size: (edge) color: black]]]
                Label   [compose/deep [text (to-size size) (text) black (color) (align) (valign) edge [size: (edge) color: black]]]
                Field   [
                    either size/y > 1 [
                        compose/deep [area (to-size size) (color) (color) edge [size: (edge) color: black]]
                    ][
                        either empty? text [
                            compose/deep [field (to-size size) (color) (color) edge [size: (edge) color: black]]
                        ][
                            compose/deep [drop-down (to-size size) (color) edge [size: (edge) color: black effect: none] data [(read/lines to-file text)]]
                        ]
                    ]
                ]
                Check   ['check]
                Diagram [
                    either empty? text [
                        compose [btn (to-size size) "-"]
                    ][
                        compose/deep [
                            btn (to-size size) (text) [dialog (text) [origin 0 space 0 image (to-file text)]]
                        ]
                    ]
                ]
            ]
        ]
        dialog "Run" page
    ]

    show-form: has [col row here file blk save-btn][
        file: none
        main-out: dialog "Designer" [
            across origin 8x8
            ;   actions
            sbtn "New"  [
                if file: show-request-file face/text [
                    insert clear p/pane nub-face
                    hide-nub
                ]
            ]
            sbtn "Open" [
                file: show-request-file face/text
                if all [file exists? file][
                    insert clear p/pane nub-face
                    ;   back tail ensures nub-face is last (on top)
                    ;   next blk ensures var is not passed
                    foreach blk load/all file [
                        switch first blk [
                            Heading [insert back tail p/pane make-heading/load next blk]
                            Label   [insert back tail p/pane make-label/load next blk]
                            Field   [insert back tail p/pane make-field/load next blk]
                            Check   [insert back tail p/pane make-check/load next blk]
                            Diagram [insert back tail p/pane make-diagram/load next blk]
                        ]
                    ]
                    hide-nub
                ]
            ]
            col: at save-btn: sbtn "Save"   [
                if (length? p/pane) = 1 [
                    alert rejoin ["Nothing to " lowercase face/text "."]
                    return
                ]
                if all [not file not file: show-request-file face/text][
                    alert "Form not saved."
                    return
                ]
                blk: copy []
                ;   skip first object (nub)
                foreach obj p/pane [
                    ;   type
                    ;   offset
                    ;   size
                    ;   text
                    ;   color
                    ;   edge
                    ;   align
                    ;   valign
                    if obj/text [
                        insert/only tail blk reduce [
                            obj/var
                            obj/offset / cell-size
                            obj/size / cell-size
                            obj/text
                            obj/color
                            obj/edge/size
                            obj/font/align
                            obj/font/valign
                        ]
                    ]
                ]
                ;   can't use save as lines indent
                write file trim/lines mold/only blk
            ]
            sbtn "Undo" [
                if (length? p/pane) > 1 [
                    remove back back tail p/pane
                    hide-nub
                ]
            ]
            sbtn "Run" [
                save-btn/action face 0
                if all [(length? p/pane) > 1 file exists? file][
                    run-form file
                ]
            ]
            sbtn "Help" [
                dialog "Help" [
                    image svv/image-stock/help
                    label GUI/x6 "Introduction"
                    txt to-width 6 "This script, extracted from a larger commercial application, allows you to create, save and run simple forms without actually doing anything with the data."
                    label GUI/x6 "Placing / sizing form elements"
                    txt to-width 6 "Clicking a sky colored button will place the corresponding element on the form. Move the element by left-mouse dragging it, or resize by right-mouse drag."
                    label GUI/x6 "Moving a group of elements"
                    txt to-width 6 "Right-mouse drag on the grid to select a number of elements to group. Left-mouse drag outside the grouped elements to move them."
                    label GUI/x6 "Element properties"
                    txt to-width 6 "Clicking a form element displays its modifiable properties to the left of the main grid. The top label indicates the type of element, while a field below this (for Heading / Label elements) allows the element text to be changed. Field / Image elements have a button instead that allows you to assign a list of values (for a Field) or an image (for an Image)."
                    label GUI/x6 "Running your form"
                    txt to-width 6 "Clicking 'Run' will run your form, allowing you to enter data into fields and enabling a pick-list if a list of values was specified. Clicking a check box will toggle its state, while clicking a diagram will display the associated image, if any."
                    txt ""
                    txt to-width 6 "©2004 Dobeash Investments Pty Ltd"
                    bar to-width 6
                    btn-close
                ]
            ]
            sbtn sky "Head"     [insert back tail p/pane make-heading show p]
            sbtn sky "Label"    [insert back tail p/pane make-label show p]
            sbtn sky "Field"    [insert back tail p/pane make-field show p]
            sbtn sky "Check"    [insert back tail p/pane make-check show p]
            sbtn sky "Image"    [insert back tail p/pane make-diagram show p]
            below
            ;   properties
            row: at cell-type: text "Heading" bold
            bar
            here: at cell-name: field [
                nub-obj/text: copy face/text
                show nub-obj
            ]
            at here cell-link: btn white [
                if face/data: either nub-obj/var = 'Field [
                    request-file/title/filter/keep/only "Text List" "" "*.txt"
                ][
                    request-file/title/filter/keep/only "Image File" "" ["*.png" "*.jpg" "*.bmp" "*.gif"]
                ][
                    face/text: form last split-path face/data
                    nub-obj/text: copy face/text
                    show nub-obj
                ]
            ]
            cell-edge: check-line "Border" [
                nub-obj/edge/size: either zero? nub-obj/edge/size [1x1][0x0]
                show nub-obj
            ]
            cell-color: check-line "Fill" [
                nub-obj/color: either nub-obj/color = white [silver][white]
                show nub-obj
            ]
            bar
            across
            space 0x0
            pad 12
            lt: radio [radio-align: face/var nub-obj/font/align: 'left nub-obj/font/valign: 'top show nub-obj]
            ct: radio [radio-align: face/var nub-obj/font/align: 'center nub-obj/font/valign: 'top show nub-obj]
            rt: radio [radio-align: face/var nub-obj/font/align: 'right nub-obj/font/valign: 'top show nub-obj]
            return
            pad 12
            lm: radio [radio-align: face/var nub-obj/font/align: 'left nub-obj/font/valign: 'middle show nub-obj]
            cm: radio [radio-align: face/var nub-obj/font/align: 'center nub-obj/font/valign: 'middle show nub-obj]
            rm: radio [radio-align: face/var nub-obj/font/align: 'right nub-obj/font/valign: 'middle show nub-obj]
            return
            pad 12
            lb: radio [radio-align: face/var nub-obj/font/align: 'left nub-obj/font/valign: 'bottom show nub-obj]
            cb: radio [radio-align: face/var nub-obj/font/align: 'center nub-obj/font/valign: 'bottom show nub-obj]
            rb: radio [radio-align: face/var nub-obj/font/align: 'right nub-obj/font/valign: 'bottom show nub-obj]
            ;   quick hide
            at row b: box svv/vid-face/color to-size 1x8
            ;   page
            at as-pair first col second row
            image as-pair cell-size/y * 2 cell-size/y * 34 + 1 make image! layout [
                backdrop effect [gradient 1x0 20.20.20 250.240.230 luma 60]
            ]
            ;   main form design grid with rubber-band feel
            p: box edge [size: cell-size color: white] cell-size * 26x34 + 1x1 white effect compose [grid (cell-size) (cell-size) (sky)] with [
                move-list: copy []
                old-offset: none
                event-offset: none
                pane: copy []
                feel: make feel [
                    engage: func [f a e][
                        if find [over away] a [
                            event-offset: (e/offset + (cell-size / 2)) / cell-size * cell-size
                            either empty? move-list [
                                nub-face/size: max cell-size absolute event-offset - nub-face/data
                                nub-face/offset: min nub-face/data event-offset
                            ][
                                old-offset: nub-face/offset
                                nub-face/offset: nub-face/offset + event-offset - nub-face/data
                                nub-face/offset: confine nub-face/offset nub-face/size 0x0 cell-size * 24x32
                                ;   skip rubber-band
                                foreach pos move-list [
                                    pane/:pos/offset: pane/:pos/offset + nub-face/offset - old-offset
                                ]
                                nub-face/data: event-offset
                            ]
                            show p
                        ]
                        if a = 'alt-down [
                            clear move-list
                            nub-face/offset: nub-face/data: e/offset / cell-size * cell-size
                            nub-face/size: cell-size
                            show p
                        ]
                        if a = 'down [
                            nub-face/data: e/offset / cell-size * cell-size
                            if empty? move-list [nub-face/offset: nub-face/data]
                            show p
                        ]
                        if a = 'alt-up [
                            clear move-list
                            repeat pos (length? p/pane) - 1 [
                                if all [
                                    inside? nub-face/offset + nub-face/size pane/:pos/offset
                                    inside? nub-face/offset + nub-face/size + 1x1 pane/:pos/offset + pane/:pos/size
                                    inside? pane/:pos/offset + 1x1 nub-face/offset
                                ][insert tail move-list pos]
                            ]
                            if empty? move-list [hide-nub]
                        ]
                        if a = 'up [clear move-list hide-nub]
                    ]
                ]
            ]
            do [insert p/pane nub-face]
        ]
    ]
]

designer/show-form
do-events
quit