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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
17.3 Download the Code
You can download all of the code for this program using the following links:
- FileManager Complete Code v1.1 - All of the code in one file.
- Main.lua - The Main tab class only.
- FileManager.lua - The FileManager v1.1 class only.
- ListScroll.lua - The ListScroll class from sanit.
- TextItem.lua - The TextItem class used in ListScroll from sanit.
- 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