/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.library;
import com.trollworks.gcs.advantage.Advantage;
import com.trollworks.gcs.common.CommonDockable;
import com.trollworks.gcs.common.ListFile;
import com.trollworks.gcs.preferences.SheetPreferences;
import com.trollworks.gcs.widgets.outline.ListOutline;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.ui.Fonts;
import com.trollworks.toolkit.ui.image.StdImage;
import com.trollworks.toolkit.ui.menu.RetargetableFocus;
import com.trollworks.toolkit.ui.menu.edit.JumpToSearchTarget;
import com.trollworks.toolkit.ui.scale.Scale;
import com.trollworks.toolkit.ui.scale.Scales;
import com.trollworks.toolkit.ui.widget.IconButton;
import com.trollworks.toolkit.ui.widget.Toolbar;
import com.trollworks.toolkit.ui.widget.dock.Dockable;
import com.trollworks.toolkit.ui.widget.outline.OutlineModel;
import com.trollworks.toolkit.ui.widget.outline.Row;
import com.trollworks.toolkit.ui.widget.outline.RowFilter;
import com.trollworks.toolkit.utility.FileType;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.Preferences;
import com.trollworks.toolkit.utility.PrintProxy;
import com.trollworks.toolkit.utility.notification.BatchNotifierTarget;
import com.trollworks.toolkit.utility.text.Text;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.KeyboardFocusManager;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/** A list from a library. */
public abstract class LibraryDockable extends CommonDockable implements RowFilter, DocumentListener, BatchNotifierTarget, JumpToSearchTarget, RetargetableFocus {
@Localize("Enter text here to narrow the list to only those rows containing matching items")
@Localize(locale = "de", value = "Hier Text eingeben, um eine Liste der passenden Einträge anzuzeigen")
@Localize(locale = "ru", value = "Введите текст здесь, чтобы сузить список до содержащих подходящие элементы")
@Localize(locale = "es", value = "Escribe un texto aquí para acortar la lista y mostrar sólo las filas que contengan el texto")
private static String SEARCH_FIELD_TOOLTIP;
@Localize("Any Category")
@Localize(locale = "de", value = "Beliebige Kategorie")
@Localize(locale = "ru", value = "Любая категория")
@Localize(locale = "es", value = "Cualquier Categoría")
private static String CHOOSE_CATEGORY;
@Localize("Switches between allowing editing and not")
@Localize(locale = "de", value = "Bearbeitungserlaubnis umschalten")
@Localize(locale = "ru", value = "Переключение между разрешать редактировать или нет")
@Localize(locale = "es", value = "Cambia entre edición permitida y sólo lectura")
private static String TOGGLE_EDIT_MODE_TOOLTIP;
@Localize("Opens/closes all hierarchical rows")
@Localize(locale = "de", value = "Öffnet / Schließt alle Untereinträge")
@Localize(locale = "ru", value = "Развернуть/свернуть все вложенные строки")
@Localize(locale = "es", value = "Pliega/despliega todas las filas jerarquicamente")
private static String TOGGLE_ROWS_OPEN_TOOLTIP;
@Localize("Sets the width of each column to exactly fit its contents")
@Localize(locale = "de", value = "Setzt die Breite jeder Spalte um den Inhalt komplett darzustellen")
@Localize(locale = "ru", value = "Подобрать ширину каждого столбца по его содержимому")
@Localize(locale = "es", value = "Ajusta el ancho de cada columna para encajar su contenido")
private static String SIZE_COLUMNS_TO_FIT_TOOLTIP;
static {
Localization.initialize();
}
private Toolbar mToolbar;
private JComboBox<Scales> mScaleCombo;
private JTextField mFilterField;
private JComboBox<Object> mCategoryCombo;
private IconButton mLockButton;
private JScrollPane mScroller;
private ListOutline mOutline;
/** Creates a new {@link LibraryDockable}. */
public LibraryDockable(ListFile file) {
super(file);
mOutline = createOutline();
mOutline.setDynamicRowHeight(true);
OutlineModel outlineModel = mOutline.getModel();
outlineModel.applySortConfig(outlineModel.getSortConfig());
outlineModel.setRowFilter(this);
LibraryContent content = new LibraryContent(mOutline);
LibraryHeader header = new LibraryHeader(mOutline.getHeaderPanel());
mToolbar = new Toolbar();
mScaleCombo = new JComboBox<>(Scales.values());
mScaleCombo.setSelectedItem(SheetPreferences.getInitialUIScale());
mScaleCombo.addActionListener((event) -> {
Scale scale = ((Scales) mScaleCombo.getSelectedItem()).getScale();
header.setScale(scale);
content.setScale(scale);
});
mToolbar.add(mScaleCombo);
createFilterField();
createCategoryCombo();
mLockButton = new IconButton(outlineModel.isLocked() ? StdImage.LOCKED : StdImage.UNLOCKED, TOGGLE_EDIT_MODE_TOOLTIP, () -> {
OutlineModel model = mOutline.getModel();
model.setLocked(!model.isLocked());
mLockButton.setIcon(model.isLocked() ? StdImage.LOCKED : StdImage.UNLOCKED);
});
mToolbar.add(mLockButton);
mToolbar.add(new IconButton(StdImage.TOGGLE_OPEN, TOGGLE_ROWS_OPEN_TOOLTIP, () -> mOutline.getModel().toggleRowOpenState()));
mToolbar.add(new IconButton(StdImage.SIZE_TO_FIT, SIZE_COLUMNS_TO_FIT_TOOLTIP, () -> mOutline.sizeColumnsToFit()));
add(mToolbar, BorderLayout.NORTH);
mScroller = new JScrollPane(content);
mScroller.setBorder(null);
mScroller.setColumnHeaderView(header);
add(mScroller, BorderLayout.CENTER);
Preferences.getInstance().getNotifier().add(this, Fonts.FONT_NOTIFICATION_KEY);
}
public abstract void applyScale(Scale scale);
@Override
public boolean attemptClose() {
boolean closed = super.attemptClose();
if (closed) {
Preferences.getInstance().getNotifier().remove(this);
}
return closed;
}
@Override
public Component getRetargetedFocus() {
return mOutline;
}
@Override
public ListFile getDataFile() {
return (ListFile) super.getDataFile();
}
@Override
public FileType[] getAllowedFileTypes() {
return new FileType[] { getDataFile().getFileType() };
}
@Override
public PrintProxy getPrintProxy() {
// Don't want to allow printing of the library files
return null;
}
@Override
public String getDescriptor() {
return getDataFile().getFile().getAbsolutePath();
}
/** @return The {@link Toolbar}. */
public Toolbar getToolbar() {
return mToolbar;
}
/** @return The {@link JScrollPane}. */
public JScrollPane getScroller() {
return mScroller;
}
/** @return The {@link ListOutline}. */
public ListOutline getOutline() {
return mOutline;
}
/**
* Called to create the {@link ListOutline} for this {@link Dockable}.
*
* @return The newly created {@link ListOutline}.
*/
protected abstract ListOutline createOutline();
private void createFilterField() {
mFilterField = new JTextField(10);
mFilterField.getDocument().addDocumentListener(this);
mFilterField.setToolTipText(Text.wrapPlainTextForToolTip(SEARCH_FIELD_TOOLTIP));
// This client property is specific to Mac OS X
mFilterField.putClientProperty("JTextField.variant", "search"); //$NON-NLS-1$ //$NON-NLS-2$
mFilterField.setMinimumSize(new Dimension(60, mFilterField.getPreferredSize().height));
mToolbar.add(mFilterField, Toolbar.LAYOUT_FILL);
}
private void createCategoryCombo() {
mCategoryCombo = new JComboBox<>();
// Next two client properties are specific to Mac OS X
mCategoryCombo.putClientProperty("JComponent.sizeVariant", "small"); //$NON-NLS-1$ //$NON-NLS-2$
mCategoryCombo.putClientProperty("JComboBox.isPopDown", Boolean.TRUE); //$NON-NLS-1$
mCategoryCombo.setMaximumRowCount(20);
mCategoryCombo.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setFont(getFont().deriveFont(index == 0 ? Font.ITALIC : Font.PLAIN));
return comp;
}
});
mCategoryCombo.addActionListener((event) -> {
mCategoryCombo.setFont(mCategoryCombo.getFont().deriveFont(mCategoryCombo.getSelectedIndex() == 0 ? Font.ITALIC : Font.PLAIN));
mCategoryCombo.revalidate();
mCategoryCombo.repaint();
if (mOutline != null) {
mOutline.reapplyRowFilter();
}
});
mToolbar.add(mCategoryCombo);
adjustCategoryCombo();
}
private void adjustCategoryCombo() {
mCategoryCombo.removeAllItems();
mCategoryCombo.addItem(CHOOSE_CATEGORY);
for (String category : getDataFile().getCategories()) {
mCategoryCombo.addItem(category);
}
mCategoryCombo.setPreferredSize(null);
mCategoryCombo.setFont(mCategoryCombo.getFont().deriveFont(Font.ITALIC));
mCategoryCombo.setMinimumSize(new Dimension(36, mCategoryCombo.getPreferredSize().height));
mCategoryCombo.revalidate();
mCategoryCombo.repaint();
}
@Override
public boolean isRowFiltered(Row row) {
boolean filtered = false;
if (row instanceof ListRow) {
ListRow listRow = (ListRow) row;
if (mCategoryCombo.getSelectedIndex() != 0) {
Object selectedItem = mCategoryCombo.getSelectedItem();
if (selectedItem != null) {
filtered = !listRow.getCategories().contains(selectedItem);
}
}
if (!filtered) {
String filter = mFilterField.getText();
if (filter.length() > 0) {
filtered = !listRow.contains(filter.toLowerCase(), true);
}
}
}
return filtered;
}
@Override
public void changedUpdate(DocumentEvent event) {
documentChanged();
}
@Override
public void insertUpdate(DocumentEvent event) {
documentChanged();
}
@Override
public void removeUpdate(DocumentEvent event) {
documentChanged();
}
private void documentChanged() {
mOutline.reapplyRowFilter();
}
@Override
public int getNotificationPriority() {
return 0;
}
@Override
public void enterBatchMode() {
// Not needed.
}
@Override
public void leaveBatchMode() {
// Not needed.
}
@Override
public void handleNotification(Object producer, String name, Object data) {
if (Fonts.FONT_NOTIFICATION_KEY.equals(name)) {
mOutline.updateRowHeights();
} else if (Advantage.ID_TYPE.equals(name)) {
getOutline().repaint();
} else {
adjustCategoryCombo();
}
}
@Override
public boolean isJumpToSearchAvailable() {
return mFilterField.isEnabled() && mFilterField != KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
}
@Override
public void jumpToSearchField() {
mFilterField.requestFocusInWindow();
}
}