\ io \ \ io_file \ settings ("preference") file support for io. \ \ \ note: this component is only loaded if the constant io_file? (from the file \ io_config) is set to true. Access to the file I/O is via the qwerty keyboard \ or, if io_turnkey? is true, via the menubar (see file modules:io_menus). \ \ a single file stores a single "scene," but one or more "scenes" (each a \ complete descripton of the system's state) can be read into memory to make \ a "set." \ \ \ see the file io_top for more information. \ \ \ constructor: Han-earl Park \ copyright 2008 buster & friends' C-ALTO Labs \ \ www.busterandfriends.com/io \ \ (Edinburgh, November 1996 - \ (London, August 1997 - \ (Den Haag, October 1997 - \ (Valencia, March 1999 - \ (Southampton, May 2000 - \ (Cork, April 2006 - \ \ (Cork, October 2008 - \ \ REV: 0.0.1 alpha (Southampton, October 2000) \ REV: 0.0.1 beta (Southampton, November 2000) \ REV: 0.0.1 alpha++ (Southampton, July 2004) \ REV: 0.0.1 beta++ (Cork, May 2010) \ \ \ MOD: HeP 06/01/00 Add the io_file module. \ REV: 0.0.1 alpha * Unused * \ REV: 0.0.1 beta * Unused * \ MOD: HeP 04-10-04 Sucessfully read/write the file from/to disk. \ MOD: HeP 04-11-04 Parse file, and be graceful when handed a file with an \ incorrect file format. \ Store text headers and format info in $file-format. Maybe \ better to use a struct? \ Tested read/write of data (output midi channel). \ MOD: HeP 04-12-04 Tested reading and writing all supported data. \ MOD: HeP 04-13-04 Add saving of performance duration, output reverb and \ device id# settings. \ Rewrite some sections of the code to make later updates \ a little less painful. \ Don't use ftruncate. (task-floatingpoint has redefined the \ word!?!) \ Implement menu bar functions. \ MOD: HeP 04-14-04 Get rid of "New" file menu item. \ MOD: HeP 04-14-04 Add SAVED.CHANGE. \ MOD: HeP 04-15-04 Append file name to window title. \ Use SFGETFILE instead of DILAOG.GET.FILE -- the latter \ doesn't seem to support double clicking the doc. \ MOD: HeP 04-25-04 Start simplified version using IO.SCENE (subclass of \ FILE.ELMNTS). See the file modules:io_file_scene. \ MOD: HeP 04-29-04 Make IO.SCENE a subclass of MAC.FILE.ELMNTS. \ MOD: HeP 04-30-04 Closes the file after each file operation so that multiple \ copies of the same scene may be added to the scene-list. \ Sucessfully read from/write to disk using the new code. \ MOD: HeP 05-01-04 Move all includes (and conditional includes) from the \ individual files to load_io. \ If menus are not loaded, we access the file I/O from the \ qwerty keyboard. \ MOD: HeP 05-03-04 Add SELECT.SCENE, NEXT.SCENE and PREV.SCENE. (I hope this \ is not the start of io getting hopelessly complicated.) \ Select scenes -- not perfectly elegant but it works. \ MOD: HeP 05-14-04 Add ADD.SCENE and DELETE.SCENE. \ New scenes get inserted at the current rather than being \ adding to the end of the list. \ MOD: HeP 05-16-04 Fix file menu bug that occured when close was called in \ succession. Source of bug was IO.PRINT.FILE.INFO which \ PRINT:ed objects. This action calls ?terminal (?pause) \ which could initiate a menu action. Bug only operates when \ io_test? is true. See modules:io_menus for fix. \ Modify words that track file save state (SAVED.CHANGE, \ etc) to use OB.IO.SCENE's save state. \ MOD: HeP 05-17-04 Fix bug that set UNSAVED.CHANGES? to true when switching \ scenes. See io_screen and modules:io_file_glue. \ MOD: HeP 05-19-04 Gives user the option to save all unsaved scenes at quit. \ IO.FILE.MAKE.DEFAULT works even when no scenes are open. \ Fix bug in file_elmnts_mac that caused erroneous error \ reports under certain conditions. \ MOD: HeP 05-22-04 Call UNSAVED.CHANGE in IO.FILE.NEW. \ IO.FILE.CLOSE gives option to save before closing. \ MOD: HeP 05-24-04 Changes in modules:io_file_scene to seperate tracking \ unsaved changes from the file's save state. \ MOD: HeP 06-18-04 Save current scene to memory (but not to disk) when prior \ to switching scenes. \ IO.FILE.REVERT checks if scene has been saved to disk. \ Handle user canceling of file actions. \ MOD: HeP 06-27-04 Handle file format errors in class definitions. \ MOD: HeP 06-30-04 Fix UNSAVED? which used to return false if not scene was \ open. \ MOD: HeP 07-01-04 Remove SAVED.CHANGE and UNSAVED.CHANGE from the file menu \ functions (this was getting complex and buggy). \ Simplify this code by have the file menu updated after \ _every_ file action by having the menu component query the file/scene \ file component's state. \ DISPLAY.FILE.NAME indicates the file's save state. Call it \ from UNSAVED.CHANGE. \ MOD: HeP 07-01-04 Warn user before "revert to saved..." \ MOD: HeP 07-02-04 IO.DEFAULT will attempt to open the "io preferences" file. \ New scene will be based on the "io preferences" file. \ IO.FILE.MAKE.DEFAULT will not create a new file. \ MOD: HeP 07-03-04 IO.STOP will switch to next "scene." \ REV: 0.0.1 a ++ __________________________________________________________ \ REV: 0.0.1 b ++ __________________________________________________________ \ Version for performance at Blackrock Castle Observatory, \ Cork, Ireland, May 25, 2010. \ \ \ ToDo: get rid of extraneous file not found warning when setting to default. \ BUG: open duration also loads time value rather than random... \ BUG: when one or more scenes are "open" and others are not, the hours ctrl \ may remain grayed out. anew task-io_file \ keep tracks of files variable current-scene variable current-scene-indx \ default scene ob.io.scene default-scene \ read and write files : (IO.FWRITE) { scene -- } scene_#param set.many: scene \ pdur.mode->file scene_pdur_mode put: scene pdur->file scene_pdur_sec put: scene \ ui.ctrl->file scene_ui_ctrl put: scene ui.c1->file scene_ui_c1 put: scene ui.c2->file scene_ui_c2 put: scene ui.channel->file scene_ui_channel put: scene \ output.device->file scene_output_device put: scene output.channel->file scene_output_channel put: scene output.device.id->file scene_output_device_id put: scene output.pan->file scene_output_pan put: scene output.rev->file scene_output_rev put: scene \ clear.input: scene \ input.#enabled->file scene_input_#enabled put: scene \ io_#input 0 DO i input.enabled?->file i scene_input_enabled? put.input: scene i input.device->file i scene_input_device put.input: scene i input.channel->file i scene_input_channel put.input: scene i input.bend->file i scene_input_bend put.input: scene LOOP ; : (IO.FREAD) { scene -- } scene_pdur_mode get: scene file->pdur.mode scene_pdur_sec get: scene file->pdur \ scene_ui_ctrl get: scene file->ui.ctrl scene_ui_c1 get: scene file->ui.c1 scene_ui_c2 get: scene file->ui.c2 scene_ui_channel get: scene file->ui.channel \ scene_output_device get: scene file->output.device scene_output_channel get: scene file->output.channel scene_output_device_id get: scene file->output.device.id scene_output_pan get: scene file->output.pan scene_output_rev get: scene file->output.rev \ io_#input 0 DO \ \ turn off all parsers first to avoid channel number conflicts \ i false file->input.enabled? LOOP \ io_#input 0 DO i scene_input_device get.input: scene i swap file->input.device i scene_input_channel get.input: scene i swap file->input.channel i scene_input_bend get.input: scene i swap file->input.bend LOOP \ io_#input 0 DO \ \ now that channels are set, turn the parsers on \ i scene_input_enabled? get.input: scene i swap file->input.enabled? LOOP ; : IO.FWRITE ( -- ) current-scene @ (io.fwrite) ; : IO.FREAD ( -- ) current-scene @ (io.fread) ; \ lower level file operations : IO.FNEW ( -- flag ) current-scene-indx @ 1+ instantiate.scene dup IF current-scene ! current-scene-indx @ 1+ current-scene-indx ! true THEN ; : IO.FOPEN ( -- flag , create new and open file ) current-scene-indx @ 1+ instantiate.scene dup IF dup open: [] \ -- flag IF current-scene ! current-scene-indx @ 1+ current-scene-indx ! true ELSE deinstantiate.scene false THEN THEN ; \ *** are the following two words redundant? *** : IO.FOPEN.PREFS ( -- flag , open default preference file ) current-scene @ open.default: [] ; : IO.FNEW.PREFS ( -- flag , create new default preference file ) current-scene @ save.default: [] ; : IO.FCLOSE ( -- , just close file ) current-scene @ close: [] ; : IO.FFREE ( -- , close and clearup file ) current-scene @ deinstantiate.scene ; : IO.FSAVE ( -- flag ) current-scene @ save: [] ; : IO.FSAVE.AS ( -- flag , create copy maybe with same file name ) current-scene @ dup close: [] save.as: [] ; : IO.FREVERT ( -- flag ) current-scene @ revert: [] ; \ window title create $window_title 1 io.title count nip + 3 + 31 + 2 + allot : $APPEND.TO.WINDOW.TITLE ( saved? changed? addr count -- ) hmsl-window @ IF 0 $window_title c! \ io.title count $window_title $append " : " count $window_title $append \ $window_title $append \ IF drop 19 \ character for unsaved change ELSE IF 0 ELSE 215 \ character for unsaved THEN THEN ?dup IF $window_title BL $append.char \ $string char -- $window_title swap $append.char \ $string char -- THEN \ hmsl-window @ $window_title setWTitle() ELSE 2drop 2drop THEN ; : DISPLAY.FILE.NAME ( -- ) current-scene @ ?dup IF dup ?saved: [] over ?changed: [] rot get.file.name: [] count $append.to.window.title ELSE hmsl-window @ ?dup IF io.title setWTitle() THEN THEN ; \ testing io_test? .IF : IO.PRINT.FILE.INFO ( -- ) >newline ." current-scene-indx = " current-scene-indx @ . cr current-scene @ ?dup IF print: [] THEN ; .THEN \ scenes : #SCENES@ ( -- n , number of scenes in memory ) many: scene-list ; : SCENE.NAME ( indx -- $ | false ) dup 0 #scenes@ 1- within? IF get: scene-list get.file.name: [] ELSE drop false THEN ; \ the following correspond to the deferred words declared in io_glob and \ configured in io_top. If io_turnkey? is true, these words get redefined in \ io_menus. : (SELECT.SCENE) ( indx -- ) current-scene @ IF io.fwrite \ save current state into memory, but not to disk THEN \ #scenes@ IF #scenes@ mod \ dup current-scene-indx ! get: scene-list current-scene ! \ io.fread ELSE drop 0 current-scene ! THEN \ display.file.name ; : (NEXT.SCENE) ( -- ) current-scene-indx @ 1+ (select.scene) ; : (PREV.SCENE) ( -- ) current-scene-indx @ #scenes@ 1- + abs (select.scene) ; : (ADD.SCENE) ( -- , stub ) ; : (DELETE.SCENE) ( -- , stub ) ; \ keep track of changes : (UNSAVED.CHANGE) ( -- ) current-scene @ ?dup IF changed: [] THEN \ display.file.name \ MOD: 07-01-04 ; : (SAVED.CHANGE) ( -- ) ; \ *** redundant next to CHANGED? below? *** : (UNSAVED.CHANGE?) ( -- flag , true if current scene is unsaved ) current-scene @ dup IF ?changed: [] THEN ; : UNSAVED? ( -- flag , true if not on disk ) current-scene @ ?dup IF ?saved: [] NOT ELSE true \ no scene, so could not have been saved to disk THEN ; \ *** redundant next to (UNSAVED.CHANGE?) above? *** : CHANGED? ( -- flag , true if different from version on disk ) current-scene @ dup IF ?changed: [] THEN ; : ANY.UNSAVED.CHANGES? ( -- flag , true if at least one unsaved scene ) many: scene-list dup IF FALSE \ -- flag swap 0 DO i get: scene-list dup ?saved: [] NOT \ unsaved? swap ?changed: [] \ changed? OR IF nip TRUE \ -- flag LEAVE THEN LOOP THEN ; \ menu functions : (IO.FILE.NEW) ( -- ) io.fnew IF io.fwrite \ ADD.SCENE \ \ unsaved.change \ MOD: 07-01-04 removed! THEN ; : IO.FILE.NEW ( -- ) io.default \ (io.file.new) \ display.file.name ; : IO.FILE.OPEN ( -- ) io.fopen IF io.fread io.fclose \ ADD.SCENE \ \ saved.change \ MOD: 07-01-04 removed! THEN \ display.file.name ; : (IO.FILE.CLOSE) ( -- ) io.ffree \ DELETE.SCENE \ 0 current-scene ! \ #scenes@ IF PREV.SCENE ELSE -1 current-scene-indx ! THEN ; : IO.FILE.SAVE.CHANGES ( -- ) current-scene @ unsaved.change? AND IF " Save current scene file before closing?" dialog.y/n/c CASE 1 OF io.fwrite io.fsave drop \ IF \ saved.change \ MOD: 07-01-04 removed! \ THEN io.fclose ENDOF 2 OF false fcancel ! ENDOF 3 OF true fcancel ! ENDOF ENDCASE THEN ; : IO.FILE.CLOSE ( -- ) current-scene @ IF ?shift IF (io.file.close) ELSE io.file.save.changes \ fcancel @ NOT \ not canceled by user? IF (io.file.close) THEN THEN THEN \ display.file.name ; : (IO.FILE.SAVE) ( -- ) io.fwrite io.fsave drop \ IF \ saved.change \ MOD: 07-01-04 removed! \ THEN io.fclose ; : IO.FILE.SAVE ( -- ) current-scene @ 0= IF (io.file.new) (io.file.save) \ fcancel @ \ save canceled by user? IF (io.file.close) THEN ELSE (io.file.save) THEN \ display.file.name ; : IO.FILE.SAVE.AS ( -- ) current-scene @ 0= IF io.file.new THEN \ io.fwrite io.fsave.as drop \ IF \ saved.change \ MOD: 07-01-04 removed! \ THEN io.fclose \ display.file.name ; : (IO.FILE.REVERT) ( -- ) io.frevert IF io.fread \ \ saved.change \ MOD: 07-01-04 removed! THEN io.fclose \ display.file.name ; : IO.FILE.REVERT ( -- ) current-scene @ IF current-scene @ ?saved: [] IF ?shift IF (io.file.revert) ELSE " Do you want to discard unsaved changes?" dialog.a/b IF (io.file.revert) THEN THEN THEN THEN ; : (IO.FILE.MAKE.DEFAULT) ( -- ) io.fwrite io.fnew.prefs drop \ *** handle error? *** ; : IO.FILE.MAKE.DEFAULT ( -- ) current-scene @ IF (io.file.make.default) ELSE default-scene current-scene ! (io.file.make.default) 0 current-scene ! THEN ; : IO.FILE.MAKE.DEFAULT ( -- ) current-scene @ \ default-scene current-scene ! (io.file.make.default) \ current-scene ! ; : SAVE.ALL.SCENES ( -- flag , true if saved without user cancel ) \ { | canceled? error? -- } \ false -> canceled? false -> error? \ many: scene-list 0 DO i get: scene-list dup ?saved: [] NOT IF save: [] dup 0= IF drop \ fcancel @ IF true -> canceled? ELSE " IÕm having problems saving some files. Continue to quit anyway?" dialog.a/b \ NOT -> canceled? THEN THEN ELSE drop THEN \ canceled? IF LEAVE THEN LOOP \ canceled? NOT \ -- flag ; : (IO.FILE.QUIT) ( -- ) save.all.scenes \ -- flag quit-hmsl ! ; : IO.FILE.QUIT ( -- ) any.unsaved.changes? IF " Save all open unsaved scene files before quitting?" dialog.y/n/c CASE 1 OF (io.file.quit) ENDOF 2 OF true quit-hmsl ! ENDOF 3 OF false quit-hmsl ! ENDOF ENDCASE ELSE true quit-hmsl ! THEN ; \ qwerty \ \ use keyboard if io_menus is not loaded io_turnkey? NOT .IF : FILE.QWERTY.HELP ( -- ) cr ." [ ^N ] New File" cr ." [ ^O ] Open File" cr ." [ ^W ] Close File" cr ." [ ^S ] Save File" cr ." [ ^A ] Save File As" cr ." [ ^R ] Revert to Saved" cr cr ." [ + ] Previous Scene" cr ." [ - ] Next Scene" cr ; : SCENE.QWERTY.VECTOR ( char -- flag ) CASE dup dup ascii - = swap ascii _ = OR ?OF prev.scene true ENDOF \ dup dup ascii + = swap ascii = = OR ?OF next.scene true ENDOF \ false swap ENDCASE ; : FILE.I/O.QWERTY.VECTOR ( char -- flag ) ?shift IF CASE ascii N OF io.file.new true ENDOF ascii O OF io.file.open true ENDOF ascii W OF io.file.close true ENDOF ascii S OF io.file.save true ENDOF ascii A OF io.file.save.as true ENDOF ascii R OF io.file.revert true ENDOF \ false swap ENDCASE ELSE drop false THEN ; : FILE.QWERTY.VECTOR ( char -- ) dup (config.qwerty.vector) over scene.qwerty.vector OR swap file.i/o.qwerty.vector OR \ 0= IF config.qwerty.help file.qwerty.help THEN ; : FILE.QWERTY.FUNC ( char obj -- ) drop file.qwerty.vector ; : CONFIG.DRAW.FUNC+ ( -- ) 'c file.qwerty.vector key-parser ! ; .THEN \ default : IO.FILE.DEFAULT ( -- flag , open the default scene ) open.default: default-scene dup IF default-scene (io.fread) THEN close: default-scene ; \ setup & clearup : IO.FILE.INIT ( -- ) test" IO.FILE.INIT" \ io.file.scene.init \ 0 current-scene ! -1 current-scene-indx ! \ new: default-scene \ [ io_turnkey? NOT .IF ] 'c file.qwerty.func put.key.function: config-screen 'c config.draw.func+ put.draw.function: config-screen [ .THEN ] ; : IO.FILE.TERM ( -- ) test" IO.FILE.TERM" \ io.file.scene.term \ 0 current-scene ! -1 current-scene-indx ! \ free: default-scene ; if.forgotten io.file.term