diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index bdf38d0d68..134083611e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -32,6 +32,7 @@ import net.runelite.api.ItemID; @Getter public enum Tab { + CLOCK("Timers & Stopwatches", ItemID.WATCH), BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE), ALLOTMENT("Allotment Patches", ItemID.CABBAGE), FLOWER("Flower Patches", ItemID.RED_FLOWERS), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java index 199d6c920b..cb58e0c4e7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -26,15 +26,21 @@ package net.runelite.client.plugins.timetracking; import java.awt.Dimension; import javax.swing.JPanel; -import net.runelite.client.ui.PluginPanel; public abstract class TabContentPanel extends JPanel { + /** + * Gets the update interval of this panel, in units of 200 milliseconds + * (the plugin panel checks if its contents should be updated every 200 ms; + * this can be considered its "tick rate"). + */ + public abstract int getUpdateInterval(); + public abstract void update(); @Override public Dimension getPreferredSize() { - return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); + return super.getPreferredSize(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index e36f4a2fd8..744250a8db 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -31,24 +31,39 @@ import net.runelite.client.config.ConfigItem; @ConfigGroup("timetracking") public interface TimeTrackingConfig extends Config { - String KEY_NAME = "timetracking"; + String CONFIG_GROUP = "timetracking"; String AUTOWEED = "autoweed"; String BIRD_HOUSE = "birdhouse"; + String TIMERS = "timers"; + String STOPWATCHES = "stopwatches"; @ConfigItem( keyName = "estimateRelative", name = "Show relative time", - description = "Show amount of time remaining instead of completion time" + description = "Show amount of time remaining instead of completion time", + position = 1 ) default boolean estimateRelative() { return false; } + @ConfigItem( + keyName = "timerNotification", + name = "Timer notification", + description = "Notify you whenever a timer has finished counting down", + position = 2 + ) + default boolean timerNotification() + { + return false; + } + @ConfigItem( keyName = "birdHouseNotification", name = "Bird house notification", - description = "Notify you when all bird houses are full" + description = "Notify you when all bird houses are full", + position = 3 ) default boolean birdHouseNotification() { @@ -63,7 +78,7 @@ public interface TimeTrackingConfig extends Config ) default Tab activeTab() { - return Tab.ALLOTMENT; + return Tab.CLOCK; } @ConfigItem( diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java index 4eb4ec175c..aab4b02c19 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -34,10 +34,12 @@ import javax.annotation.Nullable; import javax.swing.ImageIcon; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import lombok.extern.slf4j.Slf4j; import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.timetracking.clocks.ClockManager; import net.runelite.client.plugins.timetracking.farming.FarmingTracker; import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker; import net.runelite.client.ui.ColorScheme; @@ -61,7 +63,7 @@ class TimeTrackingPanel extends PluginPanel private TabContentPanel activeTabPanel = null; TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, - FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker) + FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager) { super(false); @@ -79,6 +81,7 @@ class TimeTrackingPanel extends PluginPanel add(tabGroup, BorderLayout.NORTH); add(display, BorderLayout.CENTER); + addTab(Tab.CLOCK, clockManager.getClockTabPanel()); addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel()); for (Tab tab : Tab.FARMING_TABS) @@ -130,6 +133,17 @@ class TimeTrackingPanel extends PluginPanel } } + /** + * Gets the update interval of the active tab panel, in units of 200 milliseconds. + */ + int getUpdateInterval() + { + return activeTabPanel == null ? Integer.MAX_VALUE : activeTabPanel.getUpdateInterval(); + } + + /** + * Updates the active tab panel, if this plugin panel is displayed. + */ void update() { if (!active || activeTabPanel == null) @@ -137,7 +151,7 @@ class TimeTrackingPanel extends PluginPanel return; } - activeTabPanel.update(); + SwingUtilities.invokeLater(activeTabPanel::update); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 36a35afe26..1eba76a304 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -28,13 +28,17 @@ package net.runelite.client.plugins.timetracking; import com.google.common.eventbus.Subscribe; import com.google.inject.Provides; import java.awt.image.BufferedImage; +import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; -import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.GameTick; import net.runelite.api.events.UsernameChanged; import net.runelite.api.widgets.Widget; @@ -43,6 +47,10 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; +import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.CONFIG_GROUP; +import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.STOPWATCHES; +import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.TIMERS; +import net.runelite.client.plugins.timetracking.clocks.ClockManager; import net.runelite.client.plugins.timetracking.farming.FarmingTracker; import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker; import net.runelite.client.task.Schedule; @@ -52,8 +60,8 @@ import net.runelite.client.util.ImageUtil; @PluginDescriptor( name = "Time Tracking", - description = "Enable the Time Tracking panel, which contains farming and bird house trackers", - tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "panel"} + description = "Enable the Time Tracking panel, which contains timers, stopwatches, and farming and bird house trackers", + tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "stopwatches", "timers", "panel"} ) @Slf4j public class TimeTrackingPlugin extends Plugin @@ -70,12 +78,20 @@ public class TimeTrackingPlugin extends Plugin @Inject private BirdHouseTracker birdHouseTracker; + @Inject + private ClockManager clockManager; + @Inject private ItemManager itemManager; @Inject private TimeTrackingConfig config; + @Inject + private ScheduledExecutorService executorService; + + private ScheduledFuture panelUpdateFuture; + private TimeTrackingPanel panel; private NavigationButton navButton; @@ -92,11 +108,13 @@ public class TimeTrackingPlugin extends Plugin @Override protected void startUp() throws Exception { + clockManager.loadTimers(); + clockManager.loadStopwatches(); birdHouseTracker.loadFromConfig(); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); - panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker); + panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager); navButton = NavigationButton.builder() .tooltip("Time Tracking") @@ -107,7 +125,7 @@ public class TimeTrackingPlugin extends Plugin clientToolbar.addNavigation(navButton); - updatePanel(); + panelUpdateFuture = executorService.scheduleAtFixedRate(this::updatePanel, 200, 200, TimeUnit.MILLISECONDS); } @Override @@ -115,9 +133,34 @@ public class TimeTrackingPlugin extends Plugin { lastTickLocation = null; lastTickPostLogin = false; + + if (panelUpdateFuture != null) + { + panelUpdateFuture.cancel(true); + panelUpdateFuture = null; + } + clientToolbar.removeNavigation(navButton); } + @Subscribe + public void onConfigChanged(ConfigChanged e) + { + if (!e.getGroup().equals(CONFIG_GROUP)) + { + return; + } + + if (clockManager.getTimers().isEmpty() && e.getKey().equals(TIMERS)) + { + clockManager.loadTimers(); + } + else if (clockManager.getStopwatches().isEmpty() && e.getKey().equals(STOPWATCHES)) + { + clockManager.loadStopwatches(); + } + } + @Subscribe public void onGameTick(GameTick t) { @@ -154,7 +197,7 @@ public class TimeTrackingPlugin extends Plugin if (birdHouseDataChanged || farmingDataChanged) { - updatePanel(); + panel.update(); } } @@ -163,7 +206,7 @@ public class TimeTrackingPlugin extends Plugin { farmingTracker.migrateConfiguration(); birdHouseTracker.loadFromConfig(); - updatePanel(); + panel.update(); } @Schedule(period = 10, unit = ChronoUnit.SECONDS) @@ -173,13 +216,24 @@ public class TimeTrackingPlugin extends Plugin if (birdHouseDataChanged) { - updatePanel(); + panel.update(); } } - @Schedule(period = 10, unit = ChronoUnit.SECONDS) - public void updatePanel() + private void updatePanel() { - SwingUtilities.invokeLater(panel::update); + long unitTime = Instant.now().toEpochMilli() / 200; + + boolean clockDataChanged = false; + + if (unitTime % 5 == 0) + { + clockDataChanged = clockManager.checkCompletion(); + } + + if (unitTime % panel.getUpdateInterval() == 0 || clockDataChanged) + { + panel.update(); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java new file mode 100644 index 0000000000..31300069c8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +abstract class Clock +{ + protected String name; + + // last updated time (recorded as seconds since epoch) + protected long lastUpdate; + + // whether the clock is currently running + protected boolean active; + + Clock(String name) + { + this.name = name; + this.lastUpdate = Instant.now().getEpochSecond(); + this.active = false; + } + + abstract long getDisplayTime(); + + abstract void setDuration(long duration); + + abstract boolean start(); + + abstract boolean pause(); + + abstract void reset(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java new file mode 100644 index 0000000000..5fc68fd375 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Singleton; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.inject.Inject; +import javax.swing.SwingUtilities; +import joptsimple.internal.Strings; +import lombok.Getter; +import net.runelite.client.Notifier; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.timetracking.TimeTrackingConfig; + +@Singleton +public class ClockManager +{ + private static final long DEFAULT_TIMER_DURATION = 60 * 5; // 5 minutes + + @Inject + private ConfigManager configManager; + + @Inject + private TimeTrackingConfig config; + + @Inject + private Notifier notifier; + + @Getter + private final List timers = new CopyOnWriteArrayList<>(); + + @Getter + private final List stopwatches = new ArrayList<>(); + + @Getter + private ClockTabPanel clockTabPanel = new ClockTabPanel(this); + + void addTimer() + { + timers.add(new Timer("Timer " + (timers.size() + 1), DEFAULT_TIMER_DURATION)); + saveTimers(); + + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + void addStopwatch() + { + stopwatches.add(new Stopwatch("Stopwatch " + (stopwatches.size() + 1))); + saveStopwatches(); + + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + void removeTimer(Timer timer) + { + timers.remove(timer); + saveTimers(); + + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + void removeStopwatch(Stopwatch stopwatch) + { + stopwatches.remove(stopwatch); + saveStopwatches(); + + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + /** + * Checks if any timers have completed, and send notifications if required. + */ + public boolean checkCompletion() + { + boolean changed = false; + + for (Timer timer : timers) + { + if (timer.isActive() && timer.getDisplayTime() == 0) + { + timer.pause(); + changed = true; + + if (config.timerNotification()) + { + notifier.notify("[" + timer.getName() + "] has finished counting down."); + } + } + } + + if (changed) + { + saveTimers(); + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + return changed; + } + + public void loadTimers() + { + final String timersJson = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.TIMERS); + + if (!Strings.isNullOrEmpty(timersJson)) + { + final Gson gson = new Gson(); + final List timers = gson.fromJson(timersJson, new TypeToken>() + { + }.getType()); + + this.timers.clear(); + this.timers.addAll(timers); + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + } + + public void loadStopwatches() + { + final String stopwatchesJson = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.STOPWATCHES); + + if (!Strings.isNullOrEmpty(stopwatchesJson)) + { + final Gson gson = new Gson(); + final List stopwatches = gson.fromJson(stopwatchesJson, new TypeToken>() + { + }.getType()); + + this.stopwatches.clear(); + this.stopwatches.addAll(stopwatches); + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + } + + public void clear() + { + timers.clear(); + stopwatches.clear(); + + SwingUtilities.invokeLater(clockTabPanel::rebuild); + } + + void saveToConfig() + { + saveTimers(); + saveStopwatches(); + } + + void saveTimers() + { + final Gson gson = new Gson(); + final String json = gson.toJson(timers); + configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.TIMERS, json); + } + + void saveStopwatches() + { + final Gson gson = new Gson(); + final String json = gson.toJson(stopwatches); + configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.STOPWATCHES, json); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java new file mode 100644 index 0000000000..3b46a6c22b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import lombok.Getter; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.components.FlatTextField; +import net.runelite.client.ui.components.IconButton; + +abstract class ClockPanel extends JPanel +{ + private static final Border NAME_BOTTOM_BORDER = new CompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR), + BorderFactory.createLineBorder(ColorScheme.DARKER_GRAY_COLOR)); + + private static final Color ACTIVE_CLOCK_COLOR = ColorScheme.LIGHT_GRAY_COLOR.brighter(); + private static final Color INACTIVE_CLOCK_COLOR = ColorScheme.LIGHT_GRAY_COLOR.darker(); + + // additional content or buttons should be added to these panels in the subclasses + final JPanel contentContainer; + final JPanel leftActions; + final JPanel rightActions; + + private final FlatTextField nameInput; + private final IconButton startPauseButton; + private final FlatTextField displayInput; + + @Getter + private final Clock clock; + + private final String clockType; + private final boolean editable; + + ClockPanel(ClockManager clockManager, Clock clock, String clockType, boolean editable) + { + this.clock = clock; + this.clockType = clockType; + this.editable = editable; + + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(3, 0, 0, 0)); + + JPanel nameWrapper = new JPanel(new BorderLayout()); + nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR); + nameWrapper.setBorder(NAME_BOTTOM_BORDER); + + nameInput = new FlatTextField(); + nameInput.setText(clock.getName()); + nameInput.setBorder(null); + nameInput.setBackground(ColorScheme.DARKER_GRAY_COLOR); + nameInput.setPreferredSize(new Dimension(0, 24)); + nameInput.getTextField().setBorder(new EmptyBorder(0, 8, 0, 0)); + nameInput.addActionListener(e -> getParent().requestFocusInWindow()); + + nameInput.getTextField().addFocusListener(new FocusListener() + { + @Override + public void focusGained(FocusEvent e) + { + nameInput.getTextField().selectAll(); + } + + @Override + public void focusLost(FocusEvent e) + { + clock.setName(nameInput.getText()); + clockManager.saveToConfig(); + } + }); + + nameWrapper.add(nameInput, BorderLayout.CENTER); + + JPanel mainContainer = new JPanel(new BorderLayout()); + mainContainer.setBorder(new EmptyBorder(5, 0, 0, 0)); + mainContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + contentContainer = new JPanel(new BorderLayout()); + contentContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + displayInput = new FlatTextField(); + displayInput.setEditable(editable); + displayInput.setBorder(null); + displayInput.setBackground(ColorScheme.DARKER_GRAY_COLOR); + displayInput.setPreferredSize(new Dimension(0, 24)); + displayInput.getTextField().setHorizontalAlignment(SwingConstants.CENTER); + displayInput.addActionListener(e -> getParent().requestFocusInWindow()); + + displayInput.getTextField().addFocusListener(new FocusListener() + { + @Override + public void focusGained(FocusEvent e) + { + displayInput.getTextField().setForeground(INACTIVE_CLOCK_COLOR); + displayInput.getTextField().selectAll(); + } + + @Override + public void focusLost(FocusEvent e) + { + String[] parts = displayInput.getText().split(":"); + long duration = 0; + + // parse from back to front, so as to accept hour:min:sec, min:sec, and sec formats + for (int i = parts.length - 1, multiplier = 1; i >= 0 && multiplier <= 3600; i--, multiplier *= 60) + { + try + { + duration += Integer.parseInt(parts[i].trim()) * multiplier; + } + catch (NumberFormatException nfe) + { + // ignored + } + } + + clock.setDuration(Math.max(0, duration)); + clock.reset(); + updateDisplayInput(); + updateActivityStatus(); + clockManager.saveTimers(); + } + }); + + updateDisplayInput(); + + contentContainer.add(displayInput, BorderLayout.NORTH); + + JPanel actionsBar = new JPanel(new BorderLayout()); + actionsBar.setBorder(new EmptyBorder(4, 0, 4, 0)); + actionsBar.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + leftActions = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); + leftActions.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + startPauseButton = new IconButton(ClockTabPanel.START_ICON); + startPauseButton.setPreferredSize(new Dimension(16, 14)); + updateActivityStatus(); + + startPauseButton.addMouseListener(new MouseAdapter() + { + @Override + public void mouseEntered(MouseEvent e) + { + startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON_HOVER : ClockTabPanel.START_ICON_HOVER); + } + + @Override + public void mouseExited(MouseEvent e) + { + startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON); + } + }); + + startPauseButton.addActionListener(e -> + { + if (clock.isActive()) + { + clock.pause(); + } + else if (!clock.start()) + { + return; + } + + updateActivityStatus(); + clockManager.saveToConfig(); + }); + + IconButton resetButton = new IconButton(ClockTabPanel.RESET_ICON, ClockTabPanel.RESET_ICON_HOVER); + resetButton.setPreferredSize(new Dimension(16, 14)); + resetButton.setToolTipText("Reset " + clockType); + + resetButton.addActionListener(e -> + { + clock.reset(); + reset(); + clockManager.saveToConfig(); + }); + + leftActions.add(startPauseButton); + leftActions.add(resetButton); + + rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0)); + rightActions.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + actionsBar.add(leftActions, BorderLayout.WEST); + actionsBar.add(rightActions, BorderLayout.EAST); + + mainContainer.add(contentContainer, BorderLayout.CENTER); + mainContainer.add(actionsBar, BorderLayout.SOUTH); + + add(nameWrapper, BorderLayout.NORTH); + add(mainContainer, BorderLayout.CENTER); + } + + void reset() + { + updateDisplayInput(); + updateActivityStatus(); + } + + void updateDisplayInput() + { + if (!displayInput.getTextField().hasFocus()) + { + displayInput.setText(getFormattedDuration(clock.getDisplayTime())); + } + } + + void updateActivityStatus() + { + boolean isActive = clock.isActive(); + + displayInput.setEditable(editable && !isActive); + displayInput.getTextField().setForeground(isActive ? ACTIVE_CLOCK_COLOR : INACTIVE_CLOCK_COLOR); + startPauseButton.setToolTipText(isActive ? "Pause " + clockType : "Start " + clockType); + startPauseButton.setIcon(isActive ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON); + + if (editable && clock.getDisplayTime() == 0 && !isActive) + { + displayInput.getTextField().setForeground(ColorScheme.PROGRESS_ERROR_COLOR.darker()); + } + } + + static String getFormattedDuration(long duration) + { + long hours = duration / (60 * 60); + long mins = (duration / 60) % 60; + long seconds = duration % 60; + + return String.format("%02d:%02d:%02d", hours, mins, seconds); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java new file mode 100644 index 0000000000..9837155eef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import net.runelite.client.plugins.timetracking.TabContentPanel; +import net.runelite.client.plugins.timetracking.TimeTrackingPlugin; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.DynamicGridLayout; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.IconButton; +import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; +import net.runelite.client.util.ImageUtil; + +public class ClockTabPanel extends TabContentPanel +{ + static final ImageIcon DELETE_ICON; + static final ImageIcon DELETE_ICON_HOVER; + static final ImageIcon LAP_ICON; + static final ImageIcon LAP_ICON_HOVER; + static final ImageIcon PAUSE_ICON; + static final ImageIcon PAUSE_ICON_HOVER; + static final ImageIcon RESET_ICON; + static final ImageIcon RESET_ICON_HOVER; + static final ImageIcon START_ICON; + static final ImageIcon START_ICON_HOVER; + + private static final ImageIcon ADD_ICON; + private static final ImageIcon ADD_ICON_HOVER; + + private final ClockManager clockManager; + + private final List clockPanels = new ArrayList<>(); + + static + { + BufferedImage deleteIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "delete_icon.png"); + BufferedImage lapIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "lap_icon.png"); + BufferedImage pauseIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "pause_icon.png"); + BufferedImage resetIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "reset_icon.png"); + BufferedImage startIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "start_icon.png"); + BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "add_icon.png"); + + DELETE_ICON = new ImageIcon(deleteIcon); + DELETE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(deleteIcon, -80)); + LAP_ICON = new ImageIcon(lapIcon); + LAP_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(lapIcon, -80)); + PAUSE_ICON = new ImageIcon(pauseIcon); + PAUSE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(pauseIcon, -80)); + RESET_ICON = new ImageIcon(resetIcon); + RESET_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(resetIcon, -80)); + START_ICON = new ImageIcon(startIcon); + START_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(startIcon, -80)); + ADD_ICON = new ImageIcon(addIcon); + ADD_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f)); + } + + ClockTabPanel(ClockManager clockManager) + { + this.clockManager = clockManager; + + setLayout(new DynamicGridLayout(0, 1, 0, 4)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + rebuild(); + } + + /** + * Clears and recreates the components of this panel. + * This should be done whenever a clock is added or removed. + */ + void rebuild() + { + removeAll(); + clockPanels.clear(); + + add(createHeaderPanel("Timers", "timer", false, e -> clockManager.addTimer())); + + for (Timer timer : clockManager.getTimers()) + { + TimerPanel panel = new TimerPanel(clockManager, timer); + + clockPanels.add(panel); + add(panel); + } + + if (clockManager.getTimers().isEmpty()) + { + add(createInfoPanel("Click the + button to add a timer.")); + } + + add(createHeaderPanel("Stopwatches", "stopwatch", true, e -> clockManager.addStopwatch())); + + for (Stopwatch stopwatch : clockManager.getStopwatches()) + { + StopwatchPanel panel = new StopwatchPanel(clockManager, stopwatch); + + clockPanels.add(panel); + add(panel); + } + + if (clockManager.getStopwatches().isEmpty()) + { + add(createInfoPanel("Click the + button to add a stopwatch.")); + } + + revalidate(); + } + + private JPanel createHeaderPanel(String title, String type, boolean largePadding, ActionListener actionListener) + { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(new EmptyBorder(largePadding ? 11 : 0, 0, 0, 0)); + panel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel headerLabel = new JLabel(title); + headerLabel.setFont(FontManager.getRunescapeSmallFont()); + panel.add(headerLabel, BorderLayout.CENTER); + + IconButton addButton = new IconButton(ADD_ICON, ADD_ICON_HOVER); + addButton.setPreferredSize(new Dimension(14, 14)); + addButton.setToolTipText("Add a " + type); + addButton.addActionListener(actionListener); + panel.add(addButton, BorderLayout.EAST); + + return panel; + } + + private JPanel createInfoPanel(String text) + { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(new EmptyBorder(7, 8, 6, 8)); + panel.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel infoLabel = new JShadowedLabel(text); + infoLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR.darker()); + infoLabel.setFont(FontManager.getRunescapeSmallFont()); + panel.add(infoLabel); + + return panel; + } + + @Override + public int getUpdateInterval() + { + return 1; // 200 milliseconds + } + + @Override + public void update() + { + for (ClockPanel panel : clockPanels) + { + if (panel.getClock().isActive()) + { + panel.updateDisplayInput(); + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java new file mode 100644 index 0000000000..5e12f58901 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +class Stopwatch extends Clock +{ + // the number of seconds elapsed, as of last updated time + private long elapsed = 0; + + // a list of lap times (recorded as seconds since epoch) + private List laps = new ArrayList<>(); + + Stopwatch(String name) + { + super(name); + } + + @Override + long getDisplayTime() + { + if (!active) + { + return elapsed; + } + + return Math.max(0, elapsed + (Instant.now().getEpochSecond() - lastUpdate)); + } + + @Override + void setDuration(long duration) + { + elapsed = duration; + } + + @Override + boolean start() + { + if (!active) + { + lastUpdate = Instant.now().getEpochSecond(); + active = true; + return true; + } + + return false; + } + + @Override + boolean pause() + { + if (active) + { + active = false; + elapsed = Math.max(0, elapsed + (Instant.now().getEpochSecond() - lastUpdate)); + lastUpdate = Instant.now().getEpochSecond(); + return true; + } + + return false; + } + + void lap() + { + laps.add(getDisplayTime()); + } + + @Override + void reset() + { + active = false; + elapsed = 0; + laps.clear(); + lastUpdate = Instant.now().getEpochSecond(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java new file mode 100644 index 0000000000..99bb8def81 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.List; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.components.IconButton; + +class StopwatchPanel extends ClockPanel +{ + private static final Color LAP_DATA_COLOR = ColorScheme.LIGHT_GRAY_COLOR.darker(); + + private final JPanel lapsContainer; + private final Stopwatch stopwatch; + + StopwatchPanel(ClockManager clockManager, Stopwatch stopwatch) + { + super(clockManager, stopwatch, "stopwatch", false); + + this.stopwatch = stopwatch; + + lapsContainer = new JPanel(new GridBagLayout()); + lapsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + rebuildLapList(); + + contentContainer.add(lapsContainer); + + IconButton lapButton = new IconButton(ClockTabPanel.LAP_ICON, ClockTabPanel.LAP_ICON_HOVER); + lapButton.setPreferredSize(new Dimension(16, 14)); + lapButton.setToolTipText("Add lap time"); + + lapButton.addActionListener(e -> + { + stopwatch.lap(); + rebuildLapList(); + clockManager.saveStopwatches(); + }); + + leftActions.add(lapButton); + + IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER); + deleteButton.setPreferredSize(new Dimension(16, 14)); + deleteButton.setToolTipText("Delete stopwatch"); + deleteButton.addActionListener(e -> clockManager.removeStopwatch(stopwatch)); + rightActions.add(deleteButton); + } + + @Override + void reset() + { + super.reset(); + rebuildLapList(); + } + + private void rebuildLapList() + { + lapsContainer.removeAll(); + + List laps = stopwatch.getLaps(); + + if (laps.isEmpty()) + { + lapsContainer.setBorder(null); + } + else + { + lapsContainer.setBorder(new EmptyBorder(5, 0, 0, 0)); + + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(4, 5, 3, 5); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 0; + c.gridy = 0; + + long previousLap = 0; + for (long lap : stopwatch.getLaps()) + { + c.gridx = 0; + lapsContainer.add(createSmallLabel("" + (c.gridy + 1)), c); + + c.gridx = 1; + lapsContainer.add(createSmallLabel(getFormattedDuration(lap - previousLap)), c); + + c.gridx = 2; + lapsContainer.add(createSmallLabel(getFormattedDuration(lap)), c); + + previousLap = lap; + c.gridy++; + } + } + + lapsContainer.revalidate(); + lapsContainer.repaint(); + } + + private JLabel createSmallLabel(String text) + { + JLabel label = new JLabel(text, SwingConstants.CENTER); + label.setFont(FontManager.getRunescapeSmallFont()); + label.setForeground(LAP_DATA_COLOR); + + return label; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java new file mode 100644 index 0000000000..d4e0de6132 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +class Timer extends Clock +{ + // the total number of seconds that the timer should run for + private long duration; + + // the number of seconds remaining on the timer, as of last updated time + private long remaining; + + Timer(String name, long duration) + { + super(name); + this.duration = duration; + this.remaining = duration; + } + + @Override + long getDisplayTime() + { + if (!active) + { + return remaining; + } + + return Math.max(0, remaining - (Instant.now().getEpochSecond() - lastUpdate)); + } + + @Override + boolean start() + { + if (!active && duration > 0) + { + if (remaining <= 0) + { + remaining = duration; + } + lastUpdate = Instant.now().getEpochSecond(); + active = true; + return true; + } + + return false; + } + + @Override + boolean pause() + { + if (active) + { + active = false; + remaining = Math.max(0, remaining - (Instant.now().getEpochSecond() - lastUpdate)); + lastUpdate = Instant.now().getEpochSecond(); + return true; + } + + return false; + } + + @Override + void reset() + { + active = false; + remaining = duration; + lastUpdate = Instant.now().getEpochSecond(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java new file mode 100644 index 0000000000..9f2bf047f8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 OWNER 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. + */ +package net.runelite.client.plugins.timetracking.clocks; + +import java.awt.Dimension; +import net.runelite.client.ui.components.IconButton; + +class TimerPanel extends ClockPanel +{ + TimerPanel(ClockManager clockManager, Timer timer) + { + super(clockManager, timer, "timer", true); + + IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER); + deleteButton.setPreferredSize(new Dimension(16, 14)); + deleteButton.setToolTipText("Delete timer"); + deleteButton.addActionListener(e -> clockManager.removeTimer(timer)); + rightActions.add(deleteButton); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 652dc3606f..429ef56ef5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -120,6 +120,12 @@ public class FarmingTabPanel extends TabContentPanel } + @Override + public int getUpdateInterval() + { + return 50; // 10 seconds + } + @Override public void update() { @@ -128,7 +134,7 @@ public class FarmingTabPanel extends TabContentPanel boolean autoweed; { - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername(); autoweed = Integer.toString(Autoweed.ON.ordinal()) .equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)); } @@ -136,7 +142,7 @@ public class FarmingTabPanel extends TabContentPanel for (TimeablePanel panel : patchPanels) { FarmingPatch patch = panel.getTimeable(); - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); String key = Integer.toString(patch.getVarbit().getId()); String storedValue = configManager.getConfiguration(group, key); long unixTime = 0; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java index ba2ce8b0d3..7d4d89b2a8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -72,7 +72,7 @@ public class FarmingTracker boolean changed = false; { - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername(); String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED)); if (!autoweed.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED))) { @@ -86,7 +86,7 @@ public class FarmingTracker { // Write config with new varbits // timetracking...=: - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + region.getRegionID(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID(); long unixNow = Instant.now().getEpochSecond(); for (Varbits varbit : region.getVarbits()) { @@ -137,7 +137,7 @@ public class FarmingTracker // migrate autoweed config { String oldGroup = OLD_KEY_NAME + "." + username; - String newGroup = TimeTrackingConfig.KEY_NAME + "." + username; + String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username; String storedValue = configManager.getConfiguration(oldGroup, TimeTrackingConfig.AUTOWEED); if (storedValue != null) @@ -151,7 +151,7 @@ public class FarmingTracker for (FarmingRegion region : farmingWorld.getRegions().values()) { String oldGroup = OLD_KEY_NAME + "." + username + "." + region.getRegionID(); - String newGroup = TimeTrackingConfig.KEY_NAME + "." + username + "." + region.getRegionID(); + String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username + "." + region.getRegionID(); for (Varbits varbit : region.getVarbits()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java index d6a46e70ef..3277938d5e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java @@ -78,6 +78,12 @@ public class BirdHouseTabPanel extends TabContentPanel } } + @Override + public int getUpdateInterval() + { + return 50; // 10 seconds + } + @Override public void update() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java index fcc10d54fe..6604813681 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java @@ -89,7 +89,7 @@ public class BirdHouseTracker { birdHouseData.clear(); - final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; + final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; for (BirdHouseSpace space : BirdHouseSpace.values()) { @@ -216,7 +216,7 @@ public class BirdHouseTracker private void saveToConfig(Map updatedData) { - final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; + final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; for (BirdHouseData data : updatedData.values()) { diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png new file mode 100644 index 0000000000..022dbff0ef Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png new file mode 100644 index 0000000000..ee9ea58a18 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png new file mode 100644 index 0000000000..6b7acbb25c Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png new file mode 100644 index 0000000000..45d8b770ac Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/reset_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/reset_icon.png new file mode 100644 index 0000000000..d439c15dc9 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/reset_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png new file mode 100644 index 0000000000..cf722905e9 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png differ