/* GNU General Public License CacheWolf is a software for PocketPC, Win and Linux that enables paperless caching. It supports the sites geocaching.com and opencaching.de Copyright (C) 2006 CacheWolf development team See http://www.cachewolf.de/ for more information. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package CacheWolf.database; import com.stevesoft.ewe_pat.Regex; import CacheWolf.Filter; import CacheWolf.MainForm; import CacheWolf.MyTableModel; import CacheWolf.OC; import CacheWolf.Preferences; import CacheWolf.Profile; import CacheWolf.utils.Common; import CacheWolf.utils.Metrics; import CacheWolf.utils.MyLocale; import CacheWolf.utils.SafeXML; import ewe.fx.FontMetrics; import ewe.fx.IconAndText; import ewe.sys.Convert; import ewe.sys.Time; import ewe.util.Vector; /** * A class to hold information on a cache.<br> * Not all attributes are filled at once. You will have to look at other classes and methods to get more information. * */ public class CacheHolder { private static final String EMPTY = ""; private static final String NOBEARING = "?"; private static final byte ISLOGUPDATED = 1; private static final byte ISUPDATED = 2; private static final byte ISNEW = 3; private static final byte ISINCOMPLETE = 4; /** The coordinates of the waypoint */ private CWPoint wpt = new CWPoint(); /** The date when the cache was hidden in format yyyy-mm-dd */ private String hidden = EMPTY; /** The code of the waypoint, beginning with 2 chars like GC or OC or CW (or any for Addis ) */ private String code = EMPTY; /** The name of the cache */ private String name = EMPTY; /** Byte 3: The cache type (@see CacheType for translation table) */ private byte type; /** The alias of the owner */ private String owner = EMPTY; /** Byte 1: The difficulty of the cache from 10 to 50 in 5 incements */ private byte difficulty = CacheTerrDiff.CW_DT_UNSET; /** Byte 2: The terrain rating of the cache from 10 to 50 in 5 incements */ private byte terrain = CacheTerrDiff.CW_DT_UNSET; /** Byte 4: The size of the cache (as per GC cache sizes Micro, Small, ....) */ private byte size = CacheSize.CW_SIZE_NOTCHOSEN; /** status is Found, Not found or a date in format yyyy-mm-dd hh:mm for found date */ private String status = EMPTY; /** attributes */ private long[] attributesBits = { 0l, 0l, 0l, 0l }; /** Bit 2: True if the cache is available for searching */ private boolean isAvailable = true; /** Bit 3: True if the cache has been archived */ private boolean isArchived = false; /** Bit 4: True if this cache has travelbugs */ private boolean hasBugs = false; /** Bit 5: True if the cache is blacklisted */ private boolean isBlack = false; /** Bit 6: True if we own this cache */ private boolean isOwned = false; /** Bit 7: True if we have found this cache */ private boolean isFound = false; /** Bit 8: True if the cache is new */ private boolean isNew = false; /** Bit 9: True if the number of logs for this cache has changed */ private boolean isLogUpdated = false; /** Bit 10: True if cache details have changed: longDescription, Hints, ...*/ private boolean isUpdated = false; /** Bit 11: True if the cache description is stored in HTML format */ private boolean isHTML = true; /** Bit 12: True if the cache data is incomplete (e.g. an error occurred during spidering */ private boolean isIncomplete = false; /** Bit 13: True if a note is entered for the cache */ private boolean hasNote = false; /** Bit 14: True if cache has solver entry */ private boolean hasSolver = false; /** Bit 15:*/ private boolean isPMCache = false; /** Bit 16:*/ private boolean isSolved = false; /** If this is true, the cache has been filtered (is currently invisible) */ private boolean isFiltered = false; /** True if the cache is part of the results of a search */ public boolean isFlagged = false; /** True if the cache has been selected using the tick box in the list view */ public boolean isChecked = false; /** True if additional waypoints for this cache should be displayed regardless of the filter settings */ private boolean showAddis = false; /** The unique OC cache ID */ private String idOC = EMPTY; /** Byte 5: The number of times this cache has not been found (max. 5) */ private byte noFindLogs = 0; /** Number of recommendations (from the opencaching logs) */ private int numRecommended = 0; /** Number of Founds since start of recommendations system */ private int numFoundsSinceRecommendation = 0; /** List of additional waypoints associated with this waypoint */ public Vector addiWpts = new Vector(); /** If this is an additional waypoint, this links back to the main waypoint */ public CacheHolder mainCache; public CacheHolderDetail details = null; /** The date this cache was last synced with OC in format yyyyMMddHHmmss */ private String lastSync = EMPTY; /** CacheHolder.ISINCOMPLETE, CacheHolder.ISNEW, CacheHolder.ISUPDATED, CacheHolder.ISLOGUPDATED */ private int modificationLevel = 0; private IconAndText modificationIcon = null; /** * When sorting the cacheDB this field is used.<br> * The relevant field is copied here.<br> * The sort is always done on this field to speed up the sorting process<br> */ public String sort; /** The distance from the centre in km */ public double kilom = -1; public double lastKilom = -2; public int lastMetric = -1; public String lastDistance = ""; /** The bearing N, NNE, NE, ENE ... from the current centre to this point */ private String bearing = NOBEARING; /** The angle (0=North, 180=South) from the current centre to this point */ public double degrees = 0; private final static String SOLVED = MyLocale.getMsg(362, "Solved"); public CacheHolder() { } public CacheHolder(String code) { this.code = code; type = CacheType.CW_TYPE_ERROR; } /** only for reading from index.xml */ public CacheHolder(String cache, int version) { int start, end; try { if (version == 3 || version == 4) { start = cache.indexOf('"') + 1; end = cache.indexOf('"', start); this.name = SafeXML.html2iso8859s1(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.owner = SafeXML.html2iso8859s1(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.wpt.latDec = Common.parseDouble(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.wpt.lonDec = Common.parseDouble(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.hidden = cache.substring(start, end); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.code = SafeXML.html2iso8859s1(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.status = cache.substring(start, end); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.idOC = cache.substring(start, end); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.lastSync = cache.substring(start, end); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.numRecommended = Convert.toInt(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.numFoundsSinceRecommendation = Convert.toInt(cache.substring(start, end)); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.attributesBits[0] = (Convert.parseLong(cache.substring(start, end))); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.attributesBits[2] = (Convert.parseLong(cache.substring(start, end))); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.long2boolFields(Convert.parseLong(cache.substring(start, end))); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.long2byteFields(Convert.parseLong(cache.substring(start, end))); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.attributesBits[1] = (Convert.parseLong(cache.substring(start, end))); start = cache.indexOf('"', end + 1) + 1; end = cache.indexOf('"', start); this.attributesBits[3] = (Convert.parseLong(cache.substring(start, end))); // move from Status to Bit if (this.status.indexOf("PM") > -1) { this.isPMCache = true; this.status = new Regex(",?\\s*PM\\s*,?", "").replaceFirst(this.status); } if (this.status.indexOf(SOLVED) > -1) { this.isSolved = true; this.status = new Regex(",?\\s*" + SOLVED + "\\s*,?", "").replaceFirst(this.status); } } if (version < Profile.CURRENTFILEFORMAT) { // forceload of details, creates waypoint.xml if missing details = getDetails(); // make sure details get (re)written in new format details.hasUnsavedChanges = true; // update information on notes and solver info setHasNote(!details.getCacheNotes().equals("")); setHasSolver(!details.getSolver().equals("")); } } catch (Exception ex) { // Preferences.itself().log("Ignored Exception in CacheHolder()", ex, true); } } // quick debug info public String toString() { return this.code; } /** The coordinates of the waypoint */ public CWPoint getWpt() { return this.wpt; } /** * The coordinates of the waypoint * @param wpt */ public void setWpt(CoordinatePoint wpt) { if (!this.wpt.equals(wpt)) { setUpdated(true); this.wpt.set(wpt); } } /** The date when the cache was hidden in format yyyy-mm-dd */ public String getHidden() { return this.hidden; } /** * The date when the cache was hidden in format yyyy-mm-dd * @param hidden */ public void setHidden(String hidden) { MainForm.profile.notifyUnsavedChanges(!hidden.equals(this.hidden)); this.hidden = hidden; } /** The code of the waypoint, beginning with 2 chars like GC or OC or CW (or any for Addis ) */ public String getCode() { return this.code; } /** * The code of the waypoint, beginning with 2 chars like GC or OC or CW (or any for Addis ) * @param code * @return true if changed, false if equal */ public boolean setCode(String code) { boolean ret = !code.equals(this.code); if (ret) { MainForm.profile.notifyUnsavedChanges(ret); this.code = code; } return ret; } /** The name of the cache */ public String getName() { return this.name; } /** * The name of the cache * @param name * @return true if changed, false if equal */ public boolean setName(String name) { boolean ret = !name.equals(this.name); if (ret) { MainForm.profile.notifyUnsavedChanges(ret); this.name = name; } return ret; } /**Byte 3: The cache type as byte (@see CacheType for translation table) */ public byte getType() { return this.type; } /** * Byte 3: Sets the type of the cache. * As the cache type values are int for the rest of CacheWolf and byte internally of CacheHolder, some conversion has to be done. * * @param type * cacheType */ public void setType(byte type) { MainForm.profile.notifyUnsavedChanges(this.type != type); this.type = type; } /** The alias of the owner */ public String getOwner() { return this.owner; } /** * The alias of the owner * @param owner */ public void setOwner(String owner) { if (!this.owner.equals(owner)) { MainForm.profile.notifyUnsavedChanges(true); this.owner = owner; this.isOwned = (this.owner.length() > 0) && (this.owner.equalsIgnoreCase(Preferences.itself().myAlias) || this.owner.equalsIgnoreCase(Preferences.itself().myAlias2)); } } /**Byte 1: The difficulty of the cache from 10 to 50 in 5 incements */ public byte getDifficulty() { return this.difficulty; } /** *Byte 1: The difficulty of the cache from 10 to 50 in 5 incements * @param difficulty */ public void setDifficulty(byte difficulty) { MainForm.profile.notifyUnsavedChanges(difficulty != this.difficulty); this.difficulty = difficulty; } /**Byte 2: The terrain rating of the cache from 10 to 50 in 5 incements */ public byte getTerrain() { return terrain; } /** *Byte 2: The terrain rating of the cache from 10 to 50 in 5 incements * @param terrain */ public void setTerrain(byte terrain) { MainForm.profile.notifyUnsavedChanges(terrain != this.terrain); this.terrain = terrain; } /**Byte 4: The size of the cache (as per GC cache sizes Micro, Small, ....) */ public byte getSize() { return size; } /** *Byte 4: The size of the cache (as per GC cache sizes Micro, Small, ....) * @param size */ public void setSize(byte size) { MainForm.profile.notifyUnsavedChanges(size != this.size); this.size = size; } /** Cachestatus is Found, Not found or a date in format yyyy-mm-dd hh:mm for found date */ public String getStatus() { return this.status; } /** * Cachestatus is Found, Not found or a date in format yyyy-mm-dd hh:mm for found date * @param status */ public void setStatus(String status) { if (!status.equals(this.status)) { this.status = status.trim(); MainForm.profile.notifyUnsavedChanges(true); if ((this.getType() == CacheType.CW_TYPE_FINAL) && (this.mainCache != null)) { this.mainCache.setStatus(this.status); // change the addi's in setFound } } } /** * The method takes into account blacklist, filters, search results - everything that determines if a cache is visible in the list or not.<br> * * @return * <code>true</code> if the waypoint should appear in the cache list.<br> */ public boolean isVisible() { int filter = MainForm.profile.getFilterActive(); boolean noShow = MainForm.profile.showBlacklisted() != this.isBlack(); noShow = noShow || MainForm.profile.showSearchResult() && !this.isFlagged; noShow = noShow || ((filter == Filter.FILTER_ACTIVE || filter == Filter.FILTER_MARKED_ONLY) && this.isFiltered() ^ MainForm.profile.isFilterInverted()); noShow = noShow || (filter == Filter.FILTER_CACHELIST) && !MainForm.itself.contains(this.code); // only from CacheTour boolean showAddi = this.showAddis() && this.mainCache != null && this.mainCache.isVisible(); noShow = noShow && !showAddi; return !noShow; } /** * <b><u>Important</u></b>: This flag no longer indicates if a cache is visible in the list.<br> * The new method for deciding if a cache is visible or not is <code>isVisible()</code>.<br> * Instead, it now <u>only</u> flags if the cache is filtered out by filter criteria.<br> * <br> * This property is affected by the following features: * <ul> * <li>"Defining and applying" a filter</li> * <li>Filtering out checked or unchecked caches</li> * </ul> * It is <u>not</u> affected by: * <ul> * <li>Inverting a filter</li> * <li>Removing a filter</li> * <li>Applying a filter</li> * <li>Applying a cache tour filter</li> * <li>Switching between normal view and blacklist view</li> * <li>Performing searches</li> * <li>Anything else that isn't directly connected to filters in it's proper sense.</li> * </ul> * * @return <code>True</code> if filter criteria are matched */ public boolean isFiltered() { return this.isFiltered; } /** * * @param isFiltered */ public void setFiltered(boolean isFiltered) { MainForm.profile.notifyUnsavedChanges(isFiltered != this.isFiltered); this.isFiltered = isFiltered; } /** Bit 2: True if the cache is available for searching */ public boolean isAvailable() { return this.isAvailable; } /** * Bit 2: True if the cache is available for searching * @param isAvailable */ public void setAvailable(boolean isAvailable) { MainForm.profile.notifyUnsavedChanges(isAvailable != this.isAvailable); this.isAvailable = isAvailable; if (this.isAvailable) { this.isArchived = false; } } /** Bit 3: True if the cache has been archived */ public boolean isArchived() { return this.isArchived; } /** * Bit 3: True if the cache has been archived * @param isArchived */ public void setArchived(boolean isArchived) { MainForm.profile.notifyUnsavedChanges(isArchived != this.isArchived); this.isArchived = isArchived; if (this.isArchived) { this.isAvailable = false; } } /** Bit 4: True if this cache has travelbugs */ public boolean hasBugs() { return this.hasBugs; } /** * Bit 4: True if this cache has travelbugs * @param b */ public void hasBugs(boolean b) { if (b != this.hasBugs) { MainForm.profile.notifyUnsavedChanges(true); this.hasBugs = b; } } /** * Bit 5: True if the cache is blacklisted<br> * Do not use this method to check if the cache should be displayed.<br> * Use <code>isVisible()</code> for this, which already does this (and other) checks.<br> * Only use this method if you really want to inform yourself about the black status of the cache!<br> * * @return <code>true</code> if the black status of the cache is set. */ public boolean isBlack() { return this.isBlack; } /** * Bit 5: True if the cache is blacklisted<br> * @param b */ public void setBlack(boolean b) { if (b != this.isBlack) { MainForm.profile.notifyUnsavedChanges(true); this.isBlack = b; } } /** Bit 6: True if we own this cache */ public boolean isOwned() { return this.isOwned; } /** * Bit 6: True if we own this cache * @param isOwned */ public void setOwned(boolean isOwned) { if (this.isOwned != isOwned) { MainForm.profile.notifyUnsavedChanges(true); this.isOwned = isOwned; if (isOwned) { if (this.owner.length() == 0) { this.isOwned = false; } else { // owner ist nicht myAlias oder myAlias2 if (!(this.owner.equalsIgnoreCase(Preferences.itself().myAlias) || this.owner.equalsIgnoreCase(Preferences.itself().myAlias2))) { this.isOwned = false; } } } } } /** Bit 7: True if we have found this cache */ public boolean isFound() { return this.isFound; } /** * Bit 7: True if we have found this cache.<br> * done in setCacheStatus this.mainCache.setCacheStatus(this.getCacheStatus());<br> * so setFound should be called after setCacheStatus<br> * @param isFound */ public void setFound(boolean isFound) { if (isFound != this.isFound) { MainForm.profile.notifyUnsavedChanges(true); this.isFound = isFound; if ((this.getType() == CacheType.CW_TYPE_FINAL) && (this.mainCache != null)) { this.mainCache.setFound(isFound); if (isFound) this.mainCache.setAttributesFromMainCacheToAddiWpts(); } } } /** Bit 8: True if the cache is new */ public boolean isNew() { return isNew; } /** * Bit 8: True if the cache is new * @param isNew */ public void setNew(boolean isNew) { if (isNew != this.isNew) { MainForm.profile.notifyUnsavedChanges(true); this.isNew = isNew; } } /** Bit 9: True if the number of logs for this cache has changed */ public boolean isLogUpdated() { return this.isLogUpdated; } /** * Bit 9: True if the number of logs for this cache has changed * @param isLogUpdated */ public void setLogUpdated(boolean isLogUpdated) { MainForm.profile.notifyUnsavedChanges(isLogUpdated != this.isLogUpdated); this.isLogUpdated = isLogUpdated; } /** Bit 10: True if cache details have changed: longDescription, Hints, ...*/ public boolean isUpdated() { return this.isUpdated; } /** * Bit 10: True if cache details have changed: longDescription, Hints, ... * @param isUpdated */ public void setUpdated(boolean isUpdated) { MainForm.profile.notifyUnsavedChanges(isUpdated != this.isUpdated); this.isUpdated = isUpdated; } /** Bit 11: True if the cache description is stored in HTML format */ public boolean isHTML() { return this.isHTML; } /** * Bit 11: True if the cache description is stored in HTML format * @param b */ public void isHTML(boolean b) { if (b != this.isHTML) { MainForm.profile.notifyUnsavedChanges(true); this.isHTML = b; } } /** Bit 12: True if the cache data is incomplete (e.g. an error occurred during spidering */ public boolean isIncomplete() { return this.isIncomplete; } /** * Bit 12: True if the cache data is incomplete (e.g. an error occurred during spidering * @param isIncomplete */ public void setIncomplete(boolean isIncomplete) { MainForm.profile.notifyUnsavedChanges(isIncomplete != this.isIncomplete); this.isIncomplete = isIncomplete; } /** Bit 13: True if a note is entered for the cache */ public boolean hasNote() { return this.hasNote; } /** * Bit 13: True if a note is entered for the cache * @param b */ public void setHasNote(boolean b) { if (b != this.hasNote) { MainForm.profile.notifyUnsavedChanges(true); this.hasNote = b; } } /** Bit 14: True if cache has solver entry */ public boolean hasSolver() { return this.hasSolver; } /** * Bit 14: True if cache has solver entry * @param b */ public void setHasSolver(boolean b) { if (b != this.hasSolver) { MainForm.profile.notifyUnsavedChanges(true); this.hasSolver = b; } } /** Bit 15:*/ public boolean isPremiumCache() { return this.isPMCache; } /** * Bit 15: * @param b */ public void setIsPremiumCache(boolean b) { if (b != this.isPMCache) { MainForm.profile.notifyUnsavedChanges(true); this.isPMCache = b; } } /** Bit 16:*/ public boolean isSolved() { return this.isSolved; } /** * Bit 16: * @param b */ public void setIsSolved(boolean b) { if (b != this.isSolved) { MainForm.profile.notifyUnsavedChanges(true); this.isSolved = b; } } /** attributes */ public long[] getAttributesBits() { return this.attributesBits; } /** * attributes * @param attributesBits */ public void setAttribsAsBits(long[] attributesBits) { MainForm.profile.notifyUnsavedChanges(attributesBits != this.attributesBits); this.attributesBits = attributesBits; } /** return true if waypoint is an additional waypoint of a cache */ public boolean isAddiWpt() { return CacheType.isAddiWpt(type); } /** return true if waypoint is a custom waypoint */ public boolean isCustomWpt() { return CacheType.isCustomWpt(type); } /** return true if waypoint is a cache main waypoint */ public boolean isCacheWpt() { return CacheType.isCacheWpt(type); } /** return true if the waypoint has one or more additional waypoints */ public boolean hasAddiWpt() { return addiWpts.getCount() > 0; } public boolean isOC() { return OC.isOC(this.code); } public boolean isGC() { return this.code.substring(0, 2).equalsIgnoreCase("GC"); } /** * Returns the distance in formatted output. Using kilometers when metric system is active, using miles when imperial system is active. * * @return The current distance. */ public String getDistance() { String result = null; String newUnit = null; if (this.kilom == this.lastKilom && Preferences.itself().metricSystem == this.lastMetric) { result = this.lastDistance; } else { if (this.kilom >= 0) { double newValue = 0; switch (Preferences.itself().metricSystem) { case Metrics.IMPERIAL: newValue = Metrics.convertUnit(this.kilom, Metrics.KILOMETER, Metrics.MILES); newUnit = Metrics.getUnit(Metrics.MILES); break; case Metrics.METRIC: default: newValue = this.kilom; newUnit = Metrics.getUnit(Metrics.KILOMETER); break; } result = MyLocale.formatDouble(newValue, "0.00") + " " + newUnit; } else { result = "? " + (Preferences.itself().metricSystem == Metrics.IMPERIAL ? Metrics.getUnit(Metrics.MILES) : Metrics.getUnit(Metrics.KILOMETER)); } // Caching values, so reevaluation is only done when really needed this.lastKilom = this.kilom; this.lastMetric = Preferences.itself().metricSystem; this.lastDistance = result; } return result; } /** * Updates Cache information with information provided by cache given as argument. This is used to update the cache with the information retrieved from files or web: The argument cache is the one that is filled with the read information, * <code>this</code> is the cache that is already in the database and subject to update. * * @param ch * The cache who's information is updating the current one * @param overwrite * If <code>true</code>, then <i>status</i>, <i>isFound</i> and <i>position</i> is updated, otherwise not. */ public void update(CacheHolder ch) { updateOwnLog(ch); this.setNumFoundsSinceRecommendation(ch.getNumFoundsSinceRecommendation()); this.setNumRecommended(ch.getNumRecommended()); this.setIsPremiumCache(ch.isPMCache); // Don't overwrite valid coordinates with invalid ones if (ch.getWpt().isValid() || !this.wpt.isValid()) { if (!this.isSolved) { this.wpt = ch.getWpt(); } } // the new from GC if coords are changed there if (ch.isSolved) { this.setIsSolved(ch.isSolved); } this.setCode(ch.getCode()); this.setName(ch.getName()); this.setOwner(ch.getOwner()); this.setHidden(ch.getHidden()); this.setSize(ch.getSize()); this.kilom = ch.kilom; this.bearing = ch.bearing; this.degrees = ch.degrees; this.setDifficulty(ch.getDifficulty()); this.setTerrain(ch.getTerrain()); this.setType(ch.getType()); this.setArchived(ch.isArchived()); this.setAvailable(ch.isAvailable()); this.setOwned(ch.isOwned()); this.setFiltered(ch.isFiltered()); this.setIncomplete(ch.isIncomplete()); this.addiWpts = ch.addiWpts; this.mainCache = ch.mainCache; this.setIdOC(ch.getIdOC()); this.setNoFindLogs(ch.getNoFindLogs()); this.hasBugs(ch.hasBugs()); this.isHTML(ch.isHTML()); this.sort = ch.sort; this.setLastSync(ch.getLastSync()); this.setAttribsAsBits(ch.getAttributesBits()); this.getDetails().update(ch.getDetails()); } public void updateOwnLog(CacheHolder ch) { ch.setStatus(ch.status.trim()); if (ch.isFound()) { if (ch.status.length() == 0) { // wenn kein Datum drin ist (also z.B. nicht von GC gespidert) ch.setStatus(CacheType.getFoundText(ch.type)); } } /* * Here we have to distinguish several cases: * this.isFound this.Status ch.isFound ch.Status this.isFound this.Status * -------------------------------------------------------------------------------- * true Found true Date = ch.Status(if not empty ?== Date ) * true yyyy-mm-dd true Date = ch.Status(if not empty ?== Date ) * true yyyy-mm-dd hh:mm true Date = ch.Status(Date) * false something false something = ch.Status(if not empty ?merge somehow ) * false something true Date true ch.Status(if not empty ?== Date ) */ if (this.isFound) { if (this.status.indexOf(":") < 0) { if (ch.getStatus().length() > 0) { // ch.isFound this.setStatus(ch.getStatus()); } } else { if (!Preferences.itself().keepTimeOnUpdate) { if (ch.getStatus().length() > 0) { // ch.isFound this.setStatus(ch.getStatus()); } } } } else { if (ch.getStatus().length() > 0) { this.setStatus(ch.getStatus()); this.setFound(ch.isFound()); } } if (ch.getDetails().getOwnLog() != null) { this.getDetails().setOwnLog(ch.getDetails().getOwnLog()); } } /** * Call it only when necessary, it takes time, because all logs must be parsed */ public void calcRecommendationScore() { // String pattern = getWayPoint().toUpperCase(); if (isOC()) { // Calculate recommendation score only when details are already loaded. // When they aren't loaded, then we assume, that there is no change. if (this.detailsLoaded()) { CacheHolderDetail chD = getDetails(); if (chD != null) { chD.CacheLogs.calcRecommendations(); setNumFoundsSinceRecommendation(chD.CacheLogs.getFoundsSinceRecommendation()); setNumRecommended(chD.CacheLogs.getNumRecommended()); } else { // cache doesn't have details setNumFoundsSinceRecommendation(-1); setNumRecommended(-1); } } } else { setNumFoundsSinceRecommendation(-1); // setNumRecommended(-1); } } /** Return a XML string containing all the cache data for storing in index.xml */ public String toXML() { calcRecommendationScore(); StringBuffer sb = new StringBuffer(530); // 390 sb.append("<CACHE name=\""); sb.append(SafeXML.string2Html(this.name)); sb.append("\" owner=\""); sb.append(SafeXML.string2Html(this.owner)); sb.append("\" lat=\""); sb.append(this.wpt.latDec); sb.append("\" lon=\""); sb.append(this.wpt.lonDec); sb.append("\" hidden=\""); sb.append(this.hidden); sb.append("\" wayp=\""); if (!(code.equals(code.toUpperCase()))) { code = code.toUpperCase(); // status = "aufGross"; this.saveCacheDetails(); } sb.append(SafeXML.string2Html(code.toUpperCase())); sb.append("\" status=\""); sb.append(this.status); sb.append("\" ocCacheID=\""); sb.append(this.idOC); sb.append("\" lastSyncOC=\""); sb.append(this.lastSync); sb.append("\" num_recommended=\""); sb.append(Convert.formatInt(this.numRecommended)); sb.append("\" num_found=\""); sb.append(Convert.formatInt(this.numFoundsSinceRecommendation)); sb.append("\" attributesYes=\""); sb.append(Convert.formatLong(this.attributesBits[0])); sb.append("\" attributesNo=\""); sb.append(Convert.formatLong(this.attributesBits[2])); sb.append("\" boolFields=\""); sb.append(Convert.formatLong(this.boolFields2long())); sb.append("\" byteFields=\""); sb.append(Convert.formatLong(this.byteFields2long())); sb.append("\" attributesYes1=\""); sb.append(Convert.formatLong(this.attributesBits[1])); sb.append("\" attributesNo1=\""); sb.append(Convert.formatLong(this.attributesBits[3])); sb.append("\"/>\n"); return sb.toString(); } public void calcDistance(CWPoint toPoint) { if (this.wpt.isValid()) { kilom = this.wpt.getDistance(toPoint); degrees = toPoint.getBearing(this.wpt); bearing = CWPoint.getDirection(degrees); } else { kilom = -1; bearing = NOBEARING; } } public void setAttributesFromMainCache() { CacheHolder mainCh = this.mainCache; if (!this.owner.equalsIgnoreCase(mainCh.getOwner())) { this.owner = mainCh.getOwner(); MainForm.profile.notifyUnsavedChanges(true); } if (this.isOwned != mainCh.isOwned()) { this.isOwned = mainCh.isOwned(); MainForm.profile.notifyUnsavedChanges(true); } if (mainCh.isFound()) { if (!this.isFound) { this.setStatus(mainCh.getStatus()); this.setFound(true); } // else addi is already found (perhaps at other time) } else { // there may be a found addi , so don't overwrite if ((this.getType() == CacheType.CW_TYPE_FINAL)) { if (this.getWpt().isValid()) { this.setStatus(mainCh.getStatus()); } this.setFound(false); } } this.setArchived(mainCh.isArchived()); this.setAvailable(mainCh.isAvailable()); this.setBlack(mainCh.isBlack()); this.setNew(mainCh.isNew()); } public void setAttributesFromMainCacheToAddiWpts() { if (this.hasAddiWpt()) { CacheHolder addiWpt; for (int i = this.addiWpts.getCount() - 1; i >= 0; i--) { addiWpt = (CacheHolder) this.addiWpts.get(i); addiWpt.setAttributesFromMainCache(); } } } /** * True if ch and this belong to the same main cache. * * @param ch * @return */ public boolean hasSameMainCache(CacheHolder ch) { if (this == ch) return true; if (ch == null) return false; if ((!this.isAddiWpt()) && (!ch.isAddiWpt())) return false; CacheHolder main1, main2; if (this.isAddiWpt()) main1 = this.mainCache; else main1 = this; if (ch.isAddiWpt()) main2 = ch.mainCache; else main2 = ch; return main1 == main2; } /** * Find out of detail object of Cache is loaded. Returns <code>true</code> if this is the case. * * @return True when details object is present */ public boolean detailsLoaded() { return details != null; } /** * Gets the CacheHolderDetail object of a cache.<br> * The detail object stores information which is not needed for every cache instantaneously, but can be loaded if the user decides to look at this cache.<br> * If the cache object is already existing, the method will return this object,<br> * otherwise it will create it and try to read it from the corresponding <waypoint>.xml file.<br> * Depending on the parameters it is allowed that the <waypoint>.xml file does not yet exist, or the user is warned that the file doesn't exist.<br> * If more than <code>maxdetails</code> details are loaded, then the 5 last recently loaded caches are unloaded (to save ram). * * @return The respective CacheHolderDetail, or null */ public CacheHolderDetail getDetails() { if (details == null) { details = new CacheHolderDetail(this); details.readCache(MainForm.profile.dataDir); if (details != null && !cachesWithLoadedDetails.contains(this)) { cachesWithLoadedDetails.add(this); if (cachesWithLoadedDetails.size() >= Preferences.itself().maxDetails) removeOldestDetails(); } } return details; } /** * Saves the cache to the corresponding <waypoint>.xml file, located in the profiles directory. The waypoint of the cache should be set to do so. */ public void saveCacheDetails() { checkIncomplete(); this.getDetails().saveCacheDetails(MainForm.profile.dataDir); } void releaseCacheDetails() { if (details != null && details.hasUnsavedChanges) { details.saveCacheDetails(MainForm.profile.dataDir); } details = null; cachesWithLoadedDetails.remove(this); } // final static int maxDetails = 50; public static Vector cachesWithLoadedDetails = new Vector(Preferences.itself().maxDetails); private void removeOldestDetails() { CacheHolder ch; for (int i = 0; i < Preferences.itself().deleteDetails; i++) { // String wp = (String) cachesWithLoadedDetails.get(i); // CacheHolder ch = MainForm.profile.cacheDB.get(wp); ch = (CacheHolder) cachesWithLoadedDetails.get(i); if (ch != null && ch.details.getParent() != this) ch.releaseCacheDetails(); } } public static void removeAllDetails() { CacheHolder ch; for (int i = cachesWithLoadedDetails.size() - 1; i >= 0; i--) { // String wp = (String) cachesWithLoadedDetails.get(i); // CacheHolder ch = MainForm.profile.cacheDB.get(wp); ch = (CacheHolder) cachesWithLoadedDetails.get(i); if (ch != null && ch.detailsLoaded()) ch.releaseCacheDetails(); } } /** * when importing caches you can set details.saveChanges = true when the import is finished call this method to save the pending changes */ public static void saveAllModifiedDetails() { CacheHolder ch; CacheHolderDetail chD; for (int i = cachesWithLoadedDetails.size() - 1; i >= 0; i--) { // String wp = (String) cachesWithLoadedDetails.get(i); // ch = MainForm.profile.cacheDB.get(wp); ch = (CacheHolder) cachesWithLoadedDetails.get(i); if (ch != null) { chD = ch.getDetails(); if (chD != null && chD.hasUnsavedChanges) { // ch.calcRecommendationScore(); chD.saveCacheDetails(MainForm.profile.dataDir); } } } } private final static int MSG_NR = 0; private final static int GC_MSG = 1; private final static int IDX_WRITENOTE = 5; private final static String[][] _logType = { { "353", "" }, { "319", "Didn't find it" }, // { "318", "Found it" }, // { "355", "Attended" }, // { "361", "Webcam Photo Taken" }, // { "314", "Write note" }, // at change do change IDX_WRITENOTE = 5; { "315", "Needs Archived" }, { "316", "Needs Maintenance" }, { "317", "Search" }, { "354", "Will Attend" }, { "320", "Owner" }, { "359", "Owner Maintenance" }, { "356", "Temporarily Disable Listing" }, { "357", "Enable Listing" }, { "358", "Post Reviewer Note" }, { "313", "Flag 1" }, { "360", "Flag 2" }, }; public final static String[] GetGuiLogTypes() { String[] ret = new String[_logType.length]; for (int i = 0; i < _logType.length; i++) { ret[i] = MyLocale.getMsg(Common.parseInt(_logType[i][MSG_NR]), ""); } return ret; } public String getGCFoundText() { int msgNr = 318; // normal found if (type == CacheType.CW_TYPE_WEBCAM) { msgNr = 361; } else if (type == CacheType.CW_TYPE_EVENT || type == CacheType.CW_TYPE_MEGA_EVENT || type == CacheType.CW_TYPE_MAZE) { msgNr = 355; } for (int i = 0; i < _logType.length; i++) { if (msgNr == Common.parseInt(_logType[i][MSG_NR])) { return _logType[i][GC_MSG]; } } return ""; } public String getGCFoundIcon() { String iconName = "2.png"; if (type == CacheType.CW_TYPE_WEBCAM) { iconName = "11.png"; } else if (type == CacheType.CW_TYPE_EVENT || type == CacheType.CW_TYPE_MEGA_EVENT || type == CacheType.CW_TYPE_MAZE) { iconName = "10.png"; } return iconName; } public String getCWLogText(String s) { for (int i = 0; i < _logType.length; i++) { if ((s).equals(_logType[i][GC_MSG])) { return MyLocale.getMsg(Common.parseInt(_logType[i][MSG_NR]), ""); } } return ""; } public final static String getGCLogType(byte type, boolean isFound, String CacheStatus) { String gcLogType = _logType[IDX_WRITENOTE][GC_MSG]; if (isFound) { for (int i = 1; i < _logType.length; i++) { if (Common.parseInt(_logType[i][MSG_NR]) == CacheType.getLogMsgNr(type)) { gcLogType = _logType[i][GC_MSG]; break; } } } else { for (int i = 1; i < _logType.length; i++) { if (CacheStatus.endsWith(MyLocale.getMsg(Common.parseInt(_logType[i][MSG_NR]), ""))) { gcLogType = _logType[i][GC_MSG]; break; } } } return gcLogType; } public String getStatusText() { if ((status.length() == 10 || status.length() == 16) && status.charAt(4) == '-') { return CacheType.getFoundText(type) + " " + status; } else { if (isFound) { return CacheType.getFoundText(type); } else { return status; } } } public String getStatusDate() { String statusDate = ""; if (isFound() || getStatus().indexOf(MyLocale.getMsg(319, "not found")) > 10) { Regex rexDate = new Regex("([0-9]{4}-[0-9]{2}-[0-9]{2})"); rexDate.search(getStatus()); if (rexDate.stringMatched(1) != null) { statusDate = rexDate.stringMatched(1); } } return statusDate; } public String getStatusTime() { String statusTime = ""; if (isFound() || getStatus().indexOf(MyLocale.getMsg(319, "not found")) > 10) { Regex rexTime = new Regex("([0-9]{1,2}:[0-9]{2})"); rexTime.search(getStatus()); if (rexTime.stringMatched(1) != null) { statusTime = rexTime.stringMatched(1); } else { Regex rexDate = new Regex("([0-9]{4}-[0-9]{2}-[0-9]{2})"); rexDate.search(getStatus()); if (rexDate.stringMatched(1) != null) { statusTime = "00:00"; } } } return statusTime; } public String getStatusUtcDate() { String statusDate = getStatusDate(); long timeZoneOffset = MainForm.profile.getTimeZoneOffsetLong(); if (timeZoneOffset != 0 || MainForm.profile.getTimeZoneAutoDST()) { //convert to UTC only if time is set Regex rexTime = new Regex("([0-9]{1,2}:[0-9]{2})"); rexTime.search(getStatus()); if (rexTime.stringMatched(1) != null) { String statusDateTime = (statusDate + " " + getStatusTime()).trim(); if (statusDateTime.length() > 0) { try { Time logTime = new Time(); logTime.parse(statusDateTime, "yyyy-MM-dd HH:mm"); long timeZoneOffsetMillis = 0; if (timeZoneOffset == 100) { //autodetect timeZoneOffsetMillis = Time.convertSystemTime(logTime.getTime(), false) - logTime.getTime(); } else { timeZoneOffsetMillis = timeZoneOffset * 3600000; } if (MainForm.profile.getTimeZoneAutoDST()) { int lsM = (byte) (31 - ((int) (5 * logTime.year / 4) + 4) % 7);//last Sunday in March int lsO = (byte) (31 - ((int) (5 * logTime.year / 4) + 1) % 7);//last Sunday in October Time dstStart = new Time(lsM, 3, logTime.year); dstStart.hour = 2; dstStart.setTime(dstStart.getTime() - timeZoneOffsetMillis); Time dstEnd = new Time(lsO, 10, logTime.year); dstEnd.hour = 1; dstEnd.minute = 59; dstEnd.setTime(dstEnd.getTime() - timeZoneOffsetMillis); if (logTime.after(dstStart) && logTime.before(dstEnd)) { timeZoneOffsetMillis += 3600000; } } logTime.setTime(logTime.getTime() - timeZoneOffsetMillis); statusDate = logTime.format("yyyy-MM-dd"); } catch (IllegalArgumentException e) { } } } } return statusDate; } public String getStatusUtcTime() { String statusTime = getStatusTime(); long timeZoneOffset = MainForm.profile.getTimeZoneOffsetLong(); if (timeZoneOffset != 0 || MainForm.profile.getTimeZoneAutoDST()) { //convert to UTC only if time is set Regex rexTime = new Regex("([0-9]{1,2}:[0-9]{2})"); rexTime.search(getStatus()); if (rexTime.stringMatched(1) != null) { String statusDateTime = (getStatusDate() + " " + statusTime).trim(); if (statusDateTime.length() > 0) { try { Time logTime = new Time(); logTime.parse(statusDateTime, "yyyy-MM-dd HH:mm"); long timeZoneOffsetMillis = 0; if (timeZoneOffset == 100) { //autodetect timeZoneOffsetMillis = Time.convertSystemTime(logTime.getTime(), false) - logTime.getTime(); } else { timeZoneOffsetMillis = timeZoneOffset * 3600000; } if (MainForm.profile.getTimeZoneAutoDST()) { int lsM = (byte) (31 - ((int) (5 * logTime.year / 4) + 4) % 7);//last Sunday in March int lsO = (byte) (31 - ((int) (5 * logTime.year / 4) + 1) % 7);//last Sunday in October Time dstStart = new Time(lsM, 3, logTime.year); dstStart.hour = 2; dstStart.setTime(dstStart.getTime() - timeZoneOffsetMillis); Time dstEnd = new Time(lsO, 10, logTime.year); dstEnd.hour = 1; dstEnd.minute = 59; dstEnd.setTime(dstEnd.getTime() - timeZoneOffsetMillis); if (logTime.after(dstStart) && logTime.before(dstEnd)) { timeZoneOffsetMillis += 3600000; } } logTime.setTime(logTime.getTime() - timeZoneOffsetMillis); statusTime = logTime.format("HH:mm"); } catch (IllegalArgumentException e) { } } } } return statusTime; } public String getCacheID() { String result = ""; if (this.isGC()) { int gcId = 0; String sequence = "0123456789ABCDEFGHJKMNPQRTVWXYZ"; String rightPart = this.code.substring(2).toUpperCase(); int base = 31; if ((rightPart.length() < 4) || (rightPart.length() == 4 && sequence.indexOf(rightPart.charAt(0)) < 16)) { base = 16; } for (int p = 0; p < rightPart.length(); p++) { gcId *= base; gcId += sequence.indexOf(rightPart.charAt(p)); } if (base == 31) { gcId += java.lang.Math.pow(16, 4) - 16 * java.lang.Math.pow(31, 3); } result = Integer.toString(gcId); } else if (isOC()) { result = getIdOC(); } return result; } /** * Initializes the caches states (and its addis) before updating, so that the "new", "updated", "log_updated" and "incomplete" properties are properly set. * * @param pNewCache * <code>true</code> if it is a new cache (i.e. a cache not existing in CacheDB), <code>false</code> otherwise. */ public void initStates(boolean isNewCache) { this.setNew(isNewCache); this.setUpdated(false); this.setLogUpdated(false); this.setIncomplete(false); if (!isNewCache && this.hasAddiWpt()) { for (int i = 0; i < this.addiWpts.size(); i++) { ((CacheHolder) this.addiWpts.get(i)).initStates(isNewCache); } } } /** * Creates a bit field of boolean values of the cache, represented as a long value. Boolean value of <code>true</code> results in <code>1</code> in the long values bits, and, vice versa, 0 for false. * * @return long value representing the boolean bit field */ private long boolFields2long() { // To get the same list of visible caches after loading a profile, // the property isVisible() is saved instead of isFiltered(), but at // the place where isFiltered() is read. long value = bool2BitMask(!this.isVisible(), 1) // | bool2BitMask(this.isAvailable, 2) // | bool2BitMask(this.isArchived, 3) // | bool2BitMask(this.hasBugs, 4) // | bool2BitMask(this.isBlack, 5) // | bool2BitMask(this.isOwned, 6) // | bool2BitMask(this.isFound, 7) // | bool2BitMask(this.isNew, 8) // | bool2BitMask(this.isLogUpdated, 9) // | bool2BitMask(this.isUpdated, 10) // | bool2BitMask(this.isHTML, 11) // | bool2BitMask(this.isIncomplete, 12) // | bool2BitMask(this.hasNote, 13) // | bool2BitMask(this.hasSolver, 14) // | bool2BitMask(this.isPMCache, 15) // | bool2BitMask(this.isSolved, 16) // ; return value; } /** * Creates a field of byte values of certain properties of the cache, represented as a long value.<br> * As a long is 8 bytes wide, one might pack 8 bytes into a long, one every 8 bits.<br> * The position indicates the group of bits where the byte is packed,<br> * counting starting from one by the right side of the long.<br> * * @return long value representing the byte field */ private long byteFields2long() { long value = byteBitMask(this.difficulty, 1) | byteBitMask(this.terrain, 2) | byteBitMask(this.type, 3) | byteBitMask(this.size, 4) | byteBitMask(this.noFindLogs, 5); return value; } /** * sets Difficulty, Terrain, Type, Size and NoFindLogs on reading DB * * @param value * The long value which contains up to 8 bytes. */ private void long2byteFields(long value) { this.difficulty = byteFromLong(value, 1); this.terrain = byteFromLong(value, 2); this.type = byteFromLong(value, 3); this.size = byteFromLong(value, 4); this.noFindLogs = byteFromLong(value, 5); if (this.difficulty == CacheTerrDiff.CW_DT_ERROR || this.terrain == CacheTerrDiff.CW_DT_ERROR || this.size == CacheSize.CW_SIZE_ERROR || this.type == CacheType.CW_TYPE_ERROR) { setIncomplete(true); } } /** * Extracts a byte from a long value. The position is the number of the 8-bit block of the long (which contains 8 8-bit blocks), counted from 1 to 8, starting from the right side of the long. * * @param value * The long value which contains the bytes * @param position * The position of the byte, from 1 to 8 * @return The decoded byte value */ private byte byteFromLong(long value, int position) { byte b = -1; // = 11111111 return (byte) ((value & this.byteBitMask(b, position)) >>> (position - 1) * 8); } /** * Evaluates boolean values from a long value, which is seen as bit field. * * @param value * The bit field as long value */ private void long2boolFields(long value) { isFiltered = (value & this.bool2BitMask(true, 1)) != 0; isAvailable = (value & this.bool2BitMask(true, 2)) != 0; isArchived = (value & this.bool2BitMask(true, 3)) != 0; hasBugs = (value & this.bool2BitMask(true, 4)) != 0; isBlack = (value & this.bool2BitMask(true, 5)) != 0; isOwned = (value & this.bool2BitMask(true, 6)) != 0; isFound = (value & this.bool2BitMask(true, 7)) != 0; isNew = (value & this.bool2BitMask(true, 8)) != 0; isLogUpdated = (value & this.bool2BitMask(true, 9)) != 0; isUpdated = (value & this.bool2BitMask(true, 10)) != 0; isHTML = (value & this.bool2BitMask(true, 11)) != 0; isIncomplete = (value & this.bool2BitMask(true, 12)) != 0 || this.isIncomplete; hasNote = (value & this.bool2BitMask(true, 13)) != 0; hasSolver = (value & this.bool2BitMask(true, 14)) != 0; isPMCache = isPMCache || (value & this.bool2BitMask(true, 15)) != 0; // only used on reading the index, previously updated from Status isSolved = isSolved || (value & this.bool2BitMask(true, 16)) != 0; // only used on reading the index, previously updated from Status } /** * Represents a bit mask as long value for a boolean value which is saved at a specified position in the long field. * * @param value * The boolean value we want to code * @param position * Position of the value in the bit mask * @return The corresponding bit mask:<br> * A long value where all bits are set to 0 except for the one we like to represent:<br> * This is 1 if the value is true, 0 if not. */ private long bool2BitMask(boolean value, int position) { if (value) { return (1L << (position - 1)); } else { return 0L; } } /** * Coding a long field which has only the bits of the byte value set. The position is the number (from 1 to 8) of the byte block which is used from the long. * * @param value * Byte to encode * @param position * Position of the byte value in the long * @return Encoded byte value as long */ private long byteBitMask(byte value, int position) { long result = (0xFF & (long) value) << ((position - 1) * 8); return result; } public String getBearing() { return bearing; } /** * Gets an IconAndText object for the cache.<br> * If the level of the Icon is equal to the last call of the method, the same (cached) object is returned.<br> * If the object is null or the level is different, a new object is created.<br> * * @param fm * Font metrics * @return New or old IconAndText object */ public IconAndText getModificationIcon(FontMetrics fm) { if (this.isIncomplete) { boolean doit = false; if (this.modificationIcon == null) doit = true; else { if (modificationLevel != CacheHolder.ISINCOMPLETE || !modificationIcon.text.equals(this.code)) { doit = true; } } if (doit) { modificationIcon = new IconAndText(CacheType.getTypeImage(CacheType.CW_TYPE_ERROR), this.code, fm); modificationLevel = CacheHolder.ISINCOMPLETE; } } else if (this.isNew) { boolean doit = false; if (this.modificationIcon == null) doit = true; else { if (modificationLevel != CacheHolder.ISNEW || !modificationIcon.text.equals(this.code)) { doit = true; } } if (doit) { modificationIcon = new IconAndText(MyTableModel.yellow, this.code, fm); modificationLevel = CacheHolder.ISNEW; } } else if (this.isUpdated) { boolean doit = false; if (this.modificationIcon == null) doit = true; else { if (modificationLevel != CacheHolder.ISUPDATED || !modificationIcon.text.equals(this.code)) { doit = true; } } if (doit) { modificationIcon = new IconAndText(MyTableModel.red, this.code, fm); modificationLevel = CacheHolder.ISUPDATED; } } else if (this.isLogUpdated) { boolean doit = false; if (this.modificationIcon == null) doit = true; else { if (modificationLevel != CacheHolder.ISLOGUPDATED || !modificationIcon.text.equals(this.code)) { doit = true; } } if (doit) { modificationIcon = new IconAndText(MyTableModel.blue, this.code, fm); modificationLevel = CacheHolder.ISLOGUPDATED; } } else { modificationLevel = 0; modificationIcon = null; } return modificationIcon; } /** * If this returns <code>true</code>, then the additional waypoints for this cache should be displayed regardless how the filter is set. If it is <code>false</code>, then the normal filter settings apply.<br> * This property is not saved in index.xml, so if you reload the data, then this information is gone. * * @return <code>True</code>: Always display additional waypoints for cache. */ public boolean showAddis() { return this.showAddis; } /** * Setter for <code>showAddis()</code>. If this returns <code>true</code>, then the additional waypoints for this cache should be displayed regardless how the filter is set. If it is <code>false</code>, then the normal filter settings apply.<br> * This property is not saved in index.xml, so if you reload the data, then this information is gone. * * @param value * <code>True</code>: Always display additional waypoints for cache. */ public void setShowAddis(boolean value) { // This value is always stored in the main cache and all addis. CacheHolder mc = null; if (this.mainCache == null) { mc = this; } else { mc = this.mainCache; } if (mc.showAddis != value) { mc.showAddis = value; for (int i = 0; i < mc.addiWpts.size(); i++) { CacheHolder ac = (CacheHolder) mc.addiWpts.get(i); ac.showAddis = value; } } } /** checks the waypoint data integrity to set a warning flag if something is missing */ public boolean checkIncomplete() { // TODO: discuss if we should only check cache waypoints and silently "fix" everything else boolean ret; if (isCacheWpt()) { if (this.code.length() < 3 || getDifficulty() < CacheTerrDiff.CW_DT_UNSET || getTerrain() < CacheTerrDiff.CW_DT_UNSET || getSize() == CacheSize.CW_SIZE_ERROR || getOwner().length() == 0 || getHidden().length() == 0 || getName().length() == 0) ret = true; else ret = false; } else if (isAddiWpt()) { // FIXME: do not check for mainCache == null, since it will be null during initial import // FIXME: find out why we only check waypoints with IDs of a certain length ??? // if (mainCache == null // || getHard() != CacheTerrDiff.CW_DT_UNSET if (getDifficulty() != CacheTerrDiff.CW_DT_UNSET || getSize() != CacheSize.CW_SIZE_NOTCHOSEN || getTerrain() != CacheTerrDiff.CW_DT_UNSET || this.code.length() < 3 // || getCacheOwner().length() > 0 // || getDateHidden().length() > 0 || getName().length() == 0) ret = true; else ret = false; } else if (isCustomWpt()) { if (getDifficulty() != CacheTerrDiff.CW_DT_UNSET || getTerrain() != CacheTerrDiff.CW_DT_UNSET || getSize() != CacheSize.CW_SIZE_NOTCHOSEN || this.code.length() < 3 // || getCacheOwner().length() > 0 // || getDateHidden().length() > 0 // || getCacheName().length() == 0 ) ret = true; else ret = false; } else { // we should not get here, so let's set a warning just in case ret = true; } setIncomplete(ret); return ret; } public String getIdOC() { return this.idOC; } public void setIdOC(String idOC) { if (!idOC.equals(this.idOC)) { MainForm.profile.notifyUnsavedChanges(true); this.idOC = idOC; } } public byte getNoFindLogs() { return noFindLogs; } public void setNoFindLogs(byte b) { if (b != this.noFindLogs) { MainForm.profile.notifyUnsavedChanges(true); this.noFindLogs = b; } } public int getNumRecommended() { return this.numRecommended; } public String getRecommended() { if (!isCacheWpt()) return ""; if (isOC()) { return Convert.formatInt(LogList.getScore(this.numRecommended, this.numFoundsSinceRecommendation)) + " (" + Convert.formatInt(numRecommended) + ")"; } else { if (Preferences.itself().useGCFavoriteValue) { return "" + numRecommended; } else { int gcVote = numRecommended; if (gcVote < 100) { // Durchschnittswert der Abstimmung 1, 1.5 ... 4.5, 5 (nur eine Stimme) return MyLocale.formatDouble((double) gcVote / 10.0, "0.0"); } else { int votes = gcVote / 100; // Anzahl Stimmen gcVote = gcVote - 100 * votes; // Durchschnittswert der Abstimmung 1, 1.5 ... 4.5, 5 return MyLocale.formatDouble((double) gcVote / 10.0, "0.0") + " (" + Convert.formatInt(votes) + ")"; } } } } public void setNumRecommended(int i) { if (i != this.numRecommended) { MainForm.profile.notifyUnsavedChanges(true); this.numRecommended = i; } } public int getNumFoundsSinceRecommendation() { return this.numFoundsSinceRecommendation; } public void setNumFoundsSinceRecommendation(int i) { if (i != this.numFoundsSinceRecommendation) { MainForm.profile.notifyUnsavedChanges(true); this.numFoundsSinceRecommendation = i; } } public String getLastSync() { return this.lastSync; } public void setLastSync(String s) { if (!s.equals(this.lastSync)) { MainForm.profile.notifyUnsavedChanges(true); this.lastSync = s; } } /** * rename a waypoint ?and all its associated files * * @param newCode * new waypoint id (will be converted to upper case) * @return true on success, false on error */ public boolean rename(String newCode) { newCode = newCode.toUpperCase(); details = getDetails(); if (details.rename(newCode)) { setCode(newCode); saveCacheDetails(); // the xml-file MainForm.profile.notifyUnsavedChanges(true); return true; } else { return false; } } }