/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.swingui; import ca.bcit.geekkit.CalendarTableModel; import ca.bcit.geekkit.JCalendar; import java.awt.BorderLayout; import java.awt.Color; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import net.java.dev.spellcast.utilities.JComponentUtilities; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.persistence.HolidayDatabase; import net.sourceforge.kolmafia.swingui.widget.RequestPane; import net.sourceforge.kolmafia.utilities.StringUtilities; public class CalendarFrame extends GenericFrame implements ListSelectionListener { public static final SimpleDateFormat SHORT_FORMAT = new SimpleDateFormat( "yyyyMMdd", Locale.US ); public static final SimpleDateFormat LONG_FORMAT = new SimpleDateFormat( "MMMM d, yyyy", Locale.US ); static { // all dates are presented as if the day begins at rollover CalendarFrame.SHORT_FORMAT.setTimeZone( TimeZone.getTimeZone( "GMT-0330" ) ); CalendarFrame.LONG_FORMAT.setTimeZone( TimeZone.getTimeZone( "GMT-0330" ) ); } // static final array of file names (not including .gif extension) // for the various months in the KoL calendar. public static final String[] CALENDARS = { "", "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; // The following are static final variables used to track the calendar. // They are made static final as a design decision to allow the oracle // table nested inside of this class the access it needs to data. private static int calendarDay = 0; private static int ronaldPhase = -1; private static int grimacePhase = -1; private static int hamburglarPosition = -1; private static JCalendar calendar; private static OracleTable oracleTable; private static RequestPane dailyDisplay; private static RequestPane predictDisplay; private static Calendar selectedDate; private static int selectedRow, selectedColumn; public CalendarFrame() { super( "Farmer's Almanac" ); CalendarFrame.selectedRow = -1; CalendarFrame.selectedColumn = -1; try { CalendarFrame.selectedDate = Calendar.getInstance( TimeZone.getTimeZone( "GMT-0330" ), Locale.US ); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } CalendarFrame.calculatePhases( CalendarFrame.selectedDate.getTime() ); dailyDisplay = new RequestPane(); JComponentUtilities.setComponentSize( dailyDisplay, 400, 335 ); predictDisplay = new RequestPane(); JComponentUtilities.setComponentSize( predictDisplay, 400, 335 ); this.tabs.addTab( "KoL One-a-Day", dailyDisplay ); this.tabs.addTab( "Upcoming Events", predictDisplay ); CalendarFrame.calendar = new JCalendar( OracleTable.class ); CalendarFrame.oracleTable = (OracleTable) CalendarFrame.calendar.getTable(); CalendarFrame.oracleTable.getSelectionModel().addListSelectionListener( this ); CalendarFrame.oracleTable.getColumnModel().getSelectionModel().addListSelectionListener( this ); JPanel calendarPanel = new JPanel( new BorderLayout() ); calendarPanel.add( this.tabs, BorderLayout.CENTER ); calendarPanel.add( CalendarFrame.calendar, BorderLayout.EAST ); this.setCenterComponent( calendarPanel ); this.updateTabs(); } /** * Listener method which updates the main HTML panel with information, pending on the user's calendar day selection. */ public void valueChanged( final ListSelectionEvent e ) { // If the person has not yet released the // mouse, then do nothing. if ( e.getValueIsAdjusting() ) { return; } // Compute which date is being selected // in the calendar table and update the // HTML on the center pane as appropriate if ( CalendarFrame.oracleTable.getSelectedRow() != CalendarFrame.selectedRow || CalendarFrame.oracleTable.getSelectedColumn() != CalendarFrame.selectedColumn ) { try { CalendarFrame.selectedRow = CalendarFrame.oracleTable.getSelectedRow(); CalendarFrame.selectedColumn = CalendarFrame.oracleTable.getSelectedColumn(); CalendarFrame.selectedDate.set( CalendarFrame.calendar.getModel().getCurrentYear(), CalendarFrame.calendar.getModel().getCurrentMonth(), StringUtilities.parseInt( (String) CalendarFrame.calendar.getModel().getValueAt( CalendarFrame.selectedRow, CalendarFrame.selectedColumn ) ) ); CalendarFrame.calculatePhases( CalendarFrame.selectedDate.getTime() ); this.updateTabs(); } catch ( Exception e1 ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e1 ); } } } /** * Recalculates the moon phases given the time noted in the constructor. This calculation assumes that the * straightforward algorithm has no errors. */ private static final void calculatePhases( final Date time ) { // In order to ensure that everything is computed // based on new-year, wrap the date inside of the // formatter (which strips time information) and // reparse the date. CalendarFrame.calendarDay = HolidayDatabase.getCalendarDay( time ); int phaseStep = ( CalendarFrame.calendarDay + 16 ) % 16; CalendarFrame.ronaldPhase = phaseStep % 8; CalendarFrame.grimacePhase = phaseStep / 2; CalendarFrame.hamburglarPosition = HolidayDatabase.getHamburglarPosition( time ); } /** * Updates the HTML which displays the date and the information relating to the given date. This should be called * after all recalculation attempts. */ private static final void updateDailyPage() { if ( KoLConstants.DAILY_FORMAT.format( CalendarFrame.selectedDate.getTime() ).equals( "20051027" ) ) { CalendarFrame.dailyDisplay.setText( "<center><h1>White Wednesday</h1></center>" ); return; } StringBuffer displayHTML = new StringBuffer(); // First display today's date along with the // appropriate calendar picture. Include the // link shown in the clan calendar. displayHTML.append( "<center><table><tr><td valign=top>" ); displayHTML.append( "<center><table border=1><tr><td align=center>drawn by <b>" ); // Display either girls or boys of loathing, as desired String artistURL; String artistName; String artDirectory; if ( KoLConstants.RNG.nextInt( 2 ) == 1 ) { artistURL = "http://elfwood.lysator.liu.se/loth/l/e/leigh/leigh.html"; artistName = "SpaceMonkey"; artDirectory = "bikini"; } else { artistURL = "http://www.myimagehosting.com/album.php?u_id=2341UT9vj"; artistName = "Cynn"; artDirectory = "beefcake"; } displayHTML.append( "<a href=\"" + artistURL + "\">" + artistName + "</a></b></td></tr>" ); displayHTML.append( "<tr><td><img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "otherimages/" ); displayHTML.append( artDirectory ); displayHTML.append( "/" ); displayHTML.append( CalendarFrame.CALENDARS[ HolidayDatabase.getCalendarMonth( CalendarFrame.selectedDate.getTime() ) ] ); displayHTML.append( ".gif\"></td></tr><tr><td align=center>" ); displayHTML.append( CalendarFrame.LONG_FORMAT.format( CalendarFrame.selectedDate.getTime() ) ); displayHTML.append( "</td></tr><tr><td align=center><font size=+1><b>" ); displayHTML.append( HolidayDatabase.getCalendarDayAsString( CalendarFrame.selectedDate.getTime() ) ); displayHTML.append( "</b></font></td></tr></table></center>" ); displayHTML.append( "</td><td valign=top>" ); displayHTML.append( "<center><table>" ); // Holidays should probably be in the first // row, just in case. displayHTML.append( "<tr><td colspan=2 align=center><b>" ); displayHTML.append( HolidayDatabase.getHoliday( CalendarFrame.selectedDate.getTime() ) ); displayHTML.append( "</b></td></tr><tr><td colspan=2></td></tr>" ); // Next display today's moon phases, including // the uber-spiffy name for each phase. Just // like in the browser, Ronald then Grimace. displayHTML.append( "<tr><td colspan=2 align=\"center\">" ); int hamburglarLight = HolidayDatabase.getHamburglarLight( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase, CalendarFrame.hamburglarPosition ); if ( CalendarFrame.hamburglarPosition == 7 ) { displayHTML.append( "<img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "itemimages/minimoon" ); if ( hamburglarLight == 0 ) { displayHTML.append( "2" ); } displayHTML.append( ".gif\">" ); } displayHTML.append( "<img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "itemimages/smoon" ); displayHTML.append( CalendarFrame.ronaldPhase + 1 ); if ( CalendarFrame.hamburglarPosition == 8 || CalendarFrame.hamburglarPosition == 9 ) { displayHTML.append( CalendarFrame.hamburglarPosition == 8 ? "a" : "b" ); } displayHTML.append( ".gif\">" ); if ( CalendarFrame.hamburglarPosition == 4 || CalendarFrame.hamburglarPosition == 5 || CalendarFrame.hamburglarPosition == 10 ) { displayHTML.append( "<img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "itemimages/minimoon" ); if ( hamburglarLight == 0 ) { displayHTML.append( "2" ); } displayHTML.append( ".gif\">" ); } displayHTML.append( "<img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "itemimages/smoon" ); displayHTML.append( CalendarFrame.grimacePhase + 1 ); if ( CalendarFrame.hamburglarPosition == 0 || CalendarFrame.hamburglarPosition == 1 ) { displayHTML.append( CalendarFrame.hamburglarPosition == 0 ? "a" : "b" ); } displayHTML.append( ".gif\">" ); if ( CalendarFrame.hamburglarPosition == 2 ) { displayHTML.append( "<img src=\"" ); displayHTML.append( KoLmafia.imageServerPath() ); displayHTML.append( "itemimages/minimoon" ); if ( hamburglarLight == 0 ) { displayHTML.append( "2" ); } displayHTML.append( ".gif\">" ); } displayHTML.append( "</td></tr><tr><td colspan=2></td></tr>" ); displayHTML.append( "<tr><td align=right><b>Ronald</b>: </td><td>" ); displayHTML.append( HolidayDatabase.getPhaseName( CalendarFrame.ronaldPhase ) ); displayHTML.append( "</td></tr>" ); displayHTML.append( "<tr><td align=right><b>Grimace</b>: </td><td>" ); displayHTML.append( HolidayDatabase.getPhaseName( CalendarFrame.grimacePhase ) ); displayHTML.append( "</td></tr>" ); displayHTML.append( "<tr><td align=right><b>Stats</b>: </td><td>" ); displayHTML.append( HolidayDatabase.getMoonEffect( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase ) ); displayHTML.append( "</td></tr><td align=right><b>Grue</b>: </td><td>" ); displayHTML.append( HolidayDatabase.getGrueEffect( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase, CalendarFrame.hamburglarPosition ) ? "bloodlusty" : "pacifistic" ); displayHTML.append( "</td></tr><td align=right><b>Blood</b>: </td><td>" ); CalendarFrame.appendModifierPercentage( displayHTML, (int) HolidayDatabase.getBloodEffect( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase, CalendarFrame.hamburglarPosition ) ); displayHTML.append( "</td></tr><td align=right><b>Baio</b>: </td><td>" ); CalendarFrame.appendModifierPercentage( displayHTML, HolidayDatabase.getBaioEffect( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase, CalendarFrame.hamburglarPosition ) ); displayHTML.append( "</td></tr><td align=right><b>Jekyllin</b>: </td><td>" ); displayHTML.append( HolidayDatabase.getJekyllinEffect( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase, CalendarFrame.hamburglarPosition ) ); displayHTML.append( "</td></tr></table></center>" ); // That completes the table display! More data // relevant to the current date may follow. // A forecast section, maybe, too - but for now, // this simple data should be enough. displayHTML.append( "</td></tr></table></center>" ); // Now that the HTML has been completely // constructed, clear the display dailyBuffer // and append the appropriate text. CalendarFrame.dailyDisplay.setText( displayHTML.toString() ); } /** * Updates the HTML which displays the predictions for upcoming events on the KoL calendar. */ private static final void updatePredictionsPage() { StringBuffer displayHTML = new StringBuffer(); int phaseStep = HolidayDatabase.getPhaseStep( CalendarFrame.ronaldPhase, CalendarFrame.grimacePhase ); // First display today's date along with the // appropriate calendar picture. Include the // link shown in the clan calendar. displayHTML.append( "<b><u>" ); displayHTML.append( CalendarFrame.LONG_FORMAT.format( CalendarFrame.selectedDate.getTime() ) ); displayHTML.append( "</u></b><br><i>" ); displayHTML.append( HolidayDatabase.getCalendarDayAsString( CalendarFrame.selectedDate.getTime() ) ); displayHTML.append( "</i><br> <br>" ); HolidayDatabase.addPredictionHTML( displayHTML, CalendarFrame.selectedDate.getTime(), phaseStep ); CalendarFrame.predictDisplay.setText( displayHTML.toString() ); } /** * Utility method which appends the given percentage to the given string dailyBuffer, complete with + and % signs, * wherever applicable. Also appends "no effect" if the percentage is zero. */ private static final void appendModifierPercentage( final StringBuffer buffer, final int percentage ) { if ( percentage > 0 ) { buffer.append( '+' ); buffer.append( percentage ); buffer.append( '%' ); } else if ( percentage < 0 ) { buffer.append( percentage ); buffer.append( '%' ); } else if ( percentage == 0 ) { buffer.append( "no effect" ); } } /** * Internal class which functions as a table for the JCalendar object. Unlike the standard implementation used by * JCalendar, this also highlights stat days and holidays on the KoL calendar. */ public static class OracleTable extends JTable { private final Calendar dateCalculator; private final CalendarTableModel model; private final DefaultTableCellRenderer normalRenderer, todayRenderer, specialRenderer, holidayRenderer; private final DefaultTableCellRenderer muscleRenderer, mysticalityRenderer, moxieRenderer; public OracleTable( final CalendarTableModel model ) { super( model ); this.model = model; this.dateCalculator = Calendar.getInstance( TimeZone.getTimeZone( "GMT-0330" ), Locale.US ); this.normalRenderer = new DefaultTableCellRenderer(); this.todayRenderer = new DefaultTableCellRenderer(); this.todayRenderer.setForeground( new Color( 255, 255, 255 ) ); this.todayRenderer.setBackground( new Color( 128, 128, 128 ) ); this.specialRenderer = new DefaultTableCellRenderer(); this.specialRenderer.setForeground( new Color( 255, 255, 255 ) ); this.specialRenderer.setBackground( new Color( 0, 0, 0 ) ); this.holidayRenderer = new DefaultTableCellRenderer(); this.holidayRenderer.setForeground( new Color( 0, 0, 0 ) ); this.holidayRenderer.setBackground( new Color( 255, 255, 204 ) ); this.muscleRenderer = new DefaultTableCellRenderer(); this.muscleRenderer.setForeground( new Color( 0, 0, 0 ) ); this.muscleRenderer.setBackground( new Color( 255, 204, 204 ) ); this.mysticalityRenderer = new DefaultTableCellRenderer(); this.mysticalityRenderer.setForeground( new Color( 0, 0, 0 ) ); this.mysticalityRenderer.setBackground( new Color( 204, 204, 255 ) ); this.moxieRenderer = new DefaultTableCellRenderer(); this.moxieRenderer.setForeground( new Color( 0, 0, 0 ) ); this.moxieRenderer.setBackground( new Color( 204, 255, 204 ) ); } @Override public TableCellRenderer getCellRenderer( final int row, final int column ) { try { // First, if the date today is equal to the // date selected, highlight it. String dayString = (String) this.model.getValueAt( row, column ); if ( dayString.equals( "" ) ) { return this.normalRenderer; } this.dateCalculator.set( this.model.getCurrentYear(), this.model.getCurrentMonth(), StringUtilities.parseInt( dayString ) ); Date selectedTime = this.dateCalculator.getTime(); if ( CalendarFrame.SHORT_FORMAT.format( new Date() ).equals( CalendarFrame.SHORT_FORMAT.format( this.dateCalculator.getTime() ) ) ) { return this.todayRenderer; } // White wednesday special highlighting. // But, because white doesn't show up, // make it black instead. if ( KoLConstants.DAILY_FORMAT.format( this.dateCalculator.getTime() ).equals( "20051027" ) ) { return this.specialRenderer; } // Otherwise, if the date selected is equal // to a special day, then highlight it. if ( HolidayDatabase.isRealLifeHoliday( selectedTime ) ) { return this.holidayRenderer; } if ( HolidayDatabase.isHoliday( selectedTime ) ) { return this.holidayRenderer; } if ( HolidayDatabase.isMuscleDay( selectedTime ) ) { return this.muscleRenderer; } if ( HolidayDatabase.isMysticalityDay( selectedTime ) ) { return this.mysticalityRenderer; } if ( HolidayDatabase.isMoxieDay( selectedTime ) ) { return this.moxieRenderer; } } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } return this.normalRenderer; } } public synchronized void updateTabs() { CalendarFrame.updateDailyPage(); CalendarFrame.updatePredictionsPage(); } }