--- /dev/null
+/*\r
+ * Copyright 2008 ZXing authors\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.google.zxing.client.rim;\r
+\r
+import com.google.zxing.DecodeHintType;\r
+import com.google.zxing.MonochromeBitmapSource;\r
+import com.google.zxing.MultiFormatReader;\r
+import com.google.zxing.Reader;\r
+import com.google.zxing.ReaderException;\r
+import com.google.zxing.Result;\r
+import com.google.zxing.client.j2me.LCDUIImageMonochromeBitmapSource;\r
+import com.google.zxing.client.rim.persistence.AppSettings;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistory;\r
+import com.google.zxing.client.rim.persistence.history.DecodeHistoryItem;\r
+import com.google.zxing.client.rim.util.Log;\r
+import com.google.zxing.client.rim.util.ReasonableTimer;\r
+import com.google.zxing.client.rim.util.URLDecoder;\r
+import net.rim.blackberry.api.browser.Browser;\r
+import net.rim.blackberry.api.browser.BrowserSession;\r
+import net.rim.device.api.ui.DrawStyle;\r
+import net.rim.device.api.ui.Field;\r
+import net.rim.device.api.ui.FieldChangeListener;\r
+import net.rim.device.api.ui.Manager;\r
+import net.rim.device.api.ui.UiApplication;\r
+import net.rim.device.api.ui.component.ButtonField;\r
+import net.rim.device.api.ui.component.Dialog;\r
+import net.rim.device.api.ui.component.LabelField;\r
+import net.rim.device.api.ui.container.DialogFieldManager;\r
+import net.rim.device.api.ui.container.MainScreen;\r
+import net.rim.device.api.ui.container.PopupScreen;\r
+import net.rim.device.api.ui.container.VerticalFieldManager;\r
+\r
+import javax.microedition.io.Connector;\r
+import javax.microedition.io.file.FileConnection;\r
+import javax.microedition.lcdui.Image;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.Hashtable;\r
+\r
+/**\r
+ * The main appication menu screen.\r
+ *\r
+ * This code was contributed by LifeMarks.\r
+ *\r
+ * @author Matt York (matt@lifemarks.mobi)\r
+ */\r
+final class ZXingLMMainScreen extends MainScreen {\r
+\r
+ private final ZXingUiApplication app;\r
+ private final QRCapturedJournalListener imageListener;\r
+ private PopupScreen popup;\r
+ private final Reader reader;\r
+ private final Hashtable readerHints;\r
+\r
+ ZXingLMMainScreen() {\r
+ super(DEFAULT_MENU | DEFAULT_CLOSE);\r
+ setTitle(new LabelField("ZXing", DrawStyle.ELLIPSIS | USE_ALL_WIDTH));\r
+ setChangeListener(null);\r
+\r
+ Manager vfm = new VerticalFieldManager(USE_ALL_WIDTH);\r
+ FieldChangeListener buttonListener = new ButtonListener();\r
+\r
+ //0\r
+ Field snapButton = new ButtonField("Snap", FIELD_HCENTER | ButtonField.CONSUME_CLICK | USE_ALL_WIDTH);\r
+ snapButton.setChangeListener(buttonListener);\r
+ vfm.add(snapButton);\r
+\r
+ //1\r
+ Field historyButton = new ButtonField("History", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+ historyButton.setChangeListener(buttonListener);\r
+ vfm.add(historyButton);\r
+\r
+ //2\r
+ Field settingsButton = new ButtonField("Settings", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+ settingsButton.setChangeListener(buttonListener);\r
+ vfm.add(settingsButton);\r
+\r
+ //3\r
+ Field aboutButton = new ButtonField("About", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+ aboutButton.setChangeListener(buttonListener);\r
+ vfm.add(aboutButton);\r
+\r
+ //4\r
+ Field helpButton = new ButtonField("Help", FIELD_HCENTER | ButtonField.CONSUME_CLICK);\r
+ helpButton.setChangeListener(buttonListener);\r
+ vfm.add(helpButton);\r
+\r
+ vfm.setChangeListener(null);\r
+ add(vfm);\r
+\r
+\r
+ app = (ZXingUiApplication) UiApplication.getUiApplication();\r
+ imageListener = new QRCapturedJournalListener(this);\r
+\r
+ reader = new MultiFormatReader();\r
+ readerHints = new Hashtable(1);\r
+ readerHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);\r
+ }\r
+\r
+\r
+ /**\r
+ * Handles the newly created file. If the file is a jpg image, from the camera, the images is assumed to be\r
+ * a qrcode and decoding is attempted.\r
+ */\r
+ void imageSaved(String imagePath) {\r
+ Log.info("Image saved: " + imagePath);\r
+ app.removeFileSystemJournalListener(imageListener);\r
+ if (imagePath.endsWith(".jpg") && imagePath.indexOf("IMG") >= 0) // a blackberry camera image file\r
+ {\r
+ Log.info("imageSaved - Got file: " + imagePath);\r
+ Camera.getInstance().exit();\r
+ Log.info("camera exit finished");\r
+ app.requestForeground();\r
+\r
+ DialogFieldManager manager = new DialogFieldManager();\r
+ popup = new PopupScreen(manager);\r
+ manager.addCustomField(new LabelField("Decoding image..."));\r
+\r
+ app.pushScreen(popup); // original\r
+ Log.info("started progress screen.");\r
+\r
+ Runnable fct = new FileConnectionThread(imagePath);\r
+ Log.info("Starting file connection thread.");\r
+ app.invokeLater(fct);\r
+ Log.info("Finished file connection thread.");\r
+ } else {\r
+ Log.error("Failed to locate camera image.");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Closes the application and persists all required data.\r
+ */\r
+ public void close() {\r
+ app.removeFileSystemJournalListener(imageListener);\r
+ DecodeHistory.getInstance().persist();\r
+ super.close();\r
+ }\r
+\r
+ /**\r
+ * This method is overriden to remove the 'save changes' dialog when exiting.\r
+ */\r
+ public boolean onSavePrompt() {\r
+ setDirty(false);\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Listens for selected buttons and starts the required screen.\r
+ */\r
+ private final class ButtonListener implements FieldChangeListener {\r
+ public void fieldChanged(Field field, int context) {\r
+ Log.debug("*** fieldChanged: " + field.getIndex());\r
+ switch (field.getIndex()) {\r
+ case 0: // snap\r
+ try {\r
+ app.addFileSystemJournalListener(imageListener);\r
+ Camera.getInstance().invoke(); // start camera\r
+ return;\r
+ }\r
+ catch (Exception e) {\r
+ Log.error("!!! Problem invoking camera.!!!: " + e);\r
+ }\r
+ break;\r
+ case 1: // history\r
+ app.pushScreen(new HistoryScreen());\r
+ break;\r
+ case 2: // settings\r
+ app.pushScreen(new SettingsScreen());\r
+ break;\r
+ case 3: //about\r
+ app.pushScreen(new AboutScreen());\r
+ break;\r
+ case 4: //help\r
+ app.pushScreen(new HelpScreen());\r
+ break;\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Thread that decodes the newly created image. If the image is successfully decoded and the data is a URL,\r
+ * the browser is invoked and pointed to the given URL.\r
+ */\r
+ private final class FileConnectionThread implements Runnable {\r
+\r
+ private final String imagePath;\r
+\r
+ private FileConnectionThread(String imagePath) {\r
+ this.imagePath = imagePath;\r
+ }\r
+\r
+ public void run() {\r
+ FileConnection file = null;\r
+ InputStream is = null;\r
+ Image capturedImage = null;\r
+ try {\r
+ file = (FileConnection) Connector.open("file://" + imagePath, Connector.READ_WRITE);\r
+ is = file.openInputStream();\r
+ capturedImage = Image.createImage(is);\r
+ } catch (IOException e) {\r
+ Log.error("Problem creating image: " + e);\r
+ removeProgressBar();\r
+ invalidate();\r
+ showMessage("An error occured processing the image.");\r
+ return;\r
+ } finally {\r
+ if (is != null) {\r
+ try {\r
+ is.close();\r
+ } catch (IOException ioe) {\r
+ }\r
+ }\r
+ if (file != null && file.exists()) {\r
+ if (file.isOpen()) {\r
+ //file.close();\r
+ }\r
+ //file.delete();\r
+ Log.info("Deleted image file.");\r
+ }\r
+ }\r
+\r
+ if (capturedImage != null) {\r
+ Log.info("Got image...");\r
+ MonochromeBitmapSource source = new LCDUIImageMonochromeBitmapSource(capturedImage);\r
+ Result result;\r
+ ReasonableTimer decodingTimer = null;\r
+ try {\r
+ decodingTimer = new ReasonableTimer();\r
+ Log.info("Attempting to decode image...");\r
+ result = reader.decode(source, readerHints);\r
+ decodingTimer.finished();\r
+ } catch (ReaderException e) {\r
+ Log.error("Could not decode image: " + e);\r
+ decodingTimer.finished();\r
+ removeProgressBar();\r
+ invalidate();\r
+ boolean showResolutionMsg =\r
+ !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue();\r
+ if (showResolutionMsg) {\r
+ showMessage("A QR Code was not found in the image. " +\r
+ "We detected that the decoding process took quite a while. " +\r
+ "It will be much faster if you decrease your camera's resolution (640x480).");\r
+ } else {\r
+ showMessage("A QR Code was not found in the image.");\r
+ }\r
+ return;\r
+ }\r
+ if (result != null) {\r
+ String resultText = result.getText();\r
+ Log.info("result: " + resultText);\r
+ if (isURI(resultText)) {\r
+ resultText = URLDecoder.decode(resultText);\r
+ removeProgressBar();\r
+ invalidate();\r
+ if (!decodingTimer.wasResonableTime() &&\r
+ !AppSettings.getInstance().getBooleanItem(AppSettings.SETTING_CAM_RES_MSG).booleanValue()) {\r
+ showMessage("We detected that the decoding process took quite a while. " +\r
+ "It will be much faster if you decrease your camera's resolution (640x480).");\r
+ }\r
+ DecodeHistory.getInstance().addHistoryItem(new DecodeHistoryItem(resultText));\r
+ invokeBrowser(resultText);\r
+ return;\r
+ }\r
+ } else {\r
+ removeProgressBar();\r
+ invalidate();\r
+ showMessage("A QR Code was not found in the image.");\r
+ return;\r
+ }\r
+\r
+ }\r
+\r
+ removeProgressBar();\r
+ invalidate();\r
+ }\r
+\r
+ /**\r
+ * Quick check to see if the result of decoding the qr code was a valid uri.\r
+ */\r
+ private boolean isURI(String uri) {\r
+ return uri.startsWith("http://");\r
+ }\r
+\r
+ /**\r
+ * Invokes the web browser and browses to the given uri.\r
+ */\r
+ private void invokeBrowser(String uri) {\r
+ BrowserSession browserSession = Browser.getDefaultSession();\r
+ browserSession.displayPage(uri);\r
+ }\r
+\r
+ /**\r
+ * Syncronized version of removing progress dialog.\r
+ * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()\r
+ */\r
+ private void removeProgressBar() {\r
+ synchronized (app.getAppEventLock()) {\r
+ if (popup != null) {\r
+ app.popScreen(popup);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Syncronized version of showing a message dialog.\r
+ * NOTE: All methods accessing the gui that are in seperate threads should syncronize on app.getEventLock()\r
+ */\r
+ private void showMessage(String message) {\r
+ synchronized (app.getAppEventLock()) {\r
+ Dialog.alert(message);\r
+ }\r
+ }\r
+ }\r
+\r
+}\r