home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-05-16 | 18.8 KB | 438 lines | [TEXT/3PRM] |
- module database // Small database program to manipulate a simple database
- import StdEnv
- import deltaEventIO, deltaDialog, deltaIOSystem, deltaMenu, deltaWindow, deltaFont
- import deltaPicture, deltaIOState, deltaFileSelect, deltaControls, deltaSystem
- import listextensions
-
- :: *IO :== IOState DataBase // Synonym for IOState (see deltaEventIO)
- :: *DataBase :== (State, Files) // State contains all relevant info
- :: Record :== [ String ] // [Content]
- :: Descriptor :== [ String ] // [Fieldname]
- :: State = { records :: [Record] // All records
- , descriptor :: Descriptor // All fieldnames
- , selection :: Int // Indicating current record selected
- , query :: Record // Record to look for
- , name :: String // Name of database
- , editinfoid :: DialogItemId // Id of info about use of editdialog (query or record)
- , fw :: Int // Max width of field contents
- , dw :: Int // Max width of descriptor fields
- }
- :: InfoFont = { font :: Font // The font which is used
- , width :: Int // Its widest character
- , height :: Int // Its line height
- }
-
- MinDbDomainSize :== (100,1) // Minimal size of recordwindow
- CharsInInputBox :== 20 // Input width (number of characters)
- InputBoxWidth :== Pixel (CharsInInputBox*DfFont.width)// Width of boxes in fields, queries and field names
-
- DontCareId :== 0
- RecordWindowId :== 0 // Id of window in which the records are shown
- EdDialogId :== 0; FieldDialogId :== 1 // Ids of main dialogs used
-
- Replace :== True // Replace current selection when adding new record
- Separator :== ": " // Separates field names and contents
-
- DbFont =: {font = f, width = maxwidth, height = ascent+descent+leading}
- where // Global graph def: font used in this database
- (ascent,descent,maxwidth,leading) = FontMetrics f
- (_,f) = SelectFont "courier" [] 10
-
- DfFont =: {font = f, width = maxwidth, height = ascent+descent+leading}
- where // Global graph def: default font (in dialogs)
- (ascent,descent,maxwidth,leading) = FontMetrics f
- (_,f) = SelectFont name styles length
- (name,styles,length) = DefaultFont
-
- Start :: *World -> *World
- Start world
- # (events,world) = OpenEvents world
- (files, world) = openfiles world
- ((_,finalfiles),finalevents)
- = StartIO [MenuSystem [menu]] (initState,files) initIO events
- world = CloseEvents finalevents world
- world = closefiles finalfiles world
- = world
- where
- menu = PullDownMenu DontCareId "Commands" Able
- [ MenuItem DontCareId "Show Records" (Key 'r') Able ShowRecords
- , MenuItem DontCareId "Edit..." (Key 'e') Able ShowEditDialog
- , MenuItem DontCareId "Change Set Up..." (Key 'u') Able ShowFieldDialog
- , MenuItem DontCareId "Read new..." (Key 'o') Able (\s io->seqIO initIO (s, seq closeIO io))
- , MenuItem DontCareId "Save As..." (Key 's') Able SaveRecords
- , MenuSeparator
- , MenuItem DontCareId "Quit" (Key 'q') Able Quit
- ]
- initIO = [ ReadDataBase, ShowRecords, ShowEditDialog ]
- closeIO = [ CloseWindows [RecordWindowId], closeDbDialogs ]
- initState = { records=[],descriptor=[],selection=0,query=[],name="",editinfoid=0,fw=0,dw=0 }
-
- // The CallBack and initialisation Functions of the menu:
-
- ReadDataBase :: DataBase IO -> (DataBase, IO)
- ReadDataBase db io
- # (done,dbname,(state, files),io) = SelectInputFile db io
- | not done = ((state,files),io)
- # (open,dbfile,files) = fopen dbname FReadText files
- | not open = ((state,files),Beep io)
- # (descr,dbfile) = FReadDescr dbfile
- (recs, dbfile) = FReadRecords (inc (length descr)) dbfile // lines = length descr + empty line
- (close,files) = fclose dbfile files
- | not close = ((state,files),Beep io)
- | otherwise = (({state & records=recs,descriptor=descr,query=repeatn (length descr) "",selection=0,name=dbname,
- fw=MaxWidth DbFont.font (flatten recs),dw=MaxWidth DbFont.font descr},files)
- ,io
- )
- where
- FReadDescr file
- # (nroffields,file) = FReadStrippedLine file
- (descr,file) = seqList (repeatn (toInt nroffields) FReadStrippedLine) file
- = (descr,file)
-
- FReadRecords nroflines file
- | sfend file = ([], file)
- # ([_:record],file) = seqList (repeatn nroflines FReadStrippedLine) file
- (records, file) = FReadRecords nroflines file
- = ([record : records], file)
-
- FReadStrippedLine file
- # (line, file) = freadline file
- = (line%(0,size line - 2),file) // strip "\n"
-
- ShowRecords :: DataBase IO -> (DataBase, IO)
- ShowRecords (state=:{records,descriptor,dw,name}, files) io
- = ((state,files),OpenWindows [window] io)
- where
- window = ScrollWindow RecordWindowId (5,5) namewithoutdirectories
- (ScrollBar (Thumb left) (Scroll DbFont.width)) (ScrollBar (Thumb top) (Scroll DbFont.height))
- domain MinDbDomainSize (right - left,bottom - top)
- UpdateRecordWindow [Mouse Able MouseSelectItem]
- namewithoutdirectories = toString (last (splitby DirSeparator (fromString name)))
- ((left,top),(right,bottom)) = domain
- domain = DbPictureDomain state 0 (max (length records) 1)
-
- ShowEditDialog :: DataBase IO -> (DataBase, IO)
- ShowEditDialog (state=:{descriptor=descr,records=recs,selection},files) io
- # io = OpenDialog editDialog io
- io = SetTextFields infoid infostring descr (if (isEmpty recs) [] (recs!!selection)) io
- = (({state & editinfoid = infoid},files), io)
- where
- infostring = "Current Record Number: "+++toString selection
- editDialog = CommandDialog EdDialogId "Edit Record" [] addId dialogitems
- dialogitems = [ DynamicText infoid Left InputBoxWidth "" ]
- ++ flatten [inputfield sid eid field \\ field <- descr & eid <- [0..] & sid <- [length descr..]]
- ++
- [ DialogButton dispQId (Below (length descr - 1)) "DisplQ" Able DisplQuery
- , DialogButton setQId (RightTo dispQId) "SetQ" Able SetQuery
- , DialogButton srchQId (RightTo setQId) "SearchQ" Able Search
- , DialogButton slctQId (RightTo srchQId) "SelectAllQ" Able SelectAll
- , DialogButton replId (Below dispQId) "Replace" Able (AddRecord Replace)
- , DialogButton delId (RightTo replId) "Delete" Able DeleteRecord
- , DialogButton addId (RightTo delId) "Add" Able (AddRecord (not Replace))
- , DialogButton sortId (RightTo addId) "Sort" Able Sort
- ]
-
- inputfield sid eid field
- = [StaticText sid Left field, EditText eid pos InputBoxWidth 1 ""]
- where
- pos = case eid of 0 -> XOffset sid offset; else -> Below (dec eid)
- offset = Pixel (DfFont.width + MaxWidth DfFont.font descr - MaxWidth DfFont.font [field])
-
- [infoid,dispQId,setQId,srchQId,slctQId,replId,delId,addId,sortId:_] = [2*(length descr)..]
-
- ShowFieldDialog :: DataBase IO -> (DataBase, IO)
- ShowFieldDialog db=:({descriptor=d},_) io
- | isEmpty d = inputdialog "Give first field" InputBoxWidth (\input->FieldChangeIO (add (-1) input)) db io
- | otherwise = (db,OpenDialog fielddialog (CloseDialog EdDialogId io))
- with
- fielddialog = CommandDialog FieldDialogId "Change Set Up" [] addId
- [StaticText DontCareId Left "Select Field...",
- RadioButtons selectId Left (Columns 1) firstRadioId (radioitems firstRadioId d),
- DialogButton deleteId Left "Delete" Able (DeleteField getselectedfield),
- DialogButton moveId (RightTo deleteId) "Move" Able (MoveField getselectedfield),
- DialogButton renameId Left "Rename" Able (RenameField getselectedfield),
- DialogButton addId (Below moveId) "Add New" Able (AddField getselectedfield)]
-
- getselectedfield dialoginfo = GetSelectedRadioItemId selectId dialoginfo - firstRadioId
-
- [deleteId,moveId,renameId,addId,selectId,firstRadioId:_] = [0..]
-
- SaveRecords :: DataBase IO -> (DataBase, IO)
- SaveRecords db=:({name,descriptor,records},_) io
- # (done,dbname,db,io) = SelectOutputFile "Save As: " name db io
- | not done = (db, io)
- # (state,files) = db
- (open,dbfile,files) = fopen dbname FWriteText files
- | not open = ((state,files), Beep io)
- # (close,files) = fclose (seq (writedescriptor++writerecords) dbfile) files
- | not close = ((state, files), Beep io)
- | otherwise = ((state, files), io)
- where
- writedescriptor = [fwritei (length descriptor), FWriteRecord descriptor]
- writerecords = [FWriteRecord rec \\ rec <- records]
- FWriteRecord rec = fwrites (foldl (+++) "\n" (map (\field -> field +++ "\n") rec))
-
- Quit :: DataBase IO -> (DataBase, IO)
- Quit database io = (database, QuitIO io)
-
- // Field set up changes
-
- FieldChangeIO :: (State -> State) DataBase IO -> (DataBase,IO)
- FieldChangeIO changefun (state,files) io = UpdateDbDomain (changefun state,files) (closeDbDialogs io)
-
- AddField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
- AddField getfield dialoginfo db=:(state,files) io
- = inputdialog infotext InputBoxWidth (\input->FieldChangeIO (add fieldname input)) db io
- where
- infotext = "Add after '"+++state.descriptor!!fieldname+++"' new field"
- fieldname = getfield dialoginfo
-
- RenameField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
- RenameField getfield dialoginfo db=:(state,files) io
- = inputdialog infotext InputBoxWidth (\input->FieldChangeIO (rename fieldtorename input)) db io
- where
- infotext = "Rename '"+++state.descriptor!!fieldtorename+++"' to"
- fieldtorename = getfield dialoginfo
-
- MoveField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
- MoveField getfield dialoginfo db=:({descriptor=d},_) io
- = (db,OpenDialog movedialog io)
- where
- fieldtomove = getfield dialoginfo
- movedialog
- = CommandDialog moveDialogId "Move Field" [] okId
- [ StaticText infoId Left ("Move '"+++(d!!fieldtomove)+++ "' before: ")
- , RadioButtons selectId Left (Rows (inc (length d))) firstRadioId (radioitems firstRadioId (d++[""]))
- , DialogButton cancelId Left Cancel Able cancel
- , DialogButton okId (RightTo cancelId) "Move" Able (ok (move fieldtomove))
- ]
-
- [moveDialogId,cancelId,okId,infoId, selectId,firstRadioId:_] = [0..]
-
- ok mvf dlginfo s io
- = FieldChangeIO (mvf destinationfield) s (CloseDialog moveDialogId io)
- where
- destinationfield = GetSelectedRadioItemId selectId dlginfo - firstRadioId
-
- DeleteField :: (DialogInfo -> Int) DialogInfo DataBase IO -> (DataBase, IO)
- DeleteField getfield dialoginfo db io
- = warn ["Are you sure?"] (FieldChangeIO (delete (getfield dialoginfo))) db io
-
- add afterfield fieldname state=:{records=rs,descriptor=d,query=q,dw}
- = {state & records=map (ins "") rs,descriptor=ins fieldname d,query=ins "" q,dw=descrwidth}
- where
- ins x ys = insertAt (inc afterfield) x ys
- descrwidth = max (MaxWidth DbFont.font [fieldname]) dw
-
- rename selectedfield newfieldname s=:{descriptor=d}
- = {s & descriptor=newdescr,dw=MaxWidth DbFont.font newdescr}
- where
- newdescr = updateAt selectedfield newfieldname d
-
- move sf df s=:{records=rs,descriptor=d,query=q}
- = {s & records=map (moveinlist sf df) rs,descriptor=moveinlist sf df d,query=moveinlist sf df q}
-
- delete i s=:{records=rs,descriptor=d,query=q}
- = {s & records=newrs,descriptor=newdescr,query=remove i q,dw=MaxWidth DbFont.font newdescr,fw=nfw}
- where
- newrs = map (remove i) rs
- newdescr = remove i d
- nfw = MaxWidth DbFont.font (flatten newrs)
-
- // Handling the edit dialog
-
- DisplQuery ::DialogInfo DataBase IO -> (DataBase, IO)
- DisplQuery info db=:({descriptor,query,editinfoid},_) io
- = (db,SetTextFields editinfoid "Query :" descriptor query io)
-
- SetQuery ::DialogInfo DataBase IO -> (DataBase, IO)
- SetQuery info (state, files) io
- # (nquery,io) = GetTextFields state.descriptor io
- = (({state & query = nquery},files), io)
-
- Search ::DialogInfo DataBase IO -> (DataBase, IO)
- Search info database=:(state=:{records,query,selection=sel},files) io
- | isEmpty found = (database, Beep io)
- | otherwise = MakeSelectionVisible ({state & selection=nsel},files) (ChangeSelection state sel nsel io)
- where
- nsel = hd found
- found = [i \\ e <- el ++ bl & i <- [sel+1 .. length records - 1] ++ [0..] | QueryRecord query e]
- (bl,el) = splitAt (sel+1) records
-
- QueryRecord :: Record Record -> Bool
- QueryRecord query e
- = and [ EqPref qf f \\ f <- e & qf <- query ]
- where
- EqPref pref name
- | size pref > size name = False
- | otherwise = pref == name%(0,size pref - 1)
-
- SelectAll :: DialogInfo DataBase IO -> (DataBase, IO)
- SelectAll info database=:(state=:{records,query,selection,descriptor},files) io
- | isEmpty recs = (database, Beep io)
- # io = ChangeSelection state selection 0 io
- io = ChangeWindowTitle RecordWindowId selname io
- | otherwise = UpdateDbDomain (nstate,files) io
- where
- recs = filter (QueryRecord query) records
- nstate = {state & selection=0,records=recs,name=selname,fw=MaxWidth DbFont.font (flatten recs)}
- selname = "Select"
-
- MakeSelectionVisible :: DataBase IO -> (DataBase,IO)
- MakeSelectionVisible db=:({records,selection,descriptor},_) io
- | isEmpty records = (db,io)
- | selection_invisible = ChangeScrollBar RecordWindowId (ChangeVThumb selthumb) db io1
- | otherwise = (db,io1)
- where
- (((_,visibletop),(_,visiblebot)), io1)
- = WindowGetFrame RecordWindowId io
- selection_invisible = selthumb < visibletop || selthumb >= visiblebot
- selthumb = toPicCo descriptor selection
-
- DeleteRecord :: DialogInfo DataBase IO -> (DataBase, IO)
- DeleteRecord dialogInfo db=:(state=:{records=oldrecs,selection=index,descriptor,fw},files) io
- | isEmpty oldrecs = (db,Beep io)
- | otherwise = UpdateDbDomain (nstate,files) io
- where
- newrecs = remove index oldrecs
- fieldwidth = if recalcwidth (MaxWidth DbFont.font (flatten newrecs)) fw
- recalcwidth = fw == MaxWidth DbFont.font (oldrecs!!index)
- nindex = if (isEmpty newrecs) 0 (index mod length newrecs)
- nstate = {state & records = newrecs, selection = nindex, fw = fieldwidth}
-
- AddRecord :: Bool DialogInfo DataBase IO -> (DataBase, IO)
- AddRecord replace dialogInfo db=:(state=:{descriptor,selection,records=recs,fw},files) io
- | isEmpty recs && replace = (db,Beep io)
- | otherwise = UpdateDbDomain (nstate,files) io1
- where
- (newrec,io1) = GetTextFields descriptor io
- (index,newrecs) = insertindex (\a b -> a <= b) newrec (if replace (remove selection recs) recs)
- fieldwidth = if recalc (MaxWidth DbFont.font (flatten newrecs)) (max (MaxWidth DbFont.font newrec) fw)
- recalc = replace && MaxWidth DbFont.font (recs!!selection) < fw
- nstate = {state & records=newrecs,selection=index,fw=fieldwidth}
-
- Sort :: DialogInfo DataBase IO -> (DataBase, IO)
- Sort dialogInfo (state=:{records=recs},files) io
- = UpdateDbDomain ({state & records = sort recs},files) io
-
- GetTextFields :: Descriptor IO -> (Record,IO)
- GetTextFields descr io
- = ([GetEditText id dialogInfo \\ id <- [0..(length descr - 1)]],nio)
- where
- (_,dialogInfo,nio) = GetDialogInfo EdDialogId io
-
- SetTextFields :: Int String Descriptor Record IO ->IO
- SetTextFields infoid s d rec io
- = ChangeDialog EdDialogId dialogchanges io
- where
- dialogchanges = [ChangeDynamicText infoid s : [ChangeEditText id f \\ id <- [0.. length d - 1] & f <- rec]]
-
- // Handling mouse clicks in database window
-
- MouseSelectItem :: MouseState DataBase IO -> (DataBase, IO)
- MouseSelectItem ((_,mvpos), ButtonDown, _) (state=:{records,descriptor,selection}, files) io
- | isEmpty records = ((state, files), io)
- | otherwise = (({state & selection=index},files),ChangeSelection state selection index io)
- where
- index = toRecCo descriptor mvpos
- MouseSelectItem _ database io
- = (database, io)
-
- // Drawing utilities
-
- DbPictureDomain :: State Int Int -> PictureDomain
- DbPictureDomain state=:{descriptor=d,records,dw,fw} fr to
- | (right-left,bottom-top) < MinDbDomainSize
- = ((~whiteMargin, 0),(~whiteMargin+width,height))
- | otherwise = ((left ,top),( right,bottom))
- where
- (width,height) = MinDbDomainSize
- whiteMargin = DbFont.width
- ((left,top),(right,bottom)) = ( (~whiteMargin ,toPicCo d fr)
- , (dw + MaxWidth DbFont.font [Separator] + fw + whiteMargin,toPicCo d to)
- )
-
- UpdateDbDomain :: DataBase IO -> (DataBase,IO)
- UpdateDbDomain db=:(state,files) io
- # (db,io) = ChangePictureDomain RecordWindowId (DbPictureDomain state 0 (max (length state.records) 1)) db io
- (db,io) = DrawInWindowFrame RecordWindowId UpdateRecordWindow db io
- (db,io) = MakeSelectionVisible db io
- = (db,io)
-
- UpdateRecordWindow :: UpdateArea DataBase -> (DataBase, [DrawFunction])
- UpdateRecordWindow domains db=:(state=:{records=recs,descriptor=descr,selection}, _)
- = (db,[SetFont DbFont.font : flatten (map Update domains)] ++ HiliteSelection state selection)
- where
- Update domain=:((_,top),(_,bottom))
- | isEmpty recs = [EraseRectangle domain]
- | otherwise = [EraseRectangle domain, MovePenTo (0,topofvisiblerecs) : map (DrawRec descr) (recs%(toprec,botrec))]
- where
- topofvisiblerecs= toPicCo descr toprec
- toprec = toRecCo descr top
- botrec = toRecCo descr (dec bottom)
-
- DrawRec descr rec
- = seq (drawLine "" ++ flatten [drawLine (d +++ Separator +++ f) \\ d<-normwidth descr & f<-rec])
- where
- normwidth descr = [f +++ toString (spaces ((maxList (map (size ) descr)) - size f)) \\ f <- descr]
- drawLine s = [DrawString s,MovePen (~(FontStringWidth s DbFont.font),DbFont.height)]
-
- ChangeSelection:: State Int Int IO -> IO
- ChangeSelection state=:{descriptor=descr,records,editinfoid} old new io
- # io = DrawInWindow RecordWindowId (HiliteSelection state old ++ HiliteSelection state new) io
- io = SetTextFields editinfoid infostring descr (records!!new) io
- = io
- where
- infostring = "Current Rec Nr: "+++toString new
-
- HiliteSelection :: State Int -> [Picture -> Picture]
- HiliteSelection s i
- = [ SetPenMode HiliteMode, FillRectangle (DbPictureDomain s i (inc i)), SetPenNormal, SetPenColour BlackColour ]
-
- // Switching between picture coordinates and indices in the list of records ('record coordinates')
-
- toPicCo:: Descriptor Int -> Int
- toPicCo descr n = n * (inc (length descr) * DbFont.height)
-
- toRecCo:: Descriptor Int -> Int
- toRecCo descr n = n / (inc (length descr) * DbFont.height)
-
- // Various useful functions
-
- closeDbDialogs io = seq (map CloseDialog [FieldDialogId,EdDialogId]) io
-
- radioitems firstid titles = [RadioItem id t Able (\ _ x -> x) \\ id <- [firstid..] & t <- titles]
-
- MaxWidth font [] = 0
- MaxWidth font list = maxList (FontStringWidths list font)
-
- // functions that should be library functions
-
- seqIO fs = seq (map uncurry fs) // should be in deltaEventIO, will be obsolete with new IO-library
-
-
- Cancel :== "Cancel"
- OK :== "OK"
-
- inputdialog name width fun s io
- = (s,OpenDialog dialogdef io)
- where
- dialogdef = CommandDialog dlgId name [] okId
- [ StaticText nameId Left (name+++": "),EditText inputId (RightTo nameId) width 1 ""
- , DialogButton cancelId (Below inputId) Cancel Able cancel
- , DialogButton okId (RightTo cancelId) OK Able (ok fun)
- ]
- ok fun dlginfo s io = fun (GetEditText inputId dlginfo) s (CloseDialog dlgId io)
- [dlgId,nameId,inputId,cancelId,okId:_] = [0..]
-
- warn info fun s io
- # (choiceId,s,io) = OpenNotice warningdef s io
- | choiceId == cancelId = (s,io)
- | otherwise = fun s io
- where
- warningdef = Notice info (NoticeButton cancelId Cancel) [NoticeButton okId OK]
- cancelId = 0
- okId = 1
-
- cancel _ s io = (s, CloseActiveDialog io)
-