From db8111ed65fd0abd50e219cb33f051319c263c82 Mon Sep 17 00:00:00 2001
From: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
Date: Mon, 23 Jun 2025 15:27:29 -0600
Subject: [PATCH 1/2] Modified ViewContentDialog for map variable viewing
---
.../connect/client/ui/ViewContentDialog.form | 94 --
.../connect/client/ui/ViewContentDialog.java | 819 +++++++++++++++---
2 files changed, 711 insertions(+), 202 deletions(-)
delete mode 100644 client/src/com/mirth/connect/client/ui/ViewContentDialog.form
diff --git a/client/src/com/mirth/connect/client/ui/ViewContentDialog.form b/client/src/com/mirth/connect/client/ui/ViewContentDialog.form
deleted file mode 100644
index a0772d0e20..0000000000
--- a/client/src/com/mirth/connect/client/ui/ViewContentDialog.form
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
diff --git a/client/src/com/mirth/connect/client/ui/ViewContentDialog.java b/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
index c24c6bcc62..7693f45fe0 100644
--- a/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
+++ b/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
@@ -1,121 +1,724 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
-
-package com.mirth.connect.client.ui;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Point;
-
-public class ViewContentDialog extends MirthDialog {
-
- private Frame parent;
-
- public ViewContentDialog(String text) {
- super(PlatformUI.MIRTH_FRAME);
- this.parent = PlatformUI.MIRTH_FRAME;
- initComponents();
- messageContent.setText(text.replaceAll("\\t", "\n"));
- messageContent.setCaretPosition(0);
- messageContent.setLineWrap(true);
- setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setModal(true);
- pack();
- Dimension dlgSize = getPreferredSize();
- Dimension frmSize = parent.getSize();
- Point loc = parent.getLocation();
-
- if ((frmSize.width == 0 && frmSize.height == 0) || (loc.x == 0 && loc.y == 0)) {
- setLocationRelativeTo(null);
- } else {
- setLocation((frmSize.width - dlgSize.width) / 2 + loc.x, (frmSize.height - dlgSize.height) / 2 + loc.y);
- }
- setVisible(true);
- }
-
- // @formatter:off
+package com.mirth.connect.client.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
+import org.fife.ui.rtextarea.RTextScrollPane;
+import org.fife.ui.rtextarea.SearchContext;
+import org.fife.ui.rtextarea.SearchEngine;
+import org.fife.ui.rtextarea.SearchResult;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+/**
+ * Enhanced dialog for viewing content with improved formatting,
+ * search capabilities, and user experience features.
+ * This is a read-only view - content cannot be edited.
+ */
+public class ViewContentDialog extends MirthDialog {
+
+ // Constants
+ private static final int DEFAULT_WIDTH = 900;
+ private static final int DEFAULT_HEIGHT = 700;
+ private static final String DIALOG_TITLE = "View Content";
+
+ // Components
+ private final Frame parent;
+ private RSyntaxTextArea messageContent;
+ private JLabel statusLabel;
+ private SearchDialog searchDialog;
+ private String originalContent;
+ private ContentFormatter formatter;
+
+ // State
+ private boolean isFormatted = false;
+ private String currentFormat = "NONE";
+
/**
- * This method is called from within the constructor to initialize the form.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
+ * Creates a new ViewContentDialog with the specified parent and content.
+ *
+ * @param parent the parent frame
+ * @param text the content to display
*/
- // //GEN-BEGIN:initComponents
+ public ViewContentDialog(Frame parent, String text) {
+ super(parent);
+ this.parent = parent;
+ this.originalContent = text;
+ this.formatter = new ContentFormatter();
+
+ initComponents();
+ setupKeyBindings();
+
+ messageContent.setText(text.replaceAll("\\t", " ")); // Convert tabs to spaces
+ messageContent.setCaretPosition(0);
+ updateStatus();
+
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ setModal(true);
+ centerDialog();
+ setVisible(true);
+ }
+
+ /**
+ * Backward compatibility constructor
+ */
+ public ViewContentDialog(String text) {
+ this(PlatformUI.MIRTH_FRAME, text);
+ }
+
private void initComponents() {
+ setTitle(DIALOG_TITLE);
+ setLayout(new BorderLayout());
+
+ // Create main content area
+ createContentArea();
+
+ // Create button panel (now includes all buttons)
+ createButtonPanel();
+
+ // Setup the layout
+ add(createScrollPane(), BorderLayout.CENTER);
+ add(createButtonPanel(), BorderLayout.SOUTH);
+ }
+
+ private void createContentArea() {
+ messageContent = new RSyntaxTextArea();
+ setupContextMenu();
+ messageContent.setEditable(false); // Read-only view
+ messageContent.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE);
+ messageContent.setCodeFoldingEnabled(true);
+ messageContent.setTabSize(4);
+ messageContent.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+ // Add caret listener for status updates
+ messageContent.addCaretListener(e -> updateStatus());
+ }
+
+ private JScrollPane createScrollPane() {
+ RTextScrollPane scrollPane = new RTextScrollPane(messageContent);
+ scrollPane.setLineNumbersEnabled(true);
+ scrollPane.setFoldIndicatorEnabled(true);
+ return scrollPane;
+ }
+
+ private void setupContextMenu() {
+ JPopupMenu popupMenu = messageContent.getPopupMenu(); // Get the existing RSyntaxTextArea menu
+
+ // Add a separator to keep it clean
+ popupMenu.addSeparator();
+
+ // Word Wrap toggle
+ JCheckBoxMenuItem wrapItem = new JCheckBoxMenuItem("Word Wrap", messageContent.getLineWrap());
+ wrapItem.addActionListener(e -> {
+ boolean enabled = wrapItem.isSelected();
+ messageContent.setLineWrap(enabled);
+ messageContent.setWrapStyleWord(enabled);
+ });
+ popupMenu.add(wrapItem);
+
+ // Show Line Endings toggle
+ JCheckBoxMenuItem eolItem = new JCheckBoxMenuItem("Show Line Endings", messageContent.getEOLMarkersVisible());
+ eolItem.addActionListener(e -> messageContent.setEOLMarkersVisible(eolItem.isSelected()));
+ popupMenu.add(eolItem);
+ }
+
+ private JPanel createButtonPanel() {
+ JPanel buttonPanel = new JPanel(new BorderLayout());
+
+ statusLabel = new JLabel("Ready");
+ statusLabel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
+ BorderFactory.createEmptyBorder(5, 10, 5, 10)));
+ buttonPanel.add(statusLabel, BorderLayout.WEST);
+
+ JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 5));
+
+ // Platform-aware modifier key for tooltips
+ String modifierText = System.getProperty("os.name").toLowerCase().contains("mac") ? "⌘" : "Ctrl";
+
+ JButton formatButton = new JButton("Format");
+ formatButton.setToolTipText("Format content (" + modifierText + "+Shift+F)");
+ formatButton.addActionListener(e -> formatContent());
+ actionButtonsPanel.add(formatButton);
+
+ JButton undoFormatButton = new JButton("Reset");
+ undoFormatButton.setToolTipText("Reset to original content (" + modifierText + "+R)");
+ undoFormatButton.addActionListener(e -> resetContent());
+ actionButtonsPanel.add(undoFormatButton);
+
+ JButton searchButton = new JButton("Search");
+ searchButton.setToolTipText("Search content (" + modifierText + "+F)");
+ searchButton.addActionListener(e -> showSearchDialog());
+ actionButtonsPanel.add(searchButton);
+
+ JButton saveButton = new JButton("Export");
+ saveButton.setToolTipText("Save to file (" + modifierText + "+S)");
+ saveButton.addActionListener(e -> saveContentToFile());
+ actionButtonsPanel.add(saveButton);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.setToolTipText("Close dialog (Escape)");
+ closeButton.addActionListener(e -> dispose());
+ actionButtonsPanel.add(closeButton);
+
+ // Ensure all buttons have the same size
+ Dimension buttonSize = new Dimension(90, 30);
+ formatButton.setPreferredSize(buttonSize);
+ undoFormatButton.setPreferredSize(buttonSize);
+ searchButton.setPreferredSize(buttonSize);
+ saveButton.setPreferredSize(buttonSize);
+ closeButton.setPreferredSize(buttonSize);
+
+ buttonPanel.add(actionButtonsPanel, BorderLayout.EAST);
+ return buttonPanel;
+ }
+
+ private void setupKeyBindings() {
+ // Detect platform for proper modifier keys
+ int modifierKey = System.getProperty("os.name").toLowerCase().contains("mac")
+ ? InputEvent.META_DOWN_MASK
+ : InputEvent.CTRL_DOWN_MASK;
+
+ // Ctrl/Cmd+F for search
+ KeyStroke findKey = KeyStroke.getKeyStroke(KeyEvent.VK_F, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(findKey, "openSearch");
+ messageContent.getActionMap().put("openSearch", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ showSearchDialog();
+ }
+ });
+
+ // Ctrl/Cmd+S for save
+ KeyStroke saveKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(saveKey, "saveFile");
+ messageContent.getActionMap().put("saveFile", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveContentToFile();
+ }
+ });
+
+ // Ctrl/Cmd+Shift+F for format
+ KeyStroke formatKey = KeyStroke.getKeyStroke(KeyEvent.VK_F, modifierKey | InputEvent.SHIFT_DOWN_MASK);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(formatKey, "formatContent");
+ messageContent.getActionMap().put("formatContent", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ formatContent();
+ }
+ });
+
+ // Ctrl/Cmd+R for reset/reload
+ KeyStroke resetKey = KeyStroke.getKeyStroke(KeyEvent.VK_R, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(resetKey, "resetContent");
+ messageContent.getActionMap().put("resetContent", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ resetContent();
+ }
+ });
+
+ // Ctrl/Cmd+W for close (standard on macOS)
+ KeyStroke closeKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, modifierKey);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(closeKey, "closeDialog");
+ getRootPane().getActionMap().put("closeDialog", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+
+ // Escape to close
+ KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "closeDialog");
+ getRootPane().getActionMap().put("closeDialog", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+ }
+
+ private void showSearchDialog() {
+ if (searchDialog == null) {
+ searchDialog = new SearchDialog(this, messageContent);
+ }
+ searchDialog.setVisible(true);
+ }
+
+ private void formatContent() {
+ String text = messageContent.getText();
+
+ if (text.trim().isEmpty()) {
+ showWarning("No content to format");
+ return;
+ }
+
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+ try {
+ ContentFormatter.FormatResult result = formatter.format(text);
+
+ if (result.isSuccessful()) {
+ messageContent.setText(result.getFormattedContent());
+ messageContent.setSyntaxEditingStyle(result.getSyntaxStyle());
+ messageContent.setCaretPosition(0);
+ isFormatted = true;
+ currentFormat = result.getFormatType();
+ } else {
+ showWarning("Could not format content. Format not recognized or content is invalid.");
+ }
+ } finally {
+ setCursor(Cursor.getDefaultCursor());
+ updateStatus();
+ }
+ }
+
+ private void resetContent() {
+ messageContent.setText(originalContent);
+ messageContent.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE);
+ messageContent.setCaretPosition(0);
+ isFormatted = false;
+ currentFormat = "NONE";
+ updateStatus();
+ }
+
+ private void saveContentToFile() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle("Save Content");
+
+ // Add file filters
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("JSON files (*.json)", "json"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("XML files (*.xml)", "xml"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("Text files (*.txt)", "txt"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("All files (*.*)", "*"));
+
+ // Set default directory to user home
+ chooser.setCurrentDirectory(new File(System.getProperty("user.home")));
+
+ // Suggest filename based on format
+ if (isFormatted) {
+ String extension = currentFormat.equalsIgnoreCase("JSON") ? ".json"
+ : currentFormat.equalsIgnoreCase("XML") ? ".xml" : ".txt";
+ chooser.setSelectedFile(new File("content" + extension));
+ }
+
+ if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+ File file = chooser.getSelectedFile();
- jPanel1 = new javax.swing.JPanel();
- jScrollPane1 = new javax.swing.JScrollPane();
- messageContent = new javax.swing.JTextArea();
- jButton1 = new javax.swing.JButton();
+ // Check for overwrite
+ if (file.exists()) {
+ int confirm = JOptionPane.showConfirmDialog(this,
+ "File '" + file.getName() + "' already exists.\nDo you want to replace it?",
+ "Confirm Save", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (confirm != JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+
+ // Save the file
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ try {
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(file), java.nio.charset.StandardCharsets.UTF_8)) {
+ writer.write(messageContent.getText());
+ }
- setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
- setTitle("Mapping Value");
+ showInfo("File saved successfully to:\n" + file.getAbsolutePath());
+ } catch (IOException ex) {
+ showError("Error saving file:\n" + ex.getMessage());
+ } finally {
+ setCursor(Cursor.getDefaultCursor());
+ }
+ }
+ }
- jPanel1.setBackground(new java.awt.Color(255, 255, 255));
+ private void updateStatus() {
+ SwingUtilities.invokeLater(() -> {
+ int caretPos = messageContent.getCaretPosition();
+ try {
+ int line = messageContent.getLineOfOffset(caretPos) + 1;
+ int col = caretPos - messageContent.getLineStartOffset(line - 1) + 1;
+ int totalLines = messageContent.getLineCount();
+ int length = messageContent.getText().length();
- messageContent.setEditable(false);
- messageContent.setBackground(UIConstants.BACKGROUND_COLOR);
- jScrollPane1.setViewportView(messageContent);
+ String status = String.format("Line %d, Col %d | %d lines | %d chars | Format: %s",
+ line, col, totalLines, length, currentFormat);
- jButton1.setText("Close");
- jButton1.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- jButton1ActionPerformed(evt);
+ if (isFormatted) {
+ status += " (Formatted)";
+ }
+
+ statusLabel.setText(status);
+ } catch (Exception e) {
+ statusLabel.setText("Ready");
}
});
+ }
+
+ private void centerDialog() {
+ setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
+ pack();
+ if (parent != null) {
+ setLocationRelativeTo(parent);
+ } else {
+ setLocationRelativeTo(null);
+ }
+ }
+
+ // Utility methods for messaging
+ private void showInfo(String message) {
+ JOptionPane.showMessageDialog(this, message, "Information", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ private void showWarning(String message) {
+ JOptionPane.showMessageDialog(this, message, "Warning", JOptionPane.WARNING_MESSAGE);
+ }
+
+ private void showError(String message) {
+ JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ @Override
+ public void dispose() {
+ if (searchDialog != null) {
+ searchDialog.dispose();
+ searchDialog = null;
+ }
+ messageContent.setText(""); // Free memory
+ originalContent = null;
+ super.dispose();
+ }
+}
+
+/**
+ * Separate class for handling content formatting
+ */
+class ContentFormatter {
+ private final ObjectMapper jsonMapper;
+
+ public ContentFormatter() {
+ this.jsonMapper = new ObjectMapper();
+ }
+
+ public FormatResult format(String content) {
+ if (!isLikelyText(content)) {
+ return FormatResult.failure("Content appears to be binary");
+ }
+
+ // Try JSON first
+ try {
+ Object json = jsonMapper.readTree(content);
+ ObjectWriter writer = jsonMapper.writerWithDefaultPrettyPrinter();
+ String formatted = writer.writeValueAsString(json);
+ return FormatResult.success(formatted, SyntaxConstants.SYNTAX_STYLE_JSON, "JSON");
+ } catch (Exception e) {
+ // Try XML
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(new InputSource(new StringReader(content)));
+
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+
+ return FormatResult.success(writer.toString(), SyntaxConstants.SYNTAX_STYLE_XML, "XML");
+ } catch (Exception e2) {
+ return FormatResult.failure("Unsupported format");
+ }
+ }
+ }
+
+ private boolean isLikelyText(String text) {
+ if (text == null || text.isEmpty())
+ return false;
+
+ int controlChars = 0;
+ int totalChars = Math.min(text.length(), 1000); // Sample first 1000 chars
+
+ for (int i = 0; i < totalChars; i++) {
+ char c = text.charAt(i);
+ if (Character.isISOControl(c) && c != '\n' && c != '\r' && c != '\t') {
+ controlChars++;
+ }
+ }
+
+ // If more than 5% control characters, probably binary
+ return (controlChars / (double) totalChars) < 0.05;
+ }
+
+ public static class FormatResult {
+ private final boolean successful;
+ private final String formattedContent;
+ private final String syntaxStyle;
+ private final String formatType;
+ private final String errorMessage;
+
+ private FormatResult(boolean successful, String formattedContent,
+ String syntaxStyle, String formatType, String errorMessage) {
+ this.successful = successful;
+ this.formattedContent = formattedContent;
+ this.syntaxStyle = syntaxStyle;
+ this.formatType = formatType;
+ this.errorMessage = errorMessage;
+ }
+
+ public static FormatResult success(String content, String syntax, String type) {
+ return new FormatResult(true, content, syntax, type, null);
+ }
- javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
- jPanel1.setLayout(jPanel1Layout);
- jPanel1Layout.setHorizontalGroup(
- jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
- .addComponent(jButton1))
- .addContainerGap())
- );
- jPanel1Layout.setVerticalGroup(
- jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jButton1)
- .addContainerGap())
- );
-
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
- getContentPane().setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
+ public static FormatResult failure(String error) {
+ return new FormatResult(false, null, null, null, error);
+ }
+ // Getters
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ public String getFormattedContent() {
+ return formattedContent;
+ }
+
+ public String getSyntaxStyle() {
+ return syntaxStyle;
+ }
+
+ public String getFormatType() {
+ return formatType;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+ }
+}
+
+/**
+ * Search dialog for read-only content viewing - matches Mirth standard layout
+ */
+class SearchDialog extends JDialog {
+ private final RSyntaxTextArea textArea;
+ private JTextField searchField;
+ private JCheckBox matchCaseBox;
+ private JCheckBox regexBox;
+ private JCheckBox wholeWordBox;
+ private JRadioButton forwardRadio;
+ private JRadioButton backwardRadio;
+ private JButton findButton;
+ private JLabel resultLabel;
+ private SearchContext currentContext;
+
+ public SearchDialog(JDialog parent, RSyntaxTextArea textArea) {
+ super(parent, "Find", false); // Non-modal
+ this.textArea = textArea;
+ initComponents();
+ setupKeyBindings();
pack();
- }// //GEN-END:initComponents
- // @formatter:on
-
- private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST:event_jButton1ActionPerformed
- {// GEN-HEADEREND:event_jButton1ActionPerformed
- this.dispose();
- }// GEN-LAST:event_jButton1ActionPerformed
- // Variables declaration - do not modify//GEN-BEGIN:variables
-
- private javax.swing.JButton jButton1;
- private javax.swing.JPanel jPanel1;
- private javax.swing.JScrollPane jScrollPane1;
- private javax.swing.JTextArea messageContent;
- // End of variables declaration//GEN-END:variables
-}
+ setResizable(false);
+ positionDialog(parent);
+ }
+
+ private void initComponents() {
+ setLayout(new BorderLayout());
+ setTitle("Find");
+
+ JPanel mainPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Find text field
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.insets = new Insets(10, 10, 5, 5);
+ JLabel searchLabel = new JLabel("Find text:");
+ mainPanel.add(searchLabel, gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weightx = 1.0;
+ gbc.insets = new Insets(10, 5, 5, 10);
+ searchField = new JTextField(25);
+ searchField.addActionListener(e -> findNext());
+ mainPanel.add(searchField, gbc);
+
+ // Direction panel
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.insets = new Insets(10, 10, 5, 10);
+ JPanel directionPanel = new JPanel();
+ directionPanel.setBorder(BorderFactory.createTitledBorder("Direction"));
+ directionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+ ButtonGroup directionGroup = new ButtonGroup();
+ forwardRadio = new JRadioButton("Forward", true);
+ backwardRadio = new JRadioButton("Backward");
+ directionGroup.add(forwardRadio);
+ directionGroup.add(backwardRadio);
+ directionPanel.add(forwardRadio);
+ directionPanel.add(backwardRadio);
+ mainPanel.add(directionPanel, gbc);
+
+ // Options panel
+ gbc.gridy = 2;
+ gbc.insets = new Insets(5, 10, 10, 10);
+ JPanel optionsPanel = new JPanel();
+ optionsPanel.setBorder(BorderFactory.createTitledBorder("Options"));
+ optionsPanel.setLayout(new GridLayout(1, 3, 10, 5));
+
+ regexBox = new JCheckBox("Regular Expression");
+ matchCaseBox = new JCheckBox("Match Case");
+ wholeWordBox = new JCheckBox("Whole Word");
+
+ // Add listener to disable other options when regex is selected
+ regexBox.addActionListener(e -> {
+ boolean regexSelected = regexBox.isSelected();
+ matchCaseBox.setEnabled(!regexSelected);
+ wholeWordBox.setEnabled(!regexSelected);
+ if (regexSelected) {
+ matchCaseBox.setSelected(false);
+ wholeWordBox.setSelected(false);
+ }
+ });
+
+ optionsPanel.add(regexBox);
+ optionsPanel.add(matchCaseBox);
+ optionsPanel.add(wholeWordBox);
+ mainPanel.add(optionsPanel, gbc);
+
+ // Buttons panel with result label
+ JPanel bottomPanel = new JPanel(new BorderLayout());
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 10));
+
+ findButton = new JButton("Find");
+ findButton.setPreferredSize(new Dimension(80, 25));
+ findButton.addActionListener(e -> {
+ if (backwardRadio.isSelected()) {
+ findPrevious();
+ } else {
+ findNext();
+ }
+ });
+ buttonPanel.add(findButton);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.setPreferredSize(new Dimension(80, 25));
+ closeButton.addActionListener(e -> setVisible(false));
+ buttonPanel.add(closeButton);
+
+ // Result label (small status at bottom)
+ resultLabel = new JLabel(" ");
+ resultLabel.setFont(resultLabel.getFont().deriveFont(Font.ITALIC, 11f));
+ resultLabel.setHorizontalAlignment(SwingConstants.CENTER);
+ resultLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+
+ bottomPanel.add(buttonPanel, BorderLayout.CENTER);
+ bottomPanel.add(resultLabel, BorderLayout.SOUTH);
+
+ add(mainPanel, BorderLayout.CENTER);
+ add(bottomPanel, BorderLayout.SOUTH);
+ }
+
+ private void setupKeyBindings() {
+ // Enter to find next
+ KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+ searchField.getInputMap().put(enter, "findNext");
+ searchField.getActionMap().put("findNext", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ findNext();
+ }
+ });
+
+ // Escape to close
+ KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "close");
+ getRootPane().getActionMap().put("close", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+ });
+ }
+
+ private void findNext() {
+ String searchText = searchField.getText();
+ if (searchText.isEmpty()) {
+ resultLabel.setText("Enter text to find");
+ return;
+ }
+
+ updateSearchContext();
+ currentContext.setSearchForward(true);
+ SearchResult result = SearchEngine.find(textArea, currentContext);
+ handleSearchResult(result);
+ }
+
+ private void findPrevious() {
+ String searchText = searchField.getText();
+ if (searchText.isEmpty()) {
+ resultLabel.setText("Enter text to find");
+ return;
+ }
+
+ updateSearchContext();
+ currentContext.setSearchForward(false);
+ SearchResult result = SearchEngine.find(textArea, currentContext);
+ handleSearchResult(result);
+ }
+
+ private void updateSearchContext() {
+ currentContext = new SearchContext(searchField.getText());
+ currentContext.setMatchCase(matchCaseBox.isSelected());
+ currentContext.setRegularExpression(regexBox.isSelected());
+ currentContext.setWholeWord(wholeWordBox.isSelected());
+ }
+
+ private void handleSearchResult(SearchResult result) {
+ if (result.wasFound()) {
+ resultLabel.setText("");
+ } else {
+ resultLabel.setText("\"" + searchField.getText() + "\" not found");
+ }
+ }
+
+ private void positionDialog(JDialog parent) {
+ if (parent != null) {
+ Point parentLocation = parent.getLocationOnScreen();
+ setLocation(parentLocation.x + parent.getWidth() - getWidth() - 20,
+ parentLocation.y + 50);
+ } else {
+ setLocationRelativeTo(null);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ searchField.requestFocus();
+ searchField.selectAll();
+ }
+ super.setVisible(visible);
+ }
+}
\ No newline at end of file
From a67f365a4905f11fb7411feca64ad3ed0bca027e Mon Sep 17 00:00:00 2001
From: Finnegan's Owner <44065187+pacmano1@users.noreply.github.com>
Date: Mon, 23 Jun 2025 15:27:29 -0600
Subject: [PATCH 2/2] Modified ViewContentDialog for map variable viewing
---
.../connect/client/ui/ViewContentDialog.form | 94 --
.../connect/client/ui/ViewContentDialog.java | 819 +++++++++++++++---
2 files changed, 711 insertions(+), 202 deletions(-)
delete mode 100644 client/src/com/mirth/connect/client/ui/ViewContentDialog.form
diff --git a/client/src/com/mirth/connect/client/ui/ViewContentDialog.form b/client/src/com/mirth/connect/client/ui/ViewContentDialog.form
deleted file mode 100644
index a0772d0e20..0000000000
--- a/client/src/com/mirth/connect/client/ui/ViewContentDialog.form
+++ /dev/null
@@ -1,94 +0,0 @@
-
-
-
diff --git a/client/src/com/mirth/connect/client/ui/ViewContentDialog.java b/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
index c24c6bcc62..7693f45fe0 100644
--- a/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
+++ b/client/src/com/mirth/connect/client/ui/ViewContentDialog.java
@@ -1,121 +1,724 @@
-/*
- * Copyright (c) Mirth Corporation. All rights reserved.
- *
- * http://www.mirthcorp.com
- *
- * The software in this package is published under the terms of the MPL license a copy of which has
- * been included with this distribution in the LICENSE.txt file.
- */
-
-package com.mirth.connect.client.ui;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Point;
-
-public class ViewContentDialog extends MirthDialog {
-
- private Frame parent;
-
- public ViewContentDialog(String text) {
- super(PlatformUI.MIRTH_FRAME);
- this.parent = PlatformUI.MIRTH_FRAME;
- initComponents();
- messageContent.setText(text.replaceAll("\\t", "\n"));
- messageContent.setCaretPosition(0);
- messageContent.setLineWrap(true);
- setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setModal(true);
- pack();
- Dimension dlgSize = getPreferredSize();
- Dimension frmSize = parent.getSize();
- Point loc = parent.getLocation();
-
- if ((frmSize.width == 0 && frmSize.height == 0) || (loc.x == 0 && loc.y == 0)) {
- setLocationRelativeTo(null);
- } else {
- setLocation((frmSize.width - dlgSize.width) / 2 + loc.x, (frmSize.height - dlgSize.height) / 2 + loc.y);
- }
- setVisible(true);
- }
-
- // @formatter:off
+package com.mirth.connect.client.ui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
+import org.fife.ui.rtextarea.RTextScrollPane;
+import org.fife.ui.rtextarea.SearchContext;
+import org.fife.ui.rtextarea.SearchEngine;
+import org.fife.ui.rtextarea.SearchResult;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+/**
+ * Enhanced dialog for viewing content with improved formatting,
+ * search capabilities, and user experience features.
+ * This is a read-only view - content cannot be edited.
+ */
+public class ViewContentDialog extends MirthDialog {
+
+ // Constants
+ private static final int DEFAULT_WIDTH = 900;
+ private static final int DEFAULT_HEIGHT = 700;
+ private static final String DIALOG_TITLE = "View Content";
+
+ // Components
+ private final Frame parent;
+ private RSyntaxTextArea messageContent;
+ private JLabel statusLabel;
+ private SearchDialog searchDialog;
+ private String originalContent;
+ private ContentFormatter formatter;
+
+ // State
+ private boolean isFormatted = false;
+ private String currentFormat = "NONE";
+
/**
- * This method is called from within the constructor to initialize the form.
- * WARNING: Do NOT modify this code. The content of this method is always
- * regenerated by the Form Editor.
+ * Creates a new ViewContentDialog with the specified parent and content.
+ *
+ * @param parent the parent frame
+ * @param text the content to display
*/
- // //GEN-BEGIN:initComponents
+ public ViewContentDialog(Frame parent, String text) {
+ super(parent);
+ this.parent = parent;
+ this.originalContent = text;
+ this.formatter = new ContentFormatter();
+
+ initComponents();
+ setupKeyBindings();
+
+ messageContent.setText(text.replaceAll("\\t", " ")); // Convert tabs to spaces
+ messageContent.setCaretPosition(0);
+ updateStatus();
+
+ setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ setModal(true);
+ centerDialog();
+ setVisible(true);
+ }
+
+ /**
+ * Backward compatibility constructor
+ */
+ public ViewContentDialog(String text) {
+ this(PlatformUI.MIRTH_FRAME, text);
+ }
+
private void initComponents() {
+ setTitle(DIALOG_TITLE);
+ setLayout(new BorderLayout());
+
+ // Create main content area
+ createContentArea();
+
+ // Create button panel (now includes all buttons)
+ createButtonPanel();
+
+ // Setup the layout
+ add(createScrollPane(), BorderLayout.CENTER);
+ add(createButtonPanel(), BorderLayout.SOUTH);
+ }
+
+ private void createContentArea() {
+ messageContent = new RSyntaxTextArea();
+ setupContextMenu();
+ messageContent.setEditable(false); // Read-only view
+ messageContent.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE);
+ messageContent.setCodeFoldingEnabled(true);
+ messageContent.setTabSize(4);
+ messageContent.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+ // Add caret listener for status updates
+ messageContent.addCaretListener(e -> updateStatus());
+ }
+
+ private JScrollPane createScrollPane() {
+ RTextScrollPane scrollPane = new RTextScrollPane(messageContent);
+ scrollPane.setLineNumbersEnabled(true);
+ scrollPane.setFoldIndicatorEnabled(true);
+ return scrollPane;
+ }
+
+ private void setupContextMenu() {
+ JPopupMenu popupMenu = messageContent.getPopupMenu(); // Get the existing RSyntaxTextArea menu
+
+ // Add a separator to keep it clean
+ popupMenu.addSeparator();
+
+ // Word Wrap toggle
+ JCheckBoxMenuItem wrapItem = new JCheckBoxMenuItem("Word Wrap", messageContent.getLineWrap());
+ wrapItem.addActionListener(e -> {
+ boolean enabled = wrapItem.isSelected();
+ messageContent.setLineWrap(enabled);
+ messageContent.setWrapStyleWord(enabled);
+ });
+ popupMenu.add(wrapItem);
+
+ // Show Line Endings toggle
+ JCheckBoxMenuItem eolItem = new JCheckBoxMenuItem("Show Line Endings", messageContent.getEOLMarkersVisible());
+ eolItem.addActionListener(e -> messageContent.setEOLMarkersVisible(eolItem.isSelected()));
+ popupMenu.add(eolItem);
+ }
+
+ private JPanel createButtonPanel() {
+ JPanel buttonPanel = new JPanel(new BorderLayout());
+
+ statusLabel = new JLabel("Ready");
+ statusLabel.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
+ BorderFactory.createEmptyBorder(5, 10, 5, 10)));
+ buttonPanel.add(statusLabel, BorderLayout.WEST);
+
+ JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 5));
+
+ // Platform-aware modifier key for tooltips
+ String modifierText = System.getProperty("os.name").toLowerCase().contains("mac") ? "⌘" : "Ctrl";
+
+ JButton formatButton = new JButton("Format");
+ formatButton.setToolTipText("Format content (" + modifierText + "+Shift+F)");
+ formatButton.addActionListener(e -> formatContent());
+ actionButtonsPanel.add(formatButton);
+
+ JButton undoFormatButton = new JButton("Reset");
+ undoFormatButton.setToolTipText("Reset to original content (" + modifierText + "+R)");
+ undoFormatButton.addActionListener(e -> resetContent());
+ actionButtonsPanel.add(undoFormatButton);
+
+ JButton searchButton = new JButton("Search");
+ searchButton.setToolTipText("Search content (" + modifierText + "+F)");
+ searchButton.addActionListener(e -> showSearchDialog());
+ actionButtonsPanel.add(searchButton);
+
+ JButton saveButton = new JButton("Export");
+ saveButton.setToolTipText("Save to file (" + modifierText + "+S)");
+ saveButton.addActionListener(e -> saveContentToFile());
+ actionButtonsPanel.add(saveButton);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.setToolTipText("Close dialog (Escape)");
+ closeButton.addActionListener(e -> dispose());
+ actionButtonsPanel.add(closeButton);
+
+ // Ensure all buttons have the same size
+ Dimension buttonSize = new Dimension(90, 30);
+ formatButton.setPreferredSize(buttonSize);
+ undoFormatButton.setPreferredSize(buttonSize);
+ searchButton.setPreferredSize(buttonSize);
+ saveButton.setPreferredSize(buttonSize);
+ closeButton.setPreferredSize(buttonSize);
+
+ buttonPanel.add(actionButtonsPanel, BorderLayout.EAST);
+ return buttonPanel;
+ }
+
+ private void setupKeyBindings() {
+ // Detect platform for proper modifier keys
+ int modifierKey = System.getProperty("os.name").toLowerCase().contains("mac")
+ ? InputEvent.META_DOWN_MASK
+ : InputEvent.CTRL_DOWN_MASK;
+
+ // Ctrl/Cmd+F for search
+ KeyStroke findKey = KeyStroke.getKeyStroke(KeyEvent.VK_F, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(findKey, "openSearch");
+ messageContent.getActionMap().put("openSearch", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ showSearchDialog();
+ }
+ });
+
+ // Ctrl/Cmd+S for save
+ KeyStroke saveKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(saveKey, "saveFile");
+ messageContent.getActionMap().put("saveFile", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveContentToFile();
+ }
+ });
+
+ // Ctrl/Cmd+Shift+F for format
+ KeyStroke formatKey = KeyStroke.getKeyStroke(KeyEvent.VK_F, modifierKey | InputEvent.SHIFT_DOWN_MASK);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(formatKey, "formatContent");
+ messageContent.getActionMap().put("formatContent", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ formatContent();
+ }
+ });
+
+ // Ctrl/Cmd+R for reset/reload
+ KeyStroke resetKey = KeyStroke.getKeyStroke(KeyEvent.VK_R, modifierKey);
+ messageContent.getInputMap(JComponent.WHEN_FOCUSED).put(resetKey, "resetContent");
+ messageContent.getActionMap().put("resetContent", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ resetContent();
+ }
+ });
+
+ // Ctrl/Cmd+W for close (standard on macOS)
+ KeyStroke closeKey = KeyStroke.getKeyStroke(KeyEvent.VK_W, modifierKey);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(closeKey, "closeDialog");
+ getRootPane().getActionMap().put("closeDialog", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+
+ // Escape to close
+ KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "closeDialog");
+ getRootPane().getActionMap().put("closeDialog", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dispose();
+ }
+ });
+ }
+
+ private void showSearchDialog() {
+ if (searchDialog == null) {
+ searchDialog = new SearchDialog(this, messageContent);
+ }
+ searchDialog.setVisible(true);
+ }
+
+ private void formatContent() {
+ String text = messageContent.getText();
+
+ if (text.trim().isEmpty()) {
+ showWarning("No content to format");
+ return;
+ }
+
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+ try {
+ ContentFormatter.FormatResult result = formatter.format(text);
+
+ if (result.isSuccessful()) {
+ messageContent.setText(result.getFormattedContent());
+ messageContent.setSyntaxEditingStyle(result.getSyntaxStyle());
+ messageContent.setCaretPosition(0);
+ isFormatted = true;
+ currentFormat = result.getFormatType();
+ } else {
+ showWarning("Could not format content. Format not recognized or content is invalid.");
+ }
+ } finally {
+ setCursor(Cursor.getDefaultCursor());
+ updateStatus();
+ }
+ }
+
+ private void resetContent() {
+ messageContent.setText(originalContent);
+ messageContent.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE);
+ messageContent.setCaretPosition(0);
+ isFormatted = false;
+ currentFormat = "NONE";
+ updateStatus();
+ }
+
+ private void saveContentToFile() {
+ JFileChooser chooser = new JFileChooser();
+ chooser.setDialogTitle("Save Content");
+
+ // Add file filters
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("JSON files (*.json)", "json"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("XML files (*.xml)", "xml"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("Text files (*.txt)", "txt"));
+ chooser.addChoosableFileFilter(new FileNameExtensionFilter("All files (*.*)", "*"));
+
+ // Set default directory to user home
+ chooser.setCurrentDirectory(new File(System.getProperty("user.home")));
+
+ // Suggest filename based on format
+ if (isFormatted) {
+ String extension = currentFormat.equalsIgnoreCase("JSON") ? ".json"
+ : currentFormat.equalsIgnoreCase("XML") ? ".xml" : ".txt";
+ chooser.setSelectedFile(new File("content" + extension));
+ }
+
+ if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
+ File file = chooser.getSelectedFile();
- jPanel1 = new javax.swing.JPanel();
- jScrollPane1 = new javax.swing.JScrollPane();
- messageContent = new javax.swing.JTextArea();
- jButton1 = new javax.swing.JButton();
+ // Check for overwrite
+ if (file.exists()) {
+ int confirm = JOptionPane.showConfirmDialog(this,
+ "File '" + file.getName() + "' already exists.\nDo you want to replace it?",
+ "Confirm Save", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+ if (confirm != JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+
+ // Save the file
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ try {
+ try (OutputStreamWriter writer = new OutputStreamWriter(
+ new FileOutputStream(file), java.nio.charset.StandardCharsets.UTF_8)) {
+ writer.write(messageContent.getText());
+ }
- setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
- setTitle("Mapping Value");
+ showInfo("File saved successfully to:\n" + file.getAbsolutePath());
+ } catch (IOException ex) {
+ showError("Error saving file:\n" + ex.getMessage());
+ } finally {
+ setCursor(Cursor.getDefaultCursor());
+ }
+ }
+ }
- jPanel1.setBackground(new java.awt.Color(255, 255, 255));
+ private void updateStatus() {
+ SwingUtilities.invokeLater(() -> {
+ int caretPos = messageContent.getCaretPosition();
+ try {
+ int line = messageContent.getLineOfOffset(caretPos) + 1;
+ int col = caretPos - messageContent.getLineStartOffset(line - 1) + 1;
+ int totalLines = messageContent.getLineCount();
+ int length = messageContent.getText().length();
- messageContent.setEditable(false);
- messageContent.setBackground(UIConstants.BACKGROUND_COLOR);
- jScrollPane1.setViewportView(messageContent);
+ String status = String.format("Line %d, Col %d | %d lines | %d chars | Format: %s",
+ line, col, totalLines, length, currentFormat);
- jButton1.setText("Close");
- jButton1.addActionListener(new java.awt.event.ActionListener() {
- public void actionPerformed(java.awt.event.ActionEvent evt) {
- jButton1ActionPerformed(evt);
+ if (isFormatted) {
+ status += " (Formatted)";
+ }
+
+ statusLabel.setText(status);
+ } catch (Exception e) {
+ statusLabel.setText("Ready");
}
});
+ }
+
+ private void centerDialog() {
+ setPreferredSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
+ pack();
+ if (parent != null) {
+ setLocationRelativeTo(parent);
+ } else {
+ setLocationRelativeTo(null);
+ }
+ }
+
+ // Utility methods for messaging
+ private void showInfo(String message) {
+ JOptionPane.showMessageDialog(this, message, "Information", JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ private void showWarning(String message) {
+ JOptionPane.showMessageDialog(this, message, "Warning", JOptionPane.WARNING_MESSAGE);
+ }
+
+ private void showError(String message) {
+ JOptionPane.showMessageDialog(this, message, "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ @Override
+ public void dispose() {
+ if (searchDialog != null) {
+ searchDialog.dispose();
+ searchDialog = null;
+ }
+ messageContent.setText(""); // Free memory
+ originalContent = null;
+ super.dispose();
+ }
+}
+
+/**
+ * Separate class for handling content formatting
+ */
+class ContentFormatter {
+ private final ObjectMapper jsonMapper;
+
+ public ContentFormatter() {
+ this.jsonMapper = new ObjectMapper();
+ }
+
+ public FormatResult format(String content) {
+ if (!isLikelyText(content)) {
+ return FormatResult.failure("Content appears to be binary");
+ }
+
+ // Try JSON first
+ try {
+ Object json = jsonMapper.readTree(content);
+ ObjectWriter writer = jsonMapper.writerWithDefaultPrettyPrinter();
+ String formatted = writer.writeValueAsString(json);
+ return FormatResult.success(formatted, SyntaxConstants.SYNTAX_STYLE_JSON, "JSON");
+ } catch (Exception e) {
+ // Try XML
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(new InputSource(new StringReader(content)));
+
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+
+ return FormatResult.success(writer.toString(), SyntaxConstants.SYNTAX_STYLE_XML, "XML");
+ } catch (Exception e2) {
+ return FormatResult.failure("Unsupported format");
+ }
+ }
+ }
+
+ private boolean isLikelyText(String text) {
+ if (text == null || text.isEmpty())
+ return false;
+
+ int controlChars = 0;
+ int totalChars = Math.min(text.length(), 1000); // Sample first 1000 chars
+
+ for (int i = 0; i < totalChars; i++) {
+ char c = text.charAt(i);
+ if (Character.isISOControl(c) && c != '\n' && c != '\r' && c != '\t') {
+ controlChars++;
+ }
+ }
+
+ // If more than 5% control characters, probably binary
+ return (controlChars / (double) totalChars) < 0.05;
+ }
+
+ public static class FormatResult {
+ private final boolean successful;
+ private final String formattedContent;
+ private final String syntaxStyle;
+ private final String formatType;
+ private final String errorMessage;
+
+ private FormatResult(boolean successful, String formattedContent,
+ String syntaxStyle, String formatType, String errorMessage) {
+ this.successful = successful;
+ this.formattedContent = formattedContent;
+ this.syntaxStyle = syntaxStyle;
+ this.formatType = formatType;
+ this.errorMessage = errorMessage;
+ }
+
+ public static FormatResult success(String content, String syntax, String type) {
+ return new FormatResult(true, content, syntax, type, null);
+ }
- javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
- jPanel1.setLayout(jPanel1Layout);
- jPanel1Layout.setHorizontalGroup(
- jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
- .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
- .addComponent(jButton1))
- .addContainerGap())
- );
- jPanel1Layout.setVerticalGroup(
- jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
- .addContainerGap()
- .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 262, Short.MAX_VALUE)
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(jButton1)
- .addContainerGap())
- );
-
- javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
- getContentPane().setLayout(layout);
- layout.setHorizontalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
- layout.setVerticalGroup(
- layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- );
+ public static FormatResult failure(String error) {
+ return new FormatResult(false, null, null, null, error);
+ }
+ // Getters
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ public String getFormattedContent() {
+ return formattedContent;
+ }
+
+ public String getSyntaxStyle() {
+ return syntaxStyle;
+ }
+
+ public String getFormatType() {
+ return formatType;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+ }
+}
+
+/**
+ * Search dialog for read-only content viewing - matches Mirth standard layout
+ */
+class SearchDialog extends JDialog {
+ private final RSyntaxTextArea textArea;
+ private JTextField searchField;
+ private JCheckBox matchCaseBox;
+ private JCheckBox regexBox;
+ private JCheckBox wholeWordBox;
+ private JRadioButton forwardRadio;
+ private JRadioButton backwardRadio;
+ private JButton findButton;
+ private JLabel resultLabel;
+ private SearchContext currentContext;
+
+ public SearchDialog(JDialog parent, RSyntaxTextArea textArea) {
+ super(parent, "Find", false); // Non-modal
+ this.textArea = textArea;
+ initComponents();
+ setupKeyBindings();
pack();
- }// //GEN-END:initComponents
- // @formatter:on
-
- private void jButton1ActionPerformed(java.awt.event.ActionEvent evt)// GEN-FIRST:event_jButton1ActionPerformed
- {// GEN-HEADEREND:event_jButton1ActionPerformed
- this.dispose();
- }// GEN-LAST:event_jButton1ActionPerformed
- // Variables declaration - do not modify//GEN-BEGIN:variables
-
- private javax.swing.JButton jButton1;
- private javax.swing.JPanel jPanel1;
- private javax.swing.JScrollPane jScrollPane1;
- private javax.swing.JTextArea messageContent;
- // End of variables declaration//GEN-END:variables
-}
+ setResizable(false);
+ positionDialog(parent);
+ }
+
+ private void initComponents() {
+ setLayout(new BorderLayout());
+ setTitle("Find");
+
+ JPanel mainPanel = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Find text field
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.anchor = GridBagConstraints.WEST;
+ gbc.insets = new Insets(10, 10, 5, 5);
+ JLabel searchLabel = new JLabel("Find text:");
+ mainPanel.add(searchLabel, gbc);
+
+ gbc.gridx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.weightx = 1.0;
+ gbc.insets = new Insets(10, 5, 5, 10);
+ searchField = new JTextField(25);
+ searchField.addActionListener(e -> findNext());
+ mainPanel.add(searchField, gbc);
+
+ // Direction panel
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.gridwidth = 2;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ gbc.insets = new Insets(10, 10, 5, 10);
+ JPanel directionPanel = new JPanel();
+ directionPanel.setBorder(BorderFactory.createTitledBorder("Direction"));
+ directionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+
+ ButtonGroup directionGroup = new ButtonGroup();
+ forwardRadio = new JRadioButton("Forward", true);
+ backwardRadio = new JRadioButton("Backward");
+ directionGroup.add(forwardRadio);
+ directionGroup.add(backwardRadio);
+ directionPanel.add(forwardRadio);
+ directionPanel.add(backwardRadio);
+ mainPanel.add(directionPanel, gbc);
+
+ // Options panel
+ gbc.gridy = 2;
+ gbc.insets = new Insets(5, 10, 10, 10);
+ JPanel optionsPanel = new JPanel();
+ optionsPanel.setBorder(BorderFactory.createTitledBorder("Options"));
+ optionsPanel.setLayout(new GridLayout(1, 3, 10, 5));
+
+ regexBox = new JCheckBox("Regular Expression");
+ matchCaseBox = new JCheckBox("Match Case");
+ wholeWordBox = new JCheckBox("Whole Word");
+
+ // Add listener to disable other options when regex is selected
+ regexBox.addActionListener(e -> {
+ boolean regexSelected = regexBox.isSelected();
+ matchCaseBox.setEnabled(!regexSelected);
+ wholeWordBox.setEnabled(!regexSelected);
+ if (regexSelected) {
+ matchCaseBox.setSelected(false);
+ wholeWordBox.setSelected(false);
+ }
+ });
+
+ optionsPanel.add(regexBox);
+ optionsPanel.add(matchCaseBox);
+ optionsPanel.add(wholeWordBox);
+ mainPanel.add(optionsPanel, gbc);
+
+ // Buttons panel with result label
+ JPanel bottomPanel = new JPanel(new BorderLayout());
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 10));
+
+ findButton = new JButton("Find");
+ findButton.setPreferredSize(new Dimension(80, 25));
+ findButton.addActionListener(e -> {
+ if (backwardRadio.isSelected()) {
+ findPrevious();
+ } else {
+ findNext();
+ }
+ });
+ buttonPanel.add(findButton);
+
+ JButton closeButton = new JButton("Close");
+ closeButton.setPreferredSize(new Dimension(80, 25));
+ closeButton.addActionListener(e -> setVisible(false));
+ buttonPanel.add(closeButton);
+
+ // Result label (small status at bottom)
+ resultLabel = new JLabel(" ");
+ resultLabel.setFont(resultLabel.getFont().deriveFont(Font.ITALIC, 11f));
+ resultLabel.setHorizontalAlignment(SwingConstants.CENTER);
+ resultLabel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+
+ bottomPanel.add(buttonPanel, BorderLayout.CENTER);
+ bottomPanel.add(resultLabel, BorderLayout.SOUTH);
+
+ add(mainPanel, BorderLayout.CENTER);
+ add(bottomPanel, BorderLayout.SOUTH);
+ }
+
+ private void setupKeyBindings() {
+ // Enter to find next
+ KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
+ searchField.getInputMap().put(enter, "findNext");
+ searchField.getActionMap().put("findNext", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ findNext();
+ }
+ });
+
+ // Escape to close
+ KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+ getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "close");
+ getRootPane().getActionMap().put("close", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+ });
+ }
+
+ private void findNext() {
+ String searchText = searchField.getText();
+ if (searchText.isEmpty()) {
+ resultLabel.setText("Enter text to find");
+ return;
+ }
+
+ updateSearchContext();
+ currentContext.setSearchForward(true);
+ SearchResult result = SearchEngine.find(textArea, currentContext);
+ handleSearchResult(result);
+ }
+
+ private void findPrevious() {
+ String searchText = searchField.getText();
+ if (searchText.isEmpty()) {
+ resultLabel.setText("Enter text to find");
+ return;
+ }
+
+ updateSearchContext();
+ currentContext.setSearchForward(false);
+ SearchResult result = SearchEngine.find(textArea, currentContext);
+ handleSearchResult(result);
+ }
+
+ private void updateSearchContext() {
+ currentContext = new SearchContext(searchField.getText());
+ currentContext.setMatchCase(matchCaseBox.isSelected());
+ currentContext.setRegularExpression(regexBox.isSelected());
+ currentContext.setWholeWord(wholeWordBox.isSelected());
+ }
+
+ private void handleSearchResult(SearchResult result) {
+ if (result.wasFound()) {
+ resultLabel.setText("");
+ } else {
+ resultLabel.setText("\"" + searchField.getText() + "\" not found");
+ }
+ }
+
+ private void positionDialog(JDialog parent) {
+ if (parent != null) {
+ Point parentLocation = parent.getLocationOnScreen();
+ setLocation(parentLocation.x + parent.getWidth() - getWidth() - 20,
+ parentLocation.y + 50);
+ } else {
+ setLocationRelativeTo(null);
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ searchField.requestFocus();
+ searchField.selectAll();
+ }
+ super.setVisible(visible);
+ }
+}
\ No newline at end of file