/* * 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.template; import com.trollworks.gcs.advantage.Advantage; import com.trollworks.gcs.common.CommonDockable; import com.trollworks.gcs.equipment.Equipment; import com.trollworks.gcs.preferences.SheetPreferences; import com.trollworks.gcs.skill.Skill; import com.trollworks.gcs.skill.Technique; import com.trollworks.gcs.spell.Spell; import com.trollworks.gcs.widgets.outline.ListOutline; import com.trollworks.gcs.widgets.outline.ListRow; import com.trollworks.gcs.widgets.outline.RowItemRenderer; import com.trollworks.gcs.widgets.outline.RowPostProcessor; import com.trollworks.toolkit.annotation.Localize; import com.trollworks.toolkit.ui.Fonts; import com.trollworks.toolkit.ui.UIUtilities; import com.trollworks.toolkit.ui.menu.RetargetableFocus; import com.trollworks.toolkit.ui.scale.Scales; import com.trollworks.toolkit.ui.widget.Toolbar; import com.trollworks.toolkit.ui.widget.dock.Dock; import com.trollworks.toolkit.ui.widget.outline.Outline; import com.trollworks.toolkit.ui.widget.outline.OutlineModel; import com.trollworks.toolkit.ui.widget.outline.Row; import com.trollworks.toolkit.ui.widget.outline.RowIterator; import com.trollworks.toolkit.ui.widget.search.Search; import com.trollworks.toolkit.ui.widget.search.SearchTarget; 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.NotifierTarget; import com.trollworks.toolkit.utility.undo.StdUndoManager; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.KeyboardFocusManager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.swing.JComboBox; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.undo.StateEdit; /** A list of advantages and disadvantages from a library. */ public class TemplateDockable extends CommonDockable implements NotifierTarget, SearchTarget, RetargetableFocus { @Localize("Untitled Template") @Localize(locale = "de", value = "Unbenannte Vorlage") @Localize(locale = "ru", value = "Безымянный шаблон") @Localize(locale = "es", value = "Plantilla sin título") private static String UNTITLED; @Localize("Add Rows") @Localize(locale = "de", value = "Zeilen hinzufügen") @Localize(locale = "ru", value = "Добавить строки") @Localize(locale = "es", value = "Añadir filas") private static String ADD_ROWS; static { Localization.initialize(); } private static TemplateDockable LAST_ACTIVATED; private TemplateSheet mTemplate; private Toolbar mToolbar; private JComboBox<Scales> mScaleCombo; private Search mSearch; /** Creates a new {@link TemplateDockable}. */ public TemplateDockable(Template template) { super(template); Template dataFile = getDataFile(); mTemplate = new TemplateSheet(dataFile); mToolbar = new Toolbar(); mScaleCombo = new JComboBox<>(Scales.values()); mScaleCombo.setSelectedItem(SheetPreferences.getInitialUIScale()); mScaleCombo.addActionListener((event) -> mTemplate.setScale(((Scales) mScaleCombo.getSelectedItem()).getScale())); mToolbar.add(mScaleCombo); mSearch = new Search(this); mToolbar.add(mSearch, Toolbar.LAYOUT_FILL); add(mToolbar, BorderLayout.NORTH); JScrollPane scroller = new JScrollPane(mTemplate); scroller.setBorder(null); scroller.getViewport().setBackground(Color.LIGHT_GRAY); add(scroller, BorderLayout.CENTER); dataFile.setModified(false); StdUndoManager undoManager = getUndoManager(); undoManager.discardAllEdits(); dataFile.setUndoManager(undoManager); Preferences.getInstance().getNotifier().add(this, Fonts.FONT_NOTIFICATION_KEY, SheetPreferences.OPTIONAL_MODIFIER_RULES_PREF_KEY); } @Override public boolean attemptClose() { boolean closed = super.attemptClose(); if (closed) { Preferences.getInstance().getNotifier().remove(this); } return closed; } @Override public Component getRetargetedFocus() { return mTemplate; } /** @return The last activated {@link TemplateDockable}. */ public static TemplateDockable getLastActivated() { if (LAST_ACTIVATED != null) { Dock dock = UIUtilities.getAncestorOfType(LAST_ACTIVATED, Dock.class); if (dock == null) { LAST_ACTIVATED = null; } } return LAST_ACTIVATED; } @Override public void activated() { super.activated(); LAST_ACTIVATED = this; } @Override public Template getDataFile() { return (Template) super.getDataFile(); } /** @return The {@link TemplateSheet}. */ public TemplateSheet getTemplate() { return mTemplate; } @Override public PrintProxy getPrintProxy() { return null; } @Override public String getDescriptor() { // RAW: Implement return null; } @Override protected String getUntitledBaseName() { return UNTITLED; } @Override public FileType[] getAllowedFileTypes() { return new FileType[] { FileType.getByExtension(Template.EXTENSION) }; } @Override public int getNotificationPriority() { return 0; } @Override public void handleNotification(Object producer, String name, Object data) { if (Fonts.FONT_NOTIFICATION_KEY.equals(name)) { mTemplate.revalidate(); mTemplate.getAdvantageOutline().updateRowHeights(); mTemplate.getSkillOutline().updateRowHeights(); mTemplate.getSpellOutline().updateRowHeights(); mTemplate.getEquipmentOutline().updateRowHeights(); } else { getDataFile().notifySingle(Advantage.ID_LIST_CHANGED, null); } } @Override public boolean isJumpToSearchAvailable() { return mSearch.isEnabled() && mSearch != KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner(); } @Override public void jumpToSearchField() { mSearch.requestFocusInWindow(); } @Override public ListCellRenderer<Object> getSearchRenderer() { return new RowItemRenderer(); } @Override public List<Object> search(String filter) { ArrayList<Object> list = new ArrayList<>(); filter = filter.toLowerCase(); searchOne(mTemplate.getAdvantageOutline(), filter, list); searchOne(mTemplate.getSkillOutline(), filter, list); searchOne(mTemplate.getSpellOutline(), filter, list); searchOne(mTemplate.getEquipmentOutline(), filter, list); return list; } private static void searchOne(ListOutline outline, String text, ArrayList<Object> list) { for (ListRow row : new RowIterator<ListRow>(outline.getModel())) { if (row.contains(text, true)) { list.add(row); } } } @Override public void searchSelect(List<Object> selection) { HashMap<OutlineModel, ArrayList<Row>> map = new HashMap<>(); Outline primary = null; ArrayList<Row> list; mTemplate.getAdvantageOutline().getModel().deselect(); mTemplate.getSkillOutline().getModel().deselect(); mTemplate.getSpellOutline().getModel().deselect(); mTemplate.getEquipmentOutline().getModel().deselect(); for (Object obj : selection) { Row row = (Row) obj; Row parent = row.getParent(); OutlineModel model = row.getOwner(); while (parent != null) { parent.setOpen(true); model = parent.getOwner(); parent = parent.getParent(); } list = map.get(model); if (list == null) { list = new ArrayList<>(); list.add(row); map.put(model, list); } else { list.add(row); } if (primary == null) { primary = mTemplate.getAdvantageOutline(); if (model != primary.getModel()) { primary = mTemplate.getSkillOutline(); if (model != primary.getModel()) { primary = mTemplate.getSpellOutline(); if (model != primary.getModel()) { primary = mTemplate.getEquipmentOutline(); } } } } } for (OutlineModel model : map.keySet()) { model.select(map.get(model), false); } if (primary != null) { final Outline outline = primary; EventQueue.invokeLater(() -> outline.scrollSelectionIntoView()); primary.requestFocus(); } } /** * Adds rows to the display. * * @param rows The rows to add. */ public void addRows(List<Row> rows) { HashMap<ListOutline, StateEdit> map = new HashMap<>(); HashMap<Outline, ArrayList<Row>> selMap = new HashMap<>(); HashMap<Outline, ArrayList<ListRow>> nameMap = new HashMap<>(); ListOutline outline = null; for (Row row : rows) { if (row instanceof Advantage) { outline = mTemplate.getAdvantageOutline(); if (!map.containsKey(outline)) { map.put(outline, new StateEdit(outline.getModel(), ADD_ROWS)); } row = new Advantage(getDataFile(), (Advantage) row, true); addCompleteRow(outline, row, selMap); } else if (row instanceof Technique) { outline = mTemplate.getSkillOutline(); if (!map.containsKey(outline)) { map.put(outline, new StateEdit(outline.getModel(), ADD_ROWS)); } row = new Technique(getDataFile(), (Technique) row, true); addCompleteRow(outline, row, selMap); } else if (row instanceof Skill) { outline = mTemplate.getSkillOutline(); if (!map.containsKey(outline)) { map.put(outline, new StateEdit(outline.getModel(), ADD_ROWS)); } row = new Skill(getDataFile(), (Skill) row, true, true); addCompleteRow(outline, row, selMap); } else if (row instanceof Spell) { outline = mTemplate.getSpellOutline(); if (!map.containsKey(outline)) { map.put(outline, new StateEdit(outline.getModel(), ADD_ROWS)); } row = new Spell(getDataFile(), (Spell) row, true, true); addCompleteRow(outline, row, selMap); } else if (row instanceof Equipment) { outline = mTemplate.getEquipmentOutline(); if (!map.containsKey(outline)) { map.put(outline, new StateEdit(outline.getModel(), ADD_ROWS)); } row = new Equipment(getDataFile(), (Equipment) row, true); addCompleteRow(outline, row, selMap); } else { row = null; } if (row instanceof ListRow) { ArrayList<ListRow> process = nameMap.get(outline); if (process == null) { process = new ArrayList<>(); nameMap.put(outline, process); } addRowsToBeProcessed(process, (ListRow) row); } } for (ListOutline anOutline : map.keySet()) { OutlineModel model = anOutline.getModel(); model.select(selMap.get(anOutline), false); StateEdit edit = map.get(anOutline); edit.end(); anOutline.postUndo(edit); anOutline.scrollSelectionIntoView(); anOutline.requestFocus(); } if (!nameMap.isEmpty()) { EventQueue.invokeLater(new RowPostProcessor(nameMap)); } } private void addRowsToBeProcessed(ArrayList<ListRow> list, ListRow row) { int count = row.getChildCount(); list.add(row); for (int i = 0; i < count; i++) { addRowsToBeProcessed(list, (ListRow) row.getChild(i)); } } private void addCompleteRow(Outline outline, Row row, HashMap<Outline, ArrayList<Row>> selMap) { ArrayList<Row> selection = selMap.get(outline); addCompleteRow(outline.getModel(), row); outline.contentSizeMayHaveChanged(); if (selection == null) { selection = new ArrayList<>(); selMap.put(outline, selection); } selection.add(row); } private void addCompleteRow(OutlineModel outlineModel, Row row) { outlineModel.addRow(row); if (row.isOpen() && row.hasChildren()) { for (Row child : row.getChildren()) { addCompleteRow(outlineModel, child); } } } }