This class contains the bulk of the MIDlet. It creates the UI for navigating the device file system and selecting images to display. It also provides support for file management operations such as delete, rename, and directory creation.
FileSelector
includes a list that is filled by the contents of the directory
being currently explored. When invoked, FileSelector
checks whether the images directory is available on the device and
opens the file browser in that directory. Otherwise, FileSelector
displays a list of available roots. The current directory is stored
in the currentRoot
variable. A null
value indicates the roots set. When the user opens a directory,
its contents are searched for subdirectories and for BMP, JPG, and
PNG image files, which are displayed as the directory's contents.
FileSelector
uses the FileConnection API for all file operations.
To detect IOExceptions
and SecurityExceptions
, file operations are enclosed in try-catch
blocks.
To create this class:
Create the FileSelector.java
class file.
Import the required packages.
import java.io.*; import java.util.*; import javax.microedition.io.*; import javax.microedition.io.file.*; import javax.microedition.lcdui.*;
Set the FileSelector
class to extend the List
class and to implement the CommandListener
and FileSystemListener
interfaces. Create
the Commands
used later to manage the device
file system.
class FileSelector extends List implements CommandListener, FileSystemListener { private final static Image ROOT_IMAGE = ImageViewerMIDlet.makeImage("/root_1.png"); private final static Image FOLDER_IMAGE = ImageViewerMIDlet.makeImage("/folder1.png"); private final static Image FILE_IMAGE = ImageViewerMIDlet.makeImage("/file1.png"); private final OperationsQueue queue = new OperationsQueue(); private final static String FILE_SEPARATOR = (System.getProperty("file.separator") != null) ? System.getProperty("file.separator") : "/"; private final static String UPPER_DIR = ".."; private final ImageViewerMIDlet midlet; private final Command openCommand = new Command("Open", Command.ITEM, 1); private final Command createDirCommand = new Command("Create new directory", Command.ITEM, 2); private final Command deleteCommand = new Command("Delete", Command.ITEM, 3); private final Command renameCommand = new Command("Rename", Command.ITEM, 4); private final Command exitCommand = new Command("Exit", Command.EXIT, 1); private final static int RENAME_OP = 0; private final static int MKDIR_OP = 1; private final static int INIT_OP = 2; private final static int OPEN_OP = 3; private final static int DELETE_OP = 4;
The current
directory is stored in the currentRoot
variable.
A null
value indicates the roots set. Here, the
class also registers itself in the FileSystemRegistry
to listen to new file
systems being added or removed, in which case it will restart itself.
private Vector rootsList = new Vector(); // Stores the current root, if null we are showing all the roots private FileConnection currentRoot = null; private Ticker ticker = new Ticker("Image Viewer"); FileSelector(ImageViewerMIDlet midlet) { super("Image Viewer", List.IMPLICIT); setTicker(ticker); this.midlet = midlet; addCommand(openCommand); addCommand(createDirCommand); addCommand(deleteCommand); addCommand(renameCommand); addCommand(exitCommand); setSelectCommand(openCommand); setCommandListener(this); } void initialize() { queue.enqueueOperation(new ImageViewerOperations(INIT_OP)); FileSystemRegistry.addFileSystemListener(FileSelector.this); } void stop() { queue.abort(); FileSystemRegistry.removeFileSystemListener(this); } void inputReceived(String input, int code) { switch (code) { case RENAME_OP: queue.enqueueOperation(new ImageViewerOperations( input, RENAME_OP)); break; case MKDIR_OP: queue.enqueueOperation(new ImageViewerOperations( input, MKDIR_OP)); break; } } public void commandAction(Command c, Displayable d) { if (c == openCommand) { queue.enqueueOperation(new ImageViewerOperations(OPEN_OP)); } else if (c == renameCommand) { queue.enqueueOperation(new ImageViewerOperations(RENAME_OP)); } else if (c == deleteCommand) { queue.enqueueOperation(new ImageViewerOperations(DELETE_OP)); } else if (c == createDirCommand) { queue.enqueueOperation(new ImageViewerOperations(MKDIR_OP)); } else if (c == exitCommand) { midlet.fileSelectorExit(); } } // Listen for changes in the roots public void rootChanged(int state, String rootName) { queue.enqueueOperation(new ImageViewerOperations(INIT_OP)); }
Use the displayAllRoots
method to display a list of all available
roots.
private void displayAllRoots() { ticker.setString("Image Viewer - [Roots]"); deleteAll(); Enumeration roots = rootsList.elements(); while (roots.hasMoreElements()) { String root = (String) roots.nextElement(); root = root.replace('/', FILE_SEPARATOR.charAt(0)); append(root.substring(1), ROOT_IMAGE); } currentRoot = null; }
Use the createNewDir
method to create a new directory under the
current directory. The FileSystemRegistry
class
provides the listRoots
method, which returns an enumeration
of the roots in the device file system. This includes both logical
and virtual roots.
private void createNewDir() { if (currentRoot == null) { midlet.showMsg("Is not possible to create a new root"); } else { midlet.requestInput("New dir name", "", MKDIR_OP); } } private void createNewDir(String newDirURL) { if (currentRoot != null) { try { FileConnection newDir = (FileConnection) Connector.open( currentRoot.getURL() + newDirURL, Connector.WRITE); newDir.mkdir(); } catch (IOException e) { midlet.showError(e); } displayCurrentRoot(); } } private void loadRoots() { if (!rootsList.isEmpty()) { rootsList.removeAllElements(); } try { Enumeration roots = FileSystemRegistry.listRoots(); while (roots.hasMoreElements()) { rootsList.addElement("/" + (String) roots.nextElement()); } } catch (Throwable e) { midlet.showMsg(e.getMessage()); } }
Use the deleteCurrent
method to delete the currently selected
file.
private void deleteCurrent() { if (currentRoot == null) { midlet.showMsg("Is not possible to delete a root"); } else { int selectedIndex = getSelectedIndex(); if (selectedIndex >= 0) { String selectedFile = getString(selectedIndex); if (selectedFile.equals(UPPER_DIR)) { midlet.showMsg("Is not possible to delete an upper dir"); } else { try { String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/'); FileConnection fileToDelete = (FileConnection) Connector.open( currentRoot.getURL() + tmp, Connector.READ_WRITE); if (!fileToDelete.exists()) { midlet.showMsg("File " + fileToDelete.getName() + " does not exists"); } else { if (getConfirmation("Do you really want to delete " + tmp + "?")) { fileToDelete.delete(); midlet.showMsg(tmp + " deleted."); } else { midlet.showMsg("Operation cancelled."); } } } catch (IOException e) { midlet.showError(e); } catch (SecurityException e) { midlet.showError(e); } displayCurrentRoot(); } } } } boolean getConfirmation(String message) { Object lock = new Object(); Confirm prompt = new Confirm(message, lock, this); synchronized (lock) { try { lock.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); } } // set current displayable, when notification arrives Display.getDisplay(midlet).setCurrent(this); return prompt.getResponse(); }
Use the renameCurrent
method to change the name of the currently
selected file.
private void renameCurrent() { if (currentRoot == null) { midlet.showMsg("Is not possible to rename a root"); } else { int selectedIndex = getSelectedIndex(); if (selectedIndex >= 0) { String selectedFile = getString(selectedIndex); if (selectedFile.equals(UPPER_DIR)) { midlet.showMsg("Is not possible to rename the upper dir"); } else { midlet.requestInput("New name", selectedFile, RENAME_OP); } } } } private void renameCurrent(String newName) { if (currentRoot == null) { midlet.showMsg("Is not possible to rename a root"); } else { int selectedIndex = getSelectedIndex(); if (selectedIndex >= 0) { String selectedFile = getString(selectedIndex); if (selectedFile.equals(UPPER_DIR)) { midlet.showMsg("Is not possible to rename the upper dir"); } else { try { String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/'); FileConnection fileToRename = (FileConnection) Connector.open( currentRoot.getURL() + tmp, Connector.READ_WRITE); if (fileToRename.exists()) { newName = newName.replace(FILE_SEPARATOR.charAt(0), '/'); fileToRename.rename(newName); } else { midlet.showMsg("File " + fileToRename.getName() + " does not exists"); } } catch (IOException e) { e.printStackTrace(); midlet.showError(e); } catch (SecurityException e) { e.printStackTrace(); midlet.showError(e); } displayCurrentRoot(); } } } }
Use the openSelected
method to update the list of files displayed
on the screen. The setFileConnection
method resets the FileConnection
object to point to another file or directory,
thus allowing the reuse of the FileConnection
object for directory traversal.
private void openSelected() { int selectedIndex = getSelectedIndex(); if (selectedIndex >= 0) { String selectedFile = getString(selectedIndex); if (selectedFile.endsWith(FILE_SEPARATOR)) { try { String tmp = selectedFile.replace(FILE_SEPARATOR.charAt(0), '/'); if (currentRoot == null) { currentRoot = (FileConnection) Connector.open( "file:///" + tmp, Connector.READ); } else { currentRoot.setFileConnection(tmp); } displayCurrentRoot(); } catch (IOException e) { midlet.showError(e); } catch (SecurityException e) { midlet.showError(e); } } else if (selectedFile.equals(UPPER_DIR)) { if (rootsList.contains(currentRoot.getPath() + currentRoot.getName())) { displayAllRoots(); } else { try { currentRoot.setFileConnection(UPPER_DIR); displayCurrentRoot(); } catch (IOException e) { midlet.showError(e); } } } else { String url = currentRoot.getURL() + selectedFile; midlet.displayImage(url); } } }
Use the displayCurrentRoot
method to read the contents of the
current directory and display the contents on the screen.
private void displayCurrentRoot() { try { ticker.setString("Image Viewer - [" + currentRoot.getURL() + "]"); // open the root deleteAll(); append(UPPER_DIR, FOLDER_IMAGE); // list all dirs Enumeration listOfDirs = currentRoot.list("*", false); while (listOfDirs.hasMoreElements()) { String currentDir = (String) listOfDirs.nextElement(); if (currentDir.endsWith("/")) { String tmp = currentDir.replace('/', FILE_SEPARATOR.charAt(0)); append(tmp, FOLDER_IMAGE); // always display the platform specific seperator to the user } } // list all png files and dont show hidden files Enumeration listOfFiles = currentRoot.list("*.png", false); while (listOfFiles.hasMoreElements()) { String currentFile = (String) listOfFiles.nextElement(); if (currentFile.endsWith(FILE_SEPARATOR)) { append(currentFile, FOLDER_IMAGE); } else { append(currentFile, FILE_IMAGE); } } listOfFiles = currentRoot.list("*.jpg", false); while (listOfFiles.hasMoreElements()) { String currentFile = (String) listOfFiles.nextElement(); if (currentFile.endsWith(FILE_SEPARATOR)) { append(currentFile, FOLDER_IMAGE); } else { append(currentFile, FILE_IMAGE); } } listOfFiles = currentRoot.list("*.bmp", false); while (listOfFiles.hasMoreElements()) { String currentFile = (String) listOfFiles.nextElement(); if (currentFile.endsWith(FILE_SEPARATOR)) { append(currentFile, FOLDER_IMAGE); } else { append(currentFile, FILE_IMAGE); } } //Making the top item visible. setSelectedIndex(0, true); } catch (IOException e) { midlet.showError(e); } catch (SecurityException e) { midlet.showError(e); } }
Set up a new
class that implements the Operation
interface. In most cases, this
class is used to invoke the private methods of FileSelector
.
private class ImageViewerOperations implements Operation { private final String parameter; private final int operationCode; ImageViewerOperations(int operationCode) { this.parameter = null; this.operationCode = operationCode; } ImageViewerOperations(String parameter, int operationCode) { this.parameter = parameter; this.operationCode = operationCode; } public void execute() { switch (operationCode) { case INIT_OP: String initDir = System.getProperty("fileconn.dir.photos"); loadRoots(); if (initDir != null) { try { currentRoot = (FileConnection) Connector.open( initDir, Connector.READ); displayCurrentRoot(); } catch (Exception e) { midlet.showError(e); displayAllRoots(); } } else { displayAllRoots(); } break; case OPEN_OP: openSelected(); break; case DELETE_OP: deleteCurrent(); break; case RENAME_OP: if (parameter != null) { renameCurrent(parameter); } else { renameCurrent(); } break; case MKDIR_OP: if (parameter != null) { createNewDir(parameter); } else { createNewDir(); } } } }
Set up a new
class that extends the Alert
class and implements
the CommandListener
interface. This class is
used by the getConfirmation
method, which in
turn is used by the deleteCurrent
method to verify that the
user really wants to delete the selected file or directory.
private class Confirm extends Alert implements CommandListener { private Command okCommand = new Command("Yes", Command.OK, 0); private Command nokCommand = new Command("No", Command.EXIT, 0); private boolean isOkResponse; private Object waitLock; Confirm(String message, Object lock, Displayable next) { super("Image Viewer", message, midlet.getLogo(), AlertType.CONFIRMATION); addCommand(okCommand); addCommand(nokCommand); setCommandListener(this); waitLock = lock; Display.getDisplay(midlet).setCurrent(this, next); } public void commandAction(Command command, Displayable display) { if (command == okCommand) { isOkResponse = true; } else if (command == nokCommand) { isOkResponse = false; } synchronized (waitLock) { waitLock.notifyAll(); } } boolean getResponse() { return isOkResponse; } } }
This is a simple utility class that runs in a separate
thread executing commands serially. OperationsQueue
takes objects implementing the Operation
interface, making it independent
of the kinds of commands executed. Operation
implementers
are expected to handle their exceptions locally, since OperationsQueue
discards any exceptions thrown.
To create this class:
Create the OperationsQueue.java
class file.
Import the required classes.
import java.util.*;
Implement the OperationsQueue
class. Use the run
method to execute the commands in the queue.
class OperationsQueue implements Runnable { private volatile boolean running = true; private final Vector operations = new Vector(); OperationsQueue() { // Notice that all operations will be done in another // thread to avoid deadlocks with GUI thread new Thread(this).start(); } void enqueueOperation(Operation nextOperation) { operations.addElement(nextOperation); synchronized (this) { notify(); } } // stop the thread void abort() { running = false; synchronized (this) { notify(); } } public void run() { while (running) { while (operations.size() > 0) { try { // execute the first operation on the queue ((Operation) operations.firstElement()).execute(); } catch (Exception e) { // Nothing to do. It is expected that each operations handle // their own locally exception but this block is to ensure // that the queue continues to operate } operations.removeElementAt(0); } synchronized (this) { try { wait(); } catch (InterruptedException e) { // it doesn't matter } } } } }
This class defines the Operation
interface used by OperationsQueue
.
To create this interface:
Create the Operation.java
class file.
Define the interface.
interface Operation { // Implement here the operation to be executed void execute(); }
Now that you have implemented the MIDlet functionality, implement the UI classes.