// Copyright (c) 2005-2007, Mihai Preda. // Licensed under the MIT License. import javax.microedition.midlet.MIDlet; import javax.microedition.lcdui.*; import javax.microedition.io.*; import java.util.*; import java.io.*; public class M extends BasicMIDlet implements CommandListener { static UnitTest dummy; static Display display; DateCanvas dateCanvas = null; final static int HLP_COL1 = 0, HLP_COL2 = 1, HLP_ALGO = 2, HLP_ABOUT = 3, HLP_SIZE = 4; Form hlpForms[] = null; //StringBuffer str = new StringBuffer(); static final int CMD_MARK=5, CMD_CANCEL=6, CMD_PERIOD_LIST=7, CMD_STATS=8, CMD_EXPLAIN=9, CMD_BACKUP=10, CMD_TRANSFER=11, CMD_HELP=12, CMD_EXIT=13, CMD_BACK=14, CMD_OK=15, CMD_MORE=16, CMD_COLOR_LIGHT = 20, CMD_COLOR_DARK = 21, CMD_START_MONDAY = 22, CMD_START_SUNDAY = 23, CMD_PERIODLEN_4 = 25, CMD_PERIODLEN_5 = 26, CMD_PERIODLEN_6 = 27, CMD_LUTEAL_13 = 30, CMD_LUTEAL_14 = 31, CMD_LUTEAL_15 = 32, CMD_GOAL_BOTH = 35, CMD_GOAL_FERTILE = 36, CMD_GOAL_INFERTILE = 37, CMD_PROFILE = 38, CMD_SMS_SEND = 39, CMD_SMS_RECEIVE = 40, CMD_SMS_SEND2 = 41, CMD_SMS_CANCEL=42, CMD_PROFILE_START = 100, CMD_PERIOD_START = 200; static private final String _(int id) { return L.s[id]; } Cmd cmdMore = new Cmd(12, CMD_MORE), cmdOk = new Cmd(11, CMD_OK, Command.BACK), cmdSmsSend = new Cmd(72, CMD_SMS_SEND2, Command.OK), cmdSmsCancel = new Cmd(73, CMD_SMS_CANCEL, Command.CANCEL), cmdCancel = new Cmd(73, CMD_CANCEL, Command.CANCEL); Data data = null; static final String STORE_NAME = "menstral"; static final int RECORD_ID = 1; static final int SMS_PORT = 38697; SmsReceiver smsReceiver = null; static void callSerially(Runnable r) { display.callSerially(r); } public M() throws IOException { { DataInput in = (new Store(STORE_NAME)).readIS(RECORD_ID); data = new Data(in); } Menu.setBackText(L.s[9]); dateCanvas = new DateCanvas(); String days = L.s[44]; Menu menu = new Menu("Menu", new Cmd[] { new Cmd(_(4), CMD_MARK), new Menu(_(8), new Cmd[] { new Menu(_(14), new Cmd[] { new Cmd(_(48), CMD_COLOR_LIGHT), new Cmd(_(49), CMD_COLOR_DARK) }, data.activeProfile().colorScheme), new Menu(_(15), new Cmd[] { new Cmd(_(50), CMD_START_MONDAY), new Cmd(_(51), CMD_START_SUNDAY), }, data.mondayFirst?0:1), new Menu(_(16), new Cmd[] { new Cmd("4 " + days, CMD_PERIODLEN_4), new Cmd("5 " + days, CMD_PERIODLEN_5), new Cmd("6 " + days, CMD_PERIODLEN_6), }, data.activeProfile().periodLen-4), new Menu(_(17), new Cmd[] { new Cmd("13 " + days, CMD_LUTEAL_13), new Cmd("14 " + days, CMD_LUTEAL_14), new Cmd("15 " + days, CMD_LUTEAL_15), }, data.activeProfile().lutealLen-13), new Menu(_(18), new Cmd[] { new Cmd(_(54), CMD_GOAL_BOTH), new Cmd(_(52), CMD_GOAL_FERTILE), new Cmd(_(53), CMD_GOAL_INFERTILE), }, data.activeProfile().goal), new Cmd(_(19), CMD_PROFILE), }), new Cmd(_(6), CMD_PERIOD_LIST), new Cmd(_(7), CMD_STATS), new Cmd(_(13), CMD_EXPLAIN), new Cmd(_(70), CMD_BACKUP), new Menu(_(71), new Cmd[] { new Cmd(_(82), CMD_SMS_SEND), new Cmd(_(83), CMD_SMS_RECEIVE), }), new Cmd(_(5), CMD_HELP, Command.HELP), new Cmd(_(10), CMD_EXIT, Command.EXIT) }); menu.addCommandsTo(dateCanvas); menu.setParent(this, dateCanvas); dateCanvas.notifyChanged(data); } void handleReceivedSms(byte[] sms) { try { Data receivedData = new Data(sms); data.merge(receivedData); display.setCurrent(new Alert(L.s[77], L.s[86], null, AlertType.CONFIRMATION)); } catch (IOException e) { display.setCurrent(new Alert(L.s[79], "Wrong SMS format", null, AlertType.ERROR)); } //display.setCurrent(new Alert(L.s[79], ""+e, null, AlertType.ERROR), dateCanvas); } Form formHelpColors1 = null; void addHelpColor(Form f, int c1, int c2, int c3, String label) { int s = (2 * dateCanvas.sx) / 3; Image img = Image.createImage(3*s + 4, s + 2); Graphics g = img.getGraphics(); g.setColor(0xff0000); g.fillRect(0, 0, 3*s + 4, s + 2); g.setColor(dateCanvas.color.background); g.fillRect(0, 0, 3*s + 4, s + 2); g.setColor(c1); g.fillRect(1, 1, s, s); g.setColor(c2); g.fillRect(s + 2, 1, s, s); g.setColor(c3); g.fillRect(2*s + 3, 1, s, s); g.setColor(0x0000ff); g.drawString("", 3, 3, 0); f.append(new ImageItem(label, Image.createImage(img), /*ImageItem.LAYOUT_LEFT*/ ImageItem.LAYOUT_NEWLINE_AFTER, null)); //f.append(label); } void addCmd(Displayable d, Command cmd) { d.addCommand(cmd); d.setCommandListener(this); } void showHelp(int idx) { if (hlpForms == null) { hlpForms = new Form[HLP_SIZE]; } if (hlpForms[idx] == null) { Form f = new Form(L.s[(idx == 0) ? 14 : 19 + idx]); hlpForms[idx] = f; addCmd(f, cmdOk); if (idx < HLP_SIZE - 1) { f.addCommand(cmdMore); } switch (idx) { case HLP_COL1: addHelpColor(f, dateCanvas.color.red, dateCanvas.mixedColor(dateCanvas.color.red, dateCanvas.color.redWeak, 1, 2), dateCanvas.color.redWeak, L.s[23]); addHelpColor(f, dateCanvas.color.fertile, dateCanvas.mixedColor(dateCanvas.color.fertile, dateCanvas.color.fertileWeak, 1, 2), dateCanvas.color.fertileWeak, L.s[24]); addHelpColor(f, dateCanvas.color.nonfertile, dateCanvas.mixedColor(dateCanvas.color.nonfertile, dateCanvas.color.nonfertileWeak, 1, 2), dateCanvas.color.nonfertileWeak, L.s[25]); //addHelpColor(f, DateCanvas.M4, DateCanvas.F4, DateCanvas.N4, "Uncertainty\n"); break; case HLP_COL2: int sx = dateCanvas.sx; int sy = dateCanvas.sy; Image img = Image.createImage(3*sx + 1,sy + 1); Graphics g = img.getGraphics(); g.setColor(dateCanvas.color.background); g.fillRect(0, 0, 3*sx + 1, sy + 1); g.setFont(dateCanvas.regular); dateCanvas.drawOneCell(g, dateCanvas.color.nonfertile, 1, 1, 12); dateCanvas.drawOneCell(g, -1, sx + 1, 1, 13); dateCanvas.drawOneCell(g, dateCanvas.color.redWeak, 2*sx + 1, 1, 14); //it seems some timing is needed as workaround for Sony-Ericsson bug g.setColor(0x0000ff); g.drawString("", 3, 3, 0); g.setColor(0x00ff00); g.drawString("", 0, 0, 0); f.append(Image.createImage(img)); f.append("\n" + L.s[26] + '\n'); break; case HLP_ALGO: f.append(L.s[27]); break; case HLP_ABOUT: try { f.append(Image.createImage("/M.gif")); } catch (IOException e) { try { f.append(Image.createImage("M.gif")); } catch (IOException e2) { } } f.append("Menstral " + getAppProperty("MIDlet-Version") + "\n"); f.append("\u00a9 Mihai Preda\n" + getAppProperty("MIDlet-Info-URL") + "\n" + L.s[28]); f.append(L.s[3]); break; } } display.setCurrent(hlpForms[idx]); } void doHelp(Displayable d) { int pos = (hlpForms != null) ? hlpForms.length - 1 : -1; while (pos >= 0 && d != hlpForms[pos]) { --pos; } showHelp(pos + 1); } static String interval(int v1, int v2) { if (v1 == v2) { return ": " + v1 + ". "; } else { return ": " + v1 + "-" + v2 + ". "; } } void doExplain() { Form explain = new Form(L.s[29]); addCmd(explain, cmdOk); Region r = dateCanvas.getCurrentRegion(); if (r.noInfo) { explain.append(L.s[30]); } else { if (r.exact) { explain.append(L.s[31] + ": " + r.len + ". "); } else { if (r.theSize > 0) { explain.append(L.s[32] + ": " + r.theSize + ", " + L.s[33] + interval(r.min1 + r.unsure, r.max1 - r.unsure)); } explain.append(L.s[34] + ": " + r.len + ". " + L.s[35] + interval(r.min2, r.max2) + L.s[36] + interval(r.min1, r.max1)); } explain.append(L.s[37] + interval(dateCanvas.getOvl(r.min2), dateCanvas.getOvl(r.max2))); //explain.append("Infertile up to " + infert1 + " and from " + infert2 + "."); } display.setCurrent(explain); } String smsDestNumber = null; int smsDestPort = 0; void sendSMS(String destNumber, int port) { Alert alert = new Alert(L.s[74], L.s[74] + "...", null, AlertType.INFO); //alert.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINOUS_RUNNING)); display.setCurrent(alert); smsDestNumber = destNumber; smsDestPort = port; final SmsSender sender = (smsDestPort == 0) ? new SmsSender(smsDestNumber, data.activeProfile().serializeDates()) : new SmsSender(smsDestNumber, smsDestPort, data.serialize()); Runnable listener = new Runnable() { public void run() { if (sender.exception == null) { display.setCurrent(new Alert(L.s[75], L.s[75], null, AlertType.CONFIRMATION), dateCanvas); } else { display.setCurrent(new Alert(L.s[76], ""+sender.exception, null, AlertType.ERROR), dateCanvas); } } }; sender.setListener(listener); sender.start(); } void doSMSBackup() { doTransferVsBackup = false; Form form = new Form(L.s[70]); form.append(L.s[80]); smsNumberField = new TextField(L.s[81], null, 13, TextField.PHONENUMBER); form.append(smsNumberField); form.addCommand(cmdSmsSend); form.addCommand(cmdCancel); form.setCommandListener(this); display.setCurrent(form); } TextField smsNumberField; boolean doTransferVsBackup; void doSMSTransferSend() { doTransferVsBackup = true; Form form = new Form(L.s[82]); form.append(L.s[84]); smsNumberField = new TextField(L.s[81], null, 13, TextField.PHONENUMBER); form.append(smsNumberField); form.addCommand(cmdSmsSend); form.addCommand(cmdCancel); form.setCommandListener(this); display.setCurrent(form); } void doSMSTransferReceive() { smsReceiver = new SmsReceiver(SMS_PORT, new Runnable() { public void run() { if (smsReceiver.data != null) { handleReceivedSms(smsReceiver.data); } else { display.setCurrent(new Alert(L.s[76], ""+smsReceiver.exception, null, AlertType.ERROR)); } } }); smsReceiver.start(); /* } catch (IOException e) { display.setCurrent(new Alert(L.s[76], ""+e, null, AlertType.ERROR)); } */ Alert alertReceive = new Alert(L.s[83], L.s[87], null, AlertType.INFO); //alert.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINOUS_RUNNING)); //alertReceive.setTimeout(Alert.FOREVER); //alertReceive.addCommand(cmdSmsCancel); //alertReceive.setCommandListener(this); display.setCurrent(alertReceive, dateCanvas); } //int ymd[] = new int[3]; Menu periodListMenu() { String[] lines = data.activeProfile().periodList(); int size = lines.length; Cmd cmds[] = new Cmd[size]; for (int i = 0; i < size; ++i) { cmds[i] = new Cmd(lines[i], CMD_PERIOD_START+i); } return new Menu(_(38), cmds, (size > 0) ? size - 1 : 0); } void doStats() { Form form = new Form(L.s[7]); addCmd(form, cmdOk); int avgMinMax[] = new int[3]; int n = data.activeProfile().getAvgMinMax(avgMinMax); if (n >= 1) { String txt = "\n" + L.s[39] + ' ' + n + ' ' + L.s[40] + ": " + L.s[41] + ' ' + avgMinMax[0] + ", " + L.s[42] + ' ' + avgMinMax[1] + ", " + L.s[43] + ' ' + avgMinMax[2] + ' ' +L.s[44]; form.append(txt); } else { form.append(L.s[45]); } display.setCurrent(form); } Menu profileMenu() { int size = data.size(); Cmd cmds[] = new Cmd[size+1]; for (int i = 0; i < size; ++i) { //Log.log("size " + size + " i " + i); cmds[i] = new Cmd("#"+(i+1)+(i==data.active?" ("+L.s[46]+')':""), CMD_PROFILE_START+i); } cmds[size] = new Cmd("#"+(size+1)+" ("+L.s[47]+')', CMD_PROFILE_START+size); return new Menu(_(19), cmds, data.active); } public void commandAction(Command c, Displayable d) { int id = ((Cmd)c).id; Log.log("Command " + id); display.setCurrent(dateCanvas); switch (id) { case CMD_MARK: dateCanvas.mark(); break; case CMD_HELP: //fall through case CMD_MORE: doHelp(d); break; case CMD_STATS: doStats(); break; case CMD_EXPLAIN: doExplain(); break; case CMD_EXIT: exit(); break; case CMD_COLOR_LIGHT: case CMD_COLOR_DARK: data.activeProfile().colorScheme = id - CMD_COLOR_LIGHT; dateCanvas.notifyChanged(data); //dateCanvas.setColorScheme(id - CMD_COLOR_LIGHT); break; case CMD_START_MONDAY: case CMD_START_SUNDAY: data.mondayFirst = (id == CMD_START_MONDAY); dateCanvas.notifyChanged(data); //dateCanvas.mondayFirst =; //dateCanvas.computeStartDayOfWeek(); break; case CMD_PERIODLEN_4: case CMD_PERIODLEN_5: case CMD_PERIODLEN_6: data.activeProfile().periodLen = (id - CMD_PERIODLEN_4) + 4; dateCanvas.notifyChanged(data); //dateCanvas.periodLen = //writeProfile(); //dateCanvas.fullRedraw(); break; case CMD_LUTEAL_13: case CMD_LUTEAL_14: case CMD_LUTEAL_15: data.activeProfile().lutealLen = (id - CMD_LUTEAL_13) + 13; dateCanvas.notifyChanged(data); //dateCanvas.lutealLen = break; case CMD_GOAL_BOTH: case CMD_GOAL_FERTILE: case CMD_GOAL_INFERTILE: data.activeProfile().goal = id - CMD_GOAL_BOTH; //dateCanvas.goal = dateCanvas.notifyChanged(data); break; case CMD_PROFILE: { Menu submenu = profileMenu(); submenu.setParent(this, d); display.setCurrent(submenu.list); break; } case CMD_PERIOD_LIST: { Menu submenu = periodListMenu(); submenu.setParent(this, d); display.setCurrent(submenu.list); break; } case CMD_SMS_SEND: doSMSTransferSend(); break; case CMD_SMS_RECEIVE: doSMSTransferReceive(); break; case CMD_SMS_SEND2: String destNumber = ((TextField) smsNumberField).getString(); sendSMS(destNumber, doTransferVsBackup ? SMS_PORT : 0); break; case CMD_BACKUP: doSMSBackup(); break; case CMD_SMS_CANCEL: //smsConnection.close(); } if (id >= CMD_PERIOD_START) { //todo } else if (id >= CMD_PROFILE_START) { data.setActive(id - CMD_PROFILE_START); //setProfile(id - CMD_PROFILE_START + 1); dateCanvas.notifyChanged(data); } } public void startApp() { display = Display.getDisplay(this); display.setCurrent(dateCanvas); } protected void onExit() { (new Store(STORE_NAME)).write(RECORD_ID, data.serialize()); //save data } }