Редактор Java GUI-интерфейса Swing инструменты и скрипты


Я создаю проект под названием Медит (ссылка). Это достаточно мощный текстовый редактор, который включает:

  1. Подсветка синтаксиса около 91 языков.

  2. Довольно хорошо прокомментирован

  3. Написано на чистом Java

  4. Возможность написания скриптов с использованием B++ ("расширение" для JavaScript)

  5. Операции с текстом (строчная, upercase, randomcase)

  6. Регулярные выражения поиска и замены.

  7. Приятный интерфейс, 9 цветовых схем

  8. Перейти к строке функцию, редактор никогда не подвисает при загрузке файлов, даже такой большой, как 5 ГБ (доказано)

  9. Циклические ГК, если эксплоатации памяти расширяет 300Мб.

  10. Небольшой объем памяти.

  11. Возможность связать свой собственный компилятор для использования с Медит.

  12. Возможность создания собственного автозаполнения на нужный язык.

На самом деле есть только такие функции, но я планирую гораздо больше. Я хотел бы услышать, что сделано хорошо и что сделано хуже в моем проекте. Я не такой продвинутый с Java, так что я бы хотел увидеть обзор моего кода. Единственное, что я не был большой, времени тестирования - есть только некоторые простые тесты и страницы Вики о создании собственного стиля автозаполнения, и создание собственного инструмента и сценарий. Не рекомендуется скачивать файлы напрямую, так как они редко обновляются.

Мелкие детали исходного кода:

MainFrame.java

package medit;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JToolBar;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;

import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.Theme;
import org.fife.ui.rtextarea.RTextScrollPane;

import medit.ActionManagers.AboutActionManager;
import medit.ActionManagers.BottombarActionManager;
import medit.ActionManagers.CodeCompletionActionManager;
import medit.ActionManagers.EditActionManager;
import medit.ActionManagers.FileActionManager;
import medit.ActionManagers.LanguageActionManager;
import medit.ActionManagers.ScriptsActionManager;
import medit.ActionManagers.TextOPActionManager;
import medit.ActionManagers.ThemesActionManager;
import medit.ActionManagers.TimerTaskActionManager;
import medit.ActionManagers.ToolActionManager;
import medit.ActionManagers.WindowActionManager;

/**
 * Main frame for MEdit project. That's where the whole magic is done. It was
 * split to many files, which are located in ActionManagers.
 *
 * @author Krzysztof Szewczyk
 */

public class MainFrame extends JFrame {

    /**
     * Many public variables, that were privatized before. They are public, because
     * our MainFrame is not standalone class now and it references many
     * ActionManagers.
     */

    public static int instances = 1;
    public static final long serialVersionUID = 1L;
    public JPanel contentPane;
    public File currentFile = null;
    public MainFrame instance;
    public final JLabel lblReady = new JLabel(
            "Ready | Length: 0 | Filename: \"Unnamed\" | Maximum size: 0KB | INS | LCK | SCR");
    public final RSyntaxTextArea textPane = new RSyntaxTextArea();

    /**
     * Create the frame.
     */
    public MainFrame() {

        /**
         * Frame setup
         */
        this.instance = this;
        this.setIconImage(Toolkit.getDefaultToolkit()
                .getImage(MainFrame.class.getResource("/medit/assets/apps/accessories-text-editor.png")));
        this.setTitle("MEdit");
        this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        this.setBounds(100, 100, 700, 500);
        this.setMinimumSize(new Dimension(700, 500));
        this.contentPane = new JPanel();
        this.contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        this.setContentPane(this.contentPane);
        this.contentPane.setLayout(new BorderLayout(0, 0));

        /**
         * Menu bar Setup
         */
        final JMenuBar menuBar = new JMenuBar();
        this.setJMenuBar(menuBar);

        /**
         * Menus setup
         */
        final JMenu mnFile = new JMenu("File");
        menuBar.add(mnFile);
        final JMenu mnEdit = new JMenu("Edit");
        menuBar.add(mnEdit);
        final JMenu mnLanguage = new JMenu("Language");
        menuBar.add(mnLanguage);
        final JMenu mnSyntaxHighlighting = new JMenu("Syntax Highlighting");
        menuBar.add(mnSyntaxHighlighting);
        final JMenu mnThemes = new JMenu("Themes");
        menuBar.add(mnThemes);
        final JMenu mnTools = new JMenu("Tools");
        menuBar.add(mnTools);
        final JMenu mnScripts = new JMenu("Scripts");
        menuBar.add(mnScripts);
        final JMenu mnAbout = new JMenu("About");
        menuBar.add(mnAbout);
        final JMenu mnTextOperations = new JMenu("Text Operations");
        menuBar.add(mnTextOperations);

        /**
         * Menu action managers setup.
         */

        final WindowActionManager wam = new WindowActionManager(this);
        wam.Closing();

        final FileActionManager fam = new FileActionManager(this);
        fam.New(mnFile);
        fam.Open(mnFile);
        fam.Save(mnFile);
        fam.SaveAs(mnFile);
        fam.Print(mnFile);
        fam.Separator(mnFile);
        fam.ReloadFromDisk(mnFile);
        fam.OpenDir(mnFile);
        fam.RemoveFromDisk(mnFile);
        fam.Separator(mnFile);
        fam.Exit(mnFile);

        final EditActionManager eam = new EditActionManager(this);
        eam.Cut(mnEdit);
        eam.Copy(mnEdit);
        eam.Paste(mnEdit);
        eam.Delete(mnEdit);
        eam.Separator(mnEdit);
        eam.Undo(mnEdit);
        eam.Redo(mnEdit);
        eam.Separator(mnEdit);
        eam.Search(mnEdit);

        final TextOPActionManager topam = new TextOPActionManager(this);
        topam.SetupTextOP(mnTextOperations);

        final AboutActionManager aam = new AboutActionManager();
        aam.About(mnAbout);

        final CodeCompletionActionManager ccam = new CodeCompletionActionManager(this);
        ccam.SetUpCodeCompletion(SyntaxConstants.SYNTAX_STYLE_NONE);

        final LanguageActionManager lam = new LanguageActionManager(this);
        lam.SetUp(mnSyntaxHighlighting, ccam);

        final ThemesActionManager tam = new ThemesActionManager(this);
        tam.RegisterThemes(mnThemes);

        final TimerTaskActionManager ttam = new TimerTaskActionManager(this);
        ttam.SetUpTimers();

        final BottombarActionManager bbam = new BottombarActionManager(this);
        bbam.SetUpBottombar();

        final ToolActionManager toolam = new ToolActionManager(this);
        toolam.SetupTools(mnTools);

        final ScriptsActionManager sam = new ScriptsActionManager(this);
        sam.SetupScripts(mnScripts);

        /**
         * Language submenu setup
         */
        final JRadioButtonMenuItem rdbtnmntmEnglish = new JRadioButtonMenuItem("English");
        rdbtnmntmEnglish.setSelected(true);
        mnLanguage.add(rdbtnmntmEnglish);

        /**
         * Toolbar setup.
         */
        final JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);
        this.contentPane.add(toolBar, BorderLayout.NORTH);

        fam.New(toolBar);
        fam.Open(toolBar);
        fam.Save(toolBar);
        fam.Exit(toolBar);

        eam.Cut(toolBar);
        eam.Copy(toolBar);
        eam.Paste(toolBar);
        eam.Delete(toolBar);
        eam.Undo(toolBar);
        eam.Redo(toolBar);

        /**
         * Editor setup
         */
        final RTextScrollPane scrollPane = new RTextScrollPane();
        this.contentPane.add(scrollPane, BorderLayout.CENTER);

        this.textPane.setFont(new Font("Monospaced", Font.PLAIN, 13));
        scrollPane.setViewportView(this.textPane);

        this.textPane.clearParsers();
        this.textPane.setParserDelay(1);
        this.textPane.setAnimateBracketMatching(true);
        this.textPane.setAutoIndentEnabled(true);
        this.textPane.setAntiAliasingEnabled(true);
        this.textPane.setBracketMatchingEnabled(true);
        this.textPane.setCloseCurlyBraces(true);
        this.textPane.setCloseMarkupTags(true);
        this.textPane.setCodeFoldingEnabled(true);
        this.textPane.setHyperlinkForeground(Color.pink);
        this.textPane.setHyperlinksEnabled(true);
        this.textPane.setPaintMatchedBracketPair(true);
        this.textPane.setPaintTabLines(true);
        scrollPane.setIconRowHeaderEnabled(true);
        scrollPane.setLineNumbersEnabled(true);
        try {
            final Theme theme = Theme
                    .load(this.getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/default.xml"));
            theme.apply(this.textPane);
        } catch (final IOException ioe) { // Never happens
            final Crash dialog = new Crash(ioe);
            dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
        }
        scrollPane.setLineNumbersEnabled(true);
        scrollPane.setFoldIndicatorEnabled(true);

    }

}

Crash.java:

package medit;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Toolkit;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;

/**
 * Crash dialog that appears after some exception is thrown.
 *
 * @author Krzysztof Szewczyk
 *
 */

public class Crash extends JDialog {

    private static final long serialVersionUID = 1L;
    private final JPanel contentPanel = new JPanel();

    /**
     * Create the dialog.
     */
    public Crash(final Exception E1) {
        this.setIconImage(Toolkit.getDefaultToolkit()
                .getImage(Crash.class.getResource("/medit/assets/actions/process-stop.png")));
        this.setTitle("MEdit");
        this.setBounds(100, 100, 450, 300);
        this.getContentPane().setLayout(new BorderLayout());
        this.contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
        this.getContentPane().add(this.contentPanel, BorderLayout.CENTER);
        this.contentPanel.setLayout(new BorderLayout(0, 0));
        {
            final JLabel lblAnErrorOccured = new JLabel("An error occured.");
            lblAnErrorOccured.setHorizontalAlignment(SwingConstants.CENTER);
            this.contentPanel.add(lblAnErrorOccured, BorderLayout.NORTH);
        }
        {
            final JScrollPane scrollPane = new JScrollPane();
            this.contentPanel.add(scrollPane, BorderLayout.CENTER);
            {
                final JTextArea txtr = new JTextArea();
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw);
                E1.printStackTrace(pw);
                final String sStackTrace = sw.toString();
                txtr.setText(sStackTrace);
                txtr.setFont(new Font("Monospaced", Font.PLAIN, 13));
                scrollPane.setViewportView(txtr);
            }
        }
        {
            final JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
            this.getContentPane().add(buttonPane, BorderLayout.SOUTH);
            {
                final JButton okButton = new JButton("Exit");
                okButton.addActionListener(e -> System.exit(0));
                buttonPane.add(okButton);
                this.getRootPane().setDefaultButton(okButton);
            }
            {
                final JButton cancelButton = new JButton("Continue");
                cancelButton.addActionListener(e -> Crash.this.dispose());
                buttonPane.add(cancelButton);
            }
        }
    }

}

NSSLoader.java (загрузчик скриптов):

package medit.NSS;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.WindowConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import medit.Crash;

/**
 * This class is loading scripts for new script system.
 * 
 * @author Krzysztof Szewczyk
 *
 */

public class NSSLoader {

    private final List<NSSEntry> tools = new ArrayList<>();

    /**
     * This function is loading every scripts from selected file.
     * 
     * @param string
     * @return
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */

    public List<NSSEntry> loadAll(final String string) throws ParserConfigurationException, SAXException, IOException {
        if (!new File(string).exists())
            return null;
        final File inputFile = new File(string);
        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        final Document doc = dBuilder.parse(inputFile);
        doc.getDocumentElement().normalize();
        if (doc.getDocumentElement().getNodeName() != "medit") {
            final Crash dialog = new Crash(
                    new Exception("Parent element in script config file has to be equal to \"medit\"!"));
            dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
        }
        final NodeList nList = doc.getElementsByTagName("script");
        for (int temp = 0; temp < nList.getLength(); temp++) {
            final Node nNode = nList.item(temp);
            if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                final Element eElement = (Element) nNode;
                final String name = eElement.getElementsByTagName("name").item(0).getTextContent();
                final String script = eElement.getElementsByTagName("scriptfile").item(0).getTextContent();
                this.tools.add(new NSSEntry(name, script) {
                    @Override
                    public String getCodeFN() {
                        return this.codefn;
                    }

                    @Override
                    public String getName() {
                        return this.name;
                    }
                });
            }
        }
        return this.tools;
    }

}


344
1
задан 3 февраля 2018 в 06:02 Источник Поделиться
Комментарии
1 ответ

Позвольте мне начать с общих замечаний о свой функционал и тогда я пересмотрю код:

Качели Эол

Качели устарел. Как... два года назад? Все точки с использованием JavaFX и я лично склонен согласиться, хотя парадигм этих двух структур пользовательского интерфейса значительно отличаются...

JavaFX, который просто имеет более современной парадигмы, позволяет более эффективно использовать возможности языка, которые были там в течение многих лет (дженериков для одного). Кроме того, JavaFX, который упрощает некоторые функции, которые вы опишите "переключатель этот ресурс для этого ресурса"...

Я настоятельно рекомендую проверить в JavaFX.

Предпочтения и настройки.

Я хочу заранее отметить, что оформление текстового редактора высокоэластичный поставляется с множеством проблем, одной из которых является то, что пользователи будут завалены огромное количество функций.

9 цветовых схем, кажется много. Особенно, если вы могли бы просто разрешить произвольное количество цветовых схем, и просто разрешить настройку...

"Хороший интерфейс" является весьма субъективным.

В заключение я хочу отметить статью я недавно прочитал: предпочтения считаются вредными

По коду

Я пойду через код сверху донизу, просто комментируя, что приходит на ум, довольно нефильтрованное.


package medit;

Имена пакетов должны быть домены в обратном порядке. Я ожидал com, net, org или что-нибудь подобное в первую очередь.


import java.awt.[...];

AWT-это то, что вы действительно не хотите быть в контакте с непосредственно. Это конечно проблематично, что Java-то запер себя в начале девяностых пользовательского интерфейса-дизайн, сделав сырья авт публичное API (и гарантии его обеспечения обратной совместимости).

Другая проблема с AWT-это то, что вам это не поможет абстрактного пользовательского интерфейса проблемы хорошо. Это просто кабальные писать код авт, не сильно отличается от вызова ОС-процедуры напрямую, помимо того, что на уровне Java, а не системных вызовов.
Это напрямую означает, что весь код обработки пользовательского интерфейса будет очень низкого уровня, что делает это напрасно трудно понять.


import java.io.File;

Ява представила усовершенствованный способ обработки ввода/вывода java.nio пакет был заменен java.io во всех, кроме осуждения предупреждения.

Проблема качания заключается в том, что это действительно только использует java.io API, который делает его более трудным, чем это необходимо, чтобы правильно и аккуратно обрабатывать ошибки ввода-вывода.


import medit.ActionManagers.[...];

Я несколько теряю мое дерьмо. Этот узел при импорте отчетности подразумевает, что вы сильно злоупотреблять статических условиях, а также Managers.

Это означает, что вам трудно четко структурировать код, как объектно-ориентированный. Я рад, что ты не используя явных одиночек (по крайней мере здесь), но это только незначительно лучше.

Если размер программы, что ваш рекламируемый набор функций предполагает, вы должны действительно установить зависимость инъекций. Что уменьшает Manager-Бог-классы, которые отвечают за все, когда-либо, чтобы сделать с $компонентом.

Я допускаю, что это может увеличить память-след маленький, а также ГХ-раз, но это не то, что я буду переживать, если это оказалось проблемой ...


/**
* Main frame for MEdit project. That's where the whole magic is done. It was
* split to many files, which are located in ActionManagers.
*
* @author Krzysztof Szewczyk
*/

Этого комментария javadoc хорошо иллюстрирует те моменты, которые я пытался сделать выше. Особенно часть про разделение это на много файлов на самом деле управляет моей точки дома. Это Бог-класс, который делает все-все-все.

Чтобы получить "правильный объектно-ориентированный" код, вам нужно принять задачу делегации.


public class MainFrame extends JFrame {

Композицию наследованию призывает к одному. Я, возможно, один или другой разглагольствовать об этом в старых ответах моя. Вообще рекомендую почитать через вопросы здесь на сайте, чтобы получить лучшее чувство для того, как вы можете писать качели-код, который не использует это.

Я рад, что вы не использовали мерзость это implements MouseListener, ActionListener, [...].


/**
* Many public variables, that were privatized before. They are public, because
* our MainFrame is not standalone class now and it references many
* ActionManagers.
*/

Это еще больше приводит в точке О Бог-класса. Вы чрезвычайно тесно связывая универсальный для всех компонентов (и наоборот). Что делает код устойчивым к небольшим и содержал изменения.

Вместо того, чтобы менять что-то, как правило, приведет к необходимости внесения изменений в большое количество файлов. Вы хотите, чтобы избежать, что так, что вместо того, чтобы ловить изменения во всей кодовой базы, вы можете уверенно менять небольших блоков кода. Обычно это также делает его гораздо легче рассуждать об изменениях, поскольку не нужно учитывать влияние изменения на всю программу (нескольких тысяч строк кода), но может рассуждать о них в контексте вашего класса (или худшем случае пакет).


public static int instances = 1;

Типа вообще не нужно знать, сколько экземпляров существует. Это очень необычно и странно. Зачем вам это нужно?


 public static final long serialVersionUID = 1L;

Это не реализовать Serializable. Также это по умолчанию жидкость. Что это не жидкость вообще, и теряется весь смысл один.

Пахнет груза-Culting. Вы знаете, почему это там?


public JPanel contentPane;
public File currentFile = null;

Первое-это хорошо, хотя она заставляет вас создать subpanes для любой "бок о бок" вид редактора. Второй, не так уж и много. Для одного ты запираешь себя в наличие только один файл открыть в одно время, а во-вторых ты предписываешь смысл null. Это можно исправить, что-то вроде следующего:

List<LayoutUnit> layoutUnits = new ArrayList<>();

в этих блоков, то вы могли бы иметь

List<StackableDisplayArea> displayStack = new ArrayList<>();
int currentlyActiveDisplayStack = 0;

а StackableDisplayArea будет что-то вроде EditorArea, а SettingsArea, а FolderViewArea ... надеюсь вы поняли :)


public MainFrame instance;

Это имеет смысл только в качестве static поле. Кто может получить доступ к этому, уже есть экземпляр. Вы должны либо MainFrame foo = [...]; или вы находитесь внутри MainFrame уже.

В первом случае foo == foo.instance и в последнем случае this.instance == this. Зачем тебе такой член?


Боковая панель: на данный момент, мы два экрана в класс. Я еще не видел любой мыслимый причина всех вещей, которые я отметил на. Я может быть исправлено позже.

Это, как говорится, я мог бы оставить это положение в целом обзор уже. Я искренне надеюсь, что вы вернетесь для больше отзывов с других частей вашего кода (и улучшенная версия того, что вы показываете здесь).



this.instance = this;

Почему?


    /**
* Menu action managers setup.
*/

До тех пор, пока здесь конструктор был хорошо, хотя и несколько большие. После этого комментария, конструктор принимает работу DependencyInjection и настройка всей программы. Есть один основной вопрос к этому:

Поведенческие определения не отделена от визуального определения. Позвольте мне объяснить:

Возвращаясь к ActionManagers кажется, что вам трудно четко отделить интерфейс от действий. Я настоятельно рекомендую вам заглянуть в общий пользовательский интерфейс модели, как MVC, MVP и MVVM.
Я лично имел лучшие результаты в разгаре, когда с помощью "ведущий-первый МВП" подход. Делаешь, что позволило мне определить поведение моей программы на абстрактном уровне (как ведущий-интерфейс). Затем я последовал путем создания "вид". это мнение, где все компоненты Swing находиться. Это связано с соображениями выше про StackedAreas. Это заботиться, это должен быть отдельный класс.

Последним шагом является то, чтобы инкапсулировать данные, которые вы используете в целом построение модели"". Вы можете повторить этот процесс для каждого из компонентов у вас есть.

Это позволяет довольно аккуратно отделить проблемы дисплея от проблем поведения из опасения данных-жилье.

И что, в свою очередь, позволяет уменьшить объем изменений, сцепление между классами и повышает ремонтопригодность и контролепригодность.
Все это важно, когда вы пишете программу в течение длительного времени. Месяцев или даже лет программирования не исключено, для такого проекта, как ваш. Особенно на те сроки, внесение изменений-это огромная экономия времени.

Я даже не буду комментировать остальные конструктора в сторону от следующего абзаца. Этот конструктор на данный момент main вашей программы. Решения главной проблемы дизайн кардинально изменит конструктор.

Конструктор должен нести ответственность только за построение класса, которому она принадлежит. Он не должен соединять компоненты. Когда ваш конструктор будет просто назначая членов, вы будете знать, что ваша цель проектирования достигнута.

2
ответ дан 4 февраля 2018 в 02:02 Источник Поделиться