From 176a596331bb843957762cffea73be466463c099 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Wed, 28 Feb 2024 01:12:51 +0900 Subject: [PATCH 1/6] Add(restore) editor gui and combinate with the window --- .../gui2/editors/GuiQuestDescEditor.java | 269 ++++++++++++++ .../client/gui2/editors/GuiQuestEditor.java | 24 +- .../client/gui2/editors/TextEditorFrame.java | 350 +++++++++++++----- .../assets/betterquesting/lang/en_us.lang | 4 + 4 files changed, 548 insertions(+), 99 deletions(-) create mode 100644 src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java diff --git a/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java b/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java new file mode 100644 index 000000000..852f6295e --- /dev/null +++ b/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java @@ -0,0 +1,269 @@ +package betterquesting.client.gui2.editors; + +import javax.annotation.Nullable; + +import org.lwjgl.input.Keyboard; + +import com.google.common.collect.Lists; + +import betterquesting.api.client.gui.misc.IVolatileScreen; +import betterquesting.api.properties.NativeProps; +import betterquesting.api.questing.IQuest; +import betterquesting.api2.client.gui.GuiScreenCanvas; +import betterquesting.api2.client.gui.controls.IPanelButton; +import betterquesting.api2.client.gui.controls.PanelButton; +import betterquesting.api2.client.gui.controls.PanelButtonStorage; +import betterquesting.api2.client.gui.controls.PanelTextField; +import betterquesting.api2.client.gui.controls.filters.FieldFilterString; +import betterquesting.api2.client.gui.events.IPEventListener; +import betterquesting.api2.client.gui.events.PEventBroadcaster; +import betterquesting.api2.client.gui.events.PanelEvent; +import betterquesting.api2.client.gui.events.types.PEventButton; +import betterquesting.api2.client.gui.misc.GuiAlign; +import betterquesting.api2.client.gui.misc.GuiPadding; +import betterquesting.api2.client.gui.misc.GuiRectangle; +import betterquesting.api2.client.gui.misc.GuiTransform; +import betterquesting.api2.client.gui.panels.CanvasTextured; +import betterquesting.api2.client.gui.panels.bars.PanelVScrollBar; +import betterquesting.api2.client.gui.panels.content.PanelTextBox; +import betterquesting.api2.client.gui.panels.lists.CanvasScrolling; +import betterquesting.api2.client.gui.themes.presets.PresetColor; +import betterquesting.api2.client.gui.themes.presets.PresetTexture; +import betterquesting.api2.utils.QuestTranslation; +import net.minecraft.util.text.TextFormatting; + +public class GuiQuestDescEditor extends GuiScreenCanvas implements IPEventListener, IVolatileScreen { + + private final int questID; + private final IQuest quest; + private final String beforeName; + private final String beforeDesc; + private String name; //TODO: Add this to GUI + private PanelTextField description; + private PanelButton close; + private @Nullable TextEditorFrame window; + + public GuiQuestDescEditor(GuiQuestEditor parent, int questID, IQuest quest) { + super(parent); + this.questID = questID; + this.quest = quest; + beforeName = quest.getProperty(NativeProps.NAME); + beforeDesc = quest.getProperty(NativeProps.DESC); + TextEditorFrame window = TextEditorFrame.get(questID); + if (window != null) { + this.window = window; + window.setGui(this); + window.toFront(); + window.requestFocus(); + //Allow just-closing(this screen) + if (close != null) + close.setEnabled(true); + name = window.getName(); + } else { + name = beforeName; + } + } + + /** + * Show the editor window. + */ + public void showWindow() { + if (window == null) + window = TextEditorFrame.getOrCreate(questID, beforeName, name, description.getRawText()); + window.setGui(this); + window.toFront(); + window.requestFocus(); + //Allow just-closing(this screen) + if (close != null) + close.setEnabled(true); + } + + /** + * Close the editor window. + */ + public void removeWindow() { + if (window == null) + return; + window.close(); + window = null; + //Disallow just-closing(this screen) + if (close != null) + close.setEnabled(false); + } + + /** + * Close the screen. + */ + public void close() { + mc.displayGuiScreen(this.parent); + } + + /** + * Save the name and the desc, and close the screen and the window. + */ + public void saveAndClose() { + quest.setProperty(NativeProps.NAME, name.trim()); + quest.setProperty(NativeProps.DESC, description.getRawText()); + GuiQuestEditor.sendChanges(questID); + removeWindow(); + close(); + } + + public void cancel() { + removeWindow(); + close(); + } + + /** + * Insert the text to the quest name. + * + * @param offset The (beginning) offset to insert. + * @param insertText The text to insert. + */ + public void insertName(int offset, String insertText) { + String text = name; + name = text.substring(0, offset) + insertText + text.substring(offset); + } + + /** + * Remove the range from the quest name. + * + * @param offset The (beginning) offset to remove. + * @param length The characters num to remove. + */ + public void removeName(int offset, int length) { + String text = name; + name = text.substring(0, offset) + text.substring(offset + length); + } + + /** + * Insert the text to the quest description. + * + * @param offset The (beginning) offset to insert. + * @param insertText The text to insert. + */ + public void insertDesc(int offset, String insertText) { + String text = description.getRawText(); + description.setText(text.substring(0, offset) + insertText + text.substring(offset)); + } + + /** + * Remove the range from the quest description. + * + * @param offset The (beginning) offset to remove. + * @param length The characters num to remove. + */ + public void removeDesc(int offset, int length) { + String text = description.getRawText(); + description.setText(text.substring(0, offset) + text.substring(offset + length)); + } + + @Override + public void initPanel() { + super.initPanel(); + + PEventBroadcaster.INSTANCE.register(this, PEventButton.class); + Keyboard.enableRepeatEvents(true); + + // Background panel + CanvasTextured cvBackground = new CanvasTextured(new GuiTransform(GuiAlign.FULL_BOX, new GuiPadding(0, 0, 0, 0), 0), + PresetTexture.PANEL_MAIN.getTexture()); + this.addPanel(cvBackground); + + close = new PanelButton(new GuiTransform(GuiAlign.BOTTOM_CENTER, -40, -16, 80, 16, 0), + 0, + QuestTranslation.translate("betterquesting.btn.edit_name_desc.just_close")); + close.setTooltip(Lists.newArrayList(QuestTranslation.translate("betterquesting.tooltip.edit_name_desc.just_close_screen"))); + close.setEnabled(window != null); + cvBackground.addPanel(close); + cvBackground.addPanel(new PanelButton(new GuiTransform(GuiAlign.BOTTOM_LEFT, 0 + 20, -16, 80, 16, 0), 1, QuestTranslation.translate("gui.cancel"))); + cvBackground.addPanel(new PanelButton(new GuiTransform(GuiAlign.BOTTOM_RIGHT, -80 - 20, -16, 80, 16, 0), 2, QuestTranslation.translate("gui.done"))); + + PanelTextBox txTitle = new PanelTextBox(new GuiTransform(GuiAlign.TOP_EDGE, new GuiPadding(0, 16, 0, -32), 0), + QuestTranslation.translate("betterquesting.title.edit_quest")).setAlignment(1); + txTitle.setColor(PresetColor.TEXT_HEADER.getColor()); + cvBackground.addPanel(txTitle); + + cvBackground.addPanel(new PanelButton(new GuiTransform(GuiAlign.TOP_RIGHT, -100 - 10, 8, 100, 16, 0), + 3, + QuestTranslation.translate("betterquesting.btn.edit_name_desc.open_window"))); + + description = new PanelTextField<>(new GuiTransform(GuiAlign.FULL_BOX, new GuiPadding(124, 32, 24, 32), 0), + description != null ? description.getRawText() : window != null ? window.getDesc() : beforeDesc, + FieldFilterString.INSTANCE); + cvBackground.addPanel(description); + description.setMaxLength(Integer.MAX_VALUE); + description.enableWrapping(true); + description.lockFocus(true); + description.setCallback(this::updateWindowDesc); + + CanvasScrolling cvFormatList = new CanvasScrolling(new GuiTransform(GuiAlign.LEFT_EDGE, new GuiPadding(16, 32, -116, 32), 0)); + cvBackground.addPanel(cvFormatList); + + TextFormatting[] tfValues = TextFormatting.values(); + for (int i = 0; i < tfValues.length; i++) { + cvFormatList.addPanel(new PanelButtonStorage<>(new GuiRectangle(0, i * 16, 100, 16), 10, tfValues[i].getFriendlyName(), tfValues[i].toString())); + } + + PanelVScrollBar scFormatScroll = new PanelVScrollBar(new GuiTransform(GuiAlign.RIGHT_EDGE, new GuiPadding(0, 0, -8, 0), 0)); + cvBackground.addPanel(scFormatScroll); + scFormatScroll.getTransform().setParent(cvFormatList.getTransform()); + cvFormatList.setScrollDriverY(scFormatScroll); + scFormatScroll.setActive(cvFormatList.getScrollBounds().getHeight() > 0); + } + + @Override + public void onGuiClosed() { + if (window != null) + window.setGui(null); + super.onGuiClosed(); + } + + @Override + public void onPanelEvent(PanelEvent event) { + if (event instanceof PEventButton) { + onButtonPress((PEventButton) event); + } + } + + @SuppressWarnings("unchecked") + private void onButtonPress(PEventButton event) { + IPanelButton btn = event.getButton(); + + switch (btn.getButtonID()) { + case 0 -> { + //Close + if (window == null) { + // Why come here? + // The button is disabled! + cancel(); + } else { + close(); + } + } + case 1 -> { + //Cancel + cancel(); + } + case 2 -> { + //Done + saveAndClose(); + } + case 3 -> { + showWindow(); + } + case 10 -> { + if (btn instanceof PanelButtonStorage) { + String format = ((PanelButtonStorage) btn).getStoredValue(); + description.writeText(format); + } + } + } + } + + private void updateWindowDesc(String value) { + if (window != null) + window.setDesc(value); + } + +} diff --git a/src/main/java/betterquesting/client/gui2/editors/GuiQuestEditor.java b/src/main/java/betterquesting/client/gui2/editors/GuiQuestEditor.java index 6995ea885..54cd045ca 100644 --- a/src/main/java/betterquesting/client/gui2/editors/GuiQuestEditor.java +++ b/src/main/java/betterquesting/client/gui2/editors/GuiQuestEditor.java @@ -27,6 +27,7 @@ import betterquesting.client.gui2.editors.nbt.GuiNbtEditor; import betterquesting.network.handlers.NetQuestEdit; import betterquesting.questing.QuestDatabase; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -155,7 +156,7 @@ public boolean onMouseClick(int mx, int my, int button) { } if (flag) { - SendChanges(); + sendChanges(questID); } return result; @@ -196,7 +197,7 @@ private void onButtonPress(PEventButton event) { { mc.displayGuiScreen(new GuiNbtEditor(this, quest.writeToNBT(new NBTTagCompound()), value -> { quest.readFromNBT(value); - SendChanges(); + sendChanges(questID); })); break; } @@ -207,7 +208,7 @@ private void onButtonPress(PEventButton event) { vis = visList[(vis.ordinal() + 1) % visList.length]; quest.setProperty(NativeProps.VISIBILITY, vis); ((PanelButton) btn).setText(QuestTranslation.translate("betterquesting.btn.show") + ": " + vis); - SendChanges(); + sendChanges(questID); break; } case 6: // Logic @@ -217,24 +218,25 @@ private void onButtonPress(PEventButton event) { logic = logicList[(logic.ordinal() + 1) % logicList.length]; quest.setProperty(NativeProps.LOGIC_TASK, logic); ((PanelButton) btn).setText(QuestTranslation.translate("betterquesting.btn.logic") + ": " + logic); - SendChanges(); + sendChanges(questID); break; } case 7: // Description Editor { - TextEditorFrame.openTextEditor(questID, quest); + mc.displayGuiScreen(new GuiQuestDescEditor(this, questID, quest)); break; } case 8: { mc.displayGuiScreen(new GuiItemSelection(this, quest.getProperty(NativeProps.ICON), value -> { quest.setProperty(NativeProps.ICON, value); - SendChanges(); + sendChanges(questID); })); } } } - private void SendChanges() { + public static void sendChanges(int questID) { + IQuest quest = QuestDatabase.INSTANCE.getValue(questID); NBTTagCompound payload = new NBTTagCompound(); NBTTagList dataList = new NBTTagList(); NBTTagCompound entry = new NBTTagCompound(); @@ -244,5 +246,13 @@ private void SendChanges() { payload.setTag("data", dataList); payload.setInteger("action", 0); NetQuestEdit.sendEdit(payload); + + GuiScreen screen = Minecraft.getMinecraft().currentScreen; + if (screen instanceof GuiQuestEditor gui) { + gui.pnTitle.setText(QuestTranslation.translate("betterquesting.title.edit_quest", QuestTranslation.translate(quest.getProperty(NativeProps.NAME)))); + gui.flName.setText(quest.getProperty(NativeProps.NAME)); + gui.flDesc.setText(quest.getProperty(NativeProps.DESC)); + } + } } diff --git a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java index aad634aba..312e14a8d 100644 --- a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java +++ b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java @@ -1,87 +1,117 @@ package betterquesting.client.gui2.editors; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.io.InputStream; + +import javax.annotation.Nullable; +import javax.imageio.ImageIO; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.border.TitledBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AbstractDocument.DefaultDocumentEvent; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.undo.UndoManager; + import betterquesting.api.properties.NativeProps; import betterquesting.api.questing.IQuest; +import betterquesting.api2.utils.QuestTranslation; import betterquesting.core.BetterQuesting; import betterquesting.core.ModReference; -import betterquesting.network.handlers.NetQuestEdit; +import betterquesting.questing.QuestDatabase; import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectMap; import net.minecraft.client.Minecraft; -import net.minecraft.client.resources.I18n; -import net.minecraft.nbt.NBTTagCompound; -import net.minecraft.nbt.NBTTagList; -import net.minecraft.util.IntHashMap; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.TextFormatting; -import org.jetbrains.annotations.Nullable; -import scala.collection.immutable.IntMap; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.border.TitledBorder; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.AbstractDocument.DefaultDocumentEvent; -import javax.swing.text.JTextComponent; -import javax.swing.undo.UndoManager; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.InputEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.io.InputStream; public class TextEditorFrame extends JFrame { + private static final int initialRowCount = 30; private static final int defaultColumns = 60; - private static final IntObjectMap opened = new IntObjectHashMap<>();// questId -> TextEditorFrame private static BufferedImage logoCache = null; - public static void openTextEditor(int questID, IQuest quest) { - if (opened.containsKey(questID)){ - TextEditorFrame frame = opened.get(questID); - frame.toFront(); - frame.requestFocus(); - Toolkit.getDefaultToolkit().beep(); - }else { - TextEditorFrame frame = new TextEditorFrame(questID, quest); - opened.put(questID, frame); - frame.requestFocus(); - } - } + // This is NOT a cache. + // This contains windows that is open. + // This makes it possible to show and remain multiple editor windows. + // questID -> TextEditorFrame + private static final IntObjectMap open = new IntObjectHashMap<>(); + + public static @Nullable TextEditorFrame get(int questID) { + return open.get(questID); + } + public static TextEditorFrame getOrCreate(int questID, String title, String name, String description) { + if (open.containsKey(questID)) + return open.get(questID); + TextEditorFrame frame = new TextEditorFrame(questID, title, name, description); + open.put(questID, frame); + return frame; + } private final int questID; - public final IQuest quest; - private final String originalName; - private final String originalDescription; + private final JTextField nameText; + private final JButton close; + private final JTextArea descText; - private final JTextField name; - private final JTextArea description; + private @Nullable GuiQuestDescEditor gui = null; - private TextEditorFrame(int questID, IQuest q) { - super("Better Questing Text Editor | " + q.getProperty(NativeProps.NAME)); + private boolean valueChangedByGuiScreen = false; + private boolean byTopCornerCloseButton = true; + + private TextEditorFrame(int questID, String title, String name, String description) { + super("Better Questing Text Editor | " + title); this.questID = questID; - quest = q; - originalName = q.getProperty(NativeProps.NAME); - originalDescription = q.getProperty(NativeProps.DESC); - if (logoCache == null) { - try (InputStream stream = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation(ModReference.MODID, "textures/items/quest_book.png")).getInputStream()) { - logoCache = ImageIO.read(stream); - } catch (Exception ex) { - BetterQuesting.logger.error(ex); - } - } + initLogoCache(); if (logoCache != null) setIconImage(logoCache); setResizable(true); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + if (byTopCornerCloseButton) { + if (gui == null) { + cancel(); + } else { + gui.cancel(); + } + } else { + open.remove(questID); + setVisible(false); + dispose(); + } + } + + }); JPanel wholePanel = new JPanel(); wholePanel.setLayout(new BoxLayout(wholePanel, BoxLayout.Y_AXIS)); @@ -92,34 +122,98 @@ private TextEditorFrame(int questID, IQuest q) { JPanel buttonPanel = add(editorPanel, new JPanel()); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); - buttonPanel.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), I18n.format("betterquesting.gui.formatting_buttons"))); + buttonPanel.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), + QuestTranslation.translate("betterquesting.gui.formatting_buttons"))); addAllFormattingCodes(buttonPanel); JPanel textPanel = add(editorPanel, new JPanel()); textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS)); - name = add(textPanel, new JTextField(originalName, defaultColumns)); - name.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), I18n.format("betterquesting.gui.name"))); - name.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); - UndoHelper.addUndoHelper(name); + nameText = add(textPanel, new JTextField(name, defaultColumns)); + nameText.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), QuestTranslation.translate("betterquesting.gui.name"))); + nameText.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); + nameText.getDocument().addDocumentListener(new DocumentListener() { - description = add(textPanel, new JTextArea(originalDescription, initialRowCount, defaultColumns)); - description.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), I18n.format("betterquesting.gui.description"))); - description.setLineWrap(true); - UndoHelper.addUndoHelper(description); + @Override + public void removeUpdate(DocumentEvent e) { + if (gui == null) + return; + if (valueChangedByGuiScreen) + return; + gui.removeName(e.getOffset(), e.getLength()); + } + + @Override + public void insertUpdate(DocumentEvent e) { + if (gui == null) + return; + if (valueChangedByGuiScreen) + return; + Document doc = e.getDocument(); + String text; + try { + text = doc.getText(e.getOffset(), e.getLength()); + } catch (BadLocationException ex) { + return; + } + gui.insertName(e.getOffset(), text); + } + + @Override + public void changedUpdate(DocumentEvent arg0) { + + } + + }); + UndoHelper.addUndoHelper(nameText); + + descText = add(textPanel, new JTextArea(description, initialRowCount, defaultColumns)); + descText.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), QuestTranslation.translate("betterquesting.gui.description"))); + descText.setLineWrap(true); + descText.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void removeUpdate(DocumentEvent e) { + if (gui == null) + return; + if (valueChangedByGuiScreen) + return; + gui.removeDesc(e.getOffset(), e.getLength()); + } + + @Override + public void insertUpdate(DocumentEvent e) { + if (gui == null) + return; + if (valueChangedByGuiScreen) + return; + Document doc = e.getDocument(); + String text; + try { + text = doc.getText(e.getOffset(), e.getLength()); + } catch (BadLocationException ex) { + return; + } + gui.insertDesc(e.getOffset(), text); + } - addWindowListener(new WindowAdapter() { @Override - public void windowClosed(WindowEvent e) { - opened.remove(questID); + public void changedUpdate(DocumentEvent arg0) { + } + }); + UndoHelper.addUndoHelper(descText); JPanel footerPanel = add(wholePanel, new JPanel()); footerPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 10)); - JButton cancel = add(footerPanel, new JButton(I18n.format("gui.cancel"))); + JButton cancel = add(footerPanel, new JButton(QuestTranslation.translate("gui.cancel"))); cancel.setMinimumSize(new Dimension(90, 0)); cancel.addActionListener(this::cancelClicked); - JButton done = add(footerPanel, new JButton(I18n.format("gui.done"))); + close = add(footerPanel, new JButton(QuestTranslation.translate("betterquesting.btn.edit_name_desc.just_close"))); + close.setMinimumSize(new Dimension(90, 0)); + close.addActionListener(this::closeClicked); + close.setToolTipText(QuestTranslation.translate("betterquesting.tooltip.edit_name_desc.just_close_window")); + JButton done = add(footerPanel, new JButton(QuestTranslation.translate("gui.done"))); done.addActionListener(this::doneClicked); done.setMinimumSize(new Dimension(90, 0)); @@ -129,27 +223,73 @@ public void windowClosed(WindowEvent e) { setVisible(true); } - private void cancelClicked(ActionEvent event) { + public void setGui(@Nullable GuiQuestDescEditor gui) { + this.gui = gui; + close.setEnabled(gui != null); + } + + /** + * Close the window. + */ + public void close() { + byTopCornerCloseButton = false; dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + byTopCornerCloseButton = true; + } + + public String getName() { return nameText.getText(); } + + public String getDesc() { return descText.getText(); } + + /** + * Set the quest description. + * + * @param desc The description. + */ + public void setDesc(String desc) { + SwingUtilities.invokeLater(() -> { + valueChangedByGuiScreen = true; + int caretPos = descText.getCaretPosition(); + descText.setText(desc); + descText.setCaretPosition(caretPos); + valueChangedByGuiScreen = false; + }); + } + + private void cancelClicked(ActionEvent event) { + if (gui != null) { + gui.cancel(); + } else { + cancel(); + } + } + + private void closeClicked(ActionEvent event) { + if (gui != null) { + gui.removeWindow(); + } else { + close(); + } } private void doneClicked(ActionEvent event) { - quest.setProperty(NativeProps.NAME, name.getText().trim()); - quest.setProperty(NativeProps.DESC, description.getText()); - SendChanges(); - dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + if (gui != null) { + gui.saveAndClose(); + } else { + saveAndClose(); + } } - private void SendChanges() { - NBTTagCompound payload = new NBTTagCompound(); - NBTTagList dataList = new NBTTagList(); - NBTTagCompound entry = new NBTTagCompound(); - entry.setInteger("questID", questID); - entry.setTag("config", quest.writeToNBT(new NBTTagCompound())); - dataList.appendTag(entry); - payload.setTag("data", dataList); - payload.setInteger("action", 0); - NetQuestEdit.sendEdit(payload); + private void cancel() { + close(); + } + + private void saveAndClose() { + IQuest quest = QuestDatabase.INSTANCE.getValue(questID); + quest.setProperty(NativeProps.NAME, getName().trim()); + quest.setProperty(NativeProps.DESC, getDesc()); + GuiQuestEditor.sendChanges(questID); + close(); } private void addAllFormattingCodes(JPanel panel) { @@ -165,22 +305,38 @@ private void addAllFormattingCodes(JPanel panel) { private ActionListener addFormattingCode(TextFormatting textFormatting) { return event -> { - description.insert(textFormatting.toString(), description.getCaretPosition()); + descText.insert(textFormatting.toString(), descText.getCaretPosition()); }; } + private static void initLogoCache() { + if (logoCache == null) { + try (InputStream stream = Minecraft.getMinecraft() + .getResourceManager() + .getResource(new ResourceLocation(ModReference.MODID, "textures/items/quest_book.png")) + .getInputStream()) { + logoCache = ImageIO.read(stream); + } catch (Exception ex) { + BetterQuesting.logger.error(ex); + } + } + } + private static T add(JPanel panel, T component) { panel.add(component); return component; } private static class UndoHelper { + public static final String ACTION_KEY_UNDO = "undo"; public static final String ACTION_KEY_REDO = "redo"; - UndoManager undoManager = new UndoManager(); + private final UndoManager undoManager = new UndoManager(); + public static void addUndoHelper(JTextComponent textComponent) { new UndoHelper(textComponent); } + private UndoHelper(JTextComponent textComponent) { ActionMap amap = textComponent.getActionMap(); InputMap imap = textComponent.getInputMap(); @@ -196,23 +352,27 @@ private UndoHelper(JTextComponent textComponent) { } textComponent.getDocument().addDocumentListener(new DocListener()); } - public UndoManager getUndoManager() { return undoManager; } + class UndoAction extends AbstractAction { + UndoAction() { super("Undo(U)"); - putValue(MNEMONIC_KEY, (int)'U'); + putValue(MNEMONIC_KEY, (int) 'U'); putValue(SHORT_DESCRIPTION, "Undo"); putValue(LONG_DESCRIPTION, "Undo"); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Z', InputEvent.CTRL_DOWN_MASK)); } + public void actionPerformed(ActionEvent e) { if (undoManager.canUndo()) { undoManager.undo(); } } + } class RedoAction extends AbstractAction { + RedoAction() { super("Redo(R)"); putValue(MNEMONIC_KEY, (int) 'R'); @@ -220,29 +380,35 @@ class RedoAction extends AbstractAction { putValue(LONG_DESCRIPTION, "Redo"); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke('Y', InputEvent.CTRL_DOWN_MASK)); } + public void actionPerformed(ActionEvent e) { if (undoManager.canRedo()) { undoManager.redo(); } } + } private class DocListener implements DocumentListener { + public void insertUpdate(DocumentEvent e) { - if (e instanceof DefaultDocumentEvent) { - DefaultDocumentEvent de = (DefaultDocumentEvent) e; + if (e instanceof DefaultDocumentEvent de) { undoManager.addEdit(de); } } + public void removeUpdate(DocumentEvent e) { - if (e instanceof DefaultDocumentEvent) { - DefaultDocumentEvent de = (DefaultDocumentEvent) e; + if (e instanceof DefaultDocumentEvent de) { undoManager.addEdit(de); } } + public void changedUpdate(DocumentEvent e) { // Nothing to do. } + } + } + } diff --git a/src/main/resources/assets/betterquesting/lang/en_us.lang b/src/main/resources/assets/betterquesting/lang/en_us.lang index 1983a1f89..2d2a1b911 100644 --- a/src/main/resources/assets/betterquesting/lang/en_us.lang +++ b/src/main/resources/assets/betterquesting/lang/en_us.lang @@ -65,6 +65,8 @@ betterquesting.btn.visible_always=Always show dependency line betterquesting.btn.visible_implicit=Hover to show dependency line betterquesting.btn.view_mode=View Mode betterquesting.btn.share_quest=Share to Chat +betterquesting.btn.edit_name_desc.just_close=Just Close +betterquesting.btn.edit_name_desc.open_window=Open Window betterquesting.home.exit=Exit betterquesting.home.quests=Quests @@ -110,6 +112,8 @@ betterquesting.tooltip.auto_claim=Auto Claim: %s betterquesting.tooltip.repeat=Cooldown: %s betterquesting.tooltip.repeat_with_edit_mode=Edit mode enabled, repeatable quests won't reset betterquesting.tooltip.update_quests=Update Available!%n§7A new quest line version is available.%nClick to install and migrate progress. +betterquesting.tooltip.edit_name_desc.just_close_screen=Just close the screen, but the window remains. +betterquesting.tooltip.edit_name_desc.just_close_window=Just close the window, but the screen remains. betterquesting.notice.complete=Quest Complete betterquesting.notice.update=Quest Updated From 31ac0572564b864059b72d87524f9f9a6220c374 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Thu, 29 Feb 2024 00:53:00 +0900 Subject: [PATCH 2/6] Force the window to open(may be included in the config) --- .../client/gui2/editors/GuiQuestDescEditor.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java b/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java index 852f6295e..7355646b1 100644 --- a/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java +++ b/src/main/java/betterquesting/client/gui2/editors/GuiQuestDescEditor.java @@ -34,6 +34,8 @@ public class GuiQuestDescEditor extends GuiScreenCanvas implements IPEventListener, IVolatileScreen { + private static final boolean FORCE_OPEN_WINDOW = true; + private final int questID; private final IQuest quest; private final String beforeName; @@ -50,6 +52,9 @@ public GuiQuestDescEditor(GuiQuestEditor parent, int questID, IQuest quest) { beforeName = quest.getProperty(NativeProps.NAME); beforeDesc = quest.getProperty(NativeProps.DESC); TextEditorFrame window = TextEditorFrame.get(questID); + if (FORCE_OPEN_WINDOW && window == null) { + window = TextEditorFrame.getOrCreate(questID, beforeName, beforeName, beforeDesc); + } if (window != null) { this.window = window; window.setGui(this); @@ -57,7 +62,7 @@ public GuiQuestDescEditor(GuiQuestEditor parent, int questID, IQuest quest) { window.requestFocus(); //Allow just-closing(this screen) if (close != null) - close.setEnabled(true); + close.setActive(true); name = window.getName(); } else { name = beforeName; @@ -75,7 +80,7 @@ public void showWindow() { window.requestFocus(); //Allow just-closing(this screen) if (close != null) - close.setEnabled(true); + close.setActive(true); } /** @@ -88,7 +93,7 @@ public void removeWindow() { window = null; //Disallow just-closing(this screen) if (close != null) - close.setEnabled(false); + close.setActive(false); } /** @@ -174,7 +179,7 @@ public void initPanel() { 0, QuestTranslation.translate("betterquesting.btn.edit_name_desc.just_close")); close.setTooltip(Lists.newArrayList(QuestTranslation.translate("betterquesting.tooltip.edit_name_desc.just_close_screen"))); - close.setEnabled(window != null); + close.setActive(window != null); cvBackground.addPanel(close); cvBackground.addPanel(new PanelButton(new GuiTransform(GuiAlign.BOTTOM_LEFT, 0 + 20, -16, 80, 16, 0), 1, QuestTranslation.translate("gui.cancel"))); cvBackground.addPanel(new PanelButton(new GuiTransform(GuiAlign.BOTTOM_RIGHT, -80 - 20, -16, 80, 16, 0), 2, QuestTranslation.translate("gui.done"))); From eb8bfd3b73e938e2fe4d8d75c3bb8832a1436b96 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Thu, 29 Feb 2024 01:33:00 +0900 Subject: [PATCH 3/6] Fix scrollbar --- .../betterquesting/client/gui2/editors/TextEditorFrame.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java index 312e14a8d..6edf5bb81 100644 --- a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java +++ b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java @@ -116,9 +116,8 @@ public void windowClosing(WindowEvent e) { JPanel wholePanel = new JPanel(); wholePanel.setLayout(new BoxLayout(wholePanel, BoxLayout.Y_AXIS)); - JPanel editorPanel = new JPanel(); + JPanel editorPanel = add(wholePanel, new JPanel()); editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS)); - add(wholePanel, new JScrollPane(editorPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); JPanel buttonPanel = add(editorPanel, new JPanel()); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); @@ -166,7 +165,8 @@ public void changedUpdate(DocumentEvent arg0) { }); UndoHelper.addUndoHelper(nameText); - descText = add(textPanel, new JTextArea(description, initialRowCount, defaultColumns)); + descText = new JTextArea(description, initialRowCount, defaultColumns); + add(textPanel, new JScrollPane(descText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); descText.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), QuestTranslation.translate("betterquesting.gui.description"))); descText.setLineWrap(true); descText.getDocument().addDocumentListener(new DocumentListener() { From b25b4267b1ac8186563359b68a1d1f4c2a8057f9 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Thu, 29 Feb 2024 14:32:11 +0900 Subject: [PATCH 4/6] Add line number --- .../client/gui2/editors/TextEditorFrame.java | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java index 6edf5bb81..7c4d65959 100644 --- a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java +++ b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java @@ -3,9 +3,19 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.event.InputEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -34,6 +44,7 @@ import javax.swing.text.AbstractDocument.DefaultDocumentEvent; import javax.swing.text.BadLocationException; import javax.swing.text.Document; +import javax.swing.text.Element; import javax.swing.text.JTextComponent; import javax.swing.undo.UndoManager; @@ -166,7 +177,8 @@ public void changedUpdate(DocumentEvent arg0) { UndoHelper.addUndoHelper(nameText); descText = new JTextArea(description, initialRowCount, defaultColumns); - add(textPanel, new JScrollPane(descText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + JScrollPane scroll = add(textPanel, new JScrollPane(descText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + scroll.setRowHeaderView(new LineNumberView(descText)); descText.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), QuestTranslation.translate("betterquesting.gui.description"))); descText.setLineWrap(true); descText.getDocument().addDocumentListener(new DocumentListener() { @@ -411,4 +423,116 @@ public void changedUpdate(DocumentEvent e) { } + private static class LineNumberView extends JPanel { + + private static final int LINE_NUMBER_MARGIN = 5; // Left and right padding of the line numbers + private final JTextArea textArea; + + public LineNumberView(JTextArea textArea) { + this.textArea = textArea; + textArea.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + repaint(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + repaint(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + /* not needed */ + } + + }); + textArea.addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + revalidate(); + repaint(); + } + + }); + } + + @Override + public void updateUI() { + super.updateUI(); + setOpaque(true); + EventQueue.invokeLater(() -> { + Insets i = textArea.getMargin(); + setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY), + BorderFactory.createEmptyBorder(i.top, LINE_NUMBER_MARGIN, i.bottom, LINE_NUMBER_MARGIN - 1))); + setBackground(textArea.getBackground()); + }); + } + + @Override + public Dimension getPreferredSize() { + FontMetrics fontMetrics = textArea.getFontMetrics(textArea.getFont()); + return new Dimension(getComponentWidth(fontMetrics), textArea.getHeight()); + } + + @Override + protected void paintComponent(Graphics g) { + + Graphics2D g2 = (Graphics2D) g.create(); + try { + g2.setColor(textArea.getBackground()); + Rectangle clip = g2.getClipBounds(); + g2.fillRect(clip.x, clip.y, clip.width, clip.height); + + Font font = textArea.getFont(); + g2.setFont(font); + + FontMetrics fontMetrics = g2.getFontMetrics(font); + int fontAscent = fontMetrics.getAscent(); + int fontDescent = fontMetrics.getDescent(); + int fontLeading = fontMetrics.getLeading(); + int fontHeight = fontMetrics.getHeight(); + + int componentWidth = getComponentWidth(fontMetrics); + int rMargin = getInsets().right; + int base = clip.y; + int start = getLineAtPoint(base); + int end = getLineAtPoint(base + clip.height); + int y = textArea.getBorder().getBorderInsets(textArea).top; + y += start * fontHeight; + + g2.setColor(getForeground()); + for (int line = start; line <= end;) { + y += fontAscent; + // Skip auto-wrapped lines + if (line <= getLineAtPoint(y)) { + String text = Integer.toString(line + 1); + // right alignment + int x = componentWidth - rMargin - fontMetrics.stringWidth(text); + g2.drawString(text, x, y); + line++; + } + y += fontDescent + fontLeading; + } + } finally { + g2.dispose(); + } + } + + private int getLineAtPoint(int y) { + Element root = textArea.getDocument().getDefaultRootElement(); + int pos = textArea.viewToModel(new Point(0, y)); + return root.getElementIndex(pos); + } + + private int getComponentWidth(FontMetrics fontMetrics) { + int maxDigits = Math.max(2, Integer.toString(textArea.getLineCount()).length()); + Insets insets = getInsets(); + return maxDigits * fontMetrics.stringWidth("0") + insets.left + insets.right; + } + + } + } From d0e48b7c1bd8aadc2218ed299dffc75e22684572 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Thu, 29 Feb 2024 00:31:36 +0900 Subject: [PATCH 5/6] Fix bug that the selected text is not replaced when a formatting button is pushed --- .../betterquesting/client/gui2/editors/TextEditorFrame.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java index 7c4d65959..7641ef521 100644 --- a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java +++ b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java @@ -317,7 +317,7 @@ private void addAllFormattingCodes(JPanel panel) { private ActionListener addFormattingCode(TextFormatting textFormatting) { return event -> { - descText.insert(textFormatting.toString(), descText.getCaretPosition()); + descText.replaceRange(textFormatting.toString(), descText.getSelectionStart(), descText.getSelectionEnd()); }; } From 9c422cb0d0012bdc221b8e41de762462473b4829 Mon Sep 17 00:00:00 2001 From: KatatsumuriPan Date: Thu, 29 Feb 2024 15:16:11 +0900 Subject: [PATCH 6/6] Fix window resizing bug. --- .../client/gui2/editors/TextEditorFrame.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java index 7641ef521..0b9ba2f26 100644 --- a/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java +++ b/src/main/java/betterquesting/client/gui2/editors/TextEditorFrame.java @@ -130,7 +130,8 @@ public void windowClosing(WindowEvent e) { JPanel editorPanel = add(wholePanel, new JPanel()); editorPanel.setLayout(new BoxLayout(editorPanel, BoxLayout.X_AXIS)); - JPanel buttonPanel = add(editorPanel, new JPanel()); + JPanel buttonPanel = new JPanel(); + add(editorPanel, new JScrollPane(buttonPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); buttonPanel.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.GRAY, 1, true), QuestTranslation.translate("betterquesting.gui.formatting_buttons"))); @@ -218,6 +219,8 @@ public void changedUpdate(DocumentEvent arg0) { JPanel footerPanel = add(wholePanel, new JPanel()); footerPanel.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 10)); + footerPanel.setMinimumSize(new Dimension(Integer.MAX_VALUE, 50)); + footerPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 50)); JButton cancel = add(footerPanel, new JButton(QuestTranslation.translate("gui.cancel"))); cancel.setMinimumSize(new Dimension(90, 0)); cancel.addActionListener(this::cancelClicked); @@ -309,8 +312,8 @@ private void addAllFormattingCodes(JPanel panel) { JButton btn = new JButton(value.getFriendlyName()); btn.setToolTipText(value.toString()); btn.addActionListener(addFormattingCode(value)); - btn.setMinimumSize(new Dimension(80, 20)); - btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 40)); + btn.setMinimumSize(new Dimension(80, 30)); + btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30)); add(panel, btn); } }