/*
* 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.skill;
import com.trollworks.gcs.character.GURPSCharacter;
import com.trollworks.gcs.common.DataFile;
import com.trollworks.gcs.common.ListFile;
import com.trollworks.gcs.library.LibraryFile;
import com.trollworks.gcs.menu.edit.Incrementable;
import com.trollworks.gcs.menu.edit.SkillLevelIncrementable;
import com.trollworks.gcs.menu.edit.TechLevelIncrementable;
import com.trollworks.gcs.template.Template;
import com.trollworks.gcs.widgets.outline.ListOutline;
import com.trollworks.gcs.widgets.outline.ListRow;
import com.trollworks.gcs.widgets.outline.MultipleRowUndo;
import com.trollworks.gcs.widgets.outline.RowPostProcessor;
import com.trollworks.gcs.widgets.outline.RowUndo;
import com.trollworks.toolkit.annotation.Localize;
import com.trollworks.toolkit.collections.FilteredIterator;
import com.trollworks.toolkit.ui.widget.outline.OutlineModel;
import com.trollworks.toolkit.ui.widget.outline.Row;
import com.trollworks.toolkit.utility.Localization;
import com.trollworks.toolkit.utility.text.Numbers;
import java.awt.EventQueue;
import java.awt.dnd.DropTargetDragEvent;
import java.util.ArrayList;
import java.util.List;
/** An outline specifically for skills. */
public class SkillOutline extends ListOutline implements Incrementable, TechLevelIncrementable, SkillLevelIncrementable {
@Localize("Increment Points")
@Localize(locale = "de", value = "Punkte erhöhen")
@Localize(locale = "ru", value = "Увеличить очки")
@Localize(locale = "es", value = "Incrementar Puntos")
private static String INCREMENT;
@Localize("Decrement Points")
@Localize(locale = "de", value = "Punkte verringern")
@Localize(locale = "ru", value = "Уменьшить очки")
@Localize(locale = "es", value = "Disminuir Puntos")
private static String DECREMENT;
@Localize("Increment Skill Level")
@Localize(locale = "ru", value = "Увеличить уровень умения")
private static String INCREMENT_SKILL_LEVEL;
@Localize("Decrement Skill Level")
@Localize(locale = "ru", value = "Уменьшить уровень умения")
private static String DECREMENT_SKILL_LEVEL;
static {
Localization.initialize();
}
private static OutlineModel extractModel(DataFile dataFile) {
if (dataFile instanceof GURPSCharacter) {
return ((GURPSCharacter) dataFile).getSkillsRoot();
}
if (dataFile instanceof Template) {
return ((Template) dataFile).getSkillsModel();
}
if (dataFile instanceof LibraryFile) {
return ((LibraryFile) dataFile).getSkillList().getModel();
}
return ((ListFile) dataFile).getModel();
}
/**
* Create a new skills outline.
*
* @param dataFile The owning data file.
*/
public SkillOutline(DataFile dataFile) {
this(dataFile, extractModel(dataFile));
}
/**
* Create a new skills outline.
*
* @param dataFile The owning data file.
* @param model The {@link OutlineModel} to use.
*/
public SkillOutline(DataFile dataFile, OutlineModel model) {
super(dataFile, model, Skill.ID_LIST_CHANGED);
SkillColumn.addColumns(this, dataFile);
}
@Override
public String getDecrementTitle() {
return DECREMENT;
}
@Override
public String getIncrementTitle() {
return INCREMENT;
}
@Override
public boolean canDecrement() {
return (mDataFile instanceof GURPSCharacter || mDataFile instanceof Template) && selectionHasLeafRows(true);
}
@Override
public boolean canIncrement() {
return (mDataFile instanceof GURPSCharacter || mDataFile instanceof Template) && selectionHasLeafRows(false);
}
private boolean selectionHasLeafRows(boolean requirePointsAboveZero) {
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren() && (!requirePointsAboveZero || skill.getPoints() > 0)) {
return true;
}
}
return false;
}
@SuppressWarnings("unused")
@Override
public void decrement() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
int points = skill.getPoints();
if (points > 0) {
RowUndo undo = new RowUndo(skill);
skill.setPoints(points - 1);
if (undo.finish()) {
undos.add(undo);
}
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
@SuppressWarnings("unused")
@Override
public void increment() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
RowUndo undo = new RowUndo(skill);
skill.setPoints(skill.getPoints() + 1);
if (undo.finish()) {
undos.add(undo);
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
@Override
public String getIncrementSkillLevelTitle() {
return INCREMENT_SKILL_LEVEL;
}
@Override
public String getDecrementSkillLevelTitle() {
return DECREMENT_SKILL_LEVEL;
}
@Override
public boolean canIncrementSkillLevel() {
return canIncrement();
}
@Override
public boolean canDecrementSkillLevel() {
return canDecrement();
}
@SuppressWarnings("unused")
@Override
public void incrementSkillLevel() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
int basePoints = skill.getPoints() + 1;
int maxPoints = basePoints + (skill.getDifficulty() == SkillDifficulty.W ? 12 : 4);
int oldLevel = skill.getLevel();
RowUndo undo = new RowUndo(skill);
for (int points = basePoints; points < maxPoints; points++) {
skill.setPoints(points);
if (skill.getLevel() > oldLevel) {
break;
}
}
// if skill level didn't change, perhaps we hit the limit and should reset points to
// old value
if (skill.getLevel() == oldLevel) {
skill.setPoints(basePoints - 1);
}
if (undo.finish()) {
undos.add(undo);
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
@SuppressWarnings("unused")
@Override
public void decrementSkillLevel() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
RowUndo undo = new RowUndo(skill);
int oldLevel = skill.getLevel();
int points = skill.getPoints() - 1;
skill.setPoints(points);
if (skill.getLevel() == Integer.MIN_VALUE) {
skill.setPoints(0);
} else {
while (points > 0) {
skill.setPoints(--points);
if (skill.getLevel() < oldLevel - 1) {
skill.setPoints(points + 1);
break;
}
}
}
if (undo.finish()) {
undos.add(undo);
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
@Override
public boolean canIncrementTechLevel() {
return checkTechLevel(0);
}
@Override
public boolean canDecrementTechLevel() {
return checkTechLevel(1);
}
private boolean checkTechLevel(int minLevel) {
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren() && getCurrentTechLevel(skill) >= minLevel) {
return true;
}
}
return false;
}
private static int getCurrentTechLevel(Skill skill) {
String tl = skill.getTechLevel();
if (tl != null) {
tl = tl.trim();
if (!tl.isEmpty()) {
for (int i = tl.length(); --i >= 0;) {
if (!Character.isDigit(tl.charAt(i))) {
return -1;
}
}
return Numbers.extractInteger(tl, -1, 0, Integer.MAX_VALUE, false);
}
}
return -1;
}
@SuppressWarnings("unused")
@Override
public void incrementTechLevel() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
int tl = getCurrentTechLevel(skill);
if (tl >= 0) {
RowUndo undo = new RowUndo(skill);
skill.setTechLevel(Integer.toString(tl + 1));
if (undo.finish()) {
undos.add(undo);
}
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
@SuppressWarnings("unused")
@Override
public void decrementTechLevel() {
List<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
int tl = getCurrentTechLevel(skill);
if (tl >= 1) {
RowUndo undo = new RowUndo(skill);
skill.setTechLevel(Integer.toString(tl - 1));
if (undo.finish()) {
undos.add(undo);
}
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
/**
* Returns {@code true} if all selected skills can switch defaults.
*
* @return {@code true} if all selected skills can switch defaults.
* @see Skill#canSwapDefaults(Skill)
*/
public boolean canSwapDefaults() {
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (skill.canHaveChildren() || !skill.canSwapDefaults(skill.getDefaultSkill()) && findBestSwappableSkill(skill) == null) {
return false;
}
}
return true;
}
/**
* Switches Defaults for selected Skills.
*/
@SuppressWarnings("unused")
public void swapDefaults() {
ArrayList<RowUndo> undos = new ArrayList<>();
for (Skill skill : new FilteredIterator<>(getModel().getSelectionAsList(), Skill.class)) {
if (!skill.canHaveChildren()) {
if (skill.canSwapDefaults(skill.getDefaultSkill())) {
swapDeafaults(undos, skill);
} else {
swapDeafaults(undos, findBestSwappableSkill(skill));
}
}
}
if (!undos.isEmpty()) {
repaintSelection();
new MultipleRowUndo(undos);
}
}
/**
* Swaps {@code skill} default with its current default.
*
* @param undos Undos that are created
* @param skill Skill to have its default swapped.
*/
private static void swapDeafaults(ArrayList<RowUndo> undos, Skill skill) {
if (skill != null) {
RowUndo undo1 = new RowUndo(skill);
RowUndo undo2 = new RowUndo(skill.getDefaultSkill());
skill.swapDefault();
if (undo1.finish()) {
undos.add(undo1);
}
if (undo2.finish()) {
undos.add(undo2);
}
}
}
/**
* Finds the best skill to swap its default with. The resulting skill must default to
* {@code skill} and must be swappable with {@code skill}.
*
* @param skillToSwapWith Skill to find a partner for.
* @return best skill to swap its default with.
*/
private Skill findBestSwappableSkill(Skill skillToSwapWith) {
Skill result = null;
for (Skill skill : new FilteredIterator<>(getModel().getRows(), Skill.class)) {
if (skillToSwapWith.equals(skill.getDefaultSkill()) && skill.canSwapDefaults(skillToSwapWith)) {
if (result == null || result.getLevel() < skill.getLevel()) {
result = skill;
}
}
}
return result;
}
@Override
protected boolean isRowDragAcceptable(DropTargetDragEvent dtde, Row[] rows) {
return !getModel().isLocked() && rows.length > 0 && rows[0] instanceof Skill;
}
@Override
public void convertDragRowsToSelf(List<Row> list) {
OutlineModel model = getModel();
Row[] rows = model.getDragRows();
boolean forSheetOrTemplate = mDataFile instanceof GURPSCharacter || mDataFile instanceof Template;
ArrayList<ListRow> process = forSheetOrTemplate ? new ArrayList<>() : null;
for (Row element : rows) {
ListRow row;
if (element instanceof Technique) {
row = new Technique(mDataFile, (Technique) element, forSheetOrTemplate);
} else {
row = new Skill(mDataFile, (Skill) element, true, forSheetOrTemplate);
}
model.collectRowsAndSetOwner(list, row, false);
if (forSheetOrTemplate) {
addRowsToBeProcessed(process, row);
}
}
if (forSheetOrTemplate) {
EventQueue.invokeLater(new RowPostProcessor(this, process));
}
}
}