diff options
| author | Cow <cow@volloeko.de> | 2020-06-16 02:32:15 +0200 |
|---|---|---|
| committer | Cow <cow@volloeko.de> | 2020-06-16 02:32:15 +0200 |
| commit | c2c644d9b0f8a66e757b67145935b0e3c447db97 (patch) | |
| tree | 627c087f6b8c165e4fb3506ff7b559afd2318ddc /src/main/java | |
| parent | b9d6b75423ea24c4947b3a655f199c3b34aa167a (diff) | |
| download | Cowlection-c2c644d9b0f8a66e757b67145935b0e3c447db97.tar.gz Cowlection-c2c644d9b0f8a66e757b67145935b0e3c447db97.tar.bz2 Cowlection-c2c644d9b0f8a66e757b67145935b0e3c447db97.zip | |
Added `/moo search` to search through mc log files
Diffstat (limited to 'src/main/java')
12 files changed, 740 insertions, 27 deletions
diff --git a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java index 2460058..45fb791 100644 --- a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java +++ b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java @@ -44,6 +44,7 @@ public class Cowmoonication { public void preInit(FMLPreInitializationEvent e) { instance = this; logger = e.getModLog(); + modsDir = e.getSourceFile().getParentFile(); File modDir = new File(e.getModConfigurationDirectory(), MODID + File.separatorChar); if (!modDir.exists()) { @@ -54,7 +55,6 @@ public class Cowmoonication { config = new MooConfig(this, new Configuration(new File(modDir, MODID + ".cfg"))); chatHelper = new ChatHelper(); - modsDir = e.getSourceFile().getParentFile(); } @EventHandler @@ -94,7 +94,7 @@ public class Cowmoonication { return playerCache; } - public File getModsFolder() { + public File getModsDirectory() { return modsDir; } diff --git a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java index 4ad4e2c..b4d357d 100644 --- a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java +++ b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java @@ -10,6 +10,7 @@ import eu.olli.cowmoonication.config.MooGuiConfig; import eu.olli.cowmoonication.data.Friend; import eu.olli.cowmoonication.data.HySkyBlockStats; import eu.olli.cowmoonication.data.HyStalkingData; +import eu.olli.cowmoonication.search.GuiSearch; import eu.olli.cowmoonication.util.ApiUtils; import eu.olli.cowmoonication.util.MooChatComponent; import eu.olli.cowmoonication.util.TickDelay; @@ -70,6 +71,8 @@ public class MooCommand extends CommandBase { // sub-commands: miscellaneous else if (args[0].equalsIgnoreCase("config") || args[0].equalsIgnoreCase("toggle")) { new TickDelay(() -> Minecraft.getMinecraft().displayGuiScreen(new MooGuiConfig(null)), 1); // delay by 1 tick, because the chat closing would close the new gui instantly as well. + } else if (args[0].equalsIgnoreCase("search")) { + new TickDelay(() -> Minecraft.getMinecraft().displayGuiScreen(new GuiSearch()), 1); // delay by 1 tick, because the chat closing would close the new gui instantly as well. } else if (args[0].equalsIgnoreCase("guiscale")) { int currentGuiScale = (Minecraft.getMinecraft()).gameSettings.guiScale; if (args.length == 1) { @@ -105,19 +108,19 @@ public class MooCommand extends CommandBase { .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Download the latest version of " + Cowmoonication.MODNAME + "\n\u279C Click to download latest mod file"))))) .appendSibling(new ChatComponentText("\n\u278B" + EnumChatFormatting.YELLOW + " exit Minecraft").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false) .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.GOLD + "\u278B" + EnumChatFormatting.YELLOW + " Without closing Minecraft first,\n" + EnumChatFormatting.YELLOW + "you can't delete the old .jar file!"))))) - .appendSibling(new ChatComponentText("\n\u278C" + EnumChatFormatting.YELLOW + " copy " + EnumChatFormatting.GOLD + Cowmoonication.MODNAME.replace(" ", "") + "-" + main.getVersionChecker().getNewVersion() + ".jar" + EnumChatFormatting.YELLOW + " into mods folder").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false) - .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo folder")) - .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Open mods folder with command " + EnumChatFormatting.GOLD + "/moo folder\n\u279C Click to open mods folder"))))) + .appendSibling(new ChatComponentText("\n\u278C" + EnumChatFormatting.YELLOW + " copy " + EnumChatFormatting.GOLD + Cowmoonication.MODNAME.replace(" ", "") + "-" + main.getVersionChecker().getNewVersion() + ".jar" + EnumChatFormatting.YELLOW + " into mods directory").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false) + .setChatClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/moo directory")) + .setChatHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentText(EnumChatFormatting.YELLOW + "Open mods directory with command " + EnumChatFormatting.GOLD + "/moo directory\n\u279C Click to open mods directory"))))) .appendSibling(new ChatComponentText("\n\u278D" + EnumChatFormatting.YELLOW + " delete old mod file " + EnumChatFormatting.GOLD + Cowmoonication.MODNAME.replace(" ", "") + "-" + Cowmoonication.VERSION + ".jar ").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false))) .appendSibling(new ChatComponentText("\n\u278E" + EnumChatFormatting.YELLOW + " start Minecraft again").setChatStyle(new ChatStyle().setColor(EnumChatFormatting.GOLD).setBold(false)))); } else if (args[0].equalsIgnoreCase("version")) { main.getVersionChecker().handleVersionStatus(true); - } else if (args[0].equalsIgnoreCase("folder")) { + } else if (args[0].equalsIgnoreCase("directory") || args[0].equalsIgnoreCase("folder")) { try { - Desktop.getDesktop().open(main.getModsFolder()); + Desktop.getDesktop().open(main.getModsDirectory()); } catch (IOException e) { e.printStackTrace(); - throw new MooCommandException("\u2716 An error occurred trying to open the mod's folder. I guess you have to open it manually \u00af\\_(\u30c4)_/\u00af"); + throw new MooCommandException("\u2716 An error occurred trying to open the mod's directory. I guess you have to open it manually \u00af\\_(\u30c4)_/\u00af"); } } // "catch-all" remaining sub-commands @@ -379,13 +382,14 @@ public class MooCommand extends CommandBase { .appendSibling(createCmdHelpEntry("toggle", "Toggle join/leave notifications")) .appendSibling(createCmdHelpSection(2, "Miscellaneous")) .appendSibling(createCmdHelpEntry("config", "Open mod's configuration")) + .appendSibling(createCmdHelpEntry("search", "Open Minecraft log search")) .appendSibling(createCmdHelpEntry("guiScale", "Change GUI scale")) .appendSibling(createCmdHelpEntry("shrug", "\u00AF\\_(\u30C4)_/\u00AF")) // ¯\_(ツ)_/¯ .appendSibling(createCmdHelpSection(3, "Update mod")) .appendSibling(createCmdHelpEntry("update", "Check for new mod updates")) .appendSibling(createCmdHelpEntry("updateHelp", "Show mod update instructions")) .appendSibling(createCmdHelpEntry("version", "View results of last mod update check")) - .appendSibling(createCmdHelpEntry("folder", "Open Minecraft's mods folder")); + .appendSibling(createCmdHelpEntry("directory", "Open Minecraft's mods directory")); sender.addChatMessage(usage); } @@ -410,12 +414,12 @@ public class MooCommand extends CommandBase { if (args.length == 1) { return getListOfStringsMatchingLastWord(args, /* friends */ "stalk", "stalkskyblock", "skyblockstalk", "add", "remove", "list", "nameChangeCheck", "toggle", - /* miscellaneous */ "config", "guiscale", "shrug", "apikey", - /* update mod */ "update", "updateHelp", "version", "folder", + /* miscellaneous */ "config", "search", "guiscale", "shrug", "apikey", + /* update mod */ "update", "updateHelp", "version", "directory", /* help */ "help"); } else if (args.length == 2 && args[0].equalsIgnoreCase("remove")) { return getListOfStringsMatchingLastWord(args, main.getFriendsHandler().getBestFriends()); - } else if (args.length == 2 && args[0].equalsIgnoreCase("stalk")) { + } else if (args.length == 2 && args[0].toLowerCase().contains("stalk")) { // stalk & stalkskyblock return getListOfStringsMatchingLastWord(args, main.getPlayerCache().getAllNamesSorted()); } return null; diff --git a/src/main/java/eu/olli/cowmoonication/config/MooConfig.java b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java index cb852a7..4500e34 100644 --- a/src/main/java/eu/olli/cowmoonication/config/MooConfig.java +++ b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java @@ -4,28 +4,50 @@ import eu.olli.cowmoonication.Cowmoonication; import eu.olli.cowmoonication.util.Utils; import net.minecraft.client.Minecraft; import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.Util; +import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.FMLConfigGuiFactory; import net.minecraftforge.fml.client.event.ConfigChangedEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import java.io.File; +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; +/** + * Mod configuration via ingame gui + * <p> + * Based on <a href="https://github.com/TheGreyGhost/MinecraftByExample/blob/1-8-9final/src/main/java/minecraftbyexample/mbe70_configuration/MBEConfiguration.java">TheGreyGhost's MinecraftByExample</a> + * + * @see ForgeModContainer + * @see FMLConfigGuiFactory + */ public class MooConfig { + public static final String CATEGORY_LOGS_SEARCH = "logssearch"; + // main config public static boolean doUpdateCheck; public static boolean showBestFriendNotifications; public static boolean showFriendNotifications; public static boolean showGuildNotifications; public static String[] tabCompletableNamesCommands; + // logs search config + public static String[] logsDirs; + private static String defaultStartDate; + // other stuff public static String moo; private static Configuration cfg = null; private final Cowmoonication main; private List<String> propOrderGeneral; + private List<String> propOrderLogsSearch; public MooConfig(Cowmoonication main, Configuration configuration) { this.main = main; @@ -52,7 +74,7 @@ public class MooConfig { /** * Save the GUI-altered values to disk */ - private void syncFromGUI() { + private void syncFromGui() { syncConfig(false, true); } @@ -63,6 +85,22 @@ public class MooConfig { syncConfig(false, false); } + public static LocalDate calculateStartDate() { + try { + // date format: yyyy-mm-dd + return LocalDate.parse(defaultStartDate); + } catch (DateTimeParseException e) { + // fallthrough + } + try { + int months = Integer.parseInt(defaultStartDate); + return LocalDate.now().minus(months, ChronoUnit.MONTHS); + } catch (NumberFormatException e) { + // default: 1 month + return LocalDate.now().minus(1, ChronoUnit.MONTHS); + } + } + /** * Synchronise the three copies of the data * 1) loadConfigFromFile && readFieldsFromConfig -> initialise everything from the disk file @@ -76,6 +114,8 @@ public class MooConfig { if (loadConfigFromFile) { cfg.load(); } + + // config section: main configuration propOrderGeneral = new ArrayList<>(); Property propDoUpdateCheck = addConfigEntry(cfg.get(Configuration.CATEGORY_CLIENT, @@ -94,11 +134,24 @@ public class MooConfig { cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral); + // config section: log files search + propOrderLogsSearch = new ArrayList<>(); + + Property propLogsDirs = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, + "logsDirs", resolveDefaultLogsDirs(), + "Directories with Minecraft log files"), true, CATEGORY_LOGS_SEARCH); + Property propDefaultStartDate = addConfigEntry(cfg.get(CATEGORY_LOGS_SEARCH, + "defaultStartDate", "3", "Default start date (a number means X months ago, alternatively a fixed date à la yyyy-mm-dd can be used)"), true) + .setValidationPattern(Pattern.compile("^[1-9][0-9]{0,2}|(2[0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01]))$")); + + cfg.setCategoryPropertyOrder(CATEGORY_LOGS_SEARCH, propOrderLogsSearch); + // 'manual' replacement for propTabCompletableNamesCommands.hasChanged() boolean modifiedTabCompletableCommandsList = false; String[] tabCompletableCommandsPreChange = tabCompletableNamesCommands != null ? tabCompletableNamesCommands.clone() : null; if (readFieldsFromConfig) { + // main config doUpdateCheck = propDoUpdateCheck.getBoolean(); showBestFriendNotifications = propShowBestFriendNotifications.getBoolean(); showFriendNotifications = propShowFriendNotifications.getBoolean(); @@ -106,11 +159,16 @@ public class MooConfig { tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList(); moo = propMoo.getString(); + // logs search config + logsDirs = propLogsDirs.getStringList(); + defaultStartDate = propDefaultStartDate.getString().trim(); + if (!Arrays.equals(tabCompletableCommandsPreChange, tabCompletableNamesCommands)) { modifiedTabCompletableCommandsList = true; } } + // main config propDoUpdateCheck.set(doUpdateCheck); propShowBestFriendNotifications.set(showBestFriendNotifications); propShowFriendNotifications.set(showFriendNotifications); @@ -118,6 +176,10 @@ public class MooConfig { propTabCompletableNamesCommands.set(tabCompletableNamesCommands); propMoo.set(moo); + // logs search config + propLogsDirs.set(logsDirs); + propDefaultStartDate.set(defaultStartDate); + if (cfg.hasChanged()) { if (modifiedTabCompletableCommandsList && Minecraft.getMinecraft().thePlayer != null) { main.getChatHelper().sendMessage(EnumChatFormatting.RED, "Added or removed commands with tab-completable usernames take effect after a game restart!"); @@ -126,16 +188,59 @@ public class MooConfig { } } - private Property addConfigEntry(Property property, boolean showInGui) { + private Property addConfigEntry(Property property, boolean showInGui, String category) { if (showInGui) { property.setLanguageKey(Cowmoonication.MODID + ".config." + property.getName()); } else { property.setShowInGui(false); } - propOrderGeneral.add(property.getName()); + + if (CATEGORY_LOGS_SEARCH.equals(category)) { + propOrderLogsSearch.add(property.getName()); + } else { + // == Configuration.CATEGORY_CLIENT: + propOrderGeneral.add(property.getName()); + } return property; } + private Property addConfigEntry(Property property, boolean showInGui) { + return addConfigEntry(property, showInGui, Configuration.CATEGORY_CLIENT); + } + + /** + * Tries to find/resolve default directories containing minecraft logfiles (in .log.gz format) + * + * @return list of /logs/ directories + */ + private String[] resolveDefaultLogsDirs() { + List<String> logsDirs = new ArrayList<>(); + File currentMcLogsDirFile = new File(Minecraft.getMinecraft().mcDataDir, "logs"); + if (currentMcLogsDirFile.exists() && currentMcLogsDirFile.isDirectory()) { + String currentMcLogsDir = currentMcLogsDirFile.getAbsolutePath(); + logsDirs.add(currentMcLogsDir); + } + + String defaultMcLogsDir = System.getProperty("user.home"); + Util.EnumOS osType = Util.getOSType(); + // default directories for .minecraft: https://minecraft.gamepedia.com/.minecraft + switch (osType) { + case WINDOWS: + defaultMcLogsDir += "\\AppData\\Roaming\\.minecraft\\logs"; + break; + case OSX: + defaultMcLogsDir += "/Library/Application Support/minecraft/logs"; + break; + default: + defaultMcLogsDir += "/.minecraft/logs"; + } + File defaultMcLogsDirFile = new File(defaultMcLogsDir); + if (defaultMcLogsDirFile.exists() && defaultMcLogsDirFile.isDirectory() && !currentMcLogsDirFile.equals(defaultMcLogsDirFile)) { + logsDirs.add(defaultMcLogsDirFile.getAbsolutePath()); + } + return logsDirs.toArray(new String[]{}); + } + /** * Should login/logout notifications be modified and thus monitored? * @@ -149,7 +254,7 @@ public class MooConfig { @SubscribeEvent(priority = EventPriority.NORMAL) public void onEvent(ConfigChangedEvent.OnConfigChangedEvent e) { if (Cowmoonication.MODID.equals(e.modID)) { - syncFromGUI(); + syncFromGui(); } } } diff --git a/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java b/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java index 4583dc9..7807e03 100644 --- a/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java +++ b/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java @@ -1,33 +1,80 @@ package eu.olli.cowmoonication.config; import eu.olli.cowmoonication.Cowmoonication; +import eu.olli.cowmoonication.search.GuiTooltip; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.common.config.ConfigElement; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.fml.client.config.GuiConfig; +import net.minecraftforge.fml.client.config.GuiConfigEntries; +import net.minecraftforge.fml.client.config.IConfigElement; +import org.apache.commons.lang3.reflect.FieldUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; public class MooGuiConfig extends GuiConfig { + private GuiTooltip defaultStartDateTooltip; + private GuiTextField textFieldDefaultStartDate; + private String defaultStartDateTooltipText; + public MooGuiConfig(GuiScreen parent) { super(parent, - new ConfigElement(MooConfig.getConfig().getCategory(Configuration.CATEGORY_CLIENT)).getChildElements(), + getConfigElements(), Cowmoonication.MODID, false, false, - "Configuration for " + Cowmoonication.MODNAME); - titleLine2 = MooConfig.getConfig().getConfigFile().getAbsolutePath(); + EnumChatFormatting.BOLD + "Configuration for " + Cowmoonication.MODNAME); + titleLine2 = EnumChatFormatting.GRAY + MooConfig.getConfig().getConfigFile().getAbsolutePath(); + } + + private static List<IConfigElement> getConfigElements() { + List<IConfigElement> list = new ArrayList<>(new ConfigElement(MooConfig.getConfig().getCategory(Configuration.CATEGORY_CLIENT)).getChildElements()); + list.addAll(new ConfigElement(MooConfig.getConfig().getCategory(MooConfig.CATEGORY_LOGS_SEARCH)).getChildElements()); + return list; } @Override public void initGui() { super.initGui(); // optional: add buttons and initialize fields + for (GuiConfigEntries.IConfigEntry configEntry : entryList.listEntries) { + if ("defaultStartDate".equals(configEntry.getName()) && configEntry instanceof GuiConfigEntries.StringEntry) { + GuiConfigEntries.StringEntry entry = (GuiConfigEntries.StringEntry) configEntry; + defaultStartDateTooltipText = I18n.format(configEntry.getConfigElement().getLanguageKey() + ".tooltip"); + try { + textFieldDefaultStartDate = (GuiTextField) FieldUtils.readField(entry, "textFieldValue", true); + defaultStartDateTooltip = null; + } catch (IllegalAccessException e) { + // wasn't able to access textField, abort drawing tooltip + return; + } + } + } } @Override public void drawScreen(int mouseX, int mouseY, float partialTicks) { super.drawScreen(mouseX, mouseY, partialTicks); // optional: create animations, draw additional elements, etc. + + // add tooltip to defaultStartDate textField + if (textFieldDefaultStartDate != null) { + if (defaultStartDateTooltip == null) { + if (textFieldDefaultStartDate.yPosition == 0) { + return; + } + // create GuiTooltip here instead in initGui because y-position of textField is 0 inside initGui + defaultStartDateTooltip = new GuiTooltip(textFieldDefaultStartDate, Arrays.asList(defaultStartDateTooltipText.split("\\\\n"))); + } else if (defaultStartDateTooltip.checkHover(mouseX, mouseY)) { + drawHoveringText(defaultStartDateTooltip.getText(), mouseX, mouseY, fontRendererObj); + } + } } @Override diff --git a/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java index 9ec469e..8d714a1 100644 --- a/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java +++ b/src/main/java/eu/olli/cowmoonication/handler/FriendsHandler.java @@ -148,9 +148,13 @@ public class FriendsHandler { private void loadBestFriends() { try { + boolean createdNewFile = this.bestFriendsFile.createNewFile(); + this.bestFriends.clear(); - String bestFriendsData = FileUtils.readFileToString(this.bestFriendsFile, StandardCharsets.UTF_8); - this.bestFriends.addAll(parseJson(bestFriendsData)); + if (!createdNewFile) { + String bestFriendsData = FileUtils.readFileToString(this.bestFriendsFile, StandardCharsets.UTF_8); + this.bestFriends.addAll(parseJson(bestFriendsData)); + } } catch (IOException e) { main.getLogger().error("Couldn't read best friends file " + this.bestFriendsFile, e); } catch (JsonParseException e) { diff --git a/src/main/java/eu/olli/cowmoonication/search/GuiDateField.java b/src/main/java/eu/olli/cowmoonication/search/GuiDateField.java new file mode 100644 index 0000000..136e4ac --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/search/GuiDateField.java @@ -0,0 +1,37 @@ +package eu.olli.cowmoonication.search; + +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiTextField; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +class GuiDateField extends GuiTextField { + GuiDateField(int componentId, FontRenderer fontrendererObj, int x, int y, int width, int height) { + super(componentId, fontrendererObj, x, y, width, height); + } + + LocalDate getDate() { + try { + return LocalDate.parse(this.getText()); + } catch (DateTimeParseException e) { + return LocalDate.now(); + } + } + + boolean validateDate() { + try { + LocalDate localDate = LocalDate.parse(this.getText()); + if (localDate.isAfter(LocalDate.now()) || localDate.isBefore(LocalDate.ofYearDay(2009, 1))) { + // searching for things written in the future isn't possible (yet). It is also not possible to perform a search before the existence of mc. + setTextColor(0xFFFF3333); + return false; + } + } catch (DateTimeParseException e) { + setTextColor(0xFFFF3333); + return false; + } + setTextColor(0xFFFFFF); + return true; + } +} diff --git a/src/main/java/eu/olli/cowmoonication/search/GuiSearch.java b/src/main/java/eu/olli/cowmoonication/search/GuiSearch.java new file mode 100644 index 0000000..b53aa8c --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/search/GuiSearch.java @@ -0,0 +1,359 @@ +package eu.olli.cowmoonication.search; + +import com.google.common.base.Joiner; +import eu.olli.cowmoonication.config.MooConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.*; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.client.GuiScrollingList; +import net.minecraftforge.fml.client.config.GuiButtonExt; +import net.minecraftforge.fml.client.config.GuiCheckBox; +import net.minecraftforge.fml.client.config.GuiUtils; +import org.lwjgl.input.Keyboard; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.*; +import java.util.concurrent.Executors; + +public class GuiSearch extends GuiScreen { + private static final String SEARCH_QUERY_PLACE_HOLDER = "Search for..."; + // data + private String searchQuery; + private boolean matchCase; + private boolean removeFormatting; + /** + * Cached results are required after resizing the client + */ + private List<String> searchResults; + private LocalDate dateStart; + private LocalDate dateEnd; + + // gui elements + private GuiButton buttonSearch; + private GuiButton buttonClose; + private GuiCheckBox checkboxMatchCase; + private GuiCheckBox checkboxRemoveFormatting; + private GuiTextField fieldSearchQuery; + private GuiDateField fieldDateStart; + private GuiDateField fieldDateEnd; + private SearchResults guiSearchResults; + private List<GuiTooltip> guiTooltips; + private boolean isSearchInProgress; + + public GuiSearch() { + this.searchQuery = SEARCH_QUERY_PLACE_HOLDER; + this.matchCase = false; + this.searchResults = new ArrayList<>(); + this.dateStart = MooConfig.calculateStartDate(); + this.dateEnd = LocalDate.now(); + } + + /** + * Adds the buttons (and other controls) to the screen in question. Called when the GUI is displayed and when the + * window resizes, the buttonList is cleared beforehand. + */ + @Override + public void initGui() { + this.guiTooltips = new ArrayList<>(); + + this.fieldSearchQuery = new GuiTextField(42, this.fontRendererObj, this.width / 2 - 100, 15, 200, 20); + this.fieldSearchQuery.setMaxStringLength(255); + this.fieldSearchQuery.setText(searchQuery); + if (SEARCH_QUERY_PLACE_HOLDER.equals(searchQuery)) { + this.fieldSearchQuery.setFocused(true); + this.fieldSearchQuery.setSelectionPos(0); + } + + // date fields + this.fieldDateStart = new GuiDateField(50, this.fontRendererObj, this.width / 2 + 110, 15, 70, 15); + this.fieldDateStart.setText(dateStart.toString()); + addTooltip(fieldDateStart, Arrays.asList(EnumChatFormatting.YELLOW + "Start date", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Format: " + EnumChatFormatting.RESET + "year-month-day")); + + this.fieldDateEnd = new GuiDateField(51, this.fontRendererObj, this.width / 2 + 110, 35, 70, 15); + this.fieldDateEnd.setText(dateEnd.toString()); + addTooltip(fieldDateEnd, Arrays.asList(EnumChatFormatting.YELLOW + "End date", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Format: " + EnumChatFormatting.RESET + "year-month-day")); + + // buttons + this.buttonList.add(this.buttonClose = new GuiButtonExt(0, this.width - 25, 3, 22, 20, EnumChatFormatting.RED + "X")); + addTooltip(buttonClose, Arrays.asList(EnumChatFormatting.RED + "Close search interface", "" + EnumChatFormatting.GRAY + EnumChatFormatting.ITALIC + "Hint:" + EnumChatFormatting.RESET + " alternatively press ESC")); + + this.buttonList.add(this.checkboxMatchCase = new GuiCheckBox(1, this.width / 2 - 100, 40, " Match case", matchCase)); + addTooltip(checkboxMatchCase, Collections.singletonList(EnumChatFormatting.YELLOW + "Should the search be " + EnumChatFormatting.GOLD + "case-sensitive" + EnumChatFormatting.YELLOW + "?")); + this.buttonList.add(this.checkboxRemoveFormatting = new GuiCheckBox(1, this.width / 2 - 100, 50, " Remove formatting", removeFormatting)); + addTooltip(checkboxRemoveFormatting, Collections.singletonList(EnumChatFormatting.YELLOW + "Should " + EnumChatFormatting.GOLD + "formatting " + EnumChatFormatting.YELLOW + "and " + EnumChatFormatting.GOLD + "color codes " + EnumChatFormatting.YELLOW + "be " + EnumChatFormatting.GOLD + "removed " + EnumChatFormatting.YELLOW + "from the search results?")); + this.buttonList.add(this.buttonSearch = new GuiButtonExt(100, this.width / 2 + 40, 40, 60, 20, "Search")); + + this.guiSearchResults = new SearchResults(70); + this.guiSearchResults.setResults(searchResults); + + this.setIsSearchInProgress(isSearchInProgress); + + boolean isStartDateValid = fieldDateStart.validateDate(); + boolean isEndDateValid = fieldDateEnd.validateDate(); + this.buttonSearch.enabled = !isSearchInProgress && this.fieldSearchQuery.getText().trim().length() > 1 && !this.fieldSearchQuery.getText().startsWith(SEARCH_QUERY_PLACE_HOLDER) && isStartDateValid && isEndDateValid && !dateStart.isAfter(dateEnd); + + if (isStartDateValid && isEndDateValid && dateStart.isAfter(dateEnd)) { + fieldDateStart.setTextColor(0xFFDD3333); + fieldDateEnd.setTextColor(0xFFCC3333); + } + } + + private <T extends Gui> void addTooltip(T field, List<String> tooltip) { + GuiTooltip guiTooltip = new GuiTooltip(field, tooltip); + this.guiTooltips.add(guiTooltip); + } + + @Override + public void updateScreen() { + fieldSearchQuery.updateCursorCounter(); + fieldDateStart.updateCursorCounter(); + fieldDateEnd.updateCursorCounter(); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + // allow clicks on 'close' button even while a search is in progress + super.mouseClicked(mouseX, mouseY, mouseButton); + if (isSearchInProgress) { + // search in progress, abort + return; + } + fieldSearchQuery.mouseClicked(mouseX, mouseY, mouseButton); + fieldDateStart.mouseClicked(mouseX, mouseY, mouseButton); + fieldDateEnd.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + if (isSearchInProgress && keyCode != Keyboard.KEY_ESCAPE) { + // search in progress, don't process key typed - but allow escape to exit gui + return; + } + if (dateStart.isBefore(dateEnd)) { + fieldDateStart.setTextColor(0xFFFFFFFF); + fieldDateEnd.setTextColor(0xFFFFFFFF); + } + if (keyCode == Keyboard.KEY_RETURN && this.fieldSearchQuery.isFocused()) { + // perform search + actionPerformed(buttonSearch); + } else if (this.fieldSearchQuery.textboxKeyTyped(typedChar, keyCode)) { + searchQuery = this.fieldSearchQuery.getText(); + } else if (this.fieldDateStart.textboxKeyTyped(typedChar, keyCode)) { + if (fieldDateStart.validateDate()) { + dateStart = fieldDateStart.getDate(); + } + } else if (this.fieldDateEnd.textboxKeyTyped(typedChar, keyCode)) { + if (fieldDateEnd.validateDate()) { + dateEnd = fieldDateEnd.getDate(); + } + } else if (GuiScreen.isKeyComboCtrlA(keyCode)) { + // copy all search results + String searchResults = guiSearchResults.getAllSearchResults(); + if (!searchResults.isEmpty()) { + GuiScreen.setClipboardString(EnumChatFormatting.getTextWithoutFormattingCodes(searchResults)); + } + } else if (GuiScreen.isKeyComboCtrlC(keyCode)) { + // copy current selected entry + String selectedSearchResult = guiSearchResults.getSelectedSearchResult(); + if (selectedSearchResult != null) { + GuiScreen.setClipboardString(EnumChatFormatting.getTextWithoutFormattingCodes(selectedSearchResult)); + } + } else { + super.keyTyped(typedChar, keyCode); + } + + boolean isStartDateValid = fieldDateStart.validateDate(); + boolean isEndDateValid = fieldDateEnd.validateDate(); + this.buttonSearch.enabled = !isSearchInProgress && searchQuery.trim().length() > 1 && !searchQuery.startsWith(SEARCH_QUERY_PLACE_HOLDER) && isStartDateValid && isEndDateValid && !dateStart.isAfter(dateEnd); + + if (isStartDateValid && isEndDateValid && dateStart.isAfter(dateEnd)) { + fieldDateStart.setTextColor(0xFFDD3333); + fieldDateEnd.setTextColor(0xFFCC3333); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.drawDefaultBackground(); + this.drawCenteredString(this.fontRendererObj, EnumChatFormatting.BOLD + "Minecraft Log Search", this.width / 2, 3, 0xFFFFFF); + this.fieldSearchQuery.drawTextBox(); + this.fieldDateStart.drawTextBox(); + this.fieldDateEnd.drawTextBox(); + this.guiSearchResults.drawScreen(mouseX, mouseY, partialTicks); + + super.drawScreen(mouseX, mouseY, partialTicks); + + for (GuiTooltip guiTooltip : guiTooltips) { + if (guiTooltip.checkHover(mouseX, mouseY)) { + drawHoveringText(guiTooltip.getText(), mouseX, mouseY, 300); + // only one tooltip can be displayed at a time: break! + break; + } + } + } + + @Override + protected void actionPerformed(GuiButton button) throws IOException { + if (button == this.buttonClose && button.enabled) { + this.mc.setIngameFocus(); + } + if (isSearchInProgress || !button.enabled) { + return; + } + if (button == this.buttonSearch) { + setIsSearchInProgress(true); + + Executors.newSingleThreadExecutor().execute(() -> { + searchResults = new LogFilesSearcher().searchFor(this.fieldSearchQuery.getText(), checkboxMatchCase.isChecked(), checkboxRemoveFormatting.isChecked(), dateStart, dateEnd); + |
