/* * 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.frames; import java.awt.BorderLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.border.EmptyBorder; import javax.swing.border.EtchedBorder; import jmemorize.core.Card; import jmemorize.core.CardSide; import jmemorize.core.Category; import jmemorize.core.CategoryObserver; import jmemorize.core.FormattedText; import jmemorize.core.ImageRepository; import jmemorize.core.Main; import jmemorize.core.Settings; import jmemorize.gui.LC; import jmemorize.gui.Localization; import jmemorize.gui.swing.SelectionProvider; import jmemorize.gui.swing.actions.AbstractAction2; import jmemorize.gui.swing.actions.edit.AddCardAction; import jmemorize.gui.swing.actions.edit.RemoveAction; import jmemorize.gui.swing.actions.edit.ResetCardAction; import jmemorize.gui.swing.panels.CardHeaderPanel; import jmemorize.gui.swing.panels.CardPanel; import jmemorize.gui.swing.panels.TwoSidesCardPanel; import jmemorize.gui.swing.widgets.CategoryComboBox; import jmemorize.util.EscapableFrame; import com.jgoodies.forms.factories.Borders; import com.jgoodies.forms.factories.ButtonBarFactory; /** * The window that is used to edit cards. Note this is a singleton class. The * same window will be reused for all editting. * * @author djemili */ public class EditCardFrame extends EscapableFrame implements CategoryObserver, SelectionProvider { private class NextCardAction extends AbstractAction2 { public NextCardAction() { setName(Localization.get(LC.NEXT_CARD)); setDescription(Localization.get(LC.NEXT_CARD_DESC)); setIcon("/resource/icons/card_next.gif"); //$NON-NLS-1$ setMnemonic(1); } public void actionPerformed(java.awt.event.ActionEvent e) { if (confirmCardSides()) showNext(); } } private class PreviousCardAction extends AbstractAction2 { public PreviousCardAction() { setName(Localization.get(LC.PREV_CARD)); setDescription(Localization.get(LC.PREV_CARD_DESC)); setIcon("/resource/icons/card_prev.gif"); //$NON-NLS-1$ setMnemonic(1); } public void actionPerformed(java.awt.event.ActionEvent e) { if (confirmCardSides()) showPrevious(); } } private static final int MAX_TITLE_LENGTH = 80; private static final String FRAME_ID = "editcard"; //$NON-NLS-1$ private List<SelectionObserver> m_selectionObservers = new ArrayList<SelectionObserver>(); private Action m_nextCardAction = new NextCardAction(); private Action m_previousCardAction = new PreviousCardAction(); private Card m_currentCard; private int m_currentCardIndex; private ArrayList<Card> m_cards; private Category m_category; // swing elements private JButton m_applyButton = new JButton(Localization.get(LC.APPLY)); private CardHeaderPanel m_headerPanel = new CardHeaderPanel(); private TwoSidesCardPanel m_cardPanel = new TwoSidesCardPanel(true); private static EditCardFrame m_instance; /** * @return The singleton instance. */ public static EditCardFrame getInstance() { if (m_instance == null) { m_instance = new EditCardFrame(); } return m_instance; } /** * Shows the Edit Card Frame and allows user to edit the card card. * * @param card The card that is to be shown and editted. */ public void showCard(Card card) { List<Card> cards = new ArrayList<Card>(1); cards.add(card); showCard(card, cards, card.getCategory()); } /** * Shows the Edit Card Frame and allows user to edit the card card. * * @param card the card that is to be shown and editted. * * @param cards the cards that belong to the context of the card that is to * be edited. These cards are used to allow browsing to next and previous * card. Therefore the card given on the former parameter is usually also * part of this list. The usual mode is to show the currently selected card * and to give all other cards that are part of the same card table/learn * history etc. as additional cards. * * @param category The category that includes all cards from former * parameters. */ public void showCard(Card card, List<Card> cards, Category category) { showCard(card, cards, category, null, 0, true); //HACK } public void showCard(Card card, List<Card> cards, Category category, String searchText, int side, boolean ignoreCase) { if (isVisible() && !confirmCardSides()) return; m_currentCard = card; m_currentCardIndex = cards.indexOf(card); m_cards = new ArrayList<Card>(cards); if (m_category != null) { m_category.removeObserver(this); } m_category = category; if (m_category != null) { category.addObserver(this); } updatePanel(); setVisible(true); } /** * @return True if window was closed. False if this was prevented by user * option. */ public boolean close() { if (confirmCardSides()) { hideFrame(); return true; } return false; } /* (non-Javadoc) * @see jmemorize.core.CategoryObserver */ public void onCategoryEvent(int type, Category category) { if (type == REMOVED_EVENT) { // if current card was in a deleted category branch if (category.contains(m_currentCard.getCategory())) { hideFrame(); } // delete all cards that are part of a deleted category branch for (Card card : m_cards) { if (category.contains(card.getCategory())) { m_cards.remove(card); } } m_currentCardIndex = m_cards.indexOf(m_currentCard); updateActions(); } else if (type == EDITED_EVENT) { updateCardHeader(); } } /* (non-Javadoc) * @see jmemorize.core.CategoryObserver */ public void onCardEvent(int type, Card card, Category category, int deck) { if (type == DECK_EVENT && m_currentCard == card) { updateCardHeader(); } if (type == REMOVED_EVENT) { if (m_currentCard == card) { if (hasNext()) { showNext(); } else if (hasPrevious()) { showPrevious(); } else { hideFrame(); } } if (m_cards.remove(card)) // is this card is relevant { // we need to update index because cards changed m_currentCardIndex = m_cards.indexOf(m_currentCard); updateActions(); } } } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public void addSelectionObserver(SelectionObserver observer) { m_selectionObservers.add(observer); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public void removeSelectionObserver(SelectionObserver observer) { m_selectionObservers.remove(observer); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public Category getCategory() { return m_currentCard.getCategory(); } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public JComponent getDefaultFocusOwner() { return null; // HACK } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public JFrame getFrame() { return this; } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public List<Card> getRelatedCards() { return m_cards; } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public List<Card> getSelectedCards() { ArrayList<Card> list = new ArrayList<Card>(1); list.add(m_currentCard); return list; } /* (non-Javadoc) * @see jmemorize.gui.swing.SelectionProvider */ public List<Category> getSelectedCategories() { return null; } private void hideFrame() { Settings.storeFrameState(this, FRAME_ID); setVisible(false); } /** * If the content of the text panes differ from the currently saved card * entries, this will bring up a dialog that asks if the user wants to save * the changes. If yes is selected the card sides are saved. * * This should be called everytime there is the chance of losing card * informations. * * @return True if operation wasnt aborted by user. */ private boolean confirmCardSides() { if (isChanged()) { int n = JOptionPane.showConfirmDialog(this, Localization.get("EditCard.MODIFIED_WARN"), //$NON-NLS-1$ Localization.get("EditCard.MODIFIED_WARN_TITLE"), //$NON-NLS-1$ JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); if (n == JOptionPane.CANCEL_OPTION) { return false; } if (n == JOptionPane.YES_OPTION) { return saveCard(); } } // if no changes or NO chosen return true; } /** * Creates new form EditCardFrame */ private EditCardFrame() { initComponents(); addChangeObservers(); Settings.loadFrameState(this, FRAME_ID); } private void addChangeObservers() { m_cardPanel.addObserver(new CardPanel.CardPanelObserver(){ public void onTextChanged() { updateApplyButton(); } public void onImageChanged() { updateApplyButton(); } }); m_cardPanel.getCategoryComboBox().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { updateApplyButton(); } }); } private void updateApplyButton() { m_applyButton.setEnabled(isChanged()); } private boolean isChanged() { boolean categoryChanged = m_currentCard.getCategory() != m_cardPanel.getCategoryComboBox().getSelectedCategory(); if (categoryChanged) return true; CardSide frontSide = m_currentCard.getFrontSide(); CardSide backSide = m_currentCard.getBackSide(); boolean textChanged = !m_cardPanel.getFrontText().equals(frontSide.getText()) || !m_cardPanel.getBackText().equals(backSide.getText()); if (textChanged) return true; if (!ImageRepository.equals(m_cardPanel.getFrontImages(), frontSide.getImages())) return true; if (!ImageRepository.equals(m_cardPanel.getBackImages(), backSide.getImages())) return true; return false; } private void updatePanel() { updateTitle(); CardSide frontSide = m_currentCard.getFrontSide(); CardSide backSide = m_currentCard.getBackSide(); // set sides m_cardPanel.setTextSides(frontSide.getText(), backSide.getText()); m_cardPanel.setImages(frontSide.getImages(), backSide.getImages()); highlightSearchText(); updateActions(); updateCardHeader(); Category rootCategory = Main.getInstance().getLesson().getRootCategory(); CategoryComboBox categoryComboBox = m_cardPanel.getCategoryComboBox(); categoryComboBox.setRootCategory(rootCategory); categoryComboBox.setSelectedCategory(m_currentCard.getCategory()); updateApplyButton(); } /** * Update the title of this frame. */ private void updateTitle() { // set title String title = m_currentCard.getFrontSide().getText().getUnformatted(); title = title.replace('\n', ' '); if (title.length() > MAX_TITLE_LENGTH) { title = title.substring(0, MAX_TITLE_LENGTH) + "..."; //$NON-NLS-1$ } setTitle(title); // Date dateExpired = m_currentCard.getDateExpired(); // ImageIcon icon = CardStatusIcons.getInstance().getCardIcon(dateExpired); // setIconImage(icon.getImage()); } /** * Updates the actions of this EditCardFrame i.e. enabling/disabling certain * buttons. */ private void updateActions() { if (m_cards == null) { m_nextCardAction.setEnabled(false); m_previousCardAction.setEnabled(false); } else { m_previousCardAction.setEnabled(hasPrevious()); m_nextCardAction.setEnabled(hasNext()); } } /** * @return <code>true</code> if there is another card left after this one. */ private boolean hasNext() { return m_currentCardIndex < m_cards.size() - 1; } /** * @return <code>true</code> if there is a another card before this one. */ private boolean hasPrevious() { return m_currentCardIndex > 0; } /** * Show the next card of the card list of this EditCardFrame. */ private void showNext() { m_currentCard = (Card)m_cards.get(++m_currentCardIndex); updatePanel(); } /** * Show the previous card of the card list of this EditCardFrame. */ private void showPrevious() { m_currentCard = (Card)m_cards.get(--m_currentCardIndex); updatePanel(); } private boolean saveCard() { if (m_cardPanel.isValidCard()) { FormattedText frontText = m_cardPanel.getFrontText(); FormattedText backText = m_cardPanel.getBackText(); ImageRepository repo = ImageRepository.getInstance(); List<String> frontIDs = repo.addImages(m_cardPanel.getFrontImages()); List<String> backIDs = repo.addImages(m_cardPanel.getBackImages()); m_currentCard.setSides(frontText, backText); m_currentCard.getFrontSide().setImages(frontIDs); m_currentCard.getBackSide().setImages(backIDs); CategoryComboBox categoryComboBox = m_cardPanel.getCategoryComboBox(); Category newCategory = categoryComboBox.getSelectedCategory(); if (newCategory != m_currentCard.getCategory()) { m_currentCard.getCategory().moveCard(m_currentCard, newCategory); } updateTitle(); updateCardHeader(); updateApplyButton(); return true; } else { JOptionPane.showMessageDialog(this, Localization.get(LC.EMPTY_SIDES_ALERT), Localization.get(LC.EMPTY_SIDES_ALERT_TITLE), JOptionPane.ERROR_MESSAGE); return false; } } private void updateCardHeader() { m_headerPanel.setCard(m_currentCard); } private void initComponents() { getContentPane().add(buildToolBar(), BorderLayout.NORTH); getContentPane().add(buildHeaderPanel(), BorderLayout.CENTER); getContentPane().add(buildBottomButtonBar(), BorderLayout.SOUTH); setIconImage(Toolkit.getDefaultToolkit().getImage( getClass().getResource("/resource/icons/card_edit.gif"))); //$NON-NLS-1$ pack(); } private JPanel buildHeaderPanel() { m_headerPanel.setBorder(new EtchedBorder()); m_cardPanel.setBorder(Borders.DIALOG_BORDER); JPanel panel = new JPanel(new BorderLayout()); panel.add(m_headerPanel, BorderLayout.NORTH); panel.add(m_cardPanel, BorderLayout.CENTER); return panel; } private JToolBar buildToolBar() { JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); toolBar.add(new JButton(new AddCardAction(this))); toolBar.add(new JButton(m_previousCardAction)); toolBar.add(new JButton(m_nextCardAction)); toolBar.add(new JButton(new ResetCardAction(this))); toolBar.add(new JButton(new RemoveAction(this))); return toolBar; } private JPanel buildBottomButtonBar() { JButton okayButton = new JButton(Localization.get(LC.OKAY)); okayButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveCard(); close(); } }); JButton cancelButton = new JButton(Localization.get(LC.CANCEL)); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { hideFrame(); } }); m_applyButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveCard(); } }); JPanel buttonPanel = ButtonBarFactory.buildOKCancelApplyBar( okayButton, cancelButton, m_applyButton); buttonPanel.setBorder(new EmptyBorder(3, 3, 3, 3)); getRootPane().setDefaultButton(okayButton); return buttonPanel; } private void highlightSearchText() { // if (m_searchText != null) // { // List frontPositions = null; // if (m_searchSide == SearchTool.FRONT_SIDE || m_searchSide == SearchTool.BOTH_SIDES) // { // frontPositions = SearchTool.search(m_currentCard.getFrontSide(), // m_searchText, m_searchSide, m_searchCase); // } // // List backPositions = null; // if (m_searchSide == SearchTool.FLIP_SIDE|| m_searchSide == SearchTool.BOTH_SIDES) // { // backPositions = SearchTool.search(m_currentCard.getBackSide(), // m_searchText, m_searchSide, m_searchCase); // } // // m_cardPanel.highlight(frontPositions, backPositions, m_searchText.length()); // } } }