root/rebgui-edit.r

Revision 117, 15.4 kB (checked in by ashley, 6 weeks ago)

Fixed remaining look & feel issues
Updated RebDOC.r and tour.r

Line 
1REBOL [
2        Title:          "RebGUI edit feel"
3        Owner:          "Ashley G. Trüter"
4        Purpose:        "Edit support for RebGUI widgets."
5        Acknowledgements: {
6                Edit functionality based on the work of Carl Sassenrath (%view-edit.r SDK source)
7                Undo / Redo based on the work of Romano Paolo Tenca http://www.rebol.it/~romano/edit-text-undo.txt
8                Soundex function derived from Allen Kamp's http:ne//www.rebol.org/library/scripts-download/soundex.r
9                Dictionaries sourced from AbiWord http://www.abisource.com/downloads/dictionaries/Windows/
10                Spell-check based on code in this thread: http://www.rebol.org/ml-display-thread.r?m=rmlNYQC
11        }
12        History: {
13                25      Reinstated rev#19-20 changes lost in rev#21
14                26      Added Ctrl-Enter hard tab support
15                27      process-keystroke fix (Volker)
16                39      Spell-check restores focal-face
17                48      Incorporated Cyphre's changes
18                49      Added support for new spinner widget
19                50      alt-down support for area (Graham)
20                51      Added references to behaviors/*
21                52      Added action-on-tab (Graham)
22                58      tab-on-enter now fires after next-field calc (to avoid conflict with show-focus)
23                        into-widget now handles tab-panel correctly
24                65      Cleaned up comments
25                        Spell-check creates %dictionary/ if not present
26                66      Renamed focus to show-focus and moved to %rebgui-functions.r
27                72      spellcheck moved to %rebgui-functions.r
28                78      Replaced soundex with new language-neutral algorithm
29                81      Wrapped current-word and edit-text caret handling in a gui-error/continue block
30                88      Fixed nasty long-standing focus bug in feel/engage/down (related to changing focus with mouse)
31                93      Added on-edit handler
32                94      Changed on-alt-click to trigger on 'alt-up
33        }
34]
35
36edit: make object! [
37
38        siblings:       none
39        caret:          none
40
41        letter:         make bitset! [#"A" - #"Z" #"a" - #"z" #"'"]
42        capital:        make bitset! [#"A" - #"Z"]
43        other:          negate letter
44
45        ;
46        ;       --- Spellcheck ---
47        ;
48
49        edits: make function! [
50                words [block!]
51                /local result ln w
52        ][
53                result: copy []
54                foreach word words [
55                        ; deletion
56                        repeat n ln: length? word [
57                                insert tail result head remove at copy word n
58                        ]
59                        ; transposition
60                        repeat n ln - 1 [
61                                insert tail result head change change at copy word n pick word n + 1 pick word n
62                        ]
63                        foreach ch "abcdefghijklmnopqrstuvwxyz" [
64                                ; alteration
65                                repeat n ln [
66                                        poke w: copy word n ch
67                                        insert tail result w
68                                ]
69                                ; insertion
70                                repeat n ln + 1 [
71                                        insert tail result head insert at copy word n ch
72                                ]
73                        ]
74                ]
75                result
76        ]
77
78        lookup-word: make function! [
79                word [string!]
80                /local result
81        ][
82                any [
83                        not empty? result: intersect locale*/dict make hash! word: reduce [word]
84                        not empty? result: intersect locale*/dict make hash! edits word
85                        result: word
86                ]
87                sort result
88        ]
89
90        ;
91        ;       --- Edit ---
92        ;
93
94        insert?: true
95
96        keymap: [
97                #"^H" back-char
98                #"^~" del-char
99                #"^M" enter
100                #"^A" all-text
101                #"^C" copy-text
102                #"^X" cut-text
103                #"^V" paste-text
104                #"^T" clear-tail
105                #"^Z" undo
106                #"^Y" redo
107                #"^[" undo-all
108                #"^S" spellcheck
109                #"^/" ctrl-enter
110        ]
111
112        ;       Text highlight functions (but, do not reshow the face)
113
114        hilight-text: make function! [start end][
115                view*/highlight-start: start
116                view*/highlight-end: end
117        ]
118
119        hilight-all: make function! [face] [
120                either empty? face/text [unlight-text] [
121                        view*/highlight-start: head face/text
122                        view*/highlight-end: tail face/text
123                ]
124        ]
125
126        unlight-text: make function! [] [
127                view*/highlight-start: view*/highlight-end: none
128        ]
129
130        hilight?: make function! [] [
131                all [
132                        object? view*/focal-face
133                        string? view*/highlight-start
134                        string? view*/highlight-end
135                        not zero? offset? view*/highlight-end view*/highlight-start
136                ]
137        ]
138
139        hilight-range?: make function! [/local start end] [
140                start: view*/highlight-start
141                end: view*/highlight-end
142                if negative? offset? start end [start: end end: view*/highlight-start]
143                reduce [start end]
144        ]
145
146        ;       Text focus functions
147
148        tabbed?: make function! [       ; Returns TRUE if a widget can be tabbed to.
149                face [object!]
150        ] [
151                all [
152                        face/show?
153                        find behaviors/tabbed face/type
154                        not find face/options 'info
155                        face
156                ]
157        ]
158
159        cyclic?: make function! [       ; Returns TRUE if a face sets up it's own closed tab cycle.
160                face [object!]
161        ] [
162                all [find behaviors/cyclic face/type face]
163        ]
164
165        unfocus: make function! [/local face] [
166                if face: view*/focal-face [
167                        if all [face/type <> 'face get in face/action 'on-unfocus] [
168                                unless face/action/on-unfocus face [return false]
169                        ]
170                        all [
171                                view*/caret
172                                in face 'caret
173                                face/caret: index? view*/caret
174                        ]
175                        all [
176                                face/type = 'button
177                                face/feel/over face false none
178                        ]
179                ]
180                view*/focal-face: view*/caret: none
181                unlight-text
182                all [face show face]
183                true
184        ]
185
186        ;       Copy and delete functions
187       
188        copy-selected-text: make function! [/local start end][
189                if hilight? [
190                        set [start end] hilight-range?
191                        write clipboard:// copy/part start end
192                        true
193                ] ; else return false
194        ]
195
196        delete-selected-text: make function! [/local start end] [
197                if hilight? [
198                        set [start end] hilight-range?
199                        remove/part start end
200                        view*/caret: start
201                        view*/focal-face/line-list: none
202                        unlight-text
203                        true
204                ] ; else return false
205        ]
206
207        cut-text: make function! [] [
208                undo-add face
209                copy-selected-text face
210                delete-selected-text
211        ]
212
213        paste-text: make function! [] [
214                undo-add face
215                delete-selected-text
216                face/line-list: none
217                view*/caret: insert view*/caret read clipboard://
218        ]
219
220        ;       Undo / Redo functions
221
222        undo-max: 20    ; max number of undo levels, none = unlimited
223
224        undo-add: make function! [face] [
225                if in face 'undo [
226                        insert clear face/undo at copy face/text index? view*/caret
227                        if all [undo-max undo-max < length? head face/undo] [remove head face/undo]
228                        face/undo: tail face/undo
229                ]
230        ]
231
232        undo-get: make function! [face] [
233                face/text: head view*/caret: first face/undo
234                face/line-list: none
235                remove face/undo
236        ]
237
238        ;       Cursor movement functions
239
240        word-limits: make bitset! " ^/^-^m/[](){}^""
241        word-limits: reduce [word-limits complement word-limits]
242
243        current-word: make function! [str /local s ns] [
244                unless string? str [gui-error/continue reform ["Current word trap" type? str str] exit]
245                set [s] word-limits
246                s: any [all [s: find/reverse str s next s] head str]
247                set [ns] word-limits
248                ns: any [find str ns tail str]
249                ;       hilight word
250                hilight-text s ns
251                show view*/focal-face
252        ]
253
254        next-word: make function! [str /local s ns] [
255                set [s ns] word-limits
256                any [all [s: find str s find s ns] tail str]
257        ]
258
259        back-word: make function! [str /local s ns] [
260                set [s ns] word-limits
261                any [all [ns: find/reverse str ns ns: find/reverse ns s next ns] head str]
262        ]
263
264        end-of-line: make function! [str] [
265                any [find str "^/" tail str]
266        ]
267
268        beg-of-line: make function! [str /local nstr] [
269                either nstr: find/reverse str "^/" [next nstr] [head str]
270        ]
271
272        next-field: make function! [face /wrap] [
273                unless face/parent-face [return none]
274                unless find [object! block!] type?/word get in face/parent-face 'pane [ ; An iterated face may of course be tabbable, too.
275                        return none                                                                                                                     ; I don't handle this case for now, though.
276                ]
277
278                siblings: compose [(face/parent-face/pane)]
279
280                unless wrap [siblings: find/tail siblings face]
281
282                foreach sibling siblings [                                                                                              ; Return younger siblings, nieces and nephews.
283                        if target: any [
284                                tabbed? sibling
285                                into-widget/forwards sibling
286                        ][
287                                return target
288                        ]
289                ]
290
291                all [                                                                                                                                   ; Return aunts, uncles and cousins.
292                        not cyclic? face/parent-face
293                        target: next-field face/parent-face
294                        return target
295                ]
296
297                all [                                                                                                                                   ; Return older siblings.
298                        target: next-field/wrap face
299                        return target
300                ]
301        ]
302
303        back-field: make function! [face /wrap] [
304                unless face/parent-face [return none]
305                unless find [object! block!] type?/word get in face/parent-face 'pane [ ; An iterated faces may of course be tabbable, too.
306                        return none                                                                                                                     ; I don't handle this case for now, though.
307                ]
308
309                siblings: reverse compose [(face/parent-face/pane)]
310
311                unless wrap [siblings: find/tail siblings face]
312
313                foreach sibling siblings [                                                                                              ; Return younger siblings, nieces and nephews.
314                        if target: any [
315                                tabbed? sibling
316                                into-widget/backwards sibling
317                        ][
318                                return target
319                        ]
320                ]
321                all [                                                                                                                                   ; Return aunts, uncles and cousins.
322                        not cyclic? face/parent-face
323                        target: back-field face/parent-face
324                        return target
325                ]
326
327                all [                                                                                                                                   ; Return older siblings.
328                        target: back-field/wrap face
329                        return target
330                ]
331        ]
332       
333        into-widget: make function! [
334                "Recursivly returns the first tabbable face in parent's face pane tree."
335                face [object!]
336                /forwards
337                /backwards
338                /local
339                target children
340        ][
341                unless find [object! block!] type?/word get in face 'pane [                     ; An iterated faces may of course be tabbable, too. I don't handle this case for now, though.
342                        return none
343                ]
344                unless face/show? [                                                                                                     ; Tab-panel panes have show?: false
345                        return none
346                ]
347                children: compose [(face/pane)]
348                catch [
349                        foreach child either backwards [reverse children] [children] [
350                                if target: any [
351                                        tabbed? child                                                                                   ; The successing face is tabbable, so just return it.
352                                        either backwards [
353                                                into-widget/backwards child
354                                        ][
355                                                into-widget child
356                                        ]
357                                ][
358                                        throw target
359                                ]
360                        ]
361                ]
362        ]
363       
364        keys-to-insert: make bitset! #{01000000FFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}
365
366        insert-char: make function! [face char] [
367                delete-selected-text
368                unless any [insert? tail? view*/caret "^/" = first view*/caret] [remove view*/caret] ; AGT #25
369                insert view*/caret char
370                view*/caret: next view*/caret
371        ]
372
373        move: make function! [event ctrl plain] [
374                either event/shift [
375                        any [view*/highlight-start view*/highlight-start: view*/caret]
376                ] [unlight-text]
377                view*/caret: either event/control ctrl plain
378                if event/shift [
379                        either view*/caret = view*/highlight-start [unlight-text] [view*/highlight-end: view*/caret]
380                ]
381        ]
382
383        move-y: make function! [face delta /local pos tmp tmp2] [
384                tmp: offset-to-caret face 0x2 + delta + pos: caret-to-offset face view*/caret
385                tmp2: caret-to-offset face tmp
386                either tmp2/y <> pos/y [tmp] [view*/caret]
387        ]
388
389        edit-text: make function! [
390                face event
391                /local key edge para caret scroll page-up page-down face-size
392        ][
393                face-size: face/size - either face/edge [2 * face/edge/size] [0]
394                key: event/key
395                if char? key [
396                        either find keys-to-insert key [
397                                undo-add face
398                                insert-char face key
399                        ] [key: select keymap key]
400                ]
401                if word? key [
402                        page-up:        [move-y face face-size - sizes/font-height - sizes/font-height * 0x-1]
403                        page-down:      [move-y face face-size - sizes/font-height * 0x1]
404                        do select [
405                                left            [move event [back-word view*/caret] [back view*/caret]]
406                                right           [move event [next-word view*/caret] [next view*/caret]]
407                                up                      [move event page-up [move-y face sizes/font-height * 0x-1]]
408                                down            [move event page-down [move-y face sizes/font-height * 0x1]]
409                                page-up         [move event [head view*/caret] page-up]
410                                page-down       [move event [tail view*/caret] page-down]
411                                home            [move event [head view*/caret] [beg-of-line view*/caret]]
412                                end                     [move event [tail view*/caret] [end-of-line view*/caret]]
413                                insert          [either event/shift [paste-text] [insert?: complement insert?]]
414                                back-char [
415                                        undo-add face
416                                        any [
417                                                delete-selected-text
418                                                head? view*/caret
419                                                either event/control [
420                                                        tmp: view*/caret
421                                                        remove/part view*/caret: back-word tmp tmp
422                                                ] [remove view*/caret: back view*/caret]
423                                        ]
424                                ]
425                                del-char [
426                                        undo-add face
427                                        either event/shift [unless face/type = 'password [cut-text]] [  ;       shift+Del cut
428                                                any [
429                                                        delete-selected-text
430                                                        tail? view*/caret
431                                                        either event/control [
432                                                                remove/part view*/caret back next-word view*/caret
433                                                                if tail? next view*/caret [remove back tail view*/caret]
434                                                        ] [remove view*/caret]
435                                                ]
436                                        ]
437                                ]
438                                enter [
439                                        either find behaviors/action-on-enter face/type [
440                                                all [face/type = 'spinner face/action/on-unfocus face]
441                                                set-focus face
442                                                face/action/on-click face
443                                        ][
444                                                undo-add face
445                                                insert-char face "^/"
446                                        ]
447                                ]
448                                ctrl-enter      [undo-add face insert-char face tab]
449                                all-text        [hilight-all face]
450                                copy-text       [unless face/type = 'password [copy-selected-text face unlight-text]]
451                                cut-text        [unless face/type = 'password [cut-text]]
452                                paste-text      [paste-text]
453                                clear-tail [
454                                        undo-add face
455                                        remove/part view*/caret end-of-line view*/caret
456                                ]
457                                undo [
458                                        if all [in face 'undo not head? face/undo] [
459                                                insert face/undo at copy face/text index? view*/caret
460                                                face/undo: back face/undo
461                                                undo-get face
462                                        ]
463                                ]
464                                redo [
465                                        if all [in face 'undo not tail? face/undo] [
466                                                face/undo: insert face/undo at copy face/text index? view*/caret
467                                                undo-get face
468                                        ]
469                                ]
470                                undo-all [
471                                        if in face 'esc [
472                                                clear face/text
473                                                all [in face 'undo clear face/undo]
474                                                all [string? face/esc insert face/text face/esc]
475                                                view*/caret: tail face/text
476                                        ]
477                                ]
478                                spellcheck [
479                                        request-spellcheck face
480                                ]
481                        ] key
482                ]
483                ;       scroll to keep caret visible
484                edge:   face/edge
485                para:   face/para
486                scroll: face/para/scroll
487
488                if error? try [
489                caret: caret-to-offset face view*/caret
490
491                if caret/y < (edge/size/y + para/origin/y + para/indent/y) [ ; above top visible row ?
492                        scroll/y: round/to scroll/y - caret/y sizes/font-height ; scroll to make caret visible
493                ]
494
495                if caret/y > (face-size/y - sizes/font-height) [ ; below bottom visible row ? (face-size takes edge into account)
496                        scroll/y: round/to (scroll/y + ((face-size/y - sizes/font-height) - caret/y)) sizes/font-height
497                ]
498
499                unless para/wrap? [
500                        if caret/x < (edge/size/x + para/origin/x + para/indent/x) [
501                                scroll/x: scroll/x - caret/x + (edge/size/x + para/origin/x + para/indent/x)
502                        ]
503                        if caret/x > (face-size/x - para/margin/x) [
504                                scroll/x: scroll/x + (face-size/x - para/margin/x - caret/x)
505                        ]
506                ]
507
508                if scroll <> face/para/scroll [
509                        face/para/scroll: scroll
510                        if face/type = 'area [face/key-scroll?: true]
511                ]
512                ] [gui-error/continue reform ["Caret trap" face/type face/para]]
513                show face
514        ]
515
516        feel: make object! [
517                redraw: detect: over: none
518                engage: func [face act event /local txt] [
519                        do select [
520                                key [
521                                        unless all [get in face/action 'on-key not face/action/on-key face event] [
522                                                txt: copy face/text
523                                                edit-text face event
524                                                all [
525                                                        get in face/action 'on-edit
526                                                        strict-not-equal? txt face/text
527                                                        face/action/on-edit face
528                                                ]
529                                        ]
530                                ]
531                                down [
532                                        either event/double-click [
533                                                all [view*/caret not empty? view*/caret current-word view*/caret]
534                                        ][
535                                                either face = view*/focal-face [
536                                                        unlight-text
537                                                        view*/caret: offset-to-caret face event/offset
538                                                        show face
539                                                ] [
540                                                        caret: offset-to-caret face event/offset
541                                                        set-focus face
542                                                ]
543                                        ]
544                                ]
545                                over [
546                                        unless view*/caret = offset-to-caret face event/offset [
547                                                unless view*/highlight-start [view*/highlight-start: view*/caret]
548                                                view*/highlight-end: view*/caret: offset-to-caret face event/offset
549                                                show face
550                                        ]
551                                ]
552                                alt-up          [face/action/on-alt-click face]
553                                scroll-line     [face/action/on-scroll face event/offset]
554                                scroll-page     [face/action/on-scroll/page face event/offset]
555                        ] act
556                ]
557        ]
558]
Note: See TracBrowser for help on using the browser.