IDEA插件学习(二)

2 篇文章 0 订阅
2 篇文章 0 订阅

上篇文章我们学习了基本的插进的知识,现在我们继续学习插件相关的基础知识。

本章节的主要内容是如何使用缓存,如何创建Setting布局,如何创建RightMenu布局。本章以一个DEMO的产生开始进行讲解其中的使用。前序知识点是要会java的Swing,虽然这个很难用,也很难看,也没几个人用。但是这个是插件的布局的方式。DEMO的例子都比较简单,涉及到的也只是文案,下拉列表,列表等常见控件。

1. 创建Setting布局

创建一个类继承SearchableConfigurable,即可实现Setting的配置,现在来看一下内部实现。
 private LeanENForm form;
    @NotNull
    @Override
    public String getId() {
        return "com.longshihan.learnEn.settingID";
    }

    @Nls(capitalization = Nls.Capitalization.Title)
    @Override
    public String getDisplayName() {
        return "外语学习";
    }

    @Nullable
    @Override
    public JComponent createComponent() {
        form=new LeanENForm();
        form.createUI();
        return form.getContentPane();
    }

    @Override
    public boolean isModified() {
        return form.isModified();
    }

    @Override
    public void apply() throws ConfigurationException {
        form.apply();
    }

getId:同AnAction一致,是设置界面的指定ID
getDisplayName:设置显示的名称
createComponent:创建布局,在这里我创建了一个叫LeanENForm的布局
apply:生效,对应的是IDEA的setting下面的apply

现在来看一下LeanENForm的结构,你可以点右键创建一个form。不过swing的可视化布局的确比较难用,一般我选择手写。

 public void createUI() {
        mainPanel.setLayout(new GridLayout(10,0));
        JPanel stateMainPane = new JPanel(new FlowLayout(0));
        JPanel statusPane=new JPanel();
        statusPane.add(new JLabel("是否打开近义词显示:"));
        jCheckBoxPromt.addItem("TRUE");
        jCheckBoxPromt.addItem("FALSE");
        statusPane.add(jCheckBoxPromt);
        stateMainPane.add(statusPane);
        JPanel historyPane=new JPanel();
        historyPane.add(new JLabel("是否打开例句显示:"));
        jCheckBoxHistory.addItem("TRUE");
        jCheckBoxHistory.addItem("FALSE");
        historyPane.add(jCheckBoxHistory);
        stateMainPane.add(historyPane);
        JPanel relPane=new JPanel();
        relPane.add(new JLabel("是否打开同根显示:"));
        jCheckNoxRel.addItem("TRUE");
        jCheckNoxRel.addItem("FALSE");
        relPane.add(jCheckNoxRel);
        stateMainPane.add(relPane);
        mainPanel.add(stateMainPane);
 //...持久化数据的读取
 }

上面写的布局,相信没写过SWing的童鞋也能轻轻松松的写下来,这就是一个FLOWlayout布局(流式布局),然后jpanel里面放一个label(文案)和checkbox(下拉列表):具体的界面如下图:

界面大概就是这样的,虽然长得比较丑。

下面来说说持久化数据这块,有界面有数据,肯定要保存数据,而IDEA在这块做的就很好(我觉得可以在Android上也写这么一个功能),创建一个State类,继承实现PersistentStateComponent。


@State(name = "SettingConfig",storages = {@com.intellij.openapi.components.Storage(value = "learnEn-config.xml",
        roamingType = com.intellij.openapi.components.RoamingType.DISABLED)})
public class SettingState implements PersistentStateComponent<SettingState> {
    private SettingConfig initConfig;
    public SettingState() {

    }

    @Nullable
    public static SettingState getInstance() {
        return (SettingState) ServiceManager.getService(SettingState.class);
    }

    @Nullable
    @Override
    public SettingState getState() {
        return this;
    }

    @Override
    public void loadState(@NotNull SettingState settingConfig) {
        if (settingConfig == null) {
            return;
        }
        XmlSerializerUtil.copyBean(settingConfig, this);
    }


    public SettingConfig getConfig() {
        if (initConfig==null){
            initConfig=new SettingConfig();
        }
        return initConfig;
    }


    public void setConfig(SettingConfig config) {
        initConfig=config;
    }

其中SettingConfig是一个javabean,通过这个类来实现持久化对象,另外对于一些比较重要的数据,官方也提供了其他的方法保存数据:


  PasswordSafe.getInstance().storePassword(null, getClass(), "learnDictPath","value");

   PasswordSafe.getInstance().getPassword(null, getClass(), "learnDictPath");

LearnENForm的createUI方法里面关于读取数据:

     SettingConfig config = SettingState.getInstance().getConfig();
        if (config != null) {
            this.jCheckBoxPromt.setSelectedIndex(config.isSyno()?0:1);
            this.jCheckBoxHistory.setSelectedIndex(config.isSentences()?0:1);
            this.jCheckNoxRel.setSelectedIndex(config.isRelWord()?0:1);
        }

对于apply方法则是将数据存入,具体实现如下:


 public void apply() {
        SettingConfig config=SettingState.getInstance().getConfig();
        if (config==null){
            config=new SettingConfig();
        }
        config.setSyno(jCheckBoxHistory.getSelectedIndex()==0);
        config.setSentences(jCheckBoxPromt.getSelectedIndex()==0);
        config.setRelWord(jCheckNoxRel.getSelectedIndex()==0);
        SettingState.getInstance().setConfig(config);
    }

上面就是Setting界面完整的情况,在plugin.xml里面将数据配置就大功告成。


 <extensions defaultExtensionNs="com.intellij">
    <applicationService serviceInterface="com.longshihan.learnEN2.setting.SettingState"
                        serviceImplementation="com.longshihan.learnEN2.setting.SettingState"/>
    <applicationConfigurable groupId="tools"
                             instance="com.longshihan.learnEN2.setting.LearnENSettingConfigurable"/>
  </extensions>

2. 创建RightMenu布局

先看界面吧,界面比较简单,就是一个横向的流式布局+列表。

先说下数据,数据是取的有道单词本,每日词汇的内容(如有侵权,请及时告知删除内容)。
首先创建一个类实现ToolWindowFactory,完整的代码如下:


public class LearnToolsWindowFactory implements ToolWindowFactory, RightMenuRefreshListener, HttpResponceListener {
    HttpUtils httpUtils;
    NavigatorPanel navigatorPanel;
    SettingConfig config;
    int startIndex;
    String dictId;
    int pageSize;

    @Override
    public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
        ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
        config = SettingState.getInstance().getConfig();
        if (config == null) {
            config = new SettingConfig();
        }
        if (TextUtils.isEmpty(config.getDictId())) {
            config.setDictId("BEC_2");
        }
        if (config.getPagesize() == 0) {
            config.setPagesize(20);
        }
        startIndex=config.getStartIndex();
        dictId=config.getDictId();
        pageSize=config.getPagesize();
        navigatorPanel = new NavigatorPanel(toolWindow, project, this, config);
        Content content = contentFactory.createContent(navigatorPanel, "", false);
        toolWindow.getContentManager().addContent(content);
        httpUtils = new HttpUtils(this);
        getDataUrl();
        config.setStartIndex(config.getStartIndex() + config.getPagesize());
        SettingState.getInstance().setConfig(config);
    }

    @Override
    public void onNext(int pageSize, String dictId) {
        config.setPagesize(pageSize);
        this.pageSize=pageSize;
        if (!dictId.equals(config.getDictId())) {
            config.setStartIndex(0);
            startIndex=0;
        }else {
            config.setStartIndex(config.getStartIndex()+config.getPagesize());
            startIndex=startIndex+pageSize;
        }
        config.setDictId(dictId);
        this.dictId=dictId;
        SettingState.getInstance().setConfig(config);
        getDataUrl();
    }

    @Override
    public void onRefresh() {
        getDataUrl();
    }

    @Override
    public void onGetMessage(ResponceInfo info) {
        if (info != null && info.isSuccess() && info.getData() != null) {
            Gson gson = new Gson();
            EveryDayWordInfo info1 = gson.fromJson(info.getData(), EveryDayWordInfo.class);
            if (info1 != null && info1.getWords() != null && info1.getWords().size() > 0) {
                System.out.println("刷新");
                navigatorPanel.putData(info1);
            }
        }
    }
}

其中关键的方法是:createToolWindowContent,可以看到是生成一个panel放进contentFactory中,并加载进toolwindow,就将关键的界面一一绑定。

   Content content = contentFactory.createContent(navigatorPanel, "", false);
   toolWindow.getContentManager().addContent(content);

像RightMenuRefreshListener,HttpResponceListener是布局内事件的接口回调和网络的接口回调,这边都是无关紧要的东西。具体界面布局如下:

public class NavigatorPanel extends SimpleToolWindowPanel implements DataProvider {
    private JPanel queryPanel;
    private JBScrollPane contentScrollPanel;
    private SimpleTree tree;
    private SimpleToolWindowPanel treePanel;
    private WordElement wordElement=new WordElement();
    private  JList list;
    private JComboBox dictcomboBox=new JComboBox();
    private JComboBox pageSizeomboBox=new JComboBox();
    private Map<String,String> dictMap=new HashMap<>();
    private SettingConfig config;

    public NavigatorPanel(ToolWindow toolWindow, Project project, RightMenuRefreshListener listener, SettingConfig config) {
        super(true, true);
        dictMap.put("BEC_2","商务英语词汇");
        dictMap.put("CET4luan_1","四级词汇");
        ActionManager actionManager = ActionManager.getInstance();
        treePanel=new SimpleToolWindowPanel(true,true);
        JPanel toolsPanel=new JPanel();
        toolsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        for (String dictStr :dictMap.values()) {
            dictcomboBox.addItem(dictStr);
        }
        if ("BEC_2".equals(config.getDictId())){
            dictcomboBox.setSelectedIndex(0);
        }else if ("CET4luan_1".equals(config.getDictId())){
            dictcomboBox.setSelectedIndex(1);
        }
        toolsPanel.add(dictcomboBox);
        pageSizeomboBox.addItem(20);
        pageSizeomboBox.addItem(40);
        pageSizeomboBox.addItem(60);
        if (config.getPagesize()==20){
            pageSizeomboBox.setSelectedIndex(0);
        }else if (config.getPagesize()==40){
            pageSizeomboBox.setSelectedIndex(1);
        }else if (config.getPagesize()==60){
            pageSizeomboBox.setSelectedIndex(2);
        }
        toolsPanel.add(pageSizeomboBox);
        JButton nextButton=new JButton();
        nextButton.setText("下一页");
        nextButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (listener!=null){
                    listener.onNext(pageSizeomboBox.getSelectedIndex()*20+20, (String) dictMap.keySet().toArray()[dictcomboBox.getSelectedIndex()]);
                }
            }
        });
        toolsPanel.add(nextButton);
        JButton refreshButton=new JButton();
        refreshButton.setText("刷新");
        refreshButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (listener!=null){
                    listener.onRefresh();
                }
            }
        });
        toolsPanel.add(refreshButton);
        treePanel.setToolbar(toolsPanel);

        JScrollPane scrollPane=new JScrollPane();    //创建滚动面板
        treePanel   .add(scrollPane,BorderLayout.CENTER);    //将面板增加到边界布局中央
        list=new JBList();
        //限制只能选择一个元素
        list.setCellRenderer(new LearnCellRender(config));
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        scrollPane.setViewportView(list);    //在滚动面板中显示列表
        list.setModel(wordElement);
        setContent(treePanel);

    }

    public void putData(EveryDayWordInfo info1) {
        config= SettingState.getInstance().getConfig();
        list.setModel(new WordElement(info1.getWords()));
    }
}


这里的布局咋一看复杂,其实也就是一个流式布局+列表。其中列表内元素的绘制是在LearnCellRender这个方法里面。
下面是个缩减版的代码,具体绘制是在getListCellRendererComponent这个方法中,可以看到返回的是Component。代码也没什么好说的,就是拿控件按你的布局塞进去,比较死板。

public class LearnCellRender implements ListCellRenderer {
    private SettingConfig config;

    public LearnCellRender(SettingConfig config) {
        this.config = config;
    }

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        if (value != null) {
            if (value instanceof WordsBeanX) {
                WordsBeanX data = (WordsBeanX) value;
//                JPanel jPanel = new JPanel(new GridLayout(0, 1));
                Box jPanel = Box.createVerticalBox();    //创建横向Box容器
//                jPanel.setBackground(Color.WHITE);
                if (data.getWord() != null) {
                    JLabel titleLabel = new JLabel(data.getWord().getWordHead());
                    titleLabel.setFont(new Font(null, Font.ITALIC, 20));
                    jPanel.add(titleLabel);
                    if (data.getWord().getContent() != null) {
                        jPanel.add(new JLabel("美:[" + data.getWord().getContent().getUsphone() + "]"));
                        jPanel.add(new JLabel("英:[" + data.getWord().getContent().getUsphone() + "]"));
                        Map<String, JTextArea> adjMap = new HashMap<>();
                        if (data.getWord().getContent().getSyno() != null &&
                                data.getWord().getContent().getSyno().getSynos() != null) {
                            List<WordsBeanX.WordBean.ContentBean.SynoBean.SynosBean> adjList
                                    = data.getWord().getContent().getSyno().getSynos();
                            for (int i = 0; i < adjList.size(); i++) {
                                JTextArea adjTextArea = new JTextArea();
                                adjTextArea.setLineWrap(true);
                                String adjStr = adjList.get(i).getPos() + ":" + adjList.get(i).getTran();
                                adjTextArea.append(adjStr);
                                if (config!=null&&config.isSyno()) {
                                    if (adjList.get(i).getHwds() != null) {
                                        adjTextArea.append("\n  ");
                                        adjTextArea.append("近义词:");
                                        for (int j = 0; j < adjList.get(i).getHwds().size(); j++) {
                                            if (!TextUtils.isEmpty(adjList.get(i).getHwds().get(j).getW())) {
                                                adjTextArea.append(adjList.get(i).getHwds().get(j).getW() + ",");
                                            }
                                        }
                                    }
                                }
                                adjMap.put(adjList.get(i).getPos(), adjTextArea);
                            }
                        }
                      
                        for (JTextArea jTextArea : adjMap.values()) {
                            jPanel.add(jTextArea);
                        }
                    }
                }
                jPanel.add(new JLabel("     "));
                return jPanel;
            } else {
                return new JLabel("对象解析失败");
            }
        } else {
            return new JLabel("对象解析失败");
        }
    }
}

另外还有重要的plugin.xml需要添加


  <extensions defaultExtensionNs="com.intellij">
    <toolWindow id="learnEnPlugin" secondary="true" anchor="right"
                factoryClass="com.longshihan.learnEN2.rightmenu.LearnToolsWindowFactory"/>

 </extensions>

可以看到rightMenu这块的代码也很简单,复杂的逻辑都是从简单的逻辑一点点推理出来的。所以打好基础很重要。

3. 结束语

上面的demo的源码地址是:源码
下面讲一下如何上传吧,首先plugin.xml里面相关的配置配置完善,

  <id>com.longshihan.learnEN</id>
  <name>程序员每日英语</name>
  <version>1.0</version>
  <vendor email="577093937@qq.com" url="http://longshihan.androider.com">龙逝寒</vendor>

  <description><![CDATA[
       程序员每日英语<br>
    ]]></description>

  <change-notes><![CDATA[
      Add change notes here.<br>
      <em>most HTML tags may be used</em>
    ]]>
  </change-notes>

否则打包上传的时候也会提示报错。在build目录下面有个打包选项:

点击之后就可以看到生成一个jar或zip压缩包,这时候进IDEA的插件仓库(需要登录):跳转

按照要求上传就行,这个没什么难度。

至此IDEA初级部分已经说完了,像控制台的布局等都是万变不离其宗的,基础部分关于Anaction,布局,数据持久化这块已经说完了,后面会讲到插件的中级部分,关于Editor编辑器的处理,实时监听输入,插入代码等,敬请亟待。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值