Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

RFC: addressbook.r

 [1/2] from: ingo::2b1::de at: 31-Oct-2000 14:48


Hi Rebols, after much frustration with gnomecard, I decided to roll my own addressbook, that's what I've got so far. I'd like some comments on - the user interface (I like it this way) - the xml layout (no experience there) - information in the datafile that might be missing/superflouououos (that's just like bananana, you just can't stop, once you started writing it ;-) regards, Ingo -- Attached file included as plaintext by Listar -- REBOL [ Title: "Addressbook" author: "Ingo Hohmann" email: [ingo--2b1--de] date: 31-Oct-2000 file: %addressbook.r history: [ 0.0.6 [ 31-Oct-2000 "added note display" "iho" ] 0.0.5 [ 31-Oct-2000 "added help" "iho" ] 0.0.4 [ 31-Oct-2000 "added add / delete" "iho" ] 0.0.3 [ 30-Oct-2000 "except for persons add works" "iho" ] 0.0.2 [ 29-Oct-2000 "cleaned up" "iho" ] 0.0.1 [ 28-Oct-2000 "first preview" "iho" ] ] version: first history addy: "preview" comments: { This is a little address database system, with an NQA Interface (No Questions Asked) what you type in, will immediately be part of the database (once save is called on exit, BTW how can I catch the close event?). } known-bugs: [ {no testing if changed file-as already exists} {no testing if " is present in fields} ] todo: [ {a lot of cleanups} {update 'update} {sorting} ] ] yamm/import %xml-helper.r addressbook: make object! [ doc: {This object encapsulates the address database} xml-data-block: block! ; parse-xml created block of blocks addressdb-block: block! ; addressdb block in xml-data-block current-data-block: block! ; is set to the block containing the current data current-data-pos: 0 ; position of current-data in addressdb-block current-address-pos: 1 ; position in addressdb-block list current-comm-pos: 1 ; position in communication list current-sub-pos: 1 ; set to either current-comm-pos or current-address-pos current-view-name: string! ; the tag name of the currently shown data ["person" "address" "comm"] add-new-data?: false ; true, if a lists last position is shown (don't change data) current-view: none ; function to show current view current-layout: ; pointer to current layout block ; superflouos? (sp?) current-fields: ; fields shown in current view names: copy [] ; list of names for main list xml-data-file: %address.xml current-view: function! ; function to show the current view load-xml: func ["load xml data file and parse it"] [ xml-data-block: trim-xml parse-xml read xml-data-file addressdb-block: xml-data-block/3/1/3 ] save-xml: func ["save xml data"] [ write xml-data-file xdump xml-data-block ] ;layouts person-layout: [ across text bold "Personal" text "Address" #"^A" [leave show-address] text "Communication" #"^C" [leave show-comm] text "Notes" #"^N" [leave show-note] return tabs 110 text "Filed as" tab file-as: field update: text 200x20 return text "Name" tab first-name: field return text "Last" tab last-name: field return text "Nickname" tab nick-name: field return text "Middle" tab middle-name: field return text "Prefix" tab name-prefix: field return text "Suffix" tab name-suffix: field return text "Birthday" tab birthday: field return tab tab tab button "Delete" [delete-data] ] address-layout: [ across text "Personal" #"^P" [leave address-fields show-person] text bold "Address" text "Communication" #"^C" [leave address-fields show-comm] text "Notes" #"^N" [leave show-note] return tabs 110 text "extended" tab extended: field here: at return text "street" tab street: field return text "postal-code" tab postal-code: field return text "city" tab city: field return text "region" tab region: field return text "post-office" tab post-office: field return text "Country" tab country: field return text "Type" tab attr: field return at here address-list: text-list data addrs [ leave address-fields set 'current-address-pos index? find addrs value add-new-data?: either current-address-pos = length? addrs [true][false] show-address ] return tab tab tab button "Delete" [delete-data] ] comm-layout: [ across text "Personal" #"^P" [leave comm-fields show-person] text "Address" #"^A" [leave comm-fields show-address] text bold "Communication" text "Notes" #"^N" [leave show-note] return tabs 110 text "Value" tab value: field here: at return text "" return text "Type" tab type: field return text "Attributes" tab attr: field return at here comms-list: text-list data comms [ leave comm-fields current-comm-pos: index? find comms value add-new-data?: either current-comm-pos = length? comms [true][false] show-comm ] return tab tab tab button "Delete" [delete-data] ] note-layout: [ across text "Personal" #"^P" [leave comm-fields show-person] text "Address" #"^A" [leave show-address] text "Communication" #"^C" [leave show-comm] text bold "Notes" #"^N" return note: area 450x300 return tab tab tab button "Delete" [delete-data] ] note-fields: copy [ note ] help-layout: [ help-text: area 450x350 across button "View" [show-help/topic 'howto-view] button "Update" [show-help/topic 'howto-update] button "misc" [show-help/topic 'howto-misc] tab button "Close help" [current-view] ] ; lists of fields, to set/get them easily person-fields: copy [] forall person-layout [ if all [ set-word? person-layout/1 'field = person-layout/2 ] [ append person-fields to-word person-layout/1 ] ] person-layout: head person-layout person-fields: next person-fields ; assuming 'file-as is first field address-fields: copy [] forall address-layout [ if all [ set-word? address-layout/1 'field = address-layout/2 ] [ append address-fields to-word address-layout/1 ] ] address-layout: head address-layout comm-fields: copy [] forall comm-layout [ if all [ set-word? comm-layout/1 'field = comm-layout/2 ] [ append comm-fields to-word comm-layout/1 ] ] comm-layout: head comm-layout ; ; display functions ; show-person: func ["displays the name data"][ disp/pane: layout/offset person-layout 0x0 current-view: :show-person current-view-name: "person" current-fields: :person-fields foreach pos current-data-block/3 [ if pos/1 = "person" [ foreach [attr val] pos/2 [ attr: to-word attr x: to-set-path compose [(to-word attr) text] x val ] break ] ] file-as/text: second find current-data-block/2 "file-as" update/text: second find current-data-block/2 "update" show disp ] current-view: :show-person current-layout: person-layout ; needed? current-fields: person-fields show-address: func [ "displays address data" /local addr-pos ][ current-view: :show-address current-view-name: "address" current-fields: :address-fields addrs: copy [] foreach pos current-data-block/3 [ if all [pos/1 = "address" block? pos/2] [ append addrs rejoin [ select pos/2 "postal-code" "," select pos/2 "city" "," select pos/2 "street" ] ] ] append addrs "" disp/pane: layout/offset address-layout 0x0 addr-pos: 0 foreach pos current-data-block/3 [ if all [pos/1 = "address" block? pos/2] [ addr-pos: addr-pos + 1 if addr-pos = current-address-pos [ foreach [attr val] pos/2 [ attr: to-word attr x: to-set-path compose [(to-word attr) text] x val ] break ] ] ] show disp ] show-comm: func [ "displays communication data" /local comm-pos ][ current-view: :show-comm current-view-name: "comm" current-fields: :comm-fields comms: copy [] foreach pos current-data-block/3 [ if all [pos/1 = "comm" block? pos/2] [ append comms rejoin [ select pos/2 "value" either not none? select pos/2 "attr" [rejoin [", " select pos/2 "attr"]][""] ] ] ] append comms "" disp/pane: layout/offset comm-layout 0x0 comm-pos: 0 foreach pos current-data-block/3 [ if all [pos/1 = "comm" block? pos/2] [ comm-pos: comm-pos + 1 if comm-pos = current-comm-pos [ foreach [attr val] pos/2 [ attr: to-word attr x: to-set-path compose [(to-word attr) text] x val ] break ] ] ] show disp ] show-note: func [ "displays the note" /local found? ][ disp/pane: layout/offset note-layout 0x0 current-view: :show-note current-view-name: "note" current-fields: :note-fields found?: false foreach pos current-data-block/3 [ if pos/1 = "note" [ if none? pos/3 [ change at pos 3 [""] ] note/text: pos/3/1 found?: true break ] ] if not found? [ append/only current-data-block/3 copy/deep compose [(current-view-name) none [""]] pos: back tail current-data-block/3 note/text: pos/1/3/1 ] file-as/text: second find current-data-block/2 "file-as" update/text: second find current-data-block/2 "update" show disp ] show-help: func [ "displays the help screen" /topic "show specific help-text" show-this [word!] "the topic string" ][ disp/pane: layout/offset help-layout 0x0 help-text/text: trim/auto any [ if 'howto-view = show-this [ { HOWTO -- View Data: - Change between views Click on Person / Address / Communication, respectively - Show different Persons data: Use up / down cursor keys, or click with the mouse - Show different Address / Communication: Click in the list } ] if 'howto-update = show-this [ { HOWTO -- Update data: - Update data: just change data in the fields as you like - Delete data: hit the delete key, if you are on the "Person" screen, the complete person will be deleted, in other screens, the data shown at the moment. - Add new data: click the last (empty) position in any list, a new data-set will be added - Update data on disk: Currently data on disk will only be updated once you click the "Save & Exit" Button } ] if 'howto-misc = show-this [ { HOWTO -- Miscallenous: - Send feedback: Click on my email address } ] { Addressbook help ---------------- This addressbook has an NQA/WYSIWYG User Interface, NQA: No Questions Asked -> You'll never get asked if you _really_ want to do that WYSIWYG: Data you see on the screen is in the database (except for an update timelag) Now click on a button to get specific help } ] show disp ] h: 0 set-current-data: func [ "sets position in data block, etc" pos /local new-name ][ current-data-pos: pos: max 1 min length? names pos append clear name-l/picked pick names pos show name-l if current-data-pos = length? names [ new-name: copy "Unnamed" either not find names new-name [ append/only addressdb-block compose/deep ["data" ["file-as" (new-name) "update" (to-string now)] [["person" [] none]]] create-names show name-l focus file-as ][ request/ok {Please rename previously added data first} current-data-pos: current-data-pos - 1 ] ] current-data-block: pick addressdb-block (current-data-pos) current-address-pos: 1 current-comm-pos: 1 ] ; ; check changes ; leave: func [ { tests all fields and if one is dirty updates the database (iho) globals used: current-fields, current-view-name } /local dirty count-pos txt-path txt ] [ foreach fld current-fields [ dirty: to-path compose [(:fld) dirty?] if dirty [break] ] if dirty [ ; one of the fields is changed ... current-sub-pos: any [ all [current-view-name = "address" current-address-pos] all [current-view-name = "comm" current-comm-pos] 1 ] if add-new-data? [ append/only current-data-block/3 copy/deep compose [(current-view-name) [] none] ] count-pos: 0 foreach tag-block current-data-block/3 [ if all [tag-block/1 = current-view-name block? tag-block/2] [ count-pos: count-pos + 1 if count-pos = current-sub-pos [ foreach fld2 head current-fields [ txt-path: to-path compose [(:fld2) text] txt: txt-path fld2: to-string fld2 either pos2: find tag-block/2 fld2 [ change next pos2 txt ] [ append tag-block/2 compose [(fld2) (txt)] ] ] break ] ] ] if all ["person" = current-view-name file-as/dirty?] [ probe current-data-block create-names ] ] ] create-names: func [ {creates the name list} ][ clear names if block? addressdb-block [ foreach a addressdb-block [ ;; needs error handling ... append names select a/2 "file-as" ] if not "" = last names [ append names "" ] ] ] delete-data: func [ {delete a complete person, or address/comm data} /local current-sub-pos count-pos ][ either "person" = current-view-name [ remove at addressdb-block current-data-pos create-names set-current-data current-data-pos - 1 current-view ][ current-sub-pos: any [ all [current-view-name = "address" current-address-pos] all [current-view-name = "comm" current-comm-pos] 0 ; to trap errors, if new displays are added ] count-pos: 0 data-block: current-data-block/3 forall data-block [ if current-view-name = data-block/1/1 [ count-pos: count-pos + 1 if count-pos = current-sub-pos [ remove data-block current-view break ] ] ] ] ] ; ; start the gui now ; start: func [ "starts up the gui" /local adr first last ] [ create-names main: layout [ title center rejoin [ "Addressbook (Ver " system/script/header/version " " system/script/header/addy ")"] across name-l: text-list data names [ set-current-data index? find names value current-view ] 200x400 h: at disp: box 550x480 at h + 456x-24 across space 4 arrow left keycode [up] [leave set-current-data current-data-pos - 1 current-view] arrow right keycode [down] [leave set-current-data current-data-pos + 1 current-view] at 20x500 text "Ingo Hohmann" text "<[ingo--2b1--de]>" [send-text/to [ingo--2b1--de]] tab button "Help" [show-help] tab tab button "Save & Exit" [leave save-xml unview/all halt] ] set-current-data 1 current-view view main ] ] addressbook/load-xml addressbook/start -- Attached file included as plaintext by Listar -- <addressdb> <data file-as="Joe User" update="28-Oct-2000/18:42:55+2:00"> <person birthday="1970-01-01" name-suffix="md" name-prefix="dr. h.c." middle-name="X." first-name="Joe" last-name="User" nick-name="Joey" /> <comm type="url" attr="personal" value="http://www.web-page.de" /> <comm type="email" attr="Internet" value="[joe--user--com]" /> <comm type="tel" attr="PREF;WORK;HOME;VOICE;FAX;MSG;CELL;PAGER;BBS;MODEM;CAR;ISDN;VIDEO" value="0700/JOE-USER" /> <address country="User Land" postal-code="4711" region="All around here" city="Compcity" street="Pentium-Street" extended="at the seven dwarfs" post-office="just hand it to any passenger" /> <note> Here comes a note test </note> </data> <data file-as="Joe User jun." update="28-Oct-2000/18:42:55+2:00"> <person birthday="1970-01-01" name-suffix="jun." middle-name="X." first-name="Joe" last-name="User" nick-name="Joey" /> <comm type="url" attr="personal" value="http://www.web-page-jun.de" /> <comm type="email" attr="Internet" value="[joe-jun--user--com]" /> <comm type="tel" attr="PREF;WORK;HOME;VOICE;FAX;MSG;CELL;PAGER;BBS;MODEM;CAR;ISDN;VIDEO" value="0700/JOE-USER" /> <address country="User Land" postal-

 [2/2] from: ingo::2b1::de at: 31-Oct-2000 14:58

PS: RFC: addressbook.r


Sorry, my previous post missed a helper script, that's needed to make it work ... Here it is. P.S.: you can change yamm/import %xml-helper.r to do %xml-helper.r without any bad side-effects. regards, Ingo -- Attached file included as plaintext by Listar -- [REBOL [ Title: "XML helpers" Author: [jn "Joel Neely" iho "Ingo Hohmann"] email: [iho [ingo--2b1--de]] ] trim-xml: func [ {trim whitespace from parse-xml created blocks (jn)} b [block!] /local content item ][ content: third b if found? content [ while [not tail? content] [ item: first content either block? item [ trim-xml item content: next content ][ either 0 = length? trim item [ remove content ][ content: next content ] ] ] if 0 = length? head content [ b/3: none ] ] b ] _xdump: func [ {xdump internal helper (jn)(iho)} b [block!] {xml structure} indent [string!] /start "starting the recursion" /local tag next-indent was-string out prin print ][ prin: func [s][append out s] print: func [s][append out join s newline] out: "" if start [clear out] tag: trim to-string first b prin join copy indent [join copy "<" tag] if block? second b [ foreach [n v] second b [ prin join copy " " [either string? n [trim n][n] {="} form v {"}] ] ] either not block? third b [ print " />" ][ print ">" next-indent: join copy indent " " was-string: false foreach x third b [ was-string: not any-block? x either was-string [ if 0 < length? trim x [ print join copy next-indent x ] ][ _xdump x next-indent ] ] print join copy indent [copy "</" trim tag ">"] ] if start [out] ] xdump: func [ {pretty print a block created from parse-xml (jn)(iho)} b [block!] {the xml structure from parse-xml} /complete {doesn't omit the outer blocks (normally document none [])} ][ either complete [ _xdump/start b copy "" ][ _xdump/start first third b copy "" ] ] build-xml-tag: func [ "Generates a tag from a composed block. (iho)" blk [block!] "Block of parens to evaluate and other data" /stand-alone "creates a stand-alone (self-closing) tag" /local out in ][ append out: copy # either 0 < length? blk: compose blk [ in: first head blk: next blk either not any-string? in [mold in] [in] ] [#] foreach [att val] blk [ append out rejoin [" " att {="} val {"}] ] ?? out if stand-alone [append out " /"] to-tag trim out ] ] xml-structure: ['document none! xml-element-contents] xml-element-node: [into [xml-element-name xml-element-attributes xml-element-contents]] xml-element-name: [set elt-name string! (print elt-name)] xml-element-attributes: [none! | block!] xml-element-contents: [none! | into [some [xml-element-node | string! ]]] xml-target: http://p.moreover.com/cgi-local/page?index_devoper+xml parse parse-xml read xml-target xml-structure