First commit of tasks-tracker-plugin-master

This commit is contained in:
2025-10-27 08:31:47 +08:00
parent b80f09a4ea
commit a5aab68ea4
121 changed files with 5353 additions and 3 deletions

View File

@@ -0,0 +1,41 @@
package net.reldo.taskstracker;
import java.awt.Color;
import java.net.URL;
public class HtmlUtil
{
public static String HTML_LINE_BREAK = "<br>";
public static String wrapWithHtml(String text)
{
return "<html>" + text + "</html>";
}
public static String wrapWithWrappingParagraph(String text, int width)
{
return "<p width=\"" + width + "\">" + text + "</p>";
}
public static String wrapWithBold(String text)
{
return "<b>" + text + "</b>";
}
public static String imageTag(URL url)
{
return "<img src=\"" + url + "\">";
}
public static String colorTag(String color, String text)
{
return "<span style=\"color: " + color + "\">" + text + "</span>";
}
public static String colorTag(Color color, String text)
{
String buf = Integer.toHexString(color.getRGB());
String hex = "#" + buf.substring(buf.length() - 6);
return colorTag(hex, text);
}
}

View File

@@ -0,0 +1,140 @@
package net.reldo.taskstracker;
import net.reldo.taskstracker.config.ConfigValues;
import net.runelite.client.config.Config;
import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
@ConfigGroup(TasksTrackerPlugin.CONFIG_GROUP_NAME)
public interface TasksTrackerConfig extends Config
{
@ConfigItem(
position = 10,
keyName = "untrackUponCompletion",
name = "Untrack Tasks Upon Completion",
description = "Configures whether completed tasks should also automatically untracked when the task is completed."
)
default boolean untrackUponCompletion()
{
return false;
}
@ConfigItem(
position = 11,
keyName = "filterPanelCollapsible",
name = "Filter Panels Collapsible",
description = "Shows button that allows filter panels to be hidden."
)
default boolean filterPanelCollapsible()
{
return true;
}
@ConfigItem(
position = 12,
keyName = "saveSubFilterState", //@todo generalise this to all sub-filters
name = "Save Filter State",
description = "Configures whether the state of area filters should be saved and recalled when switching task type or restarting the plugin.",
hidden = true
)
default boolean saveSubFilterState()
{
return true;
}
@ConfigItem(
position = 100,
keyName = "completedFilter",
name = "Completed Tasks Filter",
description = "Configures whether completed tasks should be displayed.",
hidden = true
)
default ConfigValues.CompletedFilterValues completedFilter()
{
return ConfigValues.CompletedFilterValues.COMPLETE_AND_INCOMPLETE;
}
@ConfigItem(
position = 101,
keyName = "trackedFilter",
name = "Tracked Tasks Filter",
description = "Configures whether tracked tasks should be displayed.",
hidden = true
)
default ConfigValues.TrackedFilterValues trackedFilter()
{
return ConfigValues.TrackedFilterValues.TRACKED_AND_UNTRACKED;
}
@ConfigItem(
position = 102,
keyName = "ignoredFilter",
name = "Ignored Tasks Filter",
description = "Configures whether ignored tasks should be displayed.",
hidden = true
)
default ConfigValues.IgnoredFilterValues ignoredFilter()
{
return ConfigValues.IgnoredFilterValues.NOT_IGNORED;
}
@ConfigItem(
position = 103,
keyName = "taskListTab",
name = "Selected Task List Tab",
description = "Configures the currently selected tab on the task list.",
hidden = true
)
default ConfigValues.TaskListTabs taskListTab()
{
return ConfigValues.TaskListTabs.ALL;
}
@ConfigItem(
position = 106,
keyName = "taskTypeJsonName",
name = "Task Type",
description = "Configures the task type which is displayed in the panel.",
hidden = true
)
default String taskTypeJsonName()
{
return "COMBAT";
}
@ConfigItem(
position = 109,
keyName = "dropdownFilter",
name = "Dropdown Filter",
description = "Configures the dropdown to filter tasks on.",
hidden = true
)
default String dropdownFilter()
{
return "";
}
@ConfigItem(
position = 110,
keyName = "sortCriteria",
name = "Sort Criteria",
description = "Configures the criteria to sort tasks on.",
hidden = true
)
default String sortCriteria()
{
return "Default";
}
@ConfigItem(
position = 111,
keyName = "sortDirection",
name = "Sort Direction",
description = "Configures the direction to sort tasks.",
hidden = true
)
default ConfigValues.SortDirections sortDirection()
{
return ConfigValues.SortDirections.ASCENDING;
}
}

View File

@@ -0,0 +1,584 @@
package net.reldo.taskstracker;
import com.google.gson.Gson;
import com.google.inject.Binder;
import com.google.inject.Provides;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.image.BufferedImage;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.Export;
import net.reldo.taskstracker.data.LongSerializer;
import net.reldo.taskstracker.data.TasksSummary;
import net.reldo.taskstracker.data.TrackerConfigStore;
import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader;
import net.reldo.taskstracker.data.jsondatastore.reader.HttpDataStoreReader;
import net.reldo.taskstracker.data.reldo.ReldoImport;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.data.task.TaskType;
import net.reldo.taskstracker.data.task.filters.FilterService;
import net.reldo.taskstracker.panel.TasksTrackerPluginPanel;
import net.runelite.api.ChatMessageType;
import net.runelite.api.Client;
import net.runelite.api.Experience;
import net.runelite.api.GameState;
import net.runelite.api.Skill;
import net.runelite.api.events.CommandExecuted;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
import net.runelite.api.events.StatChanged;
import net.runelite.api.events.VarbitChanged;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.chat.ChatMessageBuilder;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.config.RuneScapeProfileType;
import net.runelite.client.eventbus.Subscribe;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.ProfileChanged;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.plugins.Plugin;
import net.runelite.client.plugins.PluginDescriptor;
import net.runelite.client.plugins.PluginManager;
import net.runelite.client.ui.ClientToolbar;
import net.runelite.client.ui.NavigationButton;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.LinkBrowser;
@Slf4j
@PluginDescriptor(
name = "Tasks Tracker"
)
public class TasksTrackerPlugin extends Plugin
{
public static final String CONFIG_GROUP_NAME = "tasks-tracker";
public int[] playerSkills;
public String taskTextFilter;
public TasksTrackerPluginPanel pluginPanel;
private static final long VARP_UPDATE_THROTTLE_DELAY_MS = 7 * 1000;
private boolean forceUpdateVarpsFlag = false;
private Set<Integer> varpIdsToUpdate = new HashSet<>();
private long lastVarpUpdate = 0;
private NavigationButton navButton;
private RuneScapeProfileType currentProfileType;
private final Map<Skill, Integer> oldExperience = new EnumMap<>(Skill.class);
@Inject @Named("runelite.version") private String runeliteVersion;
@Inject private Gson gson;
@Inject private Client client;
@Inject private SpriteManager spriteManager;
@Inject private PluginManager pluginManager;
@Inject private ClientToolbar clientToolbar;
@Inject private ClientThread clientThread;
@Inject private ChatMessageManager chatMessageManager;
@Getter @Inject private ConfigManager configManager;
@Getter @Inject private TasksTrackerConfig config;
@Inject private TrackerConfigStore trackerConfigStore;
@Inject private TaskService taskService;
@Inject private FilterService filterService;
@Override
public void configure(Binder binder)
{
binder.bind(DataStoreReader.class).to(HttpDataStoreReader.class);
super.configure(binder);
}
@Provides
TasksTrackerConfig getConfig(ConfigManager configManager)
{
return configManager.getConfig(TasksTrackerConfig.class);
}
@Override
protected void startUp()
{
try
{
String taskTypeJsonName = config.taskTypeJsonName();
taskService.setTaskType(taskTypeJsonName);
}
catch (Exception ex)
{
log.error("error setting task type in startUp", ex);
}
forceUpdateVarpsFlag = false;
pluginPanel = new TasksTrackerPluginPanel(this, config, spriteManager, taskService);
boolean isLoggedIn = isLoggedInState(client.getGameState());
pluginPanel.setLoggedIn(isLoggedIn);
if (isLoggedIn)
{
forceUpdateVarpsFlag = true;
}
final BufferedImage icon = ImageUtil.loadImageResource(getClass(), "panel_icon.png");
navButton = NavigationButton.builder()
.tooltip("Task Tracker")
.icon(icon)
.priority(5)
.panel(pluginPanel)
.build();
clientToolbar.addNavigation(navButton);
log.info("Tasks Tracker started!");
}
@Override
protected void shutDown()
{
pluginPanel.hideLoggedInPanel();
pluginPanel = null;
taskService.clearTaskTypes();
clientToolbar.removeNavigation(navButton);
log.info("Tasks Tracker stopped!");
}
@Subscribe
public void onCommandExecuted(CommandExecuted commandExecuted)
{
if (!commandExecuted.getCommand().startsWith("tt")) return;
if (commandExecuted.getCommand().equalsIgnoreCase("tt-process-varp"))
{
String[] args = commandExecuted.getArguments();
if (args.length == 0) return;
try
{
int varpId = Integer.parseInt(args[0]);
log.debug("Processing varpId " + varpId);
processVarpAndUpdateTasks(varpId);
}
catch (NumberFormatException e)
{
log.debug("Invalid varpId, provide a valid integer");
}
}
}
@Subscribe
public void onVarbitChanged(VarbitChanged varbitChanged)
{
if (forceUpdateVarpsFlag || taskService.isTaskTypeChanged())
{
// Force update is coming on next game tick, so ignore varbit change events
return;
}
int varpId = varbitChanged.getVarpId();
if (!taskService.isVarpInCurrentTaskType(varpId))
{
return;
}
varpIdsToUpdate.add(varbitChanged.getVarpId());
}
@Subscribe
public void onConfigChanged(ConfigChanged configChanged)
{
if (!configChanged.getGroup().equals(CONFIG_GROUP_NAME))
{
return;
}
log.debug("onConfigChanged {} {}", configChanged.getKey(), configChanged.getNewValue());
if (configChanged.getKey().equals("untrackUponCompletion") && config.untrackUponCompletion())
{
forceVarpUpdate();
}
if (configChanged.getKey().equals("filterPanelCollapsible"))
{
SwingUtilities.invokeLater(pluginPanel::redraw);
}
}
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
log.debug("onGameStateChanged {}", gameStateChanged.getGameState().toString());
GameState newGameState = gameStateChanged.getGameState();
RuneScapeProfileType newProfileType = RuneScapeProfileType.getCurrent(client);
SwingUtilities.invokeLater(() -> pluginPanel.setLoggedIn(isLoggedInState(newGameState)));
// Logged in
if (newGameState == GameState.LOGGING_IN)
{
forceUpdateVarpsFlag = true;
}
// Changed game mode
if (isLoggedInState(newGameState) && currentProfileType != null && currentProfileType != newProfileType)
{
forceUpdateVarpsFlag = true;
}
currentProfileType = newProfileType;
}
private boolean isLoggedInState(GameState gameState)
{
return gameState == GameState.LOGGED_IN || gameState == GameState.HOPPING || gameState == GameState.LOADING;
}
@Subscribe
public void onGameTick(GameTick gameTick)
{
if (forceUpdateVarpsFlag || taskService.isTaskTypeChanged())
{
log.debug("forceUpdateVarpsFlag game tick {} {}", forceUpdateVarpsFlag, taskService.isTaskTypeChanged());
trackerConfigStore.loadCurrentTaskTypeFromConfig();
forceVarpUpdate();
SwingUtilities.invokeLater(() -> pluginPanel.redraw());
forceUpdateVarpsFlag = false;
taskService.setTaskTypeChanged(false);
}
// Flush throttled varp updates
long currentTimeEpoch = System.currentTimeMillis();
if (currentTimeEpoch - lastVarpUpdate > VARP_UPDATE_THROTTLE_DELAY_MS)
{
flushVarpUpdates(varpIdsToUpdate);
varpIdsToUpdate = new HashSet<>();
lastVarpUpdate = currentTimeEpoch;
}
}
@Subscribe
public void onStatChanged(StatChanged statChanged)
{
// @todo deprecate one of these, we don't need to track player skills twice.
// Cache current player skills
int[] newSkills = client.getRealSkillLevels();
boolean changed = !Arrays.equals(playerSkills, newSkills);
if (changed)
{
playerSkills = client.getRealSkillLevels();
}
final Skill skill = statChanged.getSkill();
// Modified from m0bilebtw's modification from Nightfirecat's virtual level ups plugin
final int xpAfter = client.getSkillExperience(skill);
final int levelAfter = Experience.getLevelForXp(xpAfter);
final int xpBefore = oldExperience.getOrDefault(skill, -1);
final int levelBefore = xpBefore == -1 ? -1 : Experience.getLevelForXp(xpBefore);
oldExperience.put(skill, xpAfter);
// Do not proceed if any of the following are true:
// * xpBefore == -1 (don't fire when first setting new known value)
// * xpAfter <= xpBefore (do not allow 200m -> 200m exp drops)
// * levelBefore >= levelAfter (stop if we're not actually reaching a new level)
// * levelAfter > MAX_REAL_LEVEL (stop if above 99)
if (xpBefore == -1 || xpAfter <= xpBefore || levelBefore >= levelAfter || levelAfter > Experience.MAX_REAL_LEVEL)
{
return;
}
// If we get here, 'skill' was leveled up!
SwingUtilities.invokeLater(() -> pluginPanel.taskListPanel.refreshTaskPanelsWithSkill(skill));
}
@Subscribe
public void onProfileChanged(ProfileChanged profileChanged)
{
final Optional<Plugin> taskTrackerPlugin = pluginManager.getPlugins().stream().filter(p -> p.getName().equals("Tasks Tracker")).findFirst();
if (taskTrackerPlugin.isPresent() && pluginManager.isPluginEnabled(taskTrackerPlugin.get()))
{
reloadTaskType();
}
}
public void refreshAllTasks()
{
SwingUtilities.invokeLater(() -> pluginPanel.refreshAllTasks());
}
public void reloadTaskType() {
taskService.clearTaskTypes();
filterService.clearFilterConfigs();
try {
String taskTypeJsonName = config.taskTypeJsonName();
taskService.setTaskType(taskTypeJsonName).thenAccept(isSet -> {
if (!isSet) {
return;
}
SwingUtilities.invokeLater(() ->
{
pluginPanel.redraw();
pluginPanel.refreshAllTasks();
});
});
} catch (Exception ex) {
log.error("error setting task type in reload", ex);
}
}
public void saveCurrentTaskTypeData()
{
log.debug("saveCurrentTaskTypeData");
trackerConfigStore.saveCurrentTaskTypeData();
}
public void openImportJsonDialog()
{
JOptionPane optionPane = new JOptionPane("Paste import data into the text field below to import task tracker data.", JOptionPane.INFORMATION_MESSAGE);
optionPane.setWantsInput(true);
JDialog inputDialog = optionPane.createDialog(this.pluginPanel, "Import Tasks Input");
inputDialog.setAlwaysOnTop(true);
inputDialog.setVisible(true);
if (optionPane.getInputValue().equals("") || optionPane.getInputValue().equals("uninitializedValue"))
{
this.showMessageBox("Import Tasks Error", "Input was empty so no data has been imported.", JOptionPane.ERROR_MESSAGE, false);
return;
}
String json = "";
ReldoImport reldoImport;
try
{
json = (String) optionPane.getInputValue();
reldoImport = this.gson.fromJson(json, ReldoImport.class);
}
catch (Exception ex)
{
this.showMessageBox("Import Tasks Error", "There was an issue importing task tracker data. " + ex.getMessage(), JOptionPane.ERROR_MESSAGE, false);
log.error("There was an issue importing task tracker data.", ex);
log.debug("reldoImport json: {}", json);
return;
}
if (!reldoImport.taskTypeName.equalsIgnoreCase(config.taskTypeJsonName()))
{
this.showMessageBox("Import Tasks Error", String.format("Wrong task type. Select the %s task type to import this data.", reldoImport.taskTypeName), JOptionPane.ERROR_MESSAGE, false);
return;
}
optionPane = new JOptionPane("Importing tasks will overwrite task tracker settings and cannot be undone. Are you sure you want to import tasks?", JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION);
JDialog confirmDialog = optionPane.createDialog(this.pluginPanel, "Import Tasks Overwrite Confirmation");
confirmDialog.setAlwaysOnTop(true);
confirmDialog.setVisible(true);
Object selectedValue = optionPane.getValue();
if (selectedValue == null)
{
return;
}
if (selectedValue.equals(JOptionPane.YES_OPTION))
{
HashMap<Integer, TaskFromStruct> tasksById = new HashMap<>();
taskService.getTasks().forEach((task) -> tasksById.put(task.getIntParam("id"), task));
reldoImport.getTasks().forEach((id, reldoTaskSave) -> {
TaskFromStruct task = tasksById.get(id);
task.loadReldoSave(reldoTaskSave);
});
trackerConfigStore.saveCurrentTaskTypeData();
pluginPanel.redraw();
}
}
public void sendTotalsToChat()
{
TasksSummary summary = new TasksSummary(taskService.getTasks());
int trackedTasks = summary.trackedTasksCount;
int trackedPoints = summary.trackedTasksPoints;
final String message = new ChatMessageBuilder()
.append(Color.BLACK, String.format("Task Tracker - Tracked Tasks: %s | Tracked Points: %s", trackedTasks, trackedPoints))
.build();
chatMessageManager.queue(
QueuedMessage.builder()
.type(ChatMessageType.CONSOLE)
.runeLiteFormattedMessage(message)
.build());
}
public void copyJsonToClipboard()
{
clientThread.invokeLater(() -> {
// Not worried with this complexity on the client thread because it's from an infrequent button press
String json = getCurrentTaskTypeExportJson();
final StringSelection stringSelection = new StringSelection(json);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null);
String message = "Copied " + taskService.getCurrentTaskType().getName() + " data to clipboard!";
showMessageBox("Data Exported!", message, JOptionPane.INFORMATION_MESSAGE, true);
});
}
private void forceVarpUpdate()
{
log.debug("forceVarpUpdate");
processVarpAndUpdateTasks(null).thenAccept((processed) -> {
if (processed)
{
log.debug("forceVarpUpdate processed complete, saving");
saveCurrentTaskTypeData();
}
});
}
private void flushVarpUpdates(Set<Integer> varpIds)
{
log.debug("Flushing throttled varp updates {}", varpIds);
varpIds.forEach((id) -> processVarpAndUpdateTasks(id).thenAccept(processed -> {
if (processed)
{
log.debug("flushVarpUpdates processed complete, saving");
saveCurrentTaskTypeData();
}
}));
}
private CompletableFuture<Boolean> processTaskStatus(TaskFromStruct task)
{
CompletableFuture<Boolean> future = new CompletableFuture<>();
clientThread.invoke(() -> {
int taskId = task.getIntParam("id");
int varbitIndex = taskId / 32;
int bitIndex = taskId % 32;
try
{
int varpId = task.getTaskType().getTaskVarps().get(varbitIndex);
BigInteger varpValue = BigInteger.valueOf(client.getVarpValue(varpId));
boolean isTaskCompleted = varpValue.testBit(bitIndex);
task.setCompleted(isTaskCompleted);
if (isTaskCompleted && config.untrackUponCompletion())
{
task.setTracked(false);
}
log.debug("process taskFromStruct {} ({}) {}", task.getStringParam("name"), task.getIntParam("id"), isTaskCompleted);
future.complete(isTaskCompleted);
}
catch (Exception ex)
{
log.error("Error processing task status {}", taskId, ex);
future.completeExceptionally(ex);
}
});
return future;
}
/**
* Update task completion status. If no varpId is specified, it updates all tasks in the current task type
* @param varpId varp id to update (optional)
* @return An observable that emits true if all tasks were processed
*/
private CompletableFuture<Boolean> processVarpAndUpdateTasks(@Nullable Integer varpId)
{
log.info("processVarpAndUpdateTasks: " + (varpId != null ? varpId : "all"));
List<TaskFromStruct> tasks = varpId != null ?
taskService.getTasksFromVarpId(varpId) :
taskService.getTasks();
List<CompletableFuture<Boolean>> taskFutures = new ArrayList<>();
for (TaskFromStruct task : tasks)
{
CompletableFuture<Boolean> taskFuture = processTaskStatus(task);
taskFutures.add(taskFuture);
}
CompletableFuture<Void> allTasksFuture = CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[0]));
return allTasksFuture
.thenRun(() -> {
if (varpId != null)
{
SwingUtilities.invokeLater(() -> pluginPanel.taskListPanel.refreshMultipleTasks(tasks));
} else {
SwingUtilities.invokeLater(() -> pluginPanel.refreshAllTasks());
}
})
.thenApply(v -> true);
}
private String getCurrentTaskTypeExportJson()
{
TaskType taskType = taskService.getCurrentTaskType();
Gson gson = this.gson.newBuilder()
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(float.class, new LongSerializer())
.create();
if (taskType == null)
{
String error = "Cannot export to JSON; no task type selected.";
log.error(error);
return error;
}
else
{
Export export = new Export(taskType, taskService.getTasks(), runeliteVersion, client);
return gson.toJson(export);
}
}
private void showMessageBox(final String title, final String message, int messageType, boolean showOpenLeagueTools)
{
SwingUtilities.invokeLater(() -> {
JOptionPane optionPane;
JDialog dialog;
if (showOpenLeagueTools)
{
String[] options = {"Open OS League Tools", "Ok"};
optionPane = new JOptionPane(message, messageType, JOptionPane.YES_NO_OPTION, null, options, options[1]);
}
else
{
optionPane = new JOptionPane(message, messageType);
}
dialog = optionPane.createDialog(pluginPanel, title);
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
Object selectedValue = optionPane.getValue();
if (selectedValue == null)
{
return;
}
if (selectedValue.equals("Open OS League Tools"))
{
LinkBrowser.browse("https://www.osleague.tools/tracker?open=import&tab=tasks");
}
});
}
}

View File

@@ -0,0 +1,40 @@
package net.reldo.taskstracker.config;
public class ConfigValues {
public enum CompletedFilterValues
{
COMPLETE_AND_INCOMPLETE,
COMPLETE,
INCOMPLETE;
}
public enum TrackedFilterValues
{
TRACKED_AND_UNTRACKED,
TRACKED,
UNTRACKED;
}
public enum IgnoredFilterValues
{
NOT_IGNORED,
IGNORED_AND_NOT_IGNORED,
IGNORED;
}
public enum TaskListTabs
{
TRACKED,
ALL,
CUSTOM;
}
public enum SortDirections
{
ASCENDING,
DESCENDING;
}
}

View File

@@ -0,0 +1,94 @@
package net.reldo.taskstracker.data;
import com.google.gson.annotations.Expose;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import lombok.Getter;
import net.reldo.taskstracker.data.task.ConfigTaskSave;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.reldo.taskstracker.data.task.TaskType;
import net.reldo.taskstracker.quests.DiaryData;
import net.reldo.taskstracker.quests.QuestData;
import net.runelite.api.Actor;
import net.runelite.api.Client;
@Getter
public class Export
{
private final Client client;
@Expose private final QuestData quests;
@Expose private final DiaryData diaries;
@Expose private String displayName;
@Expose private final int runescapeVersion;
@Expose private final String runeliteVersion;
@Expose private final long timestamp;
@Expose private final String taskType;
@Expose private final HashMap<Integer, Integer> varbits;
@Expose private final HashMap<Integer, Integer> varps;
@Expose private final HashMap<String, ConfigTaskSave> tasks;
public Export(TaskType taskType, List<TaskFromStruct> tasks, String runeliteVersion, Client client)
{
this.client = client;
Actor localPlayer = client.getLocalPlayer();
if (localPlayer != null)
{
this.displayName = localPlayer.getName();
}
quests = new QuestData(client);
diaries = new DiaryData(client);
runescapeVersion = client.getRevision();
this.runeliteVersion = runeliteVersion;
timestamp = Instant.now().toEpochMilli();
this.taskType = taskType.getTaskJsonName();
varbits = getVarbits(taskType);
varps = getVarps(taskType);
this.tasks = getTaskSavesById(tasks);
}
private HashMap<Integer, Integer> getVarbits(TaskType taskType)
{
assert client.isClientThread();
HashMap<Integer, Integer> varbitValueMap = new HashMap<>();
for (int varbitId : taskType.getVarbits())
{
varbitValueMap.put(varbitId, client.getVarbitValue(varbitId));
}
return varbitValueMap;
}
public HashMap<Integer, Integer> getVarps(TaskType taskType)
{
assert client.isClientThread();
HashMap<Integer, Integer> varpValueMap = new HashMap<>();
for (int varpId : taskType.getTaskVarps())
{
varpValueMap.put(varpId, client.getVarpValue(varpId));
}
for (int varpId : taskType.getOtherVarps())
{
varpValueMap.put(varpId, client.getVarpValue(varpId));
}
return varpValueMap;
}
public HashMap<String, ConfigTaskSave> getTaskSavesById(List<TaskFromStruct> tasks)
{
HashMap<String, ConfigTaskSave> taskSavesById = new HashMap<>();
for (TaskFromStruct task : tasks)
{
if (task.getCompletedOn() == 0 && task.getIgnoredOn() == 0 && task.getTrackedOn() == 0)
{
continue;
}
taskSavesById.put(String.valueOf(task.getIntParam("id")), task.getSaveData());
}
return taskSavesById;
}
}

View File

@@ -0,0 +1,17 @@
package net.reldo.taskstracker.data;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.text.DecimalFormat;
public class LongSerializer implements JsonSerializer<Long>
{
@Override
public JsonElement serialize(Long value, Type type, JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(new DecimalFormat("#").format(value));
}
}

View File

@@ -0,0 +1,24 @@
package net.reldo.taskstracker.data;
import java.util.Collection;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.task.TaskFromStruct;
@Slf4j
public class TasksSummary
{
public int trackedTasksCount = 0;
public int trackedTasksPoints = 0;
public TasksSummary(Collection<TaskFromStruct> tasks)
{
tasks.forEach(task -> {
if (task.isTracked()) {
trackedTasksCount++;
int points = task.getPoints();
log.debug("TasksSummary {} {}", task.getName(), points);
trackedTasksPoints += points;
}
});
}
}

View File

@@ -0,0 +1,94 @@
package net.reldo.taskstracker.data;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.reldo.taskstracker.data.task.ConfigTaskSave;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.data.task.TaskType;
import net.runelite.client.config.ConfigManager;
@Singleton
@Slf4j
public class TrackerConfigStore
{
public static final String CONFIG_TASKS_PREFIX = "tasks";
public static final String CONFIG_GROUP_PREFIX_SEPARATOR = "-";
public static final String CONFIG_GROUP_NAME = TasksTrackerPlugin.CONFIG_GROUP_NAME;
private final Gson customGson;
@Inject
private TaskService taskService;
@Inject
private ConfigManager configManager;
@Inject
public TrackerConfigStore(Gson gson)
{
this.customGson = gson.newBuilder()
.excludeFieldsWithoutExposeAnnotation()
.registerTypeAdapter(float.class, new LongSerializer())
.create();
}
public void loadCurrentTaskTypeFromConfig()
{
TaskType currentTaskType = taskService.getCurrentTaskType();
if (currentTaskType == null)
{
log.debug("loadTaskTypeFromConfig type is null, skipping");
return;
}
log.debug("loadTaskTypeFromConfig {}", currentTaskType.getName());
String configKey = getCurrentTaskTypeConfigKey();
String configJson = configManager.getRSProfileConfiguration(CONFIG_GROUP_NAME, configKey);
if (configJson == null)
{
log.debug("No save information for task type {}, not applying save", currentTaskType.getName());
return;
}
Type deserializeType = TypeToken.getParameterized(HashMap.class, Integer.class, ConfigTaskSave.class).getType();
try
{
HashMap<Integer, ConfigTaskSave> saveData = customGson.fromJson(configJson, deserializeType);
taskService.applySave(currentTaskType, saveData);
}
catch (JsonParseException ex)
{
log.error("{} {} json invalid. wiping saved data", CONFIG_GROUP_NAME, configKey, ex);
configManager.unsetRSProfileConfiguration(CONFIG_GROUP_NAME, configKey);
}
}
public void saveCurrentTaskTypeData()
{
log.debug("saveTaskTypeToConfig");
Map<Integer, ConfigTaskSave> saveDataByStructId = taskService.getTasks().stream()
.filter(task -> task.getCompletedOn() != 0 || task.getIgnoredOn() != 0 || task.getTrackedOn() != 0)
.collect(Collectors.toMap(
TaskFromStruct::getStructId,
TaskFromStruct::getSaveData,
(existing, replacement) -> existing,
HashMap::new
));
String configValue = this.customGson.toJson(saveDataByStructId);
String configKey = CONFIG_TASKS_PREFIX + CONFIG_GROUP_PREFIX_SEPARATOR + taskService.getCurrentTaskType().getTaskJsonName();
configManager.setRSProfileConfiguration(CONFIG_GROUP_NAME, configKey, configValue);
}
private String getCurrentTaskTypeConfigKey()
{
return CONFIG_TASKS_PREFIX + CONFIG_GROUP_PREFIX_SEPARATOR + taskService.getCurrentTaskType().getTaskJsonName();
}
}

View File

@@ -0,0 +1,50 @@
package net.reldo.taskstracker.data.jsondatastore;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import okhttp3.OkHttpClient;
@Singleton
@Slf4j
public class FilterDataClient
{
@Inject private ManifestClient manifestClient;
@Inject private OkHttpClient okHttpClient;
@Inject private Gson gson;
@Inject private DataStoreReader dataStoreReader;
public FilterDataClient()
{
log.debug("init filter data client");
}
public HashMap<String, FilterConfig> getFilterConfigs() throws Exception
{
log.debug("get filter configs");
try(InputStream stream = this.dataStoreReader.readFilterConfigs(this.manifestClient.getManifest().filterMetadata);
InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8))
{
Type listType = TypeToken.getParameterized(ArrayList.class, FilterConfig.class).getType();
List<FilterConfig> filterConfigs = this.gson.fromJson(responseReader, listType);
HashMap<String, FilterConfig> filterConfigsByConfigKey = new HashMap<>();
for (FilterConfig filterConfig : filterConfigs)
{
filterConfigsByConfigKey.put(filterConfig.getConfigKey(), filterConfig);
}
return filterConfigsByConfigKey;
}
}
}

View File

@@ -0,0 +1,6 @@
package net.reldo.taskstracker.data.jsondatastore;
public class JsonDataStore
{
public static String baseUrl = "https://raw.githubusercontent.com/osrs-reldo/task-json-store/refs/heads/main";
}

View File

@@ -0,0 +1,44 @@
package net.reldo.taskstracker.data.jsondatastore;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader;
import net.reldo.taskstracker.data.jsondatastore.types.Manifest;
import okhttp3.OkHttpClient;
@Singleton
@Slf4j
public class ManifestClient
{
@Inject private OkHttpClient okHttpClient;
@Inject private Gson gson;
@Inject private DataStoreReader dataStoreReader;
private static Manifest _manifest = null;
public ManifestClient()
{
log.debug("init manifestclient");
}
public Manifest getManifest() throws Exception
{
if (_manifest != null) {
return _manifest;
}
try(InputStream stream = this.dataStoreReader.readManifestData();
InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8))
{
String manifestJson = CharStreams.toString(responseReader); // ew, why not a stream? not working...
_manifest = this.gson.fromJson(manifestJson, Manifest.class);
log.debug("_manifest = " + _manifest);
return _manifest;
}
}
}

View File

@@ -0,0 +1,67 @@
package net.reldo.taskstracker.data.jsondatastore;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.reader.DataStoreReader;
import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition;
import net.reldo.taskstracker.data.jsondatastore.types.TaskTypeDefinition;
import net.reldo.taskstracker.data.task.TaskType;
import net.runelite.api.Client;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.game.SpriteManager;
import okhttp3.OkHttpClient;
@Singleton
@Slf4j
public class TaskDataClient
{
@Inject private ManifestClient manifestClient;
@Inject private OkHttpClient okHttpClient;
@Inject private Gson gson;
@Inject private DataStoreReader dataStoreReader;
@Inject private Client client;
@Inject private ClientThread clientThread;
@Inject private SpriteManager spriteManager;
public TaskDataClient()
{
log.debug("init task data client");
}
public HashMap<String, TaskType> getTaskTypes() throws Exception {
try (InputStream stream = this.dataStoreReader.readTaskTypes(this.manifestClient.getManifest().taskTypeMetadata);
InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8))
{
Type listType = TypeToken.getParameterized(ArrayList.class, TaskTypeDefinition.class).getType();
List<TaskTypeDefinition> taskTypeDefinitions = this.gson.fromJson(responseReader, listType);
HashMap<String, TaskType> taskTypes = new HashMap<>();
for (TaskTypeDefinition taskTypeDefinition : taskTypeDefinitions)
{
taskTypes.put(taskTypeDefinition.getTaskJsonName(), new TaskType(client, clientThread, spriteManager, taskTypeDefinition));
}
return taskTypes;
}
}
public List<TaskDefinition> getTaskDefinitions(String jsonFilename) throws Exception
{
try(InputStream stream = this.dataStoreReader.readTasks(jsonFilename);
InputStreamReader responseReader = new InputStreamReader(stream, StandardCharsets.UTF_8))
{
Type listType = TypeToken.getParameterized(ArrayList.class, TaskDefinition.class).getType();
return this.gson.fromJson(responseReader, listType);
}
}
}

View File

@@ -0,0 +1,11 @@
package net.reldo.taskstracker.data.jsondatastore.reader;
import java.io.InputStream;
public interface DataStoreReader
{
InputStream readManifestData() throws Exception;
InputStream readTaskTypes(String taskTypeFilename) throws Exception;
InputStream readTasks(String jsonFilename) throws Exception;
InputStream readFilterConfigs(String filterFilename) throws Exception;
}

View File

@@ -0,0 +1,130 @@
package net.reldo.taskstracker.data.jsondatastore.reader;
import java.io.InputStream;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.JsonDataStore;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@Singleton
@Slf4j
public class HttpDataStoreReader implements DataStoreReader
{
@Inject
private OkHttpClient okHttpClient;
@Override
public InputStream readManifestData() throws Exception
{
String manifestUrl = JsonDataStore.baseUrl + "/manifest.json";
log.debug("getManifest json from {} ...", manifestUrl);
Request request = new Request.Builder()
.url(manifestUrl)
.build();
Response response = this.okHttpClient.newCall(request).execute();
if (!response.isSuccessful())
{
String unsuccessful = "getManifest json request unsuccessful with status " + response.code();
if (response.body() != null)
{
unsuccessful += " and body \n" + response.body();
}
log.error(unsuccessful);
throw new Exception(unsuccessful);
}
if (response.body() == null)
{
log.error("getManifest returned without body");
throw new Exception("getManifest returned without body");
}
log.debug("getManifest json fetched successfully, deserializing result");
return response.body().byteStream();
}
@Override
public InputStream readTaskTypes(String taskTypeFilename) throws Exception
{
String taskJsonUrl = JsonDataStore.baseUrl + "/" + taskTypeFilename;
log.debug("getTaskTypes json from {} ...", taskJsonUrl);
Request request = new Request.Builder()
.url(taskJsonUrl)
.build();
Response response = this.okHttpClient.newCall(request).execute();
if (!response.isSuccessful())
{
String unsuccessful = "getTaskTypes json request unsuccessful with status " + response.code();
if (response.body() != null)
{
unsuccessful += " and body \n" + response.body();
}
log.error(unsuccessful);
throw new Exception(unsuccessful);
}
if (response.body() == null)
{
log.error("getTaskTypes returned without body");
throw new Exception("getTaskTypes returned without body");
}
log.debug("getTaskTypes json fetched successfully, deserializing result");
return response.body().byteStream();
}
@Override
public InputStream readTasks(String jsonFilename) throws Exception
{
String taskJsonUrl = String.format("%s/tasks/%s.min.json", JsonDataStore.baseUrl, jsonFilename);
log.debug("getTasks json from {} ...", taskJsonUrl);
Request request = new Request.Builder()
.url(taskJsonUrl)
.build();
Response response = this.okHttpClient.newCall(request).execute();
if (!response.isSuccessful())
{
String unsuccessful = "getTasks json request unsuccessful with status " + response.code();
if (response.body() != null)
{
unsuccessful += " and body \n" + response.body();
}
log.error(unsuccessful);
throw new Exception(unsuccessful);
}
if (response.body() == null)
{
log.error("getTasks returned without body");
throw new Exception("getTasks returned without body");
}
log.debug("getTasks json fetched successfully, deserializing result");
return response.body().byteStream();
}
@Override
public InputStream readFilterConfigs(String filterFilename) throws Exception
{
String filterJsonUrl = JsonDataStore.baseUrl + "/" + filterFilename;
log.debug("getTaskTypes json from {} ...", filterJsonUrl);
Request request = new Request.Builder()
.url(filterJsonUrl)
.build();
Response response = this.okHttpClient.newCall(request).execute();
if (!response.isSuccessful())
{
String unsuccessful = "getFilters json request unsuccessful with status " + response.code();
if (response.body() != null)
{
unsuccessful += " and body \n" + response.body();
}
log.error(unsuccessful);
throw new Exception(unsuccessful);
}
if (response.body() == null)
{
log.error("getFilters returned without body");
throw new Exception("getFilters returned without body");
}
log.debug("getFilters json fetched successfully, deserializing result");
return response.body().byteStream();
}
}

View File

@@ -0,0 +1,51 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import java.util.ArrayList;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Represents the configuration for a filter
*/
@Data
@AllArgsConstructor
public class FilterConfig {
/**
* Key under which to store the filter's selected values, generally prefixed by task type
*/
private String configKey;
/**
* The label displayed in the UI with the filter.
*/
private String label;
/**
* The filter type, see enum for types of filters supported.
*/
private FilterType filterType;
/**
* The source of the value(s) to use for the filter, see enum for types of values supported.
* If global is specified then configKey must match a filter config defined in filters.json
*/
private FilterValueType valueType;
/**
* The name of the param or metadata property to use for the filter.
* Can be left null for SKILL value type
*/
private String valueName;
/**
* Name of an enum specified in `TaskTypeDefinition.stringEnumMap` to provide labels for the filter
* Specifying this property will override the displayed integer value of `valueName`
*/
private String optionLabelEnum;
/**
* Item values in a button filter (dropdown not yet supported)
*/
private ArrayList<FilterCustomItem> customItems;
}

View File

@@ -0,0 +1,11 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import lombok.Data;
@Data
public class FilterCustomItem
{
private Integer value;
private String tooltip;
private Integer spriteId;
}

View File

@@ -0,0 +1,7 @@
package net.reldo.taskstracker.data.jsondatastore.types;
public enum FilterType
{
BUTTON_FILTER,
DROPDOWN_FILTER
}

View File

@@ -0,0 +1,10 @@
package net.reldo.taskstracker.data.jsondatastore.types;
public enum FilterValueType
{
PARAM_INTEGER,
PARAM_STRING,
SKILL,
METADATA,
GLOBAL
}

View File

@@ -0,0 +1,10 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import lombok.Data;
@Data
public class Manifest
{
public String taskTypeMetadata;
public String filterMetadata;
}

View File

@@ -0,0 +1,46 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import java.util.List;
import java.util.Map;
import lombok.Data;
/**
* Represents a task with various attributes
*/
@Data
public class TaskDefinition
{
/**
* Struct id for task data
*/
private Integer structId;
/**
* Sort id based on the sort order in the game's UI
*/
private Integer sortId;
/**
* Skills required for the task.
*/
private List<TaskDefinitionSkill> skills;
/**
* Metadata related to the task that isn't represented in the Struct/params
* May or may not be used for task filters
* Examples:
* - notes = extra description like "a magic cabbage is a cabbage picked at Draynor Manor"
* - category = an extra category type that isn't a param
*/
private Map<String, Object> metadata;
/**
* Notes from the OSRS wiki
*/
private String wikiNotes;
/**
* Completion percent from the OSRS wiki
*/
private Float completionPercent;
}

View File

@@ -0,0 +1,20 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import lombok.Data;
/**
* Represents a skill required for a task.
*/
@Data
public class TaskDefinitionSkill
{
/**
* The skill
*/
private String skill;
/**
* The level required
*/
private Integer level;
}

View File

@@ -0,0 +1,105 @@
package net.reldo.taskstracker.data.jsondatastore.types;
import java.util.ArrayList;
import java.util.HashMap;
import lombok.Data;
/**
* Represents a task type with relevant configuration for UI display and task management
*/
@Data
public class TaskTypeDefinition
{
/**
* Name of the task type for UI display
*/
private String name;
/**
* Description of the task type
*/
private String description;
/**
* Is the task type enabled?
*/
private boolean isEnabled;
/**
* Filename for the task JSON found in the tasks directory of task-json-store
* Extension not included.
*/
private String taskJsonName;
/**
* Filters for the task type
*/
private ArrayList<FilterConfig> filters;
/**
* A dictionary of parameters relevant to the task, with required id, name, description, tier
* The key is the plain English name for the parameter
* The value is an array of OSRS cache Struct ParamIDs that match with the plain English parameter
* Generally, there is only 1 value in the array, but multiple are available for fallback
*/
private HashMap<String, Integer> intParamMap;
/**
* A dictionary of parameters relevant to the task, with required id, name, description, tier
* The key is the plain English name for the parameter
* The value is an array of OSRS cache Struct ParamIDs that match with the plain English parameter
* Generally, there is only 1 value in the array, but multiple are available for fallback
*/
private HashMap<String, Integer> stringParamMap;
/**
* A dictionary of integer enums relevant to the task type
* The key is the plain English name describing the enum
* The value is an integer representing the enum id
* e.g. "tierSprites": 3213 (tier id maps to a sprite id)
*/
private HashMap<String, Integer> intEnumMap;
/**
* A dictionary of string enums relevant to the task type
* The key is the plain English name describing the enum
* The value is an integer representing the enum id
* e.g. "tierNames": 4757 (tier id maps to a sprite id)
*/
private HashMap<String, Integer> stringEnumMap;
/**
* A dictionary of tier sprite ids
* The key is a string representation of the tier id integer
* The value is an integer representing the sprite id
*/
private HashMap<String, Integer> tierSpriteIdMap = new HashMap<>();
/**
* Varps used to store task progress
* Used for exports from the plugin
*/
private ArrayList<Integer> taskVarps;
/**
* Other varps used for the task type
* Used for exports from the plugin
* Examples in the past: League Points, Sage Renown
*/
private int[] otherVarps;
/**
* Varbits used for the task type
* Used for exports from the plugin
* Examples in the past: Relics chosen, Tasks completed, unlocks, Fragment xp
*/
private int[] varbits;
/**
* The script id used to parse the completion of a task
* This is a rs2asm script
* Example: Combat achievements = script 4834
*/
private int taskCompletedScriptId;
}

View File

@@ -0,0 +1,13 @@
package net.reldo.taskstracker.data.reldo;
import java.util.HashMap;
import lombok.Data;
@Data
public class ReldoImport
{
public String taskTypeName;
public int version;
public String rsn;
private HashMap<Integer, ReldoTaskSave> tasks;
}

View File

@@ -0,0 +1,14 @@
package net.reldo.taskstracker.data.reldo;
import lombok.Data;
@Data
public class ReldoTaskSave
{
long completed;
long todo;
long ignored;
int order;
String notes;
long lastUpdated;
}

View File

@@ -0,0 +1,19 @@
package net.reldo.taskstracker.data.task;
import com.google.gson.annotations.Expose;
public class ConfigTaskSave
{
@Expose public final long completed;
@Expose public final long tracked;
@Expose public final Integer structId;
@Expose public final long ignored;
public ConfigTaskSave(TaskFromStruct task)
{
completed = task.getCompletedOn();
tracked = task.getTrackedOn();
ignored = task.getIgnoredOn();
structId = task.getStructId();
}
}

View File

@@ -0,0 +1,208 @@
package net.reldo.taskstracker.data.task;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition;
import net.reldo.taskstracker.data.reldo.ReldoTaskSave;
import net.runelite.api.Client;
import net.runelite.api.StructComposition;
@Slf4j
public class TaskFromStruct
{
@Getter
private final Integer structId;
@Getter
private final Integer sortId;
@Getter
private TaskType taskType;
@Getter
private final TaskDefinition taskDefinition;
@Getter
private boolean structLoaded;
@Getter @Setter
private long completedOn;
@Getter @Setter
private long trackedOn;
@Getter @Setter
private long ignoredOn;
private StructComposition _struct;
private final Map<String, String> _stringParams = new HashMap<>();
private final Map<String, Integer> _intParams = new HashMap<>();
public TaskFromStruct(TaskType taskType, TaskDefinition taskDefinition)
{
this.taskType = taskType;
this.taskDefinition = taskDefinition;
this.structId = taskDefinition.getStructId();
this.sortId = taskDefinition.getSortId();
}
public String getStringParam(String paramName)
{
return this._stringParams.get(paramName);
}
public Integer getIntParam(String paramName)
{
return this._intParams.get(paramName);
}
// TODO: Remove client from params
public boolean loadStructData(Client client)
{
assert client.isClientThread();
if (structLoaded) {
return true;
}
try
{
// log.debug("LOADING STRUCT DATA " + structId);
_struct = client.getStructComposition(structId);
taskType.getIntParamMap().forEach((paramName, paramId) -> {
int value = _struct.getIntValue(paramId);
// log.debug("{} {}", paramName, value);
_intParams.put(paramName, value);
});
taskType.getStringParamMap().forEach((paramName, paramId) -> {
String value = _struct.getStringValue(paramId);
// log.debug("{} {}", paramName, value);
_stringParams.put(paramName, value);
});
}
catch (Exception ex)
{
log.error("error loading struct data {}", ex, ex);
return false;
}
structLoaded = true;
return true;
}
public boolean isCompleted()
{
return completedOn > 0;
}
public int getPoints()
{
if (taskType.getTierPoints().size() == 0)
{
return 0;
}
Integer points = taskType.getTierPoints().get(getTier());
if (points == null)
{
return 0;
}
return points;
}
public void setCompleted(boolean completed)
{
long now = Instant.now().toEpochMilli();
if (completed && completedOn > 0 && completedOn <= now)
{
return;
}
completedOn = completed ? now : 0;
}
public boolean isTracked()
{
return trackedOn > 0;
}
public void setTracked(boolean state)
{
long now = Instant.now().toEpochMilli();
if (state && trackedOn > 0 && trackedOn <= now)
{
return;
}
trackedOn = state ? now : 0;
}
public boolean isIgnored()
{
return ignoredOn > 0;
}
public void setIgnored(boolean state)
{
long now = Instant.now().toEpochMilli();
if (state && ignoredOn > 0 && ignoredOn <= now)
{
return;
}
ignoredOn = state ? now : 0;
}
public void loadConfigSave(ConfigTaskSave loadedData)
{
setDates(loadedData.completed, loadedData.ignored, loadedData.tracked);
}
public void loadReldoSave(ReldoTaskSave loadedData)
{
setMostRecentDates(loadedData.getCompleted(), loadedData.getIgnored(), loadedData.getTodo());
}
private void setDates(long completedOn, long ignoredOn, long trackedOn)
{
// Set all dates regardless of how they compare
this.setCompletedOn(completedOn);
this.setIgnoredOn(ignoredOn);
this.setTrackedOn(trackedOn);
}
private void setMostRecentDates(long completedOn, long ignoredOn, long trackedOn)
{
// Older completions take priority; incomplete (0) also takes priority
if (completedOn < this.getCompletedOn())
{
this.setCompletedOn(completedOn);
}
// Newer ignores take priority
if (ignoredOn > this.getIgnoredOn())
{
this.setIgnoredOn(ignoredOn);
}
// Newer tracks take priority
if (trackedOn > this.getTrackedOn())
{
this.setTrackedOn(trackedOn);
}
}
public String getName()
{
return getStringParam("name");
}
public int getTier()
{
return getIntParam("tier");
}
public String getDescription()
{
return getStringParam("description");
}
public ConfigTaskSave getSaveData()
{
return new ConfigTaskSave(this);
}
public Float getCompletionPercent() {
return getTaskDefinition().getCompletionPercent();
}
}

View File

@@ -0,0 +1,306 @@
package net.reldo.taskstracker.data.task;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.jsondatastore.ManifestClient;
import net.reldo.taskstracker.data.jsondatastore.TaskDataClient;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.jsondatastore.types.FilterValueType;
import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinition;
import net.reldo.taskstracker.data.task.filters.FilterService;
import net.runelite.api.Client;
import net.runelite.api.EnumComposition;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.config.ConfigManager;
@Singleton
@Slf4j
public class TaskService
{
@Inject private ManifestClient manifestClient;
@Inject private TaskDataClient taskDataClient;
@Inject private ClientThread clientThread;
@Inject private Client client;
@Inject private FilterService filterService;
@Inject private ConfigManager configManager;
@Getter
@Setter
private boolean taskTypeChanged = false;
@Getter
private TaskType currentTaskType;
@Getter
private final List<TaskFromStruct> tasks = new ArrayList<>();
@Getter
private final HashMap<String, int[]> sortedIndexes = new HashMap<>();
private HashMap<String, TaskType> _taskTypes = new HashMap<>();
private HashSet<Integer> currentTaskTypeVarps = new HashSet<>();
private final ExecutorService futureExecutor = Executors.newSingleThreadExecutor();
public CompletableFuture<Boolean> setTaskType(String taskTypeJsonName) {
return getTaskTypesByJsonName().thenCompose(taskTypes ->
{
TaskType newTaskType = taskTypes.get(taskTypeJsonName);
if (newTaskType == null)
{
log.error("unsupported task type {}, falling back to COMBAT", taskTypeJsonName);
newTaskType = taskTypes.get("COMBAT");
}
return this.setTaskType(newTaskType);
});
}
private CompletableFuture<Boolean> loadAllTasksStructData(Collection<TaskFromStruct> tasks) {
Collection<CompletableFuture<Boolean>> taskFutures = new ArrayList<>();
for (TaskFromStruct task : tasks) {
CompletableFuture<Boolean> taskFuture = new CompletableFuture<>();
clientThread.invoke(() -> {
boolean isTaskLoaded = task.loadStructData(client);
taskFuture.complete(isTaskLoaded);
});
taskFutures.add(taskFuture);
}
return CompletableFuture.allOf(taskFutures.toArray(new CompletableFuture[0])).thenApply(v -> {
for (CompletableFuture<Boolean> future : taskFutures) {
if (!future.join()) {
return false;
}
}
return true;
});
}
public CompletableFuture<Boolean> setTaskType(TaskType newTaskType) {
log.debug("setTaskType {}", newTaskType.getTaskJsonName());
if (newTaskType.equals(currentTaskType)) {
log.debug("Skipping setTaskType, same task type selected");
return CompletableFuture.completedFuture(false);
}
currentTaskType = newTaskType;
configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskTypeJsonName", newTaskType.getTaskJsonName());
// Complete creation of any GLOBAL value type filterConfigs
for (FilterConfig filterConfig : currentTaskType.getFilters()) {
if (filterConfig.getValueType().equals(FilterValueType.GLOBAL)) {
// Set valueType to the one required by the global filter
FilterConfig globalFilterConfig = filterService.getGlobalFilterByKey(filterConfig.getConfigKey());
filterConfig.setValueType(globalFilterConfig.getValueType());
// Set any filterConfig fields not already specified
Optional.ofNullable(filterConfig.getLabel()).ifPresentOrElse(val -> {}, () -> filterConfig.setLabel(globalFilterConfig.getLabel()));
Optional.ofNullable(filterConfig.getFilterType()).ifPresentOrElse(val -> {}, () -> filterConfig.setFilterType(globalFilterConfig.getFilterType()));
Optional.ofNullable(filterConfig.getValueName()).ifPresentOrElse(val -> {}, () -> filterConfig.setValueName(globalFilterConfig.getValueName()));
Optional.ofNullable(filterConfig.getOptionLabelEnum()).ifPresentOrElse(val -> {}, () -> filterConfig.setOptionLabelEnum(globalFilterConfig.getOptionLabelEnum()));
Optional.ofNullable(filterConfig.getCustomItems()).ifPresentOrElse(val -> {}, () -> filterConfig.setCustomItems(globalFilterConfig.getCustomItems()));
}
}
List<TaskFromStruct> newTasks = new ArrayList<>();
return newTaskType.loadTaskTypeDataAsync().thenCompose((isTaskTypeLoaded) -> {
if (!isTaskTypeLoaded) {
log.error("Error loading task type during setTaskType");
return CompletableFuture.completedFuture(false);
}
CompletableFuture<Boolean> future = new CompletableFuture<>();
futureExecutor.submit(() -> {
try {
Collection<TaskDefinition> taskDefinitions = taskDataClient.getTaskDefinitions(currentTaskType.getTaskJsonName());
for (TaskDefinition definition : taskDefinitions) {
TaskFromStruct task = new TaskFromStruct(currentTaskType, definition);
newTasks.add(task);
}
loadAllTasksStructData(newTasks).thenApply(future::complete);
} catch (Exception e3) {
future.completeExceptionally(e3);
}
});
return future;
}).thenCompose(areTasksLoaded -> {
if (!areTasksLoaded) {
return CompletableFuture.completedFuture(false);
}
tasks.clear();
tasks.addAll(newTasks);
// Index task list for each property
sortedIndexes.clear();
currentTaskType.getIntParamMap().keySet().forEach(paramName -> {
sortedIndexes.put(paramName, null);
addSortedIndex(paramName, Comparator.comparingInt((TaskFromStruct task) -> task.getIntParam(paramName)));
});
currentTaskType.getStringParamMap().keySet().forEach(paramName -> {
sortedIndexes.put(paramName, null);
addSortedIndex(paramName, Comparator.comparing((TaskFromStruct task) -> task.getStringParam(paramName)));
});
// todo: make this less of a special case.
if (tasks.stream().anyMatch(task -> task.getCompletionPercent() != null)) {
sortedIndexes.put("completion %", null);
addSortedIndex("completion %",
(TaskFromStruct task1, TaskFromStruct task2) ->
{
Float comp1 = task1.getTaskDefinition().getCompletionPercent() != null ? task1.getTaskDefinition().getCompletionPercent() : 0;
Float comp2 = task2.getTaskDefinition().getCompletionPercent() != null ? task2.getTaskDefinition().getCompletionPercent() : 0;
return comp1.compareTo(comp2);
});
}
currentTaskTypeVarps.clear();
currentTaskTypeVarps = new HashSet<>(currentTaskType.getTaskVarps());
taskTypeChanged = true;
return CompletableFuture.completedFuture(true);
});
}
private void addSortedIndex(String paramName, Comparator<TaskFromStruct> comparator)
{
List<TaskFromStruct> sortedTasks = tasks.stream()
.sorted(comparator)
.collect(Collectors.toCollection(ArrayList::new));
int[] sortedIndex = new int[tasks.size()];
for(int i = 0; i < sortedTasks.size(); i++)
{
sortedIndex[i] = tasks.indexOf(sortedTasks.get(i));
}
sortedIndexes.put(paramName, sortedIndex);
}
public int getSortedTaskIndex(String sortCriteria, int position)
{
if(sortedIndexes.containsKey(sortCriteria))
{
return sortedIndexes.get(sortCriteria)[position];
}
else
{
return position;
}
}
public boolean isVarpInCurrentTaskType(int varpId)
{
return currentTaskTypeVarps.contains(varpId);
}
public void clearTaskTypes()
{
this._taskTypes.clear();
}
/**
* Get a map of task type json names to task type
*
* @return Hashmap of TaskType indexed by task type json name
*/
public CompletableFuture<HashMap<String, TaskType>> getTaskTypesByJsonName()
{
if (_taskTypes.size() > 0)
{
return CompletableFuture.completedFuture(_taskTypes);
}
try
{
CompletableFuture<HashMap<String, TaskType>> future = new CompletableFuture<>();
futureExecutor.submit(() ->
{
try
{
_taskTypes = taskDataClient.getTaskTypes();
future.complete(_taskTypes);
}
catch (Exception e) {
future.completeExceptionally(e);
}
});
return future;
}
catch (Exception ex)
{
log.error("Unable to populate task types from data client", ex);
return CompletableFuture.completedFuture(new HashMap<>());
}
}
public CompletableFuture<HashMap<Integer, String>> getStringEnumValuesAsync(String enumName)
{
Integer enumId = currentTaskType.getStringEnumMap().get(enumName);
if (enumId == null)
{
return CompletableFuture.completedFuture(new HashMap<>());
}
CompletableFuture<HashMap<Integer, String>> future = new CompletableFuture<>();
clientThread.invoke(() -> {
try
{
EnumComposition enumComposition = client.getEnum(enumId);
int[] keys = enumComposition.getKeys();
HashMap<Integer, String> map = new HashMap<>();
for (int key : keys)
{
map.put(key, enumComposition.getStringValue(key));
}
future.complete(map);
}
catch (Exception ex)
{
log.error("Error getting string enum values", ex);
future.completeExceptionally(ex);
}
});
return future;
}
public void applySave(TaskType saveTaskType, HashMap<Integer, ConfigTaskSave> saveData)
{
String currentTaskTypeName = currentTaskType.getTaskJsonName();
String saveTaskTypeName = saveTaskType.getTaskJsonName();
if (!currentTaskTypeName.equals(saveTaskTypeName))
{
log.warn("Cannot apply save, task types do not match current={} save={}", currentTaskTypeName, saveTaskTypeName);
return;
}
for (TaskFromStruct task : getTasks())
{
ConfigTaskSave configTaskSave = saveData.get(task.getStructId());
if (configTaskSave == null)
{
continue;
}
task.loadConfigSave(configTaskSave);
}
}
public List<TaskFromStruct> getTasksFromVarpId(Integer varpId)
{
int varpIndex = getCurrentTaskType().getTaskVarps().indexOf(varpId);
int minTaskId = varpIndex * 32;
int maxTaskId = minTaskId + 32;
return getTasks().stream().filter(t -> {
int taskId = t.getIntParam("id");
return taskId >= minTaskId && taskId <= maxTaskId;
}).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,147 @@
package net.reldo.taskstracker.data.task;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import lombok.Getter;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.jsondatastore.types.FilterType;
import net.reldo.taskstracker.data.jsondatastore.types.TaskTypeDefinition;
import net.runelite.api.Client;
import net.runelite.api.EnumComposition;
import net.runelite.client.callback.ClientThread;
import net.runelite.client.game.SpriteManager;
public class TaskType
{
@Getter
private final HashMap<Integer, BufferedImage> spritesById = new HashMap<>();
@Getter
private final HashMap<Integer, BufferedImage> tierSprites = new HashMap<>();
@Getter
private final HashMap<Integer, Integer> tierPoints = new HashMap<>();
private final Client client;
private final ClientThread clientThread;
private final SpriteManager spriteManager;
private final TaskTypeDefinition _taskTypeDefinition;
public TaskType(Client client, ClientThread clientThread, SpriteManager spriteManager, TaskTypeDefinition taskTypeDefinition)
{
this.client = client;
this.clientThread = clientThread;
this.spriteManager = spriteManager;
this._taskTypeDefinition = taskTypeDefinition;
}
public CompletableFuture<Boolean> loadTaskTypeDataAsync()
{
CompletableFuture<Boolean> future = new CompletableFuture<>();
clientThread.invoke(() -> {
try {
getButtonFiltersSpriteIds().forEach((spriteId) -> {
BufferedImage spriteImage = spriteManager.getSprite(spriteId, 0);
spritesById.put(spriteId, spriteImage);
});
_taskTypeDefinition.getTierSpriteIdMap().forEach((idKey, spriteId) -> {
Integer tierId = Integer.parseInt(idKey);
BufferedImage spriteImage = spriteManager.getSprite(spriteId, 0);
tierSprites.put(tierId, spriteImage);
});
if (_taskTypeDefinition.getIntEnumMap().containsKey("tierPoints"))
{
int enumId = _taskTypeDefinition.getIntEnumMap().get("tierPoints");
EnumComposition enumComposition = client.getEnum(enumId);
int[] keys = enumComposition.getKeys();
for (int key : keys)
{
tierPoints.put(key, enumComposition.getIntValue(key));
}
}
future.complete(true);
} catch (Exception e) {
future.completeExceptionally(e);
}
});
return future;
}
public String getFilterConfigPrefix()
{
return _taskTypeDefinition.getTaskJsonName() + ".";
}
private HashSet<Integer> getButtonFiltersSpriteIds()
{
HashSet<Integer> sprites = new HashSet<>();
_taskTypeDefinition.getFilters().stream().filter(
(filterConfig) -> filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER)
).forEach((filterConfig) -> {
if (filterConfig.getCustomItems() != null)
{
filterConfig.getCustomItems().forEach((customSprite) -> {
Integer spriteId = customSprite.getSpriteId();
if (spriteId == null)
{
return;
}
sprites.add(spriteId);
});
}
});
return sprites;
}
public ArrayList<Integer> getTaskVarps()
{
return _taskTypeDefinition.getTaskVarps();
}
public String getTaskJsonName()
{
return _taskTypeDefinition.getTaskJsonName();
}
public HashMap<String, Integer> getIntParamMap()
{
return _taskTypeDefinition.getIntParamMap();
}
public HashMap<String, Integer> getStringParamMap()
{
return _taskTypeDefinition.getStringParamMap();
}
public HashMap<String, Integer> getStringEnumMap()
{
return _taskTypeDefinition.getStringEnumMap();
}
public String getName()
{
return _taskTypeDefinition.getName();
}
public ArrayList<FilterConfig> getFilters()
{
return _taskTypeDefinition.getFilters();
}
public int[] getOtherVarps()
{
return _taskTypeDefinition.getOtherVarps();
}
public int[] getVarbits()
{
return _taskTypeDefinition.getVarbits();
}
public int getTaskCompletedScriptId()
{
return _taskTypeDefinition.getTaskCompletedScriptId();
}
}

View File

@@ -0,0 +1,8 @@
package net.reldo.taskstracker.data.task.filters;
import net.reldo.taskstracker.data.task.TaskFromStruct;
public abstract class Filter
{
public abstract boolean meetsCriteria(TaskFromStruct task);
}

View File

@@ -0,0 +1,47 @@
package net.reldo.taskstracker.data.task.filters;
import java.util.HashMap;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.data.jsondatastore.FilterDataClient;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
@Singleton
@Slf4j
public class FilterService
{
@Inject
private FilterDataClient filterDataClient;
// Filter config cache
private HashMap<String, FilterConfig> _filterConfigs = new HashMap<>();
public FilterConfig getGlobalFilterByKey(String filterKey)
{
// Instantiate filterConfigs if not already
if (_filterConfigs == null || _filterConfigs.isEmpty())
{
try
{
_filterConfigs = filterDataClient.getFilterConfigs();
return _filterConfigs.get(filterKey);
}
catch (Exception ex)
{
log.error("Unable to get filter configs", ex);
}
}
else
{
return _filterConfigs.get(filterKey);
}
return null;
}
public void clearFilterConfigs()
{
this._filterConfigs.clear();
}
}

View File

@@ -0,0 +1,34 @@
package net.reldo.taskstracker.data.task.filters;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.runelite.client.config.ConfigManager;
@Slf4j
public class ParamButtonFilter extends Filter
{
private final String paramName;
private final String filterConfigKey;
private final ConfigManager configManager;
public ParamButtonFilter(ConfigManager configManager, String paramName, String filterConfigKey)
{
this.configManager = configManager;
this.paramName = paramName;
this.filterConfigKey = filterConfigKey;
}
@Override
public boolean meetsCriteria(TaskFromStruct task)
{
String configValue = configManager.getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filterConfigKey);
boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1");
if (isEmptyFilterSelection)
{
return false;
}
return configValue.contains("f-" + task.getIntParam(paramName) + "-f");
}
}

View File

@@ -0,0 +1,46 @@
package net.reldo.taskstracker.data.task.filters;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.runelite.client.config.ConfigManager;
@Slf4j
public class ParamDropdownFilter extends Filter
{
private final String paramName;
private final String filterConfigKey;
private final ConfigManager configManager;
public ParamDropdownFilter(ConfigManager configManager, String paramName, String filterConfigKey)
{
this.configManager = configManager;
this.paramName = paramName;
this.filterConfigKey = filterConfigKey;
}
@Override
public boolean meetsCriteria(TaskFromStruct task)
{
String configValue = configManager.getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filterConfigKey);
boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1");
if (isEmptyFilterSelection)
{
return true;
}
if (task.getIntParam(paramName) != null)
{
try
{
Integer parsedConfigValue = Integer.parseInt(configValue);
return parsedConfigValue.equals(task.getIntParam(paramName));
}
catch (Exception ex)
{
log.warn("meetsCriteria error parsing config value for {}", configValue);
return true;
}
}
return true;
}
}

View File

@@ -0,0 +1,10 @@
package net.reldo.taskstracker.panel;
import java.awt.Color;
public class Colors {
public static Color QUALIFIED_TEXT_COLOR = new Color(34, 177, 77);
public static Color UNQUALIFIED_BACKGROUND_COLOR = new Color(70, 30, 0);
public static Color UNQUALIFIED_TEXT_COLOR = new Color(251, 93, 93);
public static Color COMPLETED_BACKGROUND_COLOR = new Color(0, 50, 0);
}

View File

@@ -0,0 +1,41 @@
package net.reldo.taskstracker.panel;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.runelite.client.util.ImageUtil;
public class Icons
{
private static final String completeBtnPath = "panel/components/complete_button/";
public static final Icon INCOMPLETE_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "incomplete_only_icon.png"));
public static final Icon COMPLETE_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "complete_only_icon.png"));
public static final Icon COMPLETE_INCOMPLETE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, completeBtnPath + "complete_and_incomplete_icon.png"));
private static final String ignoredBtnPath = "panel/components/ignored_button/";
public static final BufferedImage semivisibleimg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "semivisible_icon.png");
public static final Icon SEMIVISIBLE_ICON = new ImageIcon(ImageUtil.alphaOffset(semivisibleimg, -180));
public static final Icon INVISIBLE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "invisible_icon.png"));
public static final Icon VISIBLE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, ignoredBtnPath + "visible_icon.png"));
private static final String trackedBtnPath = "panel/components/tracked_button/";
public static final Icon UNTRACKED_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "untracked_icon.png"));
public static final Icon TRACKED_ONLY_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "tracked_icon.png"));
public static final Icon TRACKED_UNTRACKED_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, trackedBtnPath + "tracked_and_untracked_icon.png"));
private static final String sortBtnPath = "panel/components/sort_button/";
public static final Icon ASCENDING_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, sortBtnPath + "ascending_icon.png"));
public static final Icon DESCENDING_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, sortBtnPath + "descending_icon.png"));
private static final String expandBtnPath = "panel/components/";
public static final Icon MENU_EXPANDED_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_menu_expanded.png"));
public static final BufferedImage collapseImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_menu_collapsed.png");
public static final Icon MENU_ICON_HOVER = new ImageIcon(collapseImg);
public static final Icon MENU_COLLAPSED_ICON = new ImageIcon(ImageUtil.alphaOffset(collapseImg, -180));
public static final ImageIcon PLUS_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "plus.png"));
public static final ImageIcon MINUS_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "minus.png"));
public static final ImageIcon EYE_ICON = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "eye.png"));
public static final ImageIcon EYE_CROSS_GREY = new ImageIcon(ImageUtil.loadImageResource(TasksTrackerPlugin.class, "eye-cross-grey.png"));
}

View File

@@ -0,0 +1,543 @@
package net.reldo.taskstracker.panel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.plaf.basic.BasicButtonUI;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerConfig;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.config.ConfigValues;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.jsondatastore.types.FilterType;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.data.task.TaskType;
import net.reldo.taskstracker.panel.components.SearchBox;
import net.reldo.taskstracker.panel.components.TriToggleButton;
import net.reldo.taskstracker.panel.filters.ComboItem;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.util.SwingUtil;
@Slf4j
public class LoggedInPanel extends JPanel
{
public TaskListPanel taskListPanel;
private JComboBox<ComboItem<TaskType>> taskTypeDropdown;
private final TaskService taskService;
private final TasksTrackerPlugin plugin;
private final TasksTrackerConfig config;
// Filter buttons
private final TriToggleButton completedFilterBtn = new TriToggleButton();
private final TriToggleButton trackedFilterBtn = new TriToggleButton();
private final TriToggleButton ignoredFilterBtn = new TriToggleButton();
private final JPanel titlePanel = new JPanel();
// Task list tabs
private final JPanel tabPane = new JPanel();
// sub-filter panel
private SubFilterPanel subFilterPanel;
private SortPanel sortPanel;
private final JToggleButton collapseBtn = new JToggleButton();
public LoggedInPanel(TasksTrackerPlugin plugin, TasksTrackerConfig config, TaskService taskService)
{
super(false);
this.plugin = plugin;
this.taskService = taskService;
this.config = config;
createPanel();
}
@Override
public Dimension getPreferredSize()
{
return getParent().getSize();
}
public void redraw()
{
// taskTypeDropdown may become de-synced after profile change
String selectedTaskTypeJsonName = taskTypeDropdown.getItemAt(taskTypeDropdown.getSelectedIndex()).getValue().getTaskJsonName();
if(!selectedTaskTypeJsonName.equals(config.taskTypeJsonName()))
{
log.debug("Task type dropdown de-synced, attempting to find current task type");
for(int i = 0; i < taskTypeDropdown.getItemCount(); i++)
{
ComboItem<TaskType> item = taskTypeDropdown.getItemAt(i);
if(item.getValue().getTaskJsonName().equals(config.taskTypeJsonName()))
{
log.debug("Current task type found, setting selected task type");
taskTypeDropdown.setSelectedIndex(i);
break;
}
}
}
subFilterPanel.redraw();
sortPanel.redraw();
updateCollapseButtonText();
taskListPanel.redraw();
}
public void refreshAllTasks()
{
updateCollapseButtonText();
taskListPanel.refreshAllTasks();
}
private void createPanel()
{
setLayout(new BorderLayout());
setBackground(ColorScheme.DARK_GRAY_COLOR);
taskListPanel = new TaskListPanel(plugin, taskService);
add(getNorthPanel(), BorderLayout.NORTH);
add(getCenterPanel(), BorderLayout.CENTER);
add(getSouthPanel(), BorderLayout.SOUTH);
loadAndApplyFilters(config.taskListTab());
if(config.taskListTab().equals(ConfigValues.TaskListTabs.TRACKED))
{
trackedFilterBtn.setState(1);
trackedFilterBtn.setEnabled(false);
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "trackedFilter", ConfigValues.TrackedFilterValues.TRACKED);
}
}
private JPanel getCenterPanel() {
// wrapper for the task list and tab buttons
final JPanel taskListPanel = new JPanel(new BorderLayout());
taskListPanel.setBackground(ColorScheme.DARK_GRAY_COLOR);
taskListPanel.setBorder(new MatteBorder(0, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR));
taskListPanel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
tabPane.setLayout(new BoxLayout(tabPane, BoxLayout.X_AXIS));
tabPane.setBorder(new EmptyBorder(0,0,0,0));
tabPane.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH,24));
JToggleButton trackedTab = tabButton("Tracked Tasks", ConfigValues.TaskListTabs.TRACKED);
JToggleButton allTab = tabButton("All Tasks", ConfigValues.TaskListTabs.ALL);
JToggleButton customTab = tabButton("Custom", ConfigValues.TaskListTabs.CUSTOM);
ButtonGroup tabGroup = new ButtonGroup();
tabGroup.add(trackedTab);
tabGroup.add(allTab);
tabGroup.add(customTab);
tabPane.add(Box.createHorizontalGlue());
tabPane.add(trackedTab);
tabPane.add(Box.createHorizontalGlue());
tabPane.add(allTab);
tabPane.add(Box.createHorizontalGlue());
tabPane.add(customTab);
tabPane.add(Box.createHorizontalGlue());
taskListPanel.add(tabPane, BorderLayout.NORTH);
taskListPanel.add(this.taskListPanel, BorderLayout.CENTER);
// set initial filter states to "complete and incomplete", "tracked and untracked", "not ignored"
Map<String, Integer> filterStates = new HashMap<>();
filterStates.put("completed",0);
filterStates.put("tracked",0);
filterStates.put("ignored",0);
for(ConfigValues.TaskListTabs tab : ConfigValues.TaskListTabs.values())
{
filterStore.put(tab, filterStates);
}
switch (config.taskListTab())
{
case TRACKED:
trackedTab.setSelected(true);
break;
case ALL:
allTab.setSelected(true);
break;
case CUSTOM:
customTab.setSelected(true);
break;
}
tabChanged(config.taskListTab());
return taskListPanel;
}
public void tabChanged(ConfigValues.TaskListTabs newTab)
{
if(newTab != null) {
changeTab(newTab);
switch (newTab) {
case TRACKED:
trackedFilterBtn.setState(1);
trackedFilterBtn.setEnabled(false);
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab);
filterButtonAction("tracked");
break;
case ALL:
trackedFilterBtn.setState(0);
trackedFilterBtn.setEnabled(false);
completedFilterBtn.setState(0);
completedFilterBtn.setEnabled(false);
ignoredFilterBtn.setState(1);
ignoredFilterBtn.setEnabled(false);
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab);
actionAllFilterButtons();
break;
case CUSTOM:
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "taskListTab", newTab);
plugin.refreshAllTasks();
break;
default:
plugin.refreshAllTasks();
break;
}
}
}
private JToggleButton tabButton(String label, ConfigValues.TaskListTabs tab)
{
JToggleButton button = new JToggleButton(label);
button.setBorder(new EmptyBorder(2,5,2,5));
button.setBackground(ColorScheme.DARK_GRAY_COLOR);
button.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
button.addActionListener(e -> tabChanged(tab));
return button;
}
private void changeTab(ConfigValues.TaskListTabs newTab)
{
saveFilters();
resetFilters();
loadAndApplyFilters(newTab);
}
private final Map<ConfigValues.TaskListTabs, Map<String, Integer>> filterStore = new HashMap<>();
private void saveFilters()
{
ConfigValues.TaskListTabs tab = config.taskListTab();
Map<String, Integer> filterStates = new HashMap<>();
filterStates.put("completed", config.completedFilter().ordinal());
filterStates.put("tracked", config.trackedFilter().ordinal());
filterStates.put("ignored", config.ignoredFilter().ordinal());
filterStore.put(tab, filterStates);
}
private void resetFilters()
{
completedFilterBtn.setEnabled(true);
trackedFilterBtn.setEnabled(true);
ignoredFilterBtn.setEnabled(true);
}
private void loadAndApplyFilters(ConfigValues.TaskListTabs tab)
{
Map<String,Integer> filterStates = filterStore.get(tab);
if(filterStates == null) return;
Enum configValue;
completedFilterBtn.setState(filterStates.get("completed"));
trackedFilterBtn.setState(filterStates.get("tracked"));
ignoredFilterBtn.setState(filterStates.get("ignored"));
configValue = ConfigValues.CompletedFilterValues.values()[completedFilterBtn.getState()];
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "completedFilter", configValue);
configValue = ConfigValues.TrackedFilterValues.values()[trackedFilterBtn.getState()];
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "trackedFilter", configValue);
configValue = ConfigValues.IgnoredFilterValues.values()[ignoredFilterBtn.getState()];
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "ignoredFilter", configValue);
}
private JPanel getSouthPanel()
{
JPanel southPanel = new JPanel(new BorderLayout());
southPanel.setBorder(new EmptyBorder(5,0,2,0));
JButton importButton = new JButton("Import");
importButton.setBorder(new EmptyBorder(5, 5, 5, 5));
importButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET));
importButton.addActionListener(e -> plugin.openImportJsonDialog());
southPanel.add(importButton, BorderLayout.WEST);
JButton totalsButton = new JButton("Totals");
totalsButton.setBorder(new EmptyBorder(5, 5, 5, 5));
totalsButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET));
totalsButton.addActionListener(e -> plugin.sendTotalsToChat());
southPanel.add(totalsButton, BorderLayout.CENTER);
JButton exportButton = new JButton("Export");
exportButton.setBorder(new EmptyBorder(5, 5, 5, 5));
exportButton.setLayout(new BorderLayout(0, PluginPanel.BORDER_OFFSET));
exportButton.addActionListener(e -> plugin.copyJsonToClipboard());
southPanel.add(exportButton, BorderLayout.EAST);
return southPanel;
}
private JPanel getNorthPanel()
{
JPanel northPanel = new JPanel();
BoxLayout layout = new BoxLayout(northPanel, BoxLayout.Y_AXIS);
northPanel.setLayout(layout);
northPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
taskTypeDropdown = new JComboBox<>();
taskTypeDropdown.setAlignmentX(LEFT_ALIGNMENT);
taskTypeDropdown.setFocusable(false);
initTaskTypeDropdownAsync();
// Wrapper for collapsible sub-filter menu
JPanel subFilterWrapper = new JPanel();
subFilterWrapper.setLayout(new BorderLayout());
subFilterWrapper.setBorder(new MatteBorder(1, 0, 1, 0, ColorScheme.MEDIUM_GRAY_COLOR));
subFilterWrapper.setAlignmentX(LEFT_ALIGNMENT);
subFilterWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
// collapse button
SwingUtil.removeButtonDecorations(collapseBtn);
collapseBtn.setIcon(Icons.MENU_COLLAPSED_ICON);
collapseBtn.setSelectedIcon(Icons.MENU_EXPANDED_ICON);
collapseBtn.setRolloverIcon(Icons.MENU_ICON_HOVER);
SwingUtil.addModalTooltip(collapseBtn, "Collapse filters", "Expand filters");
collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR);
collapseBtn.setAlignmentX(LEFT_ALIGNMENT);
collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout
collapseBtn.addActionListener(ev -> subFilterPanel.setVisible(!subFilterPanel.isVisible()));
collapseBtn.setHorizontalTextPosition(JButton.CENTER);
collapseBtn.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
collapseBtn.setFont(FontManager.getRunescapeSmallFont());
collapseBtn.setBorder(new EmptyBorder(2, 0, 2, 0));
collapseBtn.setFocusable(false);
// panel to hold sub-filters
subFilterPanel = new SubFilterPanel(plugin, taskService);
subFilterWrapper.add(collapseBtn, BorderLayout.NORTH);
subFilterWrapper.add(subFilterPanel, BorderLayout.CENTER);
sortPanel = new SortPanel(plugin, taskService, taskListPanel);
northPanel.add(getTitleAndButtonPanel());
northPanel.add(Box.createVerticalStrut(10));
northPanel.add(taskTypeDropdown);
northPanel.add(Box.createVerticalStrut(2));
northPanel.add(getSearchPanel());
northPanel.add(Box.createVerticalStrut(2));
northPanel.add(sortPanel);
northPanel.add(Box.createVerticalStrut(2));
northPanel.add(subFilterWrapper);
return northPanel;
}
private JPanel getTitleAndButtonPanel()
{
titlePanel.setLayout(new BorderLayout());
titlePanel.setBackground(ColorScheme.DARK_GRAY_COLOR);
titlePanel.setPreferredSize(new Dimension(0, 30));
titlePanel.setBorder(new EmptyBorder(5, 5, 5, 10));
JLabel title = new JLabel("Tasks Tracker");
title.setHorizontalAlignment(SwingConstants.LEFT);
title.setForeground(Color.WHITE);
JPopupMenu reloadPluginPopup = new JPopupMenu();
reloadPluginPopup.setBorder(new EmptyBorder(5, 5, 5, 5));
JMenuItem reloadPluginMenuItem = new JMenuItem("Reload plugin");
reloadPluginMenuItem.addActionListener(e -> plugin.reloadTaskType());
reloadPluginPopup.add(reloadPluginMenuItem);
title.setComponentPopupMenu(reloadPluginPopup);
// Filter button bar
final JPanel viewControls = new JPanel();
viewControls.setLayout(new BoxLayout(viewControls, BoxLayout.X_AXIS));
viewControls.setBackground(ColorScheme.DARK_GRAY_COLOR);
// Completed tasks filter button
SwingUtil.removeButtonDecorations(completedFilterBtn);
completedFilterBtn.setIcons(Icons.COMPLETE_INCOMPLETE_ICON, Icons.COMPLETE_ONLY_ICON, Icons.INCOMPLETE_ONLY_ICON);
completedFilterBtn.setToolTips("All tasks", "Completed tasks only", "Incomplete tasks only");
completedFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR);
completedFilterBtn.setStateChangedAction(e -> filterButtonAction("completed"));
completedFilterBtn.popupMenuEnabled(true);
completedFilterBtn.setState(config.completedFilter().ordinal());
viewControls.add(completedFilterBtn);
// Tracked tasks filter button
SwingUtil.removeButtonDecorations(trackedFilterBtn);
trackedFilterBtn.setIcons(Icons.TRACKED_UNTRACKED_ICON, Icons.TRACKED_ONLY_ICON, Icons.UNTRACKED_ONLY_ICON);
trackedFilterBtn.setToolTips("All tasks", "Tracked tasks only", "Untracked tasks only");
trackedFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR);
trackedFilterBtn.setStateChangedAction(e -> filterButtonAction("tracked"));
trackedFilterBtn.popupMenuEnabled(true);
trackedFilterBtn.setState(config.trackedFilter().ordinal());
viewControls.add(trackedFilterBtn);
// Ignored tasks filter button
SwingUtil.removeButtonDecorations(ignoredFilterBtn);
ignoredFilterBtn.setIcons(Icons.SEMIVISIBLE_ICON, Icons.VISIBLE_ICON, Icons.INVISIBLE_ICON);
ignoredFilterBtn.setToolTips("Hide ignored tasks", "All tasks", "Ignored tasks only");
ignoredFilterBtn.setBackground(ColorScheme.DARK_GRAY_COLOR);
ignoredFilterBtn.setStateChangedAction(e -> filterButtonAction("ignored"));
ignoredFilterBtn.popupMenuEnabled(true);
ignoredFilterBtn.setState(config.ignoredFilter().ordinal());
viewControls.add(ignoredFilterBtn);
titlePanel.add(viewControls, BorderLayout.EAST);
titlePanel.add(title, BorderLayout.WEST);
titlePanel.setAlignmentX(LEFT_ALIGNMENT);
return titlePanel;
}
private void filterButtonActionNoRefresh(String filter)
{
int state;
Enum configValue;
switch (filter)
{
case "completed":
state = completedFilterBtn.getState();
configValue = ConfigValues.CompletedFilterValues.values()[state];
break;
case "tracked":
state = trackedFilterBtn.getState();
configValue = ConfigValues.TrackedFilterValues.values()[state];
break;
case "ignored":
state = ignoredFilterBtn.getState();
configValue = ConfigValues.IgnoredFilterValues.values()[state];
break;
default:
log.debug("Filter button action failed due to unrecognised filter.");
return;
}
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, filter + "Filter", configValue);
}
private void filterButtonAction(String filter)
{
filterButtonActionNoRefresh(filter);
plugin.refreshAllTasks();
}
private void actionAllFilterButtons()
{
filterButtonActionNoRefresh("tracked");
filterButtonActionNoRefresh("ignored");
filterButtonActionNoRefresh("completed");
plugin.refreshAllTasks();
}
private JPanel getSearchPanel()
{
JPanel filtersPanel = new JPanel();
filtersPanel.setAlignmentX(LEFT_ALIGNMENT);
filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
SearchBox textSearch = new SearchBox();
textSearch.addTextChangedListener(() -> {
plugin.taskTextFilter = textSearch.getText().toLowerCase();
plugin.refreshAllTasks();
});
filtersPanel.add(textSearch);
return filtersPanel;
}
private void updateCollapseButtonText()
{
if (taskService.getCurrentTaskType() == null) return;
ArrayList<FilterConfig> filters = taskService.getCurrentTaskType().getFilters();
int countInclusive = 0;
int countExclusive = 0;
for (FilterConfig filterConfig : filters)
{
String filterText = Optional.ofNullable(plugin.getConfigManager()
.getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME,
taskService.getCurrentTaskType().getFilterConfigPrefix() + filterConfig.getConfigKey()))
.orElse("");
int count = (filterText.isEmpty() || filterText.equals("-1")) ? 0 : filterText.split(",").length;
if (filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER)) countInclusive += count;
if (filterConfig.getFilterType().equals(FilterType.DROPDOWN_FILTER)) countExclusive += count;
}
collapseBtn.setText(countInclusive + " inclusive, " + countExclusive + " exclusive filters");
}
private void initTaskTypeDropdownAsync() {
TaskType currentTaskType = taskService.getCurrentTaskType();
taskService.getTaskTypesByJsonName().thenAccept(taskTypes -> {
ArrayList<ComboItem<TaskType>> taskTypeItems = new ArrayList<>();
taskTypes.forEach((taskTypeJsonName, taskType) -> {
ComboItem<TaskType> item = new ComboItem<>(taskType, taskType.getName());
taskTypeItems.add(item);
taskTypeDropdown.addItem(item);
});
ComboItem<TaskType> currentTaskTypeComboItem = taskTypeItems.stream()
.filter(item -> item.getValue().equals(currentTaskType))
.findFirst().orElseGet(() -> taskTypeItems.get(0));
taskTypeDropdown.setSelectedItem(currentTaskTypeComboItem);
taskTypeDropdown.addActionListener(e -> {
TaskType taskType = taskTypeDropdown.getItemAt(taskTypeDropdown.getSelectedIndex()).getValue();
taskService.setTaskType(taskType).thenAccept(wasTaskTypeChanged ->{
if (wasTaskTypeChanged) {
SwingUtilities.invokeLater(() ->
{
redraw();
plugin.refreshAllTasks();
});
}
});
});
});
}
}

View File

@@ -0,0 +1,12 @@
package net.reldo.taskstracker.panel;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class LoggedOutPanel extends JPanel
{
public LoggedOutPanel()
{
this.add(new JLabel("Log into an account to track tasks."));
}
}

View File

@@ -0,0 +1,85 @@
package net.reldo.taskstracker.panel;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.config.ConfigValues;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.panel.components.FixedWidthPanel;
import net.reldo.taskstracker.panel.components.MultiToggleButton;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.SwingUtil;
@Slf4j
public class SortPanel extends FixedWidthPanel
{
private final TasksTrackerPlugin plugin;
private final TaskService taskService;
private final TaskListPanel taskListPanel;
private final ConfigManager configManager;
private JComboBox<String> sortDropdown;
private MultiToggleButton directionButton;
public SortPanel(TasksTrackerPlugin plugin, TaskService taskService, TaskListPanel taskListPanel)
{
this.plugin = plugin;
this.configManager = plugin.getConfigManager();
this.taskService = taskService;
this.taskListPanel = taskListPanel;
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setAlignmentX(LEFT_ALIGNMENT);
}
public void redraw()
{
removeAll();
List<String> criteriaList = taskService.getSortedIndexes().keySet().stream()
.sorted()
.map((str) -> str.substring(0, 1).toUpperCase() + str.substring(1))
.collect(Collectors.toList());
criteriaList.add(0,"Default");
String[] criteriaArray = criteriaList.toArray(new String[0]);
sortDropdown = new JComboBox<>(criteriaArray);
sortDropdown.setAlignmentX(LEFT_ALIGNMENT);
sortDropdown.setSelectedIndex(0);
sortDropdown.addActionListener(e -> {
updateConfig();
SwingUtilities.invokeLater(taskListPanel::redraw);
});
sortDropdown.setFocusable(false);
directionButton = new MultiToggleButton(2);
SwingUtil.removeButtonDecorations(directionButton);
directionButton.setIcons(new Icon[]{Icons.ASCENDING_ICON, Icons.DESCENDING_ICON});
directionButton.setToolTips(new String[]{"Ascending", "Descending"});
directionButton.setBackground(ColorScheme.DARK_GRAY_COLOR);
directionButton.setStateChangedAction(e -> {
updateConfig();
SwingUtilities.invokeLater(taskListPanel::redraw);
});
add(sortDropdown);
add(directionButton);
updateConfig();
}
protected void updateConfig()
{
log.debug("updateConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortCriteria", sortDropdown.getItemAt(sortDropdown.getSelectedIndex()).toLowerCase());
configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortCriteria", sortDropdown.getItemAt(sortDropdown.getSelectedIndex()).toLowerCase());
ConfigValues.SortDirections configValue = ConfigValues.SortDirections.values()[directionButton.getState()];
log.debug("updateConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortDirection", configValue);
configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, "sortDirection", configValue);
}
}

View File

@@ -0,0 +1,122 @@
package net.reldo.taskstracker.panel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import javax.swing.BoxLayout;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.jsondatastore.types.FilterValueType;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.panel.components.FixedWidthPanel;
import net.reldo.taskstracker.panel.filters.ComboItem;
import net.reldo.taskstracker.panel.filters.DynamicButtonFilterPanel;
import net.reldo.taskstracker.panel.filters.DynamicDropdownFilterPanel;
import net.reldo.taskstracker.panel.filters.FilterPanel;
import net.runelite.client.ui.ColorScheme;
@Slf4j
public class SubFilterPanel extends FixedWidthPanel
{
private final List<FilterPanel> filterPanels = new ArrayList<>();
private TasksTrackerPlugin plugin;
private TaskService taskService;
public SubFilterPanel(TasksTrackerPlugin plugin, TaskService taskService)
{
this.plugin = plugin;
this.taskService = taskService;
log.debug("SubFilterPanel.constructor");
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(new EmptyBorder(0, 0, 0, 0));
setBackground(ColorScheme.DARKER_GRAY_COLOR);
setVisible(false);
}
public void redraw()
{
log.debug("SubFilterPanel.redraw"); // TODO: figure out why this calls multiple times upon switching task type
removeAll();
filterPanels.clear();
filterPanels.addAll(getFilterPanels(taskService.getCurrentTaskType().getFilters()));
filterPanels.forEach((filterPanel) -> {
add(filterPanel);
filterPanel.redraw();
});
}
private List<FilterPanel> getFilterPanels(ArrayList<FilterConfig> filterConfigs)
{
List<FilterPanel> filterPanels = new ArrayList<>();
for (FilterConfig filterConfig : filterConfigs)
{
try
{
FilterPanel filterPanel = createDynamicFilterPanel(filterConfig);
if (filterPanel == null)
{
continue;
}
filterPanels.add(filterPanel);
}
catch (Exception ex)
{
log.error("error creating filter panel {} {}", filterConfig.getConfigKey(), ex);
}
}
return filterPanels;
}
private FilterPanel createDynamicFilterPanel(FilterConfig filterConfig) throws Exception
{
switch (filterConfig.getFilterType())
{
case BUTTON_FILTER:
return new DynamicButtonFilterPanel(plugin, filterConfig, taskService.getCurrentTaskType());
case DROPDOWN_FILTER:
ComboItem[] dropdownItems = getDropdownItems(filterConfig);
return new DynamicDropdownFilterPanel(plugin, filterConfig, taskService.getCurrentTaskType(), dropdownItems);
default:
log.error("invalid filter type " + filterConfig.getFilterType());
return null;
}
}
private ComboItem[] getDropdownItems(FilterConfig filterConfig) throws ExecutionException, InterruptedException
{
if (filterConfig.getValueType() == null)
{
throw new Error("invalid filterConfig for dropdown items");
}
if (filterConfig.getValueType().equals(FilterValueType.PARAM_INTEGER))
{
String enumName = filterConfig.getOptionLabelEnum();
if (!enumName.isEmpty())
{
HashMap<Integer, String> enumEntries = taskService.getStringEnumValuesAsync(enumName).get(); // TODO: blocking call
ArrayList<ComboItem<Integer>> options = new ArrayList<>();
options.add(new ComboItem<>(-1, ""));
for (Map.Entry<Integer, String> entry : enumEntries.entrySet())
{
if (filterConfig.getValueName().equals("tier"))
{
if (entry.getValue().equals("All") || entry.getValue().equals("Tier"))
{
continue;
}
}
options.add(new ComboItem<>(entry.getKey(), entry.getValue()));
}
return options.toArray(new ComboItem[0]);
}
}
return new ComboItem[0];
}
}

View File

@@ -0,0 +1,202 @@
package net.reldo.taskstracker.panel;
import java.awt.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.config.ConfigValues;
import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinitionSkill;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.reldo.taskstracker.data.task.TaskService;
import net.reldo.taskstracker.panel.components.FixedWidthPanel;
import net.runelite.api.Skill;
import net.runelite.client.ui.FontManager;
@Slf4j
public class TaskListPanel extends JScrollPane
{
public TasksTrackerPlugin plugin;
private final HashMap<Integer, TaskPanel> taskPanelsByStructId = new HashMap<>();
private final TaskListListPanel taskList;
private final TaskService taskService;
private final JLabel emptyTasks = new JLabel();
public TaskListPanel(TasksTrackerPlugin plugin, TaskService taskService)
{
this.plugin = plugin;
taskList = new TaskListListPanel(plugin);
this.taskService = taskService;
setViewportView(taskList);
setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
}
public String getEmptyTaskListMessage()
{
return "No tasks match the current filters.";
}
public void redraw()
{
taskList.redraw();
}
public void refreshAllTasks()
{
log.debug("TaskListPanel.refreshAllTasks");
if (!SwingUtilities.isEventDispatchThread())
{
log.error("Task list panel refresh failed - not event dispatch thread.");
return;
}
for (TaskPanel taskPanel : taskPanelsByStructId.values())
{
taskPanel.refresh();
}
}
public void refreshMultipleTasks(Collection<TaskFromStruct> tasks)
{
log.debug("TaskListPanel.refreshMultipleTasks {}", tasks.size());
if (!SwingUtilities.isEventDispatchThread())
{
log.error("Task list panel refresh failed - not event dispatch thread.");
return;
}
for (TaskFromStruct task : tasks)
{
refresh(task);
}
}
public void refreshTask(TaskFromStruct task)
{
log.debug("TaskListPanel.refreshMultipleTasks {}", task.getName());
refresh(task);
}
private void refresh(TaskFromStruct task)
{
if (!SwingUtilities.isEventDispatchThread())
{
log.error("Task list panel refresh failed - not event dispatch thread.");
return;
}
if (task == null)
{
log.debug("Attempted to refresh null task");
return;
}
emptyTasks.setVisible(false);
TaskPanel panel = taskPanelsByStructId.get(task.getStructId());
if (panel != null)
{
panel.refresh();
}
boolean isAnyTaskPanelVisible = taskPanelsByStructId.values().stream()
.anyMatch(TaskPanel::isVisible);
if (!isAnyTaskPanelVisible)
{
emptyTasks.setVisible(true);
}
}
public void refreshTaskPanelsWithSkill(Skill skill)
{
// Refresh all task panels for tasks with 'skill' or
// 'SKILLS' (any skill) or 'TOTAL LEVEL' as a requirement.
taskPanelsByStructId.values().stream()
.filter(tp ->
{
List<TaskDefinitionSkill> skillsList = tp.task.getTaskDefinition().getSkills();
if (skillsList == null || skillsList.isEmpty())
{
return false;
}
return skillsList.stream()
.map(TaskDefinitionSkill::getSkill)
.anyMatch(s -> s.equalsIgnoreCase(skill.getName()) ||
s.equalsIgnoreCase("SKILLS") ||
s.equalsIgnoreCase("TOTAL LEVEL")
);
})
.forEach(TaskPanel::refresh);
}
private class TaskListListPanel extends FixedWidthPanel
{
private final TasksTrackerPlugin plugin;
public TaskListListPanel(TasksTrackerPlugin plugin)
{
this.plugin = plugin;
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBorder(new EmptyBorder(0, 10, 10, 10));
setAlignmentX(Component.LEFT_ALIGNMENT);
emptyTasks.setBorder(new EmptyBorder(10,0,10,0));
emptyTasks.setText("<html><center>" + getEmptyTaskListMessage() + "</center></html>");
emptyTasks.setFont(FontManager.getRunescapeSmallFont());
emptyTasks.setHorizontalAlignment(JLabel.CENTER);
emptyTasks.setVerticalAlignment(JLabel.CENTER);
add(emptyTasks);
emptyTasks.setVisible(false);
}
public void redraw()
{
log.debug("TaskListPanel.redraw");
if(SwingUtilities.isEventDispatchThread())
{
removeAll();
taskPanelsByStructId.clear();
add(emptyTasks);
emptyTasks.setVisible(false);
log.debug("TaskListPanel creating panels");
List<TaskFromStruct> tasks = taskService.getTasks();
if (tasks == null || tasks.isEmpty())
{
emptyTasks.setVisible(true);
return;
}
for (int indexPosition = 0; indexPosition < tasks.size(); indexPosition++)
{
int adjustedIndexPosition = indexPosition;
if (plugin.getConfig().sortDirection().equals(ConfigValues.SortDirections.DESCENDING))
{
adjustedIndexPosition = tasks.size() - (adjustedIndexPosition + 1);
}
TaskFromStruct task = tasks.get(taskService.getSortedTaskIndex(plugin.getConfig().sortCriteria(), adjustedIndexPosition));
TaskPanel taskPanel = new TaskPanel(plugin, task);
add(taskPanel);
taskPanelsByStructId.put(task.getStructId(), taskPanel);
}
log.debug("TaskListPanel validate and repaint");
validate();
repaint();
}
else
{
log.error("Task list panel redraw failed - not event dispatch thread.");
}
}
}
}

View File

@@ -0,0 +1,429 @@
package net.reldo.taskstracker.panel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.JToolTip;
import javax.swing.ToolTipManager;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.HtmlUtil;
import net.reldo.taskstracker.TasksTrackerConfig;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.config.ConfigValues.CompletedFilterValues;
import net.reldo.taskstracker.config.ConfigValues.IgnoredFilterValues;
import net.reldo.taskstracker.config.ConfigValues.TrackedFilterValues;
import net.reldo.taskstracker.data.jsondatastore.types.FilterType;
import net.reldo.taskstracker.data.jsondatastore.types.TaskDefinitionSkill;
import net.reldo.taskstracker.data.task.TaskFromStruct;
import net.reldo.taskstracker.data.task.filters.Filter;
import net.reldo.taskstracker.data.task.filters.ParamButtonFilter;
import net.reldo.taskstracker.data.task.filters.ParamDropdownFilter;
import net.runelite.api.Constants;
import net.runelite.api.Skill;
import net.runelite.client.game.SkillIconManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.util.SwingUtil;
@Slf4j
public class TaskPanel extends JPanel
{
public final TaskFromStruct task;
private final JLabel tierIcon = new JLabel();
private final JPanel container = new JPanel(new BorderLayout());
private final JPanel body = new JPanel(new BorderLayout());
private final JLabel name = new JLabel("task");
private final JLabel description = new JLabel("description");
private final JPanel buttons = new JPanel();
private final JToggleButton toggleTrack = new JToggleButton();
private final JToggleButton toggleIgnore = new JToggleButton();
protected final ArrayList<Filter> filters = new ArrayList<>();
protected TasksTrackerPlugin plugin;
public TaskPanel(TasksTrackerPlugin plugin, TaskFromStruct task)
{
super(new BorderLayout());
this.plugin = plugin;
this.task = task;
createPanel();
setComponentPopupMenu(getPopupMenu());
ToolTipManager.sharedInstance().registerComponent(this);
task.getTaskType().getFilters().forEach((filterConfig) -> {
String paramName = filterConfig.getValueName();
if (filterConfig.getFilterType().equals(FilterType.BUTTON_FILTER))
{
Filter filter = new ParamButtonFilter(plugin.getConfigManager(), paramName, task.getTaskType().getTaskJsonName() + "." + filterConfig.getConfigKey());
filters.add(filter);
}
else if (filterConfig.getFilterType().equals(FilterType.DROPDOWN_FILTER))
{
Filter filter = new ParamDropdownFilter(plugin.getConfigManager(), paramName, task.getTaskType().getTaskJsonName() + "." + filterConfig.getConfigKey());
filters.add(filter);
}
});
refresh();
}
public JPopupMenu getPopupMenu()
{
return null;
}
public String getTaskTooltip()
{
StringBuilder tooltipText = new StringBuilder();
tooltipText.append(HtmlUtil.wrapWithBold(task.getName())).append(HtmlUtil.HTML_LINE_BREAK);
tooltipText.append(task.getDescription()).append(HtmlUtil.HTML_LINE_BREAK);
String skillSection = getSkillSectionHtml();
if (skillSection != null)
{
tooltipText.append(skillSection).append(HtmlUtil.HTML_LINE_BREAK);
}
String wikiNotes = task.getTaskDefinition().getWikiNotes();
if (wikiNotes != null)
{
tooltipText.append(HtmlUtil.HTML_LINE_BREAK).append(wikiNotes).append(HtmlUtil.HTML_LINE_BREAK);
}
if (task.isCompleted())
{
tooltipText.append(HtmlUtil.HTML_LINE_BREAK);
String datePattern = "MM-dd-yyyy hh:mma";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
tooltipText.append("").append(simpleDateFormat.format(new Date(task.getCompletedOn())));
}
Float completionPercent = task.getTaskDefinition().getCompletionPercent();
if (completionPercent != null)
{
tooltipText.append(HtmlUtil.HTML_LINE_BREAK).append("Players Completed: ").append(completionPercent).append('%');
}
return HtmlUtil.wrapWithHtml(
HtmlUtil.wrapWithWrappingParagraph(tooltipText.toString(), 200)
);
}
public Color getTaskBackgroundColor()
{
if (plugin.playerSkills == null)
{
return ColorScheme.DARKER_GRAY_COLOR;
}
if (task.isCompleted())
{
return Colors.COMPLETED_BACKGROUND_COLOR;
}
if (task.getTaskDefinition().getSkills() == null || task.getTaskDefinition().getSkills().size() == 0)
{
return ColorScheme.DARKER_GRAY_COLOR;
}
for (TaskDefinitionSkill requiredSkill : task.getTaskDefinition().getSkills())
{
Skill skill;
String requiredSkillName = requiredSkill.getSkill().toUpperCase();
try
{
skill = Skill.valueOf(requiredSkillName);
}
catch (IllegalArgumentException ex)
{
log.error("invalid skill name " + requiredSkillName);
continue;
}
if (plugin.playerSkills[skill.ordinal()] < requiredSkill.getLevel())
{
return Colors.UNQUALIFIED_BACKGROUND_COLOR;
}
}
return ColorScheme.DARKER_GRAY_COLOR;
}
public void createPanel()
{
setLayout(new BorderLayout());
setBorder(new EmptyBorder(0, 0, 7, 0));
container.setBorder(new EmptyBorder(7, 7, 6, 0));
// Body
name.setFont(FontManager.getRunescapeSmallFont());
name.setForeground(Color.WHITE);
body.add(name, BorderLayout.NORTH);
description.setFont(FontManager.getRunescapeSmallFont());
description.setForeground(Color.GRAY);
body.add(description, BorderLayout.CENTER);
// Buttons
buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
buttons.setBorder(new EmptyBorder(0, 0, 0, 7));
toggleTrack.setPreferredSize(new Dimension(8, 8));
toggleTrack.setIcon(Icons.PLUS_ICON);
toggleTrack.setSelectedIcon(Icons.MINUS_ICON);
toggleTrack.setBorder(new EmptyBorder(5, 0, 5, 0));
toggleTrack.addActionListener(e -> {
task.setTracked(toggleTrack.isSelected());
plugin.pluginPanel.taskListPanel.refreshTask(task);
plugin.saveCurrentTaskTypeData();
});
SwingUtil.removeButtonDecorations(toggleTrack);
toggleIgnore.setPreferredSize(new Dimension(8, 8));
toggleIgnore.setIcon(Icons.EYE_CROSS_GREY);
toggleIgnore.setSelectedIcon(Icons.EYE_ICON);
SwingUtil.addModalTooltip(toggleIgnore, "Unignore", "Ignore");
toggleIgnore.setBorder(new EmptyBorder(5, 0, 5, 0));
toggleIgnore.addActionListener(e -> {
task.setIgnored(!task.isIgnored());
plugin.pluginPanel.taskListPanel.refreshTask(task);
plugin.saveCurrentTaskTypeData();
});
SwingUtil.removeButtonDecorations(toggleIgnore);
buttons.add(toggleTrack);
buttons.add(toggleIgnore);
// Full
container.add(tierIcon, BorderLayout.WEST);
container.add(body, BorderLayout.CENTER);
container.add(buttons, BorderLayout.EAST);
BufferedImage tierSprite = task.getTaskType().getTierSprites().get(task.getTier());
if (tierSprite != null)
{
tierIcon.setMinimumSize(new Dimension(Constants.ITEM_SPRITE_WIDTH, Constants.ITEM_SPRITE_HEIGHT));
tierIcon.setIcon(new ImageIcon(tierSprite));
tierIcon.setBorder(new EmptyBorder(0, 0, 0, 5));
}
else
{
tierIcon.setBorder(new EmptyBorder(0, 0, 0, 0));
}
add(container, BorderLayout.NORTH);
addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent e)
{
if (e.isPopupTrigger())
{
JPopupMenu menu = createWikiPopupMenu();
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
});
}
public JPopupMenu createWikiPopupMenu()
{
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem openWikiItem = new JMenuItem("Wiki");
openWikiItem.addActionListener(e -> openRuneScapeWiki());
popupMenu.add(openWikiItem);
return popupMenu;
}
private void openRuneScapeWiki()
{
String wikiUrl = String.format("https://oldschool.runescape.wiki/%s", URLEncoder.encode(task.getName().replace(' ', '_'), StandardCharsets.UTF_8));
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(new URI(wikiUrl));
}
catch (IOException | URISyntaxException ex)
{
ex.printStackTrace();
}
}
else
{
log.warn("Desktop browsing is not supported on this system.");
}
}
public void refresh()
{
setBackgroundColor(getTaskBackgroundColor());
name.setText(HtmlUtil.wrapWithHtml(task.getName()));
description.setText(HtmlUtil.wrapWithHtml(task.getDescription()));
toggleTrack.setSelected(task.isTracked());
toggleIgnore.setSelected(task.isIgnored());
setVisible(meetsFilterCriteria());
revalidate();
}
protected boolean meetsFilterCriteria()
{
String nameLowercase = task.getName().toLowerCase();
String descriptionLowercase = task.getDescription().toLowerCase();
if (plugin.taskTextFilter != null &&
!nameLowercase.contains(plugin.taskTextFilter) &&
!descriptionLowercase.contains(plugin.taskTextFilter))
{
return false;
}
TasksTrackerConfig config = plugin.getConfig();
for (Filter filter : filters)
{
if (!filter.meetsCriteria(task))
{
return false;
}
}
if (config.completedFilter().equals(CompletedFilterValues.INCOMPLETE) && task.isCompleted())
{
return false;
}
if (config.completedFilter().equals(CompletedFilterValues.COMPLETE) && !task.isCompleted())
{
return false;
}
if (config.ignoredFilter().equals(IgnoredFilterValues.NOT_IGNORED) && task.isIgnored())
{
return false;
}
if (config.ignoredFilter().equals(IgnoredFilterValues.IGNORED) && !task.isIgnored())
{
return false;
}
if (config.trackedFilter().equals(TrackedFilterValues.UNTRACKED) && task.isTracked())
{
return false;
}
return !config.trackedFilter().equals(TrackedFilterValues.TRACKED) || task.isTracked();
}
private void setBackgroundColor(Color color)
{
container.setBackground(color);
body.setBackground(color);
buttons.setBackground(color);
}
@Override
public Dimension getMaximumSize()
{
return new Dimension(PluginPanel.PANEL_WIDTH, getPreferredSize().height);
}
@Override
public JToolTip createToolTip()
{
JToolTip customTooltip = new JToolTip();
customTooltip.setFont(FontManager.getRunescapeSmallFont());
return customTooltip;
}
@Override
public String getToolTipText(MouseEvent mouseEvent)
{
return getTaskTooltip();
}
private String getSkillSectionHtml()
{
List<TaskDefinitionSkill> requiredSkills = task.getTaskDefinition().getSkills();
if (requiredSkills == null)
{
return null;
}
StringBuilder skillSection = new StringBuilder();
skillSection.append(HtmlUtil.HTML_LINE_BREAK);
for (TaskDefinitionSkill requiredSkill : requiredSkills)
{
Skill skill;
try
{
skill = Skill.valueOf(requiredSkill.getSkill().toUpperCase());
}
catch (IllegalArgumentException ex)
{
log.warn("unknown skill: {}", requiredSkill.getSkill().toUpperCase(), ex);
continue;
}
Integer requiredLevel = requiredSkill.getLevel();
int playerLevel = -1;
if (requiredLevel == null)
{
continue;
}
if (plugin.playerSkills != null)
{
playerLevel = plugin.playerSkills[skill.ordinal()];
}
String skillMessage = getSkillRequirementHtml(requiredSkill.getSkill().toLowerCase(), playerLevel, requiredLevel);
skillSection.append(skillMessage).append(" ");
}
return skillSection.toString();
}
private String getSkillRequirementHtml(String skillName, Integer playerLevel, int requiredLevel)
{
String skillIconPath = "/skill_icons_small/" + skillName + ".png";
URL url = SkillIconManager.class.getResource(skillIconPath);
Color color = playerLevel >= requiredLevel ? Colors.QUALIFIED_TEXT_COLOR : Colors.UNQUALIFIED_TEXT_COLOR;
return HtmlUtil.imageTag(url) + " " + HtmlUtil.colorTag(color, playerLevel + "/" + requiredLevel);
}
private String getPointsTooltipText()
{
int points = this.task.getPoints();
if (points == 0)
{
return "";
}
return " - " + points + " points";
}
}

View File

@@ -0,0 +1,8 @@
package net.reldo.taskstracker.panel;
import net.reldo.taskstracker.data.task.TaskFromStruct;
public interface TaskPanelFactory
{
TaskPanel create(TaskFromStruct task);
}

View File

@@ -0,0 +1,108 @@
package net.reldo.taskstracker.panel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerConfig;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.task.TaskService;
import net.runelite.client.game.SpriteManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
@Slf4j
public class TasksTrackerPluginPanel extends PluginPanel
{
private final LoggedInPanel loggedInPanel;
private final LoggedOutPanel loggedOutPanel = new LoggedOutPanel();
public TaskListPanel taskListPanel;
private boolean loggedInPanelVisible = false;
public TasksTrackerPluginPanel(TasksTrackerPlugin plugin, TasksTrackerConfig config, SpriteManager spriteManager, TaskService taskService)
{
super(false);
setBorder(new EmptyBorder(6, 6, 6, 6));
setBackground(ColorScheme.DARK_GRAY_COLOR);
setLayout(new BorderLayout());
loggedInPanel = new LoggedInPanel(plugin, config, taskService);
taskListPanel = loggedInPanel.taskListPanel;
add(loggedInPanel, BorderLayout.NORTH);
loggedInPanel.setVisible(false);
// Add error pane
add(loggedOutPanel);
}
@Override
public Dimension getPreferredSize()
{
return new Dimension(PANEL_WIDTH + SCROLLBAR_WIDTH, super.getPreferredSize().height);
}
public void redraw()
{
if (loggedInPanelVisible)
{
loggedInPanel.redraw();
}
}
public void refreshAllTasks()
{
if (loggedInPanelVisible)
{
loggedInPanel.refreshAllTasks();
}
}
public void setLoggedIn(boolean loggedIn)
{
if(SwingUtilities.isEventDispatchThread())
{
updateVisiblePanel(loggedIn);
}
else
{
log.error("Failed to update loggedIn state - not event dispatch thread.");
}
}
public void hideLoggedInPanel()
{
if(SwingUtilities.isEventDispatchThread())
{
updateVisiblePanel(false);
}
else
{
log.error("Failed to update logged in panel visibility - not event dispatch thread.");
}
}
private void updateVisiblePanel(boolean loggedInPanelVisible)
{
if (loggedInPanelVisible != this.loggedInPanelVisible)
{
if (loggedInPanelVisible)
{
loggedOutPanel.setVisible(false);
loggedInPanel.setVisible(true);
}
else
{
loggedInPanel.setVisible(false);
loggedOutPanel.setVisible(true);
}
validate();
repaint();
}
this.loggedInPanelVisible = loggedInPanelVisible;
}
}

View File

@@ -0,0 +1,42 @@
package net.reldo.taskstracker.panel.components;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.runelite.client.ui.ColorScheme;
import static net.runelite.client.ui.PluginPanel.PANEL_WIDTH;
public class CheckBox extends JPanel
{
private final JCheckBox jCheckBox = new JCheckBox();
public CheckBox(String name)
{
setLayout(new BorderLayout());
setMinimumSize(new Dimension(PANEL_WIDTH, 0));
JLabel label = new JLabel(name);
add(label, BorderLayout.CENTER);
jCheckBox.setBackground(ColorScheme.LIGHT_GRAY_COLOR);
add(jCheckBox, BorderLayout.EAST);
}
public boolean isSelected()
{
return jCheckBox.isSelected();
}
public void setSelected(boolean selected)
{
jCheckBox.setSelected(selected);
}
public void addActionListener(ActionListener l)
{
jCheckBox.addActionListener(l);
}
}

View File

@@ -0,0 +1,15 @@
package net.reldo.taskstracker.panel.components;
import java.awt.Dimension;
import javax.swing.JPanel;
import net.runelite.client.ui.PluginPanel;
public class FixedWidthPanel extends JPanel
{
@Override
public Dimension getPreferredSize()
{
return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height);
}
}

View File

@@ -0,0 +1,148 @@
package net.reldo.taskstracker.panel.components;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.border.EmptyBorder;
import lombok.Getter;
import lombok.Setter;
public class MultiToggleButton extends JButton
{
// Icons
private final Icon[] icons;
// Tooltips
private final String[] tooltips;
private final int stateCount;
@Setter
private ActionListener stateChangedAction = null;
@Getter
private int state = 0;
final JPopupMenu popupMenu = new JPopupMenu();
private boolean popupMenuEnabled = false;
public MultiToggleButton(int stateCount)
{
super();
this.stateCount = stateCount;
icons = new Icon[stateCount];
tooltips = new String[stateCount];
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
addActionListener(e -> changeStateThenAction());
}
public void popupMenuEnabled(boolean enabled)
{
if(popupMenuEnabled != enabled)
{
popupMenuEnabled = enabled;
if(popupMenuEnabled)
{
this.setComponentPopupMenu(popupMenu);
}
else
{
this.remove(popupMenu);
}
}
}
public void setIcon(Icon icon, int state)
{
if (state < 0 || state > stateCount || icon == null)
{
return;
}
icons[state] = icon;
if (state == this.state) setIconState();
}
public boolean setIcons(Icon[] icons)
{
if (icons == null || icons.length == 0)
{
return false;
}
for (int i = 0; i < icons.length; i++)
{
setIcon(icons[i], i);
}
return true;
}
public void setToolTip(String tooltip, int state)
{
if (state < 0 || state > 3 || tooltip == null)
{
return;
}
tooltips[state] = tooltip;
addPopupMenuItem(tooltip, state);
if (state == this.state) setTooltipState();
}
public boolean setToolTips(String[] tooltips)
{
if (tooltips == null || tooltips.length == 0)
{
return false;
}
for (int i = 0; i < tooltips.length; i++)
{
setToolTip(tooltips[i], i);
}
return true;
}
public void changeState()
{
setState((++state) % stateCount);
}
public void changeStateThenAction()
{
setStateThenAction((++state) % stateCount);
}
private void setIconState()
{
super.setIcon(icons[state]);
}
private void setTooltipState()
{
super.setToolTipText(tooltips[state]);
}
public void setState(int state)
{
this.state = state;
setIconState();
setTooltipState();
}
public void setStateThenAction(int state)
{
setState(state);
if(stateChangedAction != null) this.stateChangedAction.actionPerformed(new ActionEvent(this, 0, ""));
}
private void addPopupMenuItem(String text, int state)
{
JMenuItem menuItem = new JMenuItem(text);
menuItem.addActionListener(e -> {if(isEnabled())setState(state);});
popupMenu.add(menuItem);
}
}

View File

@@ -0,0 +1,53 @@
package net.reldo.taskstracker.panel.components;
import java.awt.Dimension;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.PluginPanel;
import net.runelite.client.ui.components.IconTextField;
public class SearchBox extends IconTextField
{
private SearchBoxCallback fn;
public SearchBox()
{
this.setIcon(IconTextField.Icon.SEARCH);
this.setPreferredSize(new Dimension(PluginPanel.PANEL_WIDTH - 20, 30));
this.setBackground(ColorScheme.DARKER_GRAY_COLOR);
this.setHoverBackgroundColor(ColorScheme.DARK_GRAY_HOVER_COLOR);
this.getDocument().addDocumentListener(new DocumentListener()
{
@Override
public void insertUpdate(DocumentEvent documentEvent)
{
fn.call();
}
@Override
public void removeUpdate(DocumentEvent documentEvent)
{
fn.call();
}
@Override
public void changedUpdate(DocumentEvent documentEvent)
{
}
});
this.addActionListener(e -> fn.call()
);
}
public void addTextChangedListener(SearchBoxCallback fn)
{
this.fn = fn;
}
public interface SearchBoxCallback
{
void call();
}
}

View File

@@ -0,0 +1,39 @@
package net.reldo.taskstracker.panel.components;
import javax.swing.Icon;
public class TriToggleButton extends MultiToggleButton
{
public TriToggleButton()
{
super(3);
}
public boolean setIcons(Icon icon0, Icon icon1, Icon icon2)
{
if (icon0 == null || icon1 == null || icon2 == null)
{
return false;
}
setIcon(icon0, 0);
setIcon(icon1, 1);
setIcon(icon2, 2);
return true;
}
public boolean setToolTips(String tooltip0, String tooltip1, String tooltip2)
{
if (tooltip0 == null || tooltip1 == null || tooltip2 == null)
{
return false;
}
setToolTip(tooltip0, 0);
setToolTip(tooltip1, 1);
setToolTip(tooltip2, 2);
return true;
}
}

View File

@@ -0,0 +1,29 @@
package net.reldo.taskstracker.panel.filters;
public class ComboItem<T>
{
private T value;
private String label;
public ComboItem(T value, String label)
{
this.value = value;
this.label = label;
}
public T getValue()
{
return this.value;
}
public String getLabel()
{
return this.label;
}
@Override
public String toString()
{
return label;
}
}

View File

@@ -0,0 +1,131 @@
package net.reldo.taskstracker.panel.filters;
import com.google.common.collect.ImmutableList;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.util.LinkedHashMap;
import java.util.List;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.border.EmptyBorder;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.jsondatastore.types.FilterCustomItem;
import net.reldo.taskstracker.data.task.TaskType;
import net.runelite.client.hiscore.HiscoreSkill;
import static net.runelite.client.hiscore.HiscoreSkill.*;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.util.ImageUtil;
public class DynamicButtonFilterPanel extends FilterButtonPanel
{
private final FilterConfig filterConfig;
private final TaskType taskType;
/**
* Real skills, ordered in the way they should be displayed in the panel.
*/
private static final List<HiscoreSkill> SKILLS = ImmutableList.of(
ATTACK, HITPOINTS, MINING,
STRENGTH, AGILITY, SMITHING,
DEFENCE, HERBLORE, FISHING,
RANGED, THIEVING, COOKING,
PRAYER, CRAFTING, FIREMAKING,
MAGIC, FLETCHING, WOODCUTTING,
RUNECRAFT, SLAYER, FARMING,
CONSTRUCTION, HUNTER
);
public DynamicButtonFilterPanel(TasksTrackerPlugin plugin, FilterConfig filterConfig, TaskType taskType)
{
super(plugin, filterConfig.getLabel());
this.filterConfig = filterConfig;
this.taskType = taskType;
this.configKey = taskType.getFilterConfigPrefix() + filterConfig.getConfigKey();
setLayout(new BorderLayout());
setBackground(ColorScheme.DARKER_GRAY_COLOR);
setBorder(new EmptyBorder(10, 10, 10, 10));
redraw();
}
@Override
protected JPanel makeButtonPanel()
{
// Panel that holds tier icons
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
LinkedHashMap<String, BufferedImage> buttonImages = getIconImages();
LinkedHashMap<String, String> buttonTooltips = getTooltips();
buttonPanel.setLayout(new GridLayout(buttonImages.size() / 3, 3));
// For each filter value create a button and add it to the UI
buttonImages.forEach((key, image) -> {
String tooltip = buttonTooltips.get(key);
JToggleButton button = makeButton(tooltip, image);
button.setSelected(getConfigButtonState(key));
buttons.put(key, button);
buttonPanel.add(button);
});
return buttonPanel;
}
@Override
protected LinkedHashMap<String, BufferedImage> getIconImages()
{
LinkedHashMap<String, BufferedImage> images = new LinkedHashMap<>();
if (filterConfig.getConfigKey().equals("skill"))
{
String skillName;
BufferedImage skillImage;
int index = 0;
for (FilterCustomItem customItem : filterConfig.getCustomItems())
{
if (customItem.getValue() != 0)
{
skillName = SKILLS.get(index).name().toLowerCase();
String directory = "/skill_icons_small/";
String skillIcon = directory + skillName + ".png";
skillImage = ImageUtil.loadImageResource(getClass(), skillIcon);
}
else
{
skillImage = ImageUtil.loadImageResource(TasksTrackerPlugin.class, "panel/components/no_skill.png");
}
String key = customItem.getValue().toString();
images.put(key, skillImage);
index++;
}
}
else
{
for (FilterCustomItem customItem : filterConfig.getCustomItems())
{
String key = customItem.getValue().toString();
images.put(key, taskType.getSpritesById().get(customItem.getSpriteId()));
}
}
return images;
}
private LinkedHashMap<String, String> getTooltips()
{
LinkedHashMap<String, String> tooltips = new LinkedHashMap<>();
for (FilterCustomItem customItem : filterConfig.getCustomItems())
{
String key = customItem.getValue().toString();
tooltips.put(key, customItem.getTooltip());
}
return tooltips;
}
}

View File

@@ -0,0 +1,92 @@
package net.reldo.taskstracker.panel.filters;
import java.awt.GridLayout;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.reldo.taskstracker.data.jsondatastore.types.FilterConfig;
import net.reldo.taskstracker.data.task.TaskType;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
@Slf4j
public class DynamicDropdownFilterPanel extends FilterPanel
{
private final String configKey;
private final FilterConfig filterConfig;
private final ConfigManager configManager;
private final TasksTrackerPlugin plugin;
private final ComboItem[] items;
private JComboBox<ComboItem> dropdown;
public DynamicDropdownFilterPanel(TasksTrackerPlugin plugin, FilterConfig filterConfig, TaskType taskType, ComboItem[] items)
{
this.configManager = plugin.getConfigManager();
this.plugin = plugin;
this.filterConfig = filterConfig;
this.items = items;
this.configKey = taskType.getFilterConfigPrefix() + filterConfig.getConfigKey();
setLayout(new GridLayout(1,2));
setBackground(ColorScheme.DARKER_GRAY_COLOR);
setBorder(new EmptyBorder(5, 10, 5, 10));
}
private JComboBox<ComboItem> makeDropdownPanel()
{
JComboBox<ComboItem> dropdown = new JComboBox<>(items);
dropdown.setFont(FontManager.getRunescapeSmallFont());
dropdown.setAlignmentX(LEFT_ALIGNMENT);
dropdown.setSelectedItem(items[0]);
dropdown.setFocusable(false);
dropdown.setBackground(ColorScheme.DARK_GRAY_COLOR.brighter());
dropdown.addActionListener(e -> {
ComboItem selection = dropdown.getItemAt(dropdown.getSelectedIndex());
updateFilterConfig();
plugin.refreshAllTasks();
log.debug("selected: {} {}", selection.getLabel(), selection.getValue());
});
return dropdown;
}
private JLabel makeLabel()
{
JLabel label = new JLabel(filterConfig.getLabel() + ":");
label.setFont(FontManager.getRunescapeSmallFont());
label.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
return label;
}
protected void updateFilterConfig()
{
log.debug("updateFilterConfig {}, {}, {}", TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, dropdown.getItemAt(dropdown.getSelectedIndex()).getValue());
configManager.setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, dropdown.getItemAt(dropdown.getSelectedIndex()).getValue());
}
public void redraw()
{
if(SwingUtilities.isEventDispatchThread())
{
removeAll();
dropdown = makeDropdownPanel();
add(makeLabel());
add(dropdown);
updateFilterConfig();
validate();
repaint();
}
else
{
log.error("Dropdown filter panel redraw failed - not event dispatch thread.");
}
}
}

View File

@@ -0,0 +1,223 @@
package net.reldo.taskstracker.panel.filters;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicBorders;
import javax.swing.plaf.basic.BasicButtonUI;
import lombok.extern.slf4j.Slf4j;
import net.reldo.taskstracker.TasksTrackerPlugin;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.SwingUtil;
@Slf4j
public abstract class FilterButtonPanel extends FilterPanel
{
protected final TasksTrackerPlugin plugin;
private final String label;
protected final Map<String, JToggleButton> buttons = new HashMap<>();
protected String configKey;
protected JPanel buttonPanel;
protected JToggleButton collapseBtn;
private final String expandBtnPath = "panel/components/";
private final BufferedImage collapseImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_buttons_collapsed.png");
private final Icon MENU_COLLAPSED_ICON = new ImageIcon(ImageUtil.alphaOffset(collapseImg, -180));
private final Icon MENU_ICON_HOVER = new ImageIcon(collapseImg);
private final BufferedImage expandedImg = ImageUtil.loadImageResource(TasksTrackerPlugin.class, expandBtnPath + "filter_buttons_expanded.png");
private final Icon MENU_EXPANDED_ICON = new ImageIcon(ImageUtil.alphaOffset(expandedImg, -180));
private final Icon MENU_ICON_HOVER_SELECTED = new ImageIcon(expandedImg);
public FilterButtonPanel(TasksTrackerPlugin plugin, String label)
{
this.plugin = plugin;
this.label = label;
}
protected abstract LinkedHashMap<String, BufferedImage> getIconImages();
protected abstract JPanel makeButtonPanel();
protected JToggleButton makeButton(String tooltip, BufferedImage image)
{
JToggleButton button = new JToggleButton();
button.setBackground(ColorScheme.DARKER_GRAY_COLOR);
button.setBorder(new BasicBorders.ToggleButtonBorder(ColorScheme.DARKER_GRAY_COLOR,
ColorScheme.DARKER_GRAY_COLOR.darker(),
ColorScheme.MEDIUM_GRAY_COLOR.darker(),
ColorScheme.MEDIUM_GRAY_COLOR));
button.setFocusable(false);
if (image != null) {
ImageIcon selectedIcon = new ImageIcon(image);
ImageIcon deselectedIcon = new ImageIcon(ImageUtil.alphaOffset(image, -180));
button.setIcon(deselectedIcon);
button.setSelectedIcon(selectedIcon);
button.setPreferredSize(new Dimension(image.getWidth(), image.getHeight() + 10));
} else {
button.setPreferredSize(new Dimension(button.getPreferredSize().width, 50));
}
button.setToolTipText(tooltip.substring(0,1).toUpperCase() + tooltip.substring(1).toLowerCase());
button.addActionListener(e -> {
updateFilterText();
updateCollapseButtonText();
plugin.refreshAllTasks();
});
button.setSelected(true);
return button;
}
protected JPanel allOrNoneButtons()
{
JPanel buttonWrapper = new JPanel();
buttonWrapper.setLayout(new BoxLayout(buttonWrapper, BoxLayout.X_AXIS));
buttonWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
buttonWrapper.setAlignmentX(JPanel.CENTER_ALIGNMENT);
JButton all = new JButton("all");
SwingUtil.removeButtonDecorations(all);
all.setFocusable(false);
all.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
all.setFont(FontManager.getRunescapeSmallFont());
all.setPreferredSize(new Dimension(50, 0));
all.addActionListener(e -> {
setAllSelected(true);
updateFilterText();
updateCollapseButtonText();
plugin.refreshAllTasks();
});
JButton none = new JButton("none");
SwingUtil.removeButtonDecorations(none);
none.setFocusable(false);
none.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
none.setFont(FontManager.getRunescapeSmallFont());
none.setPreferredSize(new Dimension(50, 0));
none.addActionListener(e -> {
setAllSelected(false);
updateFilterText();
updateCollapseButtonText();
plugin.refreshAllTasks();
});
JLabel separator = new JLabel("|");
separator.setForeground(ColorScheme.MEDIUM_GRAY_COLOR);
buttonWrapper.add(Box.createHorizontalGlue());
buttonWrapper.add(all);
buttonWrapper.add(Box.createHorizontalGlue());
buttonWrapper.add(separator);
buttonWrapper.add(Box.createHorizontalGlue());
buttonWrapper.add(none);
buttonWrapper.add(Box.createHorizontalGlue());
return buttonWrapper;
}
public JToggleButton makeCollapseButton()
{
JToggleButton collapseBtn = new JToggleButton();
// collapse button
SwingUtil.removeButtonDecorations(collapseBtn);
collapseBtn.setIcon(MENU_COLLAPSED_ICON);
collapseBtn.setSelectedIcon(MENU_EXPANDED_ICON);
collapseBtn.setRolloverIcon(MENU_ICON_HOVER);
collapseBtn.setRolloverSelectedIcon(MENU_ICON_HOVER_SELECTED);
SwingUtil.addModalTooltip(collapseBtn, "Collapse filters", "Expand filters");
collapseBtn.setBackground(ColorScheme.DARKER_GRAY_COLOR);
collapseBtn.setAlignmentX(LEFT_ALIGNMENT);
collapseBtn.setUI(new BasicButtonUI()); // substance breaks the layout
collapseBtn.addActionListener(ev -> buttonPanel.setVisible(!buttonPanel.isVisible()));
collapseBtn.setHorizontalTextPosition(JButton.CENTER);
collapseBtn.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
collapseBtn.setFont(FontManager.getRunescapeSmallFont());
collapseBtn.setBorder(new EmptyBorder(2, 0, 2, 0));
collapseBtn.setFocusable(false);
collapseBtn.setSelected(true);
return collapseBtn;
}
protected void updateFilterText()
{
String filterText = buttons.entrySet().stream()
.filter(e -> e.getValue().isSelected())
.map(e -> "f-" + e.getKey() + "-f") // prefix included to cover cases where one key name is contained in another (e.g. "Master" -> "Grandmaster")
.collect(Collectors.joining(","));
plugin.getConfigManager().setConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey, filterText);
}
protected boolean getConfigButtonState(String buttonKey)
{
String configValue = plugin.getConfigManager().getConfiguration(TasksTrackerPlugin.CONFIG_GROUP_NAME, configKey);
boolean isEmptyFilterSelection = configValue == null || configValue.isEmpty() || configValue.equals("-1");
if (isEmptyFilterSelection)
{
return true;
}
return configValue.contains("f-" + buttonKey + "-f");
}
protected void setAllSelected(boolean state)
{
buttons.values().forEach(button -> button.setSelected(state));
}
protected void updateCollapseButtonText()
{
collapseBtn.setText(label + " - " + buttons.values().stream().filter(JToggleButton::isSelected).count() + " / " + buttons.size());
}
public void redraw()
{
if(SwingUtilities.isEventDispatchThread())
{
buttons.clear();
removeAll();
collapseBtn = makeCollapseButton();
buttonPanel = makeButtonPanel();
add(collapseBtn, BorderLayout.NORTH);
add(buttonPanel, BorderLayout.CENTER);
add(allOrNoneButtons(), BorderLayout.SOUTH);
updateFilterText();
updateCollapseButtonText();
collapseBtn.setVisible(plugin.getConfig().filterPanelCollapsible());
validate();
repaint();
}
else
{
log.error("Filter button panel redraw failed - not event dispatch thread.");
}
}
}

View File

@@ -0,0 +1,8 @@
package net.reldo.taskstracker.panel.filters;
import net.reldo.taskstracker.panel.components.FixedWidthPanel;
public abstract class FilterPanel extends FixedWidthPanel
{
public abstract void redraw();
}

View File

@@ -0,0 +1,15 @@
package net.reldo.taskstracker.quests;
import java.util.HashMap;
import net.runelite.api.Client;
public class DiaryData extends HashMap<Integer, Integer>
{
public DiaryData(Client client)
{
for (DiaryVarbits diary : DiaryVarbits.values())
{
this.put(diary.id, diary.getProgress(client));
}
}
}

View File

@@ -0,0 +1,66 @@
package net.reldo.taskstracker.quests;
import net.runelite.api.Client;
public enum DiaryVarbits
{
DIARY_ARDOUGNE_EASY(4458),
DIARY_ARDOUGNE_MEDIUM(4459),
DIARY_ARDOUGNE_HARD(4460),
DIARY_ARDOUGNE_ELITE(4461),
DIARY_DESERT_EASY(4483),
DIARY_DESERT_MEDIUM(4484),
DIARY_DESERT_HARD(4485),
DIARY_DESERT_ELITE(4486),
DIARY_FALADOR_EASY(4462),
DIARY_FALADOR_MEDIUM(4463),
DIARY_FALADOR_HARD(4464),
DIARY_FALADOR_ELITE(4465),
DIARY_FREMENNIK_EASY(4491),
DIARY_FREMENNIK_MEDIUM(4492),
DIARY_FREMENNIK_HARD(4493),
DIARY_FREMENNIK_ELITE(4494),
DIARY_KANDARIN_EASY(4475),
DIARY_KANDARIN_MEDIUM(4476),
DIARY_KANDARIN_HARD(4477),
DIARY_KANDARIN_ELITE(4478),
DIARY_KARAMJA_EASY(3578),
DIARY_KARAMJA_MEDIUM(3599),
DIARY_KARAMJA_HARD(3611),
DIARY_KARAMJA_ELITE(4566),
DIARY_KOUREND_EASY(7925),
DIARY_KOUREND_MEDIUM(7926),
DIARY_KOUREND_HARD(7927),
DIARY_KOUREND_ELITE(7928),
DIARY_LUMBRIDGE_EASY(4495),
DIARY_LUMBRIDGE_MEDIUM(4496),
DIARY_LUMBRIDGE_HARD(4497),
DIARY_LUMBRIDGE_ELITE(4498),
DIARY_MORYTANIA_EASY(4487),
DIARY_MORYTANIA_MEDIUM(4488),
DIARY_MORYTANIA_HARD(4489),
DIARY_MORYTANIA_ELITE(4490),
DIARY_VARROCK_EASY(4479),
DIARY_VARROCK_MEDIUM(4480),
DIARY_VARROCK_HARD(4481),
DIARY_VARROCK_ELITE(4482),
DIARY_WESTERN_EASY(4471),
DIARY_WESTERN_MEDIUM(4472),
DIARY_WESTERN_HARD(4473),
DIARY_WESTERN_ELITE(4474),
DIARY_WILDERNESS_EASY(4466),
DIARY_WILDERNESS_MEDIUM(4467),
DIARY_WILDERNESS_HARD(4468),
DIARY_WILDERNESS_ELITE(4469);
public int id;
DiaryVarbits(int id) {
this.id = id;
}
public int getProgress(Client client)
{
return client.getVarbitValue(id);
}
}

View File

@@ -0,0 +1,17 @@
package net.reldo.taskstracker.quests;
import java.util.HashMap;
import net.runelite.api.Client;
import net.runelite.api.Quest;
import net.runelite.api.QuestState;
public class QuestData extends HashMap<Integer, QuestState>
{
public QuestData(Client client)
{
for (Quest quest : Quest.values())
{
this.put(quest.getId(), quest.getState(client));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More