Thursday, September 20, 2012

Tutorial 17 - A Simple File Manager for Codea



17.1 The Main Class

         
Tutorial 16 explained the various ways that data can be stored using Codea. In order to facilitate the loading and storing of data for our level generator we created a simple file management class.
    
This version handles Local Data, Project Data, Project Information and Global Data. Remember that all of this data is stored in string format using a pList which is accessed by a unique string called the key.
    
The File Manager is constructed using two ListScroll objects (a class written by sanit over on the Codea Forum), a text box showing the value of the selected key and a MenuBar object (a class written by dave1707 also from the Codea Forum).
   
The Main class is used to load some test data into the four data stores and then instantiate the FileManager class. We have provided stubs for the functions called when the MenuBar items are tapped. The only menu item which has been implemented is delete file. WARNING - file deletion can NOT be undone and there is no confirmation required to delete a file so back up your data and use this class with care (or delete this functionality). Feel free to delete the test data which is loaded in setup(), this will be recreated every time you run the App.
  
The data you need to take particular care with is Global Data as this may have been stored by another Codea App. For example, if you have used Spritely (the sprite creation example program which comes with Codea) then any sprites that you have created will be stored in global data.

The delete function has not been implemented for Project Info because there is no way to determine what keys are in there (i.e. there is no listProjectInfo function).

To delete a file we just assign the value of the selected key to nil. We will provide examples of the load and save function when we incorporate FileManager into our Tower Defence Level Generator (dGenerator).
   
The line FileManager:init() will setup your FileManager class and prefetch the keys for each persistent data store.
       
The class handles both portrait and landscape orientation. The orientationChanged() function adjusts the FileManager position when the iPad is rotated. It works but not very well as ListScroll wasn't designed to handle this. You may have to drag the ListScroll box to show the contents. We haven't had a chance to modify this.
      
-- FileManager
--
-- Codea FileManager v1.1
--
-- A simple file manager to examine the Codea provided
-- persistent storage. Currently handles Local Data, Project Data,
-- Project Information and Global Data.
--
-- This App demonstrates how to use this class in your project.
--
-- Reefwing Software
-- 15 September 2012
--
-- WARNING - FILE DELETION CAN NOT BE UNDONE AND DOES NOT ASK FOR CONFIRMATION.
-- USE WITH EXTREME CAUTION AND BACK UP YOUR DATA BEFORE USING THIS
-- FILE MANAGER CLASS.
supportedOrientations(ANY)
displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
-- Load test data to illustrate the FileManager class
-- First save some ProjectInfo data. This is specific to the App.
saveProjectInfo("Description", "File Manager for Codea v"..version)
saveProjectInfo("Author", "Reefwing Software")
saveProjectInfo("Date", "15 September, 2012")
saveProjectInfo("Version", version)
saveProjectInfo("Comments", "Original Release.")
print("-- Codea File Manager v"..version.."\n")
-- Save local data, this is accessible from within this App on your device.
saveLocalData("localDataKey", "Some local data. For example a high score.")
-- Save project data, this is accessible from within this App but isn't device specific.
saveProjectData("projectDataKey", "Some project data. For example level or map info.")
-- Save global data, this is accessible from any Codea App on this device.
saveGlobalData("globalDataKey", "Some global data. For example images from spritely or data from a level generator like dGenerator.")
-- Initialise the FileManager class
FileManager:init()
end
-- Menu Bar Call Back Methods
function cancelFileManager()
-- Custom implementation goes here
end
function aboutFileManager()
-- Custom implementation goes here
end
function saveFile()
-- Custom implementation goes here
end
function loadFile()
-- Custom implementation goes here
end
function deleteFile()
if currentDirectory == nil then
-- No Directory Selected
elseif currentDirectory == ProjectData and projectKeyTable[currentKey] ~= nil then
saveProjectData(projectKeyTable[currentKey], nil)
loadProjectKeys()
displayKeys(projectKeyTable)
currentKey = 1
valueString = string.truncate(readProjectData(projectKeyTable[currentKey]) or "nil",150)
elseif currentDirectory == ProjectInfo and infoKeyTable[currentKey] ~= nil then
-- delete is not currently supported for ProjectInfo because we can't
-- get a list of the keys in this pList.
elseif currentDirectory == LocalData and localKeyTable[currentKey] ~= nil then
saveLocalData(localKeyTable[currentKey], nil)
loadLocalKeys()
displayKeys(localKeyTable)
currentKey = 1
valueString = string.truncate(readLocalData(localKeyTable[currentKey]) or "nil",150)
elseif currentDirectory == GlobalData and globalKeyTable[currentKey] ~= nil then
saveGlobalData(globalKeyTable[currentKey], nil)
loadGlobalKeys()
displayKeys(globalKeyTable)
currentKey = 1
valueString = string.truncate(readGlobalData(globalKeyTable[currentKey]) or "nil",150)
end
end
-- View Handling
function orientationChanged(newOrientation)
-- Update ListScroll co-ordinates for new orientation
local y = HEIGHT - 500
if directoryList ~= nil then
directoryList.pos.y = y
end
if dataKeyList ~= nil then
dataKeyList.pos.y = y
end
-- Update Menu Bar co-ordinates for new orientation
y = HEIGHT - 80
if b1tab ~= nil then
for i = 1, #b1tab do
b1tab[i].y = y
end
end
y = HEIGHT - 110
if b2tab ~= nil then
for i = 1, #b2tab do
b2tab[i].y = y
y = y - 30
end
end
end
function draw()
-- This sets a black background color
background(0)
-- Draw the FileManager
FileManager:draw()
end
-- Touch Handler
function touched(touch)
-- Handle FileManager touches
FileManager:touched(touch)
end
view raw Main.lua hosted with ❤ by GitHub

17.2 The FileManager Class

             
Most of the hard work is done in this class. We start off by defining variables to represent the four supported Directory Types to make the code easier to read. The init() function creates the two ListScrolls (one for the list of Directories called directoryList and one for the keys it contains called dataKeyList).
     
We then pre-load the keys for Project Data, Local Data and Global Data (we already know the Project Info keys since we loaded them). We set the current directory to Project Info and set the current key to the first one. There is a variable called valueString which we use to contain the value of the selected string. Only the first 150 characters of the saved value are displayed. The last thing we do in init() is setup the MenuBar.
     
One of the modifications we had to make to the MenuBar class was to prevent touches on the ListScroll when a sub menu is displayed (otherwise you could accidentally delete the wrong file).
       
-- Define Directory Types
ProjectInfo = 1
ProjectData = 2
LocalData = 3
GlobalData = 4
-- Class version
version = 1.1
-- Codea FileManager v1.1
--
-- A simple file manager to examine the Codea provided
-- persistent storage. Currently handles Local Data, Project Data,
-- Project Information and Global Data.
--
-- Reefwing Software
-- 15 September 2012
--
-- This class requires the ListScroll and TextItem class written by @sanit and
-- the MenuBar class written by @dave1707 (with some modifications).
--
-- WARNING - FILE DELETION CAN NOT BE UNDONE AND THE CLASS DOES NOT ASK FOR CONFIRMATION.
-- USE WITH EXTREME CAUTION AND BACK UP YOUR DATA BEFORE USING THIS
-- FILE MANAGER CLASS.
FileManager = class()
function FileManager:init()
-- Initialise the FileManager parameters and
-- load up the currently supported data stores.
directoryList = ListScroll(vec2(25, HEIGHT - 500), 200, 400)
directoryContents = {"Project Info", "Project Data", "Local Data", "Global Data"}
for i = 1, #directoryContents do
local item = TextItem(i, directoryContents[i])
if i == 1 then
-- pre select Project Info "Directory"
item.selected = true
end
directoryList:add(item)
end
currentDirectory = ProjectInfo
dataKeyList = ListScroll(vec2(227, HEIGHT - 500), 260, 400)
-- Pre-fetch results
infoKeyTable = {"Description", "Author", "Date", "Version", "Comments"}
loadProjectKeys()
loadLocalKeys()
loadGlobalKeys()
displayKeys(infoKeyTable)
currentKey = 1
valueString = readProjectInfo(infoKeyTable[currentKey])
-- Setup the MenuBar
MenuSetup()
end
-- Functions to read keys from the available pLists
function loadProjectKeys()
projectKeyTable = listProjectData()
if #projectKeyTable == 0 then
projectKeyTable = {"nil"}
end
end
function loadLocalKeys()
localKeyTable = listLocalData()
if #localKeyTable == 0 then
localKeyTable = {"nil"}
end
end
function loadGlobalKeys()
globalKeyTable = listGlobalData()
if #globalKeyTable == 0 then
globalKeyTable = {"nil"}
end
end
-- This function displays the keys passed in keyTable within the dataKeyList ListScroll
function displayKeys(keyTable)
dataKeyList.items = {}
for i = 1, #keyTable do
local item = TextItem(i, keyTable[i])
if i == 1 then
-- pre select first key in Project Info
item.selected = true
end
dataKeyList:add(item)
end
end
-- Setup the menu items at the top of the ListScrolls
-- Note that the call back functions are in the Main tab.
function MenuSetup()
pointer2 = nil
pointer3 = nil
b1tab = {}
b2tab = {}
local y = HEIGHT - 80
-- Create tables for main menu bar
table.insert(b1tab, MenuBar(80, y, 100, 30, "File", b2tab))
table.insert(b1tab, MenuBar(180, y, 100, 30, "Cancel", cancelFileManager))
table.insert(b1tab, MenuBar(280, y, 100, 30, "About", aboutFileManager))
pointer1 = b1tab
y = HEIGHT - 110
-- Create table for sub menu items
table.insert(b2tab, MenuBar(80, y, 100, 30, "Save", saveFile))
table.insert(b2tab, MenuBar(80, y - 30, 100, 30, "Load", loadFile))
table.insert(b2tab, MenuBar(80, y - 60, 100, 30, "Delete", deleteFile))
-- Global variables to prevent touches on scroll list
-- while subMenu is visible and to update the key list
-- if the directory changes.
subMenuShown = false
directoryChanged = false
end
-- Update functions for List Scrolls
function updateDirectorySelection()
local dlsi = directoryList.selectedItem
if dlsi ~= currentDirectory then
if dlsi == nil then
-- No Items Selected assign to default
dlsi = ProjectInfo
displayKeys(infoKeyTable)
elseif dlsi == ProjectData then
displayKeys(projectKeyTable)
elseif dlsi == ProjectInfo then
displayKeys(infoKeyTable)
elseif dlsi == LocalData then
displayKeys(localKeyTable)
elseif dlsi == GlobalData then
displayKeys(globalKeyTable)
end
currentDirectory = dlsi
directoryChanged = true
end
end
function updateKeySelection()
local dksi = dataKeyList.selectedItem
if dksi ~= currentKey or directoryChanged then
if directoryChanged then
dataKeyList.selectedItem = 1
currentKey = 1
directoryChanged = false
else
currentKey = dksi
end
if currentDirectory == nil then
-- No Directory Selected
valueString = ""
elseif currentDirectory == ProjectData and projectKeyTable[currentKey] ~= nil then
valueString = readProjectData(projectKeyTable[currentKey]) or "nil"
elseif currentDirectory == ProjectInfo and infoKeyTable[currentKey] ~= nil then
valueString = readProjectInfo(infoKeyTable[currentKey]) or "nil"
elseif currentDirectory == LocalData and localKeyTable[currentKey] ~= nil then
valueString = readLocalData(localKeyTable[currentKey]) or "nil"
elseif currentDirectory == GlobalData and globalKeyTable[currentKey] ~= nil then
valueString = readGlobalData(globalKeyTable[currentKey]) or "nil"
end
valueString = string.truncate(valueString, 150)
end
end
function FileManager:draw()
-- Codea does not automatically call this method
-- Handle Directory Selection
updateDirectorySelection()
updateKeySelection()
directoryList:draw()
dataKeyList:draw()
-- Draw Menu Bar
pushStyle()
fill(0,0,255,255)
rect(25, HEIGHT - 98, 725, 36)
popStyle()
MenuBar:menu1()
-- Draw the results window
pushStyle()
textWrapWidth(250)
textMode(CORNER)
font("Courier")
fontSize(24)
fill(255)
rect(489, HEIGHT - 500, 260, 400)
fill(0)
local _,h = textSize(valueString)
text(valueString, 496, HEIGHT - 100 - h)
popStyle()
end
function FileManager:touched(touch)
-- Codea does not automatically call this method
if touch.state == BEGAN then
MenuBar:check(touch)
end
if pointer2 ~= nil then
-- Sub menu visible
subMenuShown = true
elseif subMenuShown and touch.state == ENDED then
subMenuShown = false
else
-- Sub menu is not visible so handle scroll touches
directoryList:touched(touch)
dataKeyList:touched(touch)
end
end
-- String Helper
function string.truncate(str, num)
if string.len(str) > num then
str = string.sub(str,1,150) .. "..."
end
return str
end
view raw FileManager.lua hosted with ❤ by GitHub

17.3 Download the Code

          
You can download all of the code for this program using the following links:
  1. FileManager Complete Code v1.1 - All of the code in one file.
  2. Main.lua - The Main tab class only.
  3. FileManager.lua - The FileManager v1.1 class only.
  4. ListScroll.lua - The ListScroll class from sanit.
  5. TextItem.lua - The TextItem class used in ListScroll from sanit.
  6. MenuBar.lua - The MenuBar class v1.1 from dave1707 (modified) .
Next up we will incorporate FileManager into our Tower Defence Level Generator.

No comments:

Post a Comment