First commit of tasks-tracker-plugin-master
15
.gitignore
vendored
@@ -21,6 +21,15 @@ group-ironmen-tracker-master/bin/
|
||||
group-ironmen-tracker-master/spotless/
|
||||
|
||||
|
||||
tasks-tracker-plugin-master/*
|
||||
|
||||
|
||||
tasks-tracker-plugin-master/.gradle
|
||||
tasks-tracker-plugin-master/build
|
||||
tasks-tracker-plugin-master/.idea/
|
||||
tasks-tracker-plugin-master/.project
|
||||
tasks-tracker-plugin-master/.settings/
|
||||
tasks-tracker-plugin-master/.classpath
|
||||
tasks-tracker-plugin-master/nbactions.xml
|
||||
tasks-tracker-plugin-master/nb-configuration.xml
|
||||
tasks-tracker-plugin-master/nbproject/
|
||||
tasks-tracker-plugin-master/.run
|
||||
tasks-tracker-plugin-master/bin
|
||||
tasks-tracker-plugin-master/**/FileDataStoreReader.java
|
||||
|
||||
12
tasks-tracker-plugin-master/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.gradle
|
||||
build
|
||||
.idea/
|
||||
.project
|
||||
.settings/
|
||||
.classpath
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
nbproject/
|
||||
.run
|
||||
bin
|
||||
**/FileDataStoreReader.java
|
||||
25
tasks-tracker-plugin-master/LICENSE
Normal file
@@ -0,0 +1,25 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2021, Tyler Hardy
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
18
tasks-tracker-plugin-master/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Tasks Tracker
|
||||

|
||||

|
||||
|
||||
|
||||
Provides a RuneLite panel to track & filter tasks for Combat Achievements and Leagues.
|
||||
|
||||
## Support
|
||||
Visit [our Discord](https://discord.gg/eCeKwhEzyK) for live support
|
||||
|
||||
## Features
|
||||
* View tasks & progress in RuneLite panel without opening game UI
|
||||
* Search tasks by text or filters
|
||||
* Track tasks in a tracked list tab
|
||||
* Export for external services
|
||||
|
||||

|
||||

|
||||
33
tasks-tracker-plugin-master/build.gradle
Normal file
@@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven {
|
||||
url = 'https://repo.runelite.net'
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
def runeLiteVersion = 'latest.release'
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'net.runelite', name:'client', version: runeLiteVersion
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation group: 'net.runelite', name:'client', version: runeLiteVersion
|
||||
testImplementation group: 'net.runelite', name:'jshell', version: runeLiteVersion
|
||||
testImplementation group: 'com.squareup.okhttp3', name: 'mockwebserver', version: '3.14.9'
|
||||
}
|
||||
|
||||
group = 'net.reldo'
|
||||
version = '1.0-SNAPSHOT'
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
options.release.set(11)
|
||||
}
|
||||
BIN
tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
tasks-tracker-plugin-master/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
252
tasks-tracker-plugin-master/gradlew
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
tasks-tracker-plugin-master/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
BIN
tasks-tracker-plugin-master/icon.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
6
tasks-tracker-plugin-master/runelite-plugin.properties
Normal file
@@ -0,0 +1,6 @@
|
||||
displayName=Tasks Tracker
|
||||
author=Reldo.net
|
||||
support=https://github.com/osrs-reldo/tasks-tracker-plugin
|
||||
description=Provides a panel to track league/combat tasks & export for external services
|
||||
tags=combat,league,leagues,achievements,tasks,league3,leagues3,relic,fragment,unlock,unlocks
|
||||
plugins=net.reldo.taskstracker.TasksTrackerPlugin
|
||||
1
tasks-tracker-plugin-master/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'tasks-tracker'
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.reldo.taskstracker.data.jsondatastore.types;
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
BUTTON_FILTER,
|
||||
DROPDOWN_FILTER
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.reldo.taskstracker.data.jsondatastore.types;
|
||||
|
||||
public enum FilterValueType
|
||||
{
|
||||
PARAM_INTEGER,
|
||||
PARAM_STRING,
|
||||
SKILL,
|
||||
METADATA,
|
||||
GLOBAL
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.reldo.taskstracker.data.jsondatastore.types;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Manifest
|
||||
{
|
||||
public String taskTypeMetadata;
|
||||
public String filterMetadata;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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."));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.reldo.taskstracker.panel;
|
||||
|
||||
import net.reldo.taskstracker.data.task.TaskFromStruct;
|
||||
|
||||
public interface TaskPanelFactory
|
||||
{
|
||||
TaskPanel create(TaskFromStruct task);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 614 B |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 270 B |
|
After Width: | Height: | Size: 265 B |
|
After Width: | Height: | Size: 227 B |
|
After Width: | Height: | Size: 389 B |
|
After Width: | Height: | Size: 403 B |
|
After Width: | Height: | Size: 380 B |
|
After Width: | Height: | Size: 389 B |
|
After Width: | Height: | Size: 425 B |
|
After Width: | Height: | Size: 398 B |
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 296 B |
|
After Width: | Height: | Size: 288 B |
|
After Width: | Height: | Size: 610 B |
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 606 B |
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 613 B |
|
After Width: | Height: | Size: 610 B |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 661 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 630 B |
|
After Width: | Height: | Size: 156 B |
|
After Width: | Height: | Size: 3.0 KiB |