ここで、紹介するJMenuStringは、
キー操作で 例えば次のようなポップアップメニューを作るためのクラスとして作りました。
メニューは、表示中のテキストの内容とキャレット位置から自動的に必要なメニュー項目だけを、
出現させる目標で作りました。
次のクラス図になっています。
JMenuString |
---|
+String item;//メニュー表示用の文字列 +int level;//表示する際のメニューレベル +String match;//前方サーチ適合文字列 +String matchScope;//前方サーチ範囲指定文字列 +int ansKeyCode;//応答するキーコード |
+JMenuString readMenuString (String s) 1行のsからJMenuStringを生成して返す。 +ArrayList <JMenuString> readMenuString_s(String str) strの複数行からJMenuString集合を生成して返す。 +JPopupMenu getPopupMenu(ArrayList <JMenuString> lst, String txt, int keyCode,ActionListener listener) txtの最後にキャレットがあるとして、keyCodeの仮想キーコード入力で必要なPopupMenuを生成して返す。 |
String htmlMenuStr0= "<html></html>,,<html,44,重複使用なし\n"+ "<head></head>,,<head,44,重複使用なし\n"+ "<body></body>,,<body,44,重複使用なし\n"+ "<title></title>,<head,</head>,44\n"+ "<style type=\"text/css\">body { background-color: #FFEEBB;}</style>,<head,</head>,44\n"+ "<script language=\"javascript\"></script>,<head,</head>,44\n"+ "<h1></h1>,<body,</body>,44\n"+ "<h2></h2>,<body,</body>,44\n"+ "<p></p>,<body,</body>,44\n"+ "<hr>,<body,</body>,44,\n"+ "インライン要素,<body,</body>,44\n"+ "\t<br>,<body,</body>,44,\n"+ "\t<img src=\"\">,<body,</body>,44\n"+ "\t<span></span>,<body,</body>,44\n"+ "\t<a href=\"\"></a>,<body,</body>,44\n"+ "width=,<img,>,\n"+ "height=,<img,>,\n"+ "特殊文字,<body,</body>,54\n"+ "\t ,<body,</body>,54\n"+ "\t<,<body,</body>,54\n"+ "\t>,<body,</body>,54\n"+ "\t&,<body,</body>,54\n"; ArrayList <JMenuString> mneuStringList=JMenuString.readMenuString_s(htmlMenuStr0);
public void keyPressed(KeyEvent e){//キーを押しているときに呼び出されます。 int pos = this.getSelectionStart();//選択している位置を取得 Rectangle rect = this.modelToView(pos);//カーソルの座標取得 String txt = this.document.getText(0, pos);//先頭からキャレットまでの文字列 JPopupMenu popupMenu = JMenuString.getPopupMenu(HtmlEditor.mneuStringList, txt, keyCode, this); if(popupMenu != null){ popupMenu.show(this, rect.x, rect.y); } //・・・・・ }
public static JPopupMenu getPopupMenu(ArrayListlst, String txt, int keyCode,ActionListener listener) lst:メニュー項目(JMenuString)のリストを作って、指定します。予め、JMenuString.readMenuString_sなどで用意しておきます。 txt:テキスト編集コンポーネントで、先頭からキャレットまでの文字列です。この内容からマッチするメニュー項目を選択登録します。 keyCode:メニュー項目(JMenuString)のインスタンス変数ansKeyCodeが、このキーコードに一致する場合に、そのメニュー項目が登録対象となります。 listener:lst内の各メニュー項目(JMenuString)に設定するインスタンス。それはメニュー選択のActionListenerをimplementsしたインスタンスです。
getPopupMenuのスタート | |
lst内の全てメニュー項目(JMenuString)に登録される以前の全てのリスナーを削除し、引数のリスナーを登録する | |
(繰り返しで各メニュー項目(JMenuString)に対して行われる。リスナーの削除でも繰り返しを使う) | |
popupMenu = new JPopupMenu();で作り、これに必要なJMenuStringを登録して、最後で戻り値にする。 | |
Stack | |
JMenuString prevMenuItem = null; 繰り返しで、前の登録要素を管理する。前の要素なので確定 | |
for(JMenuString menuStr : lst) で、lstの各要素をmenuStrにセットして、それを登録する繰り返し | |
サブメニューを登録するか?(スタックにデータがあって、そのレベルがmenuStrのレベルより小さい?) | |
後方判定繰り返し(スタックに詰まれるサブメニューを登録する繰り返し) | |
prevMenuItemのレベルがスタックサイズと同じか? | |
prevMenuItemをスタックトップのメニュー項目に入れる。prevMenuItemはnullへ | |
スタックトップのサブメニュー内にメニュー項目があか? | |
スタックトップを取り出して、減ったスタックトップのサブメニューのメニュー項目へセット | |
menuStrのレベルがスタックサイズより小さければ、上記を繰り返す。 | |
prevMenuItem が null でない? (その場合は以下でprevMenuItemを登録する。) | |
prevMenuItemのレベルより、menuStrのメニューレベルが大きい? | |
prevMenuItemをサブメニューとしてスタックに積む。 | |
スタックが空でない? (上記の繰り返しで、prevMenuItemのレベルとスタックサイズは同じに調整されている) | |
スタックトップのサブメニューへprevMenuItemのメニュー項目を追加 | |
いずれでもない? | |
トップメニューとしてprevMenuItemのメニュー項目を追加 | |
prevMenuItem=null; 登録終わったという設定 | |
menuStr.ansKeyCodeのキーコードが、引数のキーコードとマッチしない? | |
スキップ(menuStrは登録対象でない) | |
重複不可のitemが、引数txtの中で、すでに使っていると判断された? | |
スキップ(menuStrは登録対象でない) | |
引数txtがmenuStr.match〜menuStr.matchScopeの範囲内でないと判断された? | |
スキップ(menuStrは登録対象でない) | |
prevMenuItem = menuStr; menuStrのメニュー項目が適合したので、次の繰り返しで登録するとした設定 | |
スタックにサブメニューがあるか? | |
後方判定繰り返し(スタックに詰まれるサブメニューを登録する繰り返し) | |
prevMenuItemのレベルがスタックサイズと同じか? | |
prevMenuItemをスタックトップのメニュー項目に入れる。prevMenuItemはnullへ | |
スタックトップのサブメニュー内にメニュー項目があか? | |
スタックトップを取り出して、減ったスタックトップのサブメニューのメニュー項目へセット | |
スタックサイズがあれば、上記を繰り返す。 | |
prevMenuItem != null で、まだ登録されていないなら? | |
prevMenuItemをトップメニューとして登録 | |
popupMenuにメニュー項目がない? | |
return null; | |
return >popupMenu; で構築されたポップアップメニューを返す。 |
import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.event*; //---------------------------------------------------------------------------- //階層構造を持つポップアップメニュー項目を作るためのメニュー項目用クラス public class JMenuString extends JMenuItem { static final long serialVersionUID = 100009; String item;//メニュー表示用の文字列 int level;//表示する際のメニューレベル String match;//前方サーチ適合文字列 String matchScope;//前方サーチ範囲指定文字列 int ansKeyCode;//応答するキーコード // 1行ずつ読み取り、それが、'\t'が先頭にあれば再帰する。 public static JMenuString readMenuString (String s) { if(s == null) return null; String []a = s.split(","); if(a.length == 0) { System.out.println("メニュー文字列に矛盾があります。"); } //2012-3-2 コンマのデータを 、「\\,」の表記で使えるように変更する。また「\\n」を改行を使えるようにする。 for(int i=0; i< a.length;i++){ if(a[i].endsWith("\\\\")){ int k=i; while(a[i].endsWith("\\\\")){//「\\,」を,のデータに戻して、次の要素を繋げる a[i] = a[i].substring(0, a[i].length()-2) + "," + a[i+1]; // a[i+1] の要素を削除する List<String> list = new ArrayList<String>(Arrays.asList(a)); list.remove(i+1); a = (String [])list.toArray(new String[list.size()]); } i=k; } } JMenuString menuString = new JMenuString(); // a[0]のメニュー表示文字列のレベルを調べて設定 int n = 0; while(a[0].charAt(n++) == '\t'); menuString.level = n-1;//タブの数を設定 try { menuString.item = a[0].trim().replaceAll("\\\\\\\\n", "\n");//メニュー項目 2012-3-2 改行データを使える置き換えに変更 menuString.match = a[1]; menuString.matchScope = a[2]; menuString.ansKeyCode = Integer.parseInt(a[3].trim()); } catch(Exception e){ System.err.println("----- Error JMenuString data :" + menuString.item); } menuString.setText(menuString.item);//メニュー項目として表示する文字列セット return menuString; } // str から、メニューデータを1行分ずつ読み取り、ArrayListに入れ、それを返す。 public static ArrayList <JMenuString> readMenuString_s(String str) { ArrayList <JMenuString> mneuStringList = new ArrayList <JMenuString>(); String []a = str.split("\n"); JMenuString menuStr; for(int i = 0; i < a.length; i++){ if(a[i].startsWith("//")) continue; //コメントと判断して使わない if(a[i].trim().equals(""))continue; menuStr=readMenuString(a[i]); mneuStringList.add(menuStr); } return mneuStringList; } //仮想キーコード(keyCode)で出すポップアップメニュー用のJPopupMenuを、BaseMenuItemの集合から、txtに内容に合うメニュー項目で生成する。 public static JPopupMenu getPopupMenu(ArrayList <JMenuString> lst, String txt, int keyCode, ActionListener listener){ if(lst == null || keyCode < MenuKeyEvent.VK_SPACE || keyCode > MenuKeyEvent.VK_Z) return null; //lst内の全てメニュー項目(JMenuString)に登録される以前の全てのリスナーを削除し、引数のリスナーを登録する for(JMenuString jmstr : lst){ ActionListener []act = jmstr.getActionListeners(); if(act.length > 0 && act[0] == listener) break; for(int j = 0 ; j < act.length; j++){ jmstr.removeActionListener(act[j]); //以前のリスナーを削除 } jmstr.addActionListener(listener);//新しいリスナーを登録 } JPopupMenu popupMenu = new JPopupMenu();//戻り値管理用 Stack<JMenu> stack = new Stack<JMenu>(); JMenuString prevMenuItem = null; for(JMenuString menuStr : lst){// この順番で、リスト項目を追加する。 // System.out.printf("%s:%d,%d\n", menuStr.item,menuStr.ansKeyCode, keyCode); if(stack.isEmpty()==false && stack.size() > menuStr.level){ do{ if(prevMenuItem != null && prevMenuItem.level == stack.size()) { stack.peek().add(prevMenuItem); prevMenuItem = null; } JMenu submenu = stack.pop(); if(submenu.getSubElements().length > 0){ if(stack.size() == 0) { popupMenu.add(submenu);//トップへ、メニュー登録 break; } stack.peek().add(submenu);//サブへ、メニュー登録 } }while(stack.size() > menuStr.level); } if(prevMenuItem != null){ if(prevMenuItem.level < menuStr.level){//前より、メニューレベルが大きい stack.push(new JMenu(prevMenuItem.item));//サブメニューを用意 } else if(stack.isEmpty()==false){ stack.peek().add(prevMenuItem); } else { popupMenu.add(prevMenuItem);//トップへ、メニュー登録 } prevMenuItem = null; } if(menuStr.ansKeyCode != 0 && menuStr.ansKeyCode != keyCode){ continue;//操作キーが、このメニュー項目用でなければ、登録しない。 } int iRangeEnd = -1;//txtがmatchScopeと適合するかの判定用 if(menuStr.matchScope.equals("") == false){ iRangeEnd = txt.lastIndexOf(menuStr.matchScope); if(iRangeEnd != -1 && menuStr.match.equals("") == true){ continue; //重複不可のitemで、すでに使っていると判断されたので登録しない } } int iRangeStart = -1; if(menuStr.match.equals("")==false){ iRangeStart = txt.lastIndexOf(menuStr.match); if(iRangeStart == -1){ continue;//範囲先頭がないので適合しない。 } if(iRangeEnd != -1 && iRangeStart < iRangeEnd){ continue;// 範囲指定があって、適合しない。 } } //メニュー項目が適合したので登録する処理 prevMenuItem = menuStr; } //繰り返し後の処理 if(stack.empty() == false) { do{ if(prevMenuItem != null && prevMenuItem.level == stack.size()) { stack.peek().add(prevMenuItem); prevMenuItem = null; } JMenu submenu = stack.pop(); if(submenu.getSubElements().length > 0){ if(stack.size() == 0) { popupMenu.add(submenu);//トップへ、メニュー登録 break; } stack.peek().add(submenu);//サブへ、メニュー登録 } }while(stack.size()==0); } if(prevMenuItem != null){ popupMenu.add(prevMenuItem); } if(popupMenu.getSubElements().length == 0) return null; return popupMenu; } //---------------------------------------------------------------------------- // 以下は、テスト用のクラスと、main static class TestFrame extends javax.swing.JFrame implements ActionListener,KeyListener { javax.swing.JTextPane text = new javax.swing.JTextPane() { @Override public boolean getScrollableTracksViewportWidth() { Object parent = getParent(); if (parent instanceof javax.swing.JViewport) { javax.swing.JViewport port = (javax.swing.JViewport) parent; int w = port.getWidth(); // 表示できる範囲(上限) javax.swing.plaf.TextUI ui = getUI(); java.awt.Dimension sz = ui.getPreferredSize(this); // 実際の文字列サイズ if (sz.width < w) { return true;// 折り返しする } } return false; // 折り返しを行わない } }; javax.swing. JScrollPane scroll = new javax.swing.JScrollPane(text); ArrayList <JMenuString> mneuStringList; public void actionPerformed(java.awt.event.ActionEvent e){ Object obj = e.getSource(); try{ if(obj instanceof JMenuString){ JMenuString menu = (JMenuString)obj; int pos = this.text.getSelectionStart();//選択している位置を取得 this.text.getDocument().insertString(pos, menu.item, null); } } catch(Exception err){ } } public void keyPressed(KeyEvent e){//キーを押しているときに呼び出されます。 int keyCode = e.getKeyCode(); if(e.isControlDown() == false)return; int pos = this.text.getSelectionStart();//選択している位置を取得 try { java.awt.Rectangle rect = this.text.modelToView(pos);//カーソルの座標取得 String txt = this.text.getDocument().getText(0, pos);//先頭からキャレットまでの文字列 JPopupMenu popupMenu = JMenuString.getPopupMenu(this.mneuStringList, txt, keyCode, this); if(popupMenu != null){ popupMenu.show(this, rect.x, rect.y); } } catch(Exception err){} } public void keyReleased(KeyEvent e){//キーを離したときに呼び出されます。 } public void keyTyped(KeyEvent e){//キーをタイプすると呼び出されます。 } TestFrame(){//テストフレーム用コンストラクタ this.setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE); this.add(scroll); scroll.setPreferredSize( new java.awt.Dimension(600,400)); this.pack(); this.setVisible(true); String htmlMenuStr0= "<html >\\\\n</html>,,<html,44,重複使用なし\n"+ "<head></head>,,<head,44,重複使用なし\n"+ "<body></body>,,<body,44,重複使用なし\n"+ "<title></title>,<head,</head>,44\n"+ "<h1></h1>,<body,</body>,44\n"+ "<p></p>,<body,</body>,44\n"+ "インライン要素,<body,</body>,0\n"+ "\t<br>,<body,</body>,44,\n"+ "\t<img src=\"\">,<body,</body>,44\n"+ "\t<span></span>,<body,</body>,44\n"+ "\t<a href=\"\"></a>,<body,</body>,44\n"+ "\twidth=,<img,>,"+MenuKeyEvent.VK_SPACE+"\n"+ "height=,<img,>,"+MenuKeyEvent.VK_SPACE+"\n"+ "特殊文字,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"+ "\t ,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"+ "\tタグ記号,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"+ "\t\t<,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"+ "\t\t>,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"+ "\t&,<body,</body>,"+MenuKeyEvent.VK_SPACE+"\n"; try { File file = new File("HtmlEditorMenu.txt"); FileInputStream fis = new FileInputStream(file); byte []ba = new byte[(int)file.length()]; fis.read(ba); fis.close(); htmlMenuStr0 = new String(ba); } catch(Exception e){ } mneuStringList=JMenuString.readMenuString_s(htmlMenuStr0); this.text.addKeyListener(this); } } public static void main(String []a){ new TestFrame(); } }