// Copyright (c) 2005-2007, Mihai Preda import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.rms.*; import javax.microedition.io.*; import javax.wireless.messaging.*; import java.util.*; import java.io.*; #define _STR(x) #x #define STR(x) _STR(x) #define _(key) Language.strings[key] public class M extends MIDlet implements CommandListener, Runnable { //final static String _(int key) { return L._(key); } //int langId = 0; private Display display; DateCanvas dateCanvas = null; final static int SETUP = 0, COLORS = 1, WEEK = 2, PERIODLEN = 3, LUTEALLEN = 4, GOAL = 5, PROFILE = 6, LIST_END = 7; List lists[] = null, periodList = 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(); Command cmdMark, cmdHelp, cmdList, cmdStats, cmdSetup, cmdBack, cmdExit, cmdOk, cmdMore, cmdExplain, /*cmdLanguage, */cmdBackup, cmdTransfer, cmdSend, cmdCancel; public M() { dateCanvas = new DateCanvas(this); try { rs = RecordStore.openRecordStore("menstral", true); readHead(); readProfile(); #ifdef MULTILANG String locale = System.getProperty("microedition.locale"); if (locale == null) { locale = "en"; } L.loadLanguage(locale.substring(0, 2).toLowerCase()); #endif cmdMark = new Command(_(4), Command.SCREEN, 1); cmdSetup = new Command(_(8), Command.SCREEN, 2); cmdList = new Command(_(6), Command.SCREEN, 3); cmdStats = new Command(_(7), Command.SCREEN, 4); cmdExplain = new Command(_(13), Command.SCREEN, 5); cmdBackup = new Command(_(70), Command.SCREEN, 6); cmdTransfer = new Command(_(71), Command.SCREEN, 7); cmdHelp = new Command(_(5), Command.HELP, 10); cmdBack = new Command(_(9), Command.BACK, 12); cmdExit = new Command(_(10), Command.EXIT, 12); cmdOk = new Command(_(11), Command.BACK, 2); cmdMore = new Command(_(12), Command.SCREEN, 1); cmdSend = new Command(_(72), Command.OK, 1); cmdCancel = new Command(_(73), Command.CANCEL, 2); Command cmds[] = {cmdMark, cmdSetup, cmdList, cmdStats, cmdHelp, cmdExplain, cmdBackup, cmdTransfer, cmdExit}; for (int i = 0; i < cmds.length; ++i) { dateCanvas.addCommand(cmds[i]); } dateCanvas.switchLanguage(); lastProfile = rs.getNextRecordID() -2; } catch (RecordStoreException e) { //System.out.println(e); } dateCanvas.goToday(); dateCanvas.fullRedraw(); dateCanvas.setCommandListener(this); } void doMark() { //display.vibrate(40); dateCanvas.mark(); AlertType.CONFIRMATION.playSound(display); } 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.colorScheme[DateCanvas.BG]); 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(_((idx == 0) ? 14 : 19 + idx)); hlpForms[idx] = f; addCmd(f, cmdOk); if (idx < HLP_SIZE - 1) { f.addCommand(cmdMore); } //f.addCommand(cmdBack); switch (idx) { case HLP_COL1: addHelpColor(f, dateCanvas.m1, dateCanvas.mixedColor(dateCanvas.m1, dateCanvas.m2, 1, 2), dateCanvas.m2, _(23)); addHelpColor(f, dateCanvas.f1, dateCanvas.mixedColor(dateCanvas.f1, dateCanvas.f2, 1, 2), dateCanvas.f2, _(24)); addHelpColor(f, dateCanvas.n1, dateCanvas.mixedColor(dateCanvas.n1, dateCanvas.n2, 1, 2), dateCanvas.n2, _(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.colorScheme[DateCanvas.BG]); g.fillRect(0, 0, 3*sx + 1, sy + 1); g.setFont(dateCanvas.regular); dateCanvas.drawOneCell(g, dateCanvas.colorScheme[DateCanvas.N1], 1, 1, 0, new char[] {'1', '2'}); dateCanvas.drawOneCell(g, -1, sx + 1, 1, 0, new char[] {'1', '3'}); dateCanvas.drawOneCell(g, dateCanvas.colorScheme[DateCanvas.M2], 2*sx + 1, 1, 0, new char[] {'1', '4'}); //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" + _(26) + '\n'); break; case HLP_ALGO: f.append(_(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 " + STR(_VERSION_) + "\n"); f.append("\u00a9 Mihai Preda\n"+ "http://menstral.net/\n"+ _(28)); f.append(_(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(_(29)); addCmd(explain, cmdOk); Region r = dateCanvas.getCurrentRegion(); if (r.noInfo) { explain.append(_(30)); } else { if (r.exact) { explain.append(_(31) + ": " + r.len + ". "); } else { if (r.theSize > 0) { explain.append(_(32) + ": " + r.theSize + ", " + _(33) + interval(r.min1 + r.unsure, r.max1 - r.unsure)); } explain.append(_(34) + ": " + r.len + ". " + _(35) + interval(r.min2, r.max2) + _(36) + interval(r.min1, r.max1)); } explain.append(_(37) + interval(dateCanvas.getOvl(r.min2), dateCanvas.getOvl(r.max2))); //explain.append("Infertile up to " + infert1 + " and from " + infert2 + "."); } display.setCurrent(explain); } void strAddVal(int v) { if (v < 10) { str.append('0'); } str.append(v).append('/'); } static final private String SMS_HEADER = "Menstral backup v.1"; String serializeDates() { str.setLength(0); str.append(SMS_HEADER); int curYear = 0; int curMonth = 0; int year, month, day; for (int i = 1; i < theSize - 1; ++i) { toYMD(dates[i], ymd); year = ymd[0]; month = ymd[1] + 1; day = ymd[2]; if (year != curYear) { str.append('\n').append(year).append(':'); curYear = year; curMonth = 1; } str.append(' ').append(day); if (month != curMonth) { str.append('/').append(month); curMonth = month; } ++curMonth; } return str.toString(); } String smsDestNumber = null; int smsDestPort = 0; void sendSMS(String destNumber, int port) { Alert alert = new Alert(_(74), _(74) + "...", null, AlertType.INFO); //alert.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINOUS_RUNNING)); display.setCurrent(alert); smsDestNumber = destNumber; smsDestPort = port; doSend = true; new Thread(this).start(); } boolean doSend; //String receivedSms; MessageConnection smsConnection; static final int PORT_NUMBER = 38697; public void run() { if (doSend) { try { String smsDestination = "sms://" + (smsDestPort > 0 ? smsDestNumber + ':' + smsDestPort : smsDestNumber); smsConnection = (MessageConnection) Connector.open(smsDestination); Message msg = smsConnection.newMessage(smsDestPort == 0 ? MessageConnection.TEXT_MESSAGE : MessageConnection.BINARY_MESSAGE); if (smsDestPort == 0) { ((TextMessage) msg).setPayloadText(serializeDates()); } else { ((BinaryMessage) msg).setPayloadData(getRawProfile()); } smsConnection.send(msg); smsConnection.close(); display.setCurrent(new Alert(_(75), _(75), null, AlertType.CONFIRMATION), dateCanvas); } catch (Exception e) { display.setCurrent(new Alert(_(76), ""+e, null, AlertType.ERROR), dateCanvas); //System.out.println("sms " + e); } } else { //receive try { smsConnection = (MessageConnection) Connector.open("sms://:" + PORT_NUMBER); byte raw[] = ((BinaryMessage) smsConnection.receive()).getPayloadData(); if (smsConnection != null) { smsConnection.close(); } setProfile(lastProfile + 1); readProfile(raw); writeProfile(); display.setCurrent(new Alert(_(77), _(78) + " #" + activeProfile, null, AlertType.CONFIRMATION), dateCanvas); } catch (Exception e) { //receivedSms = null; display.setCurrent(new Alert(_(79), ""+e, null, AlertType.ERROR), dateCanvas); } } smsConnection = null; } void doSMSBackup() { doTransferVsBackup = false; Form form = new Form(_(70)); form.append(_(80)); smsNumberField = new TextField(_(81), null, 13, TextField.PHONENUMBER); form.append(smsNumberField); form.addCommand(cmdSend); form.addCommand(cmdCancel); form.setCommandListener(this); display.setCurrent(form); } List transferList = null; void doSMSTransfer() { if (transferList == null) { transferList = new List(_(71), Choice.IMPLICIT, new String[]{_(82), _(83)}, null); transferList.addCommand(cmdCancel); transferList.setCommandListener(this); } display.setCurrent(transferList); } TextField smsNumberField; boolean doTransferVsBackup; void doSMSTransferSend() { doTransferVsBackup = true; Form form = new Form(_(82)); form.append(_(84)); smsNumberField = new TextField(_(81), null, 13, TextField.PHONENUMBER); form.append(smsNumberField); form.addCommand(cmdSend); form.addCommand(cmdCancel); form.setCommandListener(this); display.setCurrent(form); } Alert alertReceive = null; void doSMSTransferReceive() { if (alertReceive == null) { alertReceive = new Alert(_(83), _(85), null, AlertType.INFO); //alert.setIndicator(new Gauge(null, false, Gauge.INDEFINITE, Gauge.CONTINOUS_RUNNING)); alertReceive.setTimeout(Alert.FOREVER); alertReceive.addCommand(cmdCancel); alertReceive.setCommandListener(this); } display.setCurrent(alertReceive); doSend = false; new Thread(this).start(); } int ymd[] = new int[3]; void doList() { periodList = new List(_(38), List.IMPLICIT); addCmd(periodList, cmdBack); //Form form = new Form(_(38)); //initForm(form); int curYear = 0; /* Font bold = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM); */ int year, month, day, len; for (int i = 1; i < theSize - 1; ++i) { toYMD(dates[i], ymd); year = ymd[0]; month = ymd[1] + 1; day = ymd[2]; str.setLength(0); strAddVal(day); strAddVal(month); strAddVal(year % 100); str.deleteCharAt(str.length() - 1); len = 200; if (i < theSize -2) { len = dates[i + 1] - dates[i]; } if (len < 100) { str.append(" " + len); } else { str.append(" n/a"); } //str.append('\n'); periodList.append(str.toString(), null); } int size = periodList.size(); if (size > 0) { periodList.setSelectedIndex(size - 1, true); } str.setLength(0); display.setCurrent(periodList); } static void computeAvgMinMax(int size, int vals[], int outAvgMinMax[]) { int min = vals[0], max = vals[0], sum = vals[0]; int v; for (int i = 1; i < size; ++i) { v = vals[i]; sum += v; if (v < min) { min = v; } else if (v > max) { max = v; } } outAvgMinMax[0] = (sum + (size - 1)/2) / size; outAvgMinMax[1] = min; outAvgMinMax[2] = max; } int avgMinMax[] = new int[3]; void doStats() { Form form = new Form(_(7)); addCmd(form, cmdOk); int[] info = intervals1; int n = doBefore(info, theSize - 2); if (n >= 1) { computeAvgMinMax(n, info, avgMinMax); str.setLength(0); str.append('\n' + _(39) + ' ' + n + ' ' + _(40) + ": " + _(41) + ' ' + avgMinMax[0] + ", " + _(42) + ' ' + avgMinMax[1] + ", " + _(43) + ' ' + avgMinMax[2] + ' ' +_(44)); form.append(str.toString()); } else { form.append(_(45)); } str.setLength(0); display.setCurrent(form); } /* List langList = null; String[] langs = null; void doLanguage() { if (langList == null) { if (langs == null) { langs = L.getLanguageList(); //L.loadLanguage(langId); } langList = new List("Language", Choice.IMPLICIT, langs, null); langList.addCommand(cmdBack); langList.setCommandListener(this); } langList.setSelectedIndex(langId, true); display.setCurrent(langList); } */ void doProfile() { #ifdef _PROFILE_ List list = lists[PROFILE]; for (int i = list.size() - 1; i >= 0; --i) { list.delete(i); } for (int i = 1; i <= lastProfile; ++i) { if (i == activeProfile) { list.append("#" + i + " (" + _(46) + ')', null); } else { list.append("#" + i, null); } } list.append("#" + (lastProfile + 1) + " (" +_(47) + ')', null); list.setSelectedIndex(activeProfile - 1, true); display.setCurrent(list); #endif } void initLists() { //{"3 days", "4 days", "5 days", "6 days", "7 days"} //{"12 days", "13 days", "14 days", "15 days", "16 days"} String[] periods = new String[5]; String[] luteals = new String[5]; String days = _(44); for (int i = 0; i < 5; ++i) { periods[i] = "" + (i + 3) + ' ' + days; luteals[i] = "" + (i + 12) + ' ' + days; } lists = new List[LIST_END]; String title; String entries[][] = { {}, {_(48), _(49)}, {_(50), _(51)}, periods, luteals, {_(52), _(53), _(54)}, {}}; for (int i = 0; i < LIST_END; ++i) { title = _((i == 0) ? 8 : (13 + i)); lists[i] = new List(title, Choice.IMPLICIT, entries[i], null); addCmd(lists[i], cmdBack); if (i != 0) { lists[SETUP].append(title, null); } } #ifndef _PROFILE_ lists[SETUP].delete(PROFILE - 1); #endif } public void commandAction(Command c, Displayable d) { //System.out.println("Command " + c); if (c == cmdMark) { doMark(); } else if (c == cmdHelp) { doHelp(d); } else if (c == cmdMore) { doHelp(d); } else if (c == cmdList) { doList(); } else if (c == cmdStats) { doStats(); } else if (c == cmdExplain) { doExplain(); } else if (c == cmdSetup) { if (lists == null) { initLists(); } display.setCurrent(lists[SETUP]); } else if (c == cmdExit) { notifyDestroyed(); } else if (c == List.SELECT_COMMAND) { int selection = ((List)d).getSelectedIndex(); //System.out.println("" + selection); //display.setCurrent(null); /* if (lists == null) { initLists(); } */ if (d == periodList) { //System.out.println("periodList"); //todo: go to selected period display.setCurrent(dateCanvas); periodList = null; } else if (d == transferList) { if (selection == 0) { doSMSTransferSend(); } else { doSMSTransferReceive(); } return; } else if (d == lists[SETUP]) { ++selection; //1-based if (selection == PROFILE) { doProfile(); } else { switch (selection) { case COLORS: lists[COLORS].setSelectedIndex(dateCanvas.colorSchemeNb, true); break; case WEEK: lists[WEEK].setSelectedIndex(dateCanvas.mondayFirst ? 0 : 1, true); break; case PERIODLEN: lists[PERIODLEN].setSelectedIndex(dateCanvas.periodLen - 3, true); break; case LUTEALLEN: lists[LUTEALLEN].setSelectedIndex(dateCanvas.lutealLen - 12, true); break; case GOAL: lists[GOAL].setSelectedIndex((dateCanvas.goal == 0) ? 2 : dateCanvas.goal - 1, true); break; } display.setCurrent(lists[selection]); } return; } else if (d == lists[COLORS]) { dateCanvas.setColorScheme(selection); writeProfile(); } else if (d == lists[WEEK]) { dateCanvas.mondayFirst = (selection == 0); dateCanvas.computeStartDayOfWeek(); writeHead(); } else if (d == lists[PERIODLEN]) { dateCanvas.periodLen = selection + 3; writeProfile(); } else if (d == lists[LUTEALLEN]) { dateCanvas.lutealLen = selection + 12; writeProfile(); } else if (d == lists[GOAL]) { dateCanvas.goal = (selection == 2) ? 0 : selection + 1; writeProfile(); } else if (d == lists[PROFILE]) { setProfile(selection + 1); } display.setCurrent(dateCanvas); dateCanvas.fullRedraw(); } else if (c == cmdBack) { //System.out.println("CmdBack"); List l = (List)d; if (l == periodList /*|| l == langList*/ || l == lists[SETUP]) { //maybe show menu instead? display.setCurrent(dateCanvas); periodList = null; } else { display.setCurrent(lists[SETUP]); } } else if (c == cmdOk || c == cmdCancel) { if (d == alertReceive) { try { smsConnection.close(); } catch (IOException e) { } } display.setCurrent(dateCanvas); } else if (c == cmdSend) { String destNumber = ((TextField) smsNumberField).getString(); sendSMS(destNumber, doTransferVsBackup ? PORT_NUMBER : 0); } else if (c == cmdBackup) { doSMSBackup(); } else if (c == cmdTransfer) { doSMSTransfer(); } /* if (d == langList) { langId = selection; writeHead(); switchLanguage(); } else else if (c == Alert.DISMISS_COMMAND) { display.setCurrent(dateCanvas); } */ /* if (c == cmdLanguage) { doLanguage(); } else */ } public void startApp() { display = Display.getDisplay(this); /* if (firstRun && dateCanvas.expired) { Alert alert = new Alert(_(55), _(56) + ' ' + "menstral.net", null, null); alert.addCommand(cmdExit); alert.setCommandListener(this); display.setCurrent(alert); } else { } */ display.setCurrent(dateCanvas); } protected void pauseApp() { } protected void destroyApp(boolean unconditional) { //notifyDestroyed(); } //-------- STORE --- static final int MAX_SIZE = 255; short dates[] = new short [MAX_SIZE]; int theSize; Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); static final int DAYS = 24 * 3600 * 1000; int out[] = new int[4]; RecordStore rs = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(20); int activeProfile = 0; int passwd = 0; int lastProfile = 0; #ifdef DEBUG static final void assrt(boolean b) { if (!b) { System.out.println("assertion failed"); throw new IllegalStateException(); } } #endif #ifdef _PROFILE_ void setProfile(int profile) { activeProfile = profile; writeHead(); readProfile(); } #endif void insureRecord(int recNo) throws RecordStoreException { if (rs.getNextRecordID() == recNo) { rs.addRecord(null, 0, 0); } } void readProfile(byte raw[]) { int settings = 0; if (raw == null) { settings = 0; passwd = 0; theSize = 2; dates[0] = 0; dates[1] = Short.MAX_VALUE; } else { DataInputStream in = new DataInputStream(new ByteArrayInputStream(raw)); try { settings = in.readShort(); passwd = in.readShort(); theSize = in.readShort(); //assrt(size <= MAX_SIZE); for (int i = 0; i < theSize; ++i) { dates[i] = in.readShort(); } } catch (IOException e) { } } //} catch (Exception e) { //} //assrt(size >= 2 && dates[0] == 0 && dates[size - 1] == Short.MAX_VALUE); if (activeProfile > lastProfile) { lastProfile = activeProfile; } dateCanvas.setColorScheme(settings & 0x000f); int perLen = (settings & 0x00f0) >> 4; int lutLen = (settings & 0x0f00) >> 8; dateCanvas.goal = (settings & 0x3000) >> 12; dateCanvas.periodLen = (perLen != 0) ? perLen : 5; dateCanvas.lutealLen = (lutLen != 0) ? lutLen + 4 : 14; //dateCanvas.goToday(); } void readProfile() { int recNo = activeProfile + 1; try { insureRecord(recNo); byte raw[] = rs.getRecord(recNo); readProfile(raw); } catch (RecordStoreException e) { } } byte[] getRawProfile() { bos.reset(); int settings = dateCanvas.colorSchemeNb + (dateCanvas.periodLen << 4) + ((dateCanvas.lutealLen - 4) << 8) + (dateCanvas.goal << 12); DataOutputStream out = new DataOutputStream(bos); try { out.writeShort(settings); out.writeShort(passwd); out.writeShort(theSize); for (int i = 0; i < theSize; ++i) { out.writeShort(dates[i]); } } catch (IOException e) { } return bos.toByteArray(); } void writeProfile() { try { byte data[] = getRawProfile(); rs.setRecord(activeProfile + 1, data, 0, data.length); } catch (Exception e) { //System.out.println(e); } } private void readHead() { byte b[] = null; try { insureRecord(1); b = rs.getRecord(1); } catch (RecordStoreException e) { throw new RuntimeException("RecordStoreException"); } if (b == null || b.length == 0) { //firstRun = true; activeProfile = 1; //langId = 0; dateCanvas.mondayFirst = true; } else { //assrt(b.length == 4 && b[0] == 1); activeProfile = b[1] & 0x0f; //langId = (b[2] >> 1); dateCanvas.mondayFirst = ((b[2] & 0x01) == 0); } } private void writeHead() { byte head[] = new byte[] {1, (byte)(activeProfile & 0x0f), (byte)((dateCanvas.mondayFirst ? 0 : 1) /*| (langId << 1)*/), 0}; try { rs.setRecord(1, head, 0, 4); } catch (RecordStoreException e) { } } int findPos(short date) { int pos = 0; while (pos < theSize && dates[pos] <= date) { ++pos; } //assrt(pos < size); --pos; return pos; } short getDate(int year, int month, int day) { cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month); cal.set(Calendar.DATE, day); return (short)(cal.getTime().getTime() / DAYS); } Date tmpdate = new Date(); void toYMD(short date, int ymd[]) { tmpdate.setTime((long)date * DAYS); cal.setTime(tmpdate); ymd[0] = cal.get(Calendar.YEAR); ymd[1] = cal.get(Calendar.MONTH); ymd[2] = cal.get(Calendar.DATE); } void toggle(short date) { int pos = findPos(date); if (dates[pos] == date) { remove(date); } else { add(date); } writeProfile(); } private void add(short date) { if (theSize == MAX_SIZE) { return; //full, can't add } int pos = findPos(date); if (dates[pos] != date) { ++pos; ++theSize; for (int i = theSize; i > pos; --i) { dates[i] = dates[i - 1]; } dates[pos] = date; } } private void remove(short date) { int pos = findPos(date); if (dates[pos] == date) { --theSize; for (int i = pos; i < theSize; ++i) { dates[i] = dates[i + 1]; } } } static final int MIN_INTERVAL = 21; static final int MAX_INTERVAL = 39; boolean isMarked(short d) { return dates[findPos(d)] == d; } static void computeFitInterval(int avg, int date1, int date2, int date, int out[]) { int len = date2 - date1; int fitLen = computeFitLen(avg, len); //assrt(date1 <= date && date <= date2); int dist = date - date1; int i = 1; while((i * fitLen + 49) / 100 <= dist) ++i; --i; int n = len * 100 / fitLen; int p1 = (i * fitLen + 49) / 100; int p2 = ((i + 1) * fitLen + 49) / 100; out[0] = i; out[1] = n; out[2] = date1 + p1; out[3] = p2 - p1; } static int computeFitLen(int avg, int len) { len *= 100; avg *= 100; int n = len / avg; if (n == 0) { return len; } int s1 = len / n; int s2 = len / (n + 1); return (s1 - avg < avg - s2) ? s1 : s2; } public int getDaysInfo(short date, Region outRegs[]) { int nRegs = 0; short endDate = (short)(date + DateCanvas.N_DAYS_1); int firstDay = 0; int ret; Region r; r = outRegs[nRegs++]; ret = doRegion(date, r); firstDay = ret + 1; //assrt(r.avg > 0); date += r.len + ret; while (date < endDate && nRegs < DateCanvas.N_REGS) { r = outRegs[nRegs++]; ret = doRegion(date, r); //assrt(ret == 0 && r.avg > 0); date += r.len; //System.out.println(r); } return firstDay; } int intervals1[] = new int[13], intervals2[] = new int[13]; int doBefore(int intervals[], int pos) { int intSize = 0; int len; for (int i = pos; i > 0 && intSize < 10; --i, ++intSize) { len = dates[i] - dates[i - 1]; if (len < M.MIN_INTERVAL || len > M.MAX_INTERVAL) { break; } intervals[intSize] = len; } return intSize; } int doAfter(int intervals[], int pos) { int intSize = 0; int len; for (int i = pos + 1; i < theSize - 1 && intSize < 10; ++i, ++intSize) { len = dates[i + 1] - dates[i]; if (len < M.MIN_INTERVAL || len > M.MAX_INTERVAL) { break; } intervals[intSize] = len; } return intSize; } int computeExpectedLen(int size, int intervals[]) { if (size == 0) { return 28; } if (size > 7) { size = 7; } computeAvgMinMax(size, intervals, avgMinMax); return avgMinMax[0]; } int doRegion(short date, Region outReg) { int pos = findPos(date); boolean hasBefore = (pos > 0), hasAfter = (pos < theSize -2); //System.out.println("begin " + beginDay + // " pos " + pos + " b " + hasBefore + "; " + hasAfter); if (!hasBefore && !hasAfter) { outReg.setNoInfo(DateCanvas.N_DAYS); return 0; } int size1 = 0, size2 = 0; if (hasBefore) { size1 = doBefore(intervals1, pos); } if (hasAfter) { size2 = doAfter(intervals2, pos); } int beg, distN; int intervals[] = null; int intSize; if (size1 <= size2) { intervals = intervals2; intSize = size2; } else { intervals = intervals1; intSize = size1; } int avg = computeExpectedLen(intSize, intervals); if (hasBefore && hasAfter) { computeFitInterval(avg, dates[pos], dates[pos + 1], date, out); int n1 = out[0], n = out[1], len = out[3]; beg = out[2]; distN = (n1 <= (n - n1 - 1)) ? n1 : (n - n1 -1); if (len < MIN_INTERVAL || len > MAX_INTERVAL) { outReg.setNoInfo(len); } else if (n == 1) { outReg.setExact(len); } else { outReg.set(len, intSize, intervals); } } else { if (hasBefore) { int dist = date - dates[pos]; //distN = dist / avg; beg = date - dist % avg; } else { int dist = dates[pos + 1] - date -1; //distN = dist / avg; beg = date - (avg - dist%avg - 1); } outReg.set(avg, intSize, intervals); } return beg - date; } }