/*
* jMemorize - Learning made easy (and fun) - A Leitner flashcards tool
* Copyright(C) 2004-2008 Riad Djemili and contributors
*
* 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; either version 1, or (at your option)
* any later version.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jmemorize.gui.swing.panels;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import jmemorize.core.Card;
import jmemorize.core.Main;
import jmemorize.core.learn.LearnSession;
import jmemorize.core.learn.LearnSessionObserver;
import jmemorize.core.learn.LearnSettings;
import jmemorize.core.learn.LearnSession.LearnCardObserver;
import jmemorize.gui.LC;
import jmemorize.gui.Localization;
import jmemorize.gui.swing.ColorConstants;
import jmemorize.gui.swing.widgets.ExtentProgressBar;
import jmemorize.gui.swing.widgets.PartialProgressBar;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.PieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
/**
* A panel that is used in the learn sidebar and shows how many cards have been
* learned in the running learn session.
*
* @author djemili
*/
public class CardCounterPanel extends JPanel
{
private static boolean USE_PIECHART = false;
private static boolean USE_EXTENT_BAR = false;
private static boolean USE_PARTIAL_BAR = false;
private int m_cardsPassed;
private int m_cardsPartiallyPassed;
private int m_cardsTarget;
private boolean m_showPartiallyPassed;
private LearnSession m_session;
private List<Card> m_cards;
private DefaultPieDataset m_pieDataset;
private JProgressBar m_bar;
private JTextField m_textField = new JTextField();
public CardCounterPanel()
{
if (USE_PARTIAL_BAR)
attachPartialProgressBar();
}
public void start(int target)
{
m_cardsTarget = target;
m_cardsPassed = 0;
m_cardsPartiallyPassed = 0;
m_showPartiallyPassed = false;
initComponents(target);
}
public void start()
{
start(-1);
}
public void setCardsPassed(int passed, int partiallyPassed)
{
m_cardsPassed = passed;
m_cardsPartiallyPassed = partiallyPassed;
if (partiallyPassed > 0)
{
m_showPartiallyPassed = true;
}
if (m_cardsTarget > -1)
{
if (USE_PIECHART)
{
int cardsUnlearned = m_cardsTarget - (m_cardsPassed + m_cardsPartiallyPassed);
m_pieDataset.setValue(Localization.get(LC.STATUS_LEARNED), m_cardsPassed);
m_pieDataset.setValue(Localization.get(LC.STATUS_PARTIAL), m_cardsPartiallyPassed);
m_pieDataset.setValue(Localization.get(LC.STATUS_UNLEARNED), cardsUnlearned);
}
else
{
if (!USE_PARTIAL_BAR)
m_bar.setValue(m_cardsPassed);
m_bar.setString(getCardString());
m_bar.setForeground(ColorConstants.LEARNED_CARDS.darker());
if (USE_EXTENT_BAR)
{
ExtentProgressBar bar = (ExtentProgressBar)m_bar;
bar.setValue(m_cardsPartiallyPassed);
bar.setExtent(m_cardsPassed);
}
}
}
else
{
m_textField.setText(getCardString());
}
}
private void initComponents(int target)
{
removeAll();
setLayout(new BorderLayout());
// if there is a card limit show a progess bar or pie chart
if (m_cardsTarget > -1)
{
if (USE_PIECHART)
{
m_pieDataset = new DefaultPieDataset();
m_pieDataset.setValue(Localization.get(LC.STATUS_LEARNED), 0);
m_pieDataset.setValue(Localization.get(LC.STATUS_PARTIAL), 0);
m_pieDataset.setValue(Localization.get(LC.STATUS_UNLEARNED) , m_cardsTarget);
add(buildPiePanel(), BorderLayout.CENTER);
}
else
{
if (USE_EXTENT_BAR)
m_bar = buildExtentProgressBar();
else if (USE_PARTIAL_BAR)
m_bar = buildPartialProgressBar();
else
m_bar = new JProgressBar();
m_bar.setMaximum(target);
m_bar.setMinimum(0);
m_bar.setStringPainted(true);
m_bar.setString(getCardString());
m_bar.setValue(0);
add(m_bar, BorderLayout.CENTER);
}
}
// otherwise show a textfield
else
{
m_textField.setText(getCardString());
m_textField.setHorizontalAlignment(JTextField.CENTER);
m_textField.setEditable(false);
add(m_textField, BorderLayout.CENTER);
}
setPreferredSize(new Dimension(140, USE_PIECHART ? 140 : 22));
}
private ChartPanel buildPiePanel()
{
JFreeChart chart = ChartFactory.createPieChart(null, m_pieDataset,
true, false, false );
setupPiePlot((PiePlot)chart.getPlot());
setupPieLegend(chart);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setDisplayToolTips(false);
/*
* RED_FLAG this is a WORKAROUND TO a JFreeChart bug (as of 1.0.4) which
* causes an affine transform to be applied to charts smaller than
* 300x200. If the minimum sizes are not set to the same value, the
* chart and legend are scaled by the proprtion! see
* http://www.jfree.org/phpBB2/viewtopic.php?t=16972
*/
chartPanel.setMinimumDrawHeight(300);
chartPanel.setMinimumDrawWidth(300);
return chartPanel;
}
private void setupPieLegend(JFreeChart chart)
{
LegendTitle legend = chart.getLegend();
Font legendFont = legend.getItemFont();
legend.setItemFont(legendFont.deriveFont(Font.BOLD, 20));
}
private void setupPiePlot(PiePlot plot)
{
plot.setMaximumLabelWidth(0.0);
plot.setLabelGap(0.0);
plot.setInteriorGap(0.0);
//piePlot.setLabelLinkMargin(0.0);
plot.setCircular(true);
plot.setLabelGenerator(new PieSectionLabelGenerator() {
public String generateSectionLabel(PieDataset arg0, Comparable arg1)
{
return null;
}
public AttributedString generateAttributedSectionLabel(
PieDataset arg0, Comparable arg1)
{
return null;
}
});
//piePlot.setForegroundAlpha(0.5f);
plot.setSectionPaint(Localization.get(LC.STATUS_LEARNED), ColorConstants.LEARNED_CARDS);
plot.setSectionPaint(Localization.get(LC.STATUS_PARTIAL), ColorConstants.PARTIAL_LEARNED_CARDS);
plot.setSectionPaint(Localization.get(LC.STATUS_UNLEARNED), ColorConstants.UNLEARNED_CARDS);
}
private ExtentProgressBar buildExtentProgressBar()
{
ExtentProgressBar bar = new ExtentProgressBar();
bar.setForeground(Color.BLUE);
Color grn = Color.GREEN.darker();
Color transparentGreen = new Color(grn.getRed(), grn.getGreen(), grn.getBlue(), 128);
bar.setExtentForeground(transparentGreen);
bar.setExtent(0);
return bar;
}
private float[] getValues()
{
float[] vals = new float[m_cards.size()];
LearnSettings settings = m_session.getSettings();
int frontTargetAmount = settings.getAmountToTest(true);
int backTargetAmount = settings.getAmountToTest(false);
float targetAmount = frontTargetAmount + backTargetAmount;
for (int i = 0; i < m_cards.size(); i++)
{
Card card = (Card)m_cards.get(i);
if (m_session.getPassedCards().contains(card) ||
m_session.getRelearnedCards().contains(card))
{
vals[i] = 1f;
}
else
{
int frontLearnedAmount = card.getLearnedAmount(true);
int backLearnedAmount = card.getLearnedAmount(false);
frontLearnedAmount = Math.min(frontLearnedAmount, frontTargetAmount);
backLearnedAmount = Math.min(backLearnedAmount, backTargetAmount);
vals[i] = (frontLearnedAmount + backLearnedAmount) / targetAmount;
}
}
//Arrays.sort(vals);
return vals;
}
private void attachPartialProgressBar()
{
Main.getInstance().addLearnSessionObserver(new LearnSessionObserver() {
class LearnCardObs implements LearnCardObserver
{
public void nextCardFetched(Card nextCard, boolean flippedMode)
{
if (m_cards == null)
{
m_cards = new ArrayList<Card>();
m_cards.addAll(m_session.getCardsLeft());
}
if (! m_cards.contains(nextCard) )
{
// if the new card is not in m_cards, then a card has been skipped and
// this card added. We have to figure out the skipped card
// this is kind of inefficient, but only happens on a skipped card...
// note also that we don't necessarily see the new card immediately
// after the skip, so the card positions may not update immediately.
Set<Card> cardsToRemove = new HashSet<Card>();
cardsToRemove.addAll(m_session.getSkippedCards());
cardsToRemove.retainAll(m_cards);
m_cards.removeAll(cardsToRemove);
m_cards.add(nextCard);
}
PartialProgressBar bar = (PartialProgressBar)m_bar;
bar.setValues(getValues());
}
}
private LearnCardObserver m_obs;
public void sessionEnded(LearnSession session)
{
session.removeObserver(m_obs);
}
public void sessionStarted(LearnSession session)
{
m_session = session;
m_cards = null;
m_obs = new LearnCardObs();
session.addObserver(m_obs);
}
});
}
private PartialProgressBar buildPartialProgressBar()
{
return new PartialProgressBar();
}
/**
* @return The string that is used to show the number of cards in progressbar.
*/
private String getCardString()
{
// if progress bar
if (m_cardsTarget > -1)
{
// if (m_showPartiallyPassed)
// {
// return m_cardsPartiallyPassed + " + " + m_cardsPassed + " / "
// + m_cardsTarget; //$NON-NLS-1$
// }
// else
{
return m_cardsPassed + " / " + m_cardsTarget; //$NON-NLS-1$
}
}
// else text field
else
{
// TODO consider adding lang specific strings
if (m_showPartiallyPassed)
{
return m_cardsPartiallyPassed + " / " + m_cardsPassed;
}
return Integer.toString(m_cardsPassed);
}
}
}