マイペースなプログラミング日記

DTMやプログラミングにお熱なd-kamiがマイペースに書くブログ

簡易テキストエディタ作成 その2

前回は単純にテキストを表示するだけだったが、今回はキーワードに色を付けて表示するようにした。今回のスクリーンショットを載せる。

今回はTextTokenizerとJavaTokenizerというクラスを作り、前回作ったEditorViewを編集した。プロジェクトのソースコードはこんな感じで配置している

まずはTextTokenizer。こいつはabstractなクラスで、これから作るであろう様々なTokenizerのスーパークラス。解析する文字の配列をコンストラクタで受け取り、abstractなgetNextTokenメソッドを定義している

package text.edit.token;

public abstract class TextTokenizer {
    protected final char[] text;
    protected int index;
    
    protected TextTokenizer(char[] text){
        this.text = text;
        index = 0;
    }
    
    public abstract String getNextToken();
}

次にJavaTokenizer。これはJavaソースコードを解析するためのクラス。解析とは言っても区切り文字で分割してるだけだけど…。このクラスのgetNextTokenメソッドで返された文字列をEditorViewで描画し、特定のキーワードなら色分けするようにする

package text.edit.token;

import java.util.Set;
import java.util.HashSet;

public class JavaTokenizer extends TextTokenizer{
    private int index;

    private static final Set<Character> SEPARATOR_SET;
    
    static{
        SEPARATOR_SET = new HashSet<Character>();
        
        SEPARATOR_SET.add('.');
        SEPARATOR_SET.add('(');
        SEPARATOR_SET.add(')');
        SEPARATOR_SET.add('[');
        SEPARATOR_SET.add(']');
        SEPARATOR_SET.add(' ');
        SEPARATOR_SET.add('\t');
        SEPARATOR_SET.add('\n');
    }
    
    public JavaTokenizer(String text){
        super(text.toCharArray());
    }
    
    public JavaTokenizer(char[] text){
        super(text);
    }
    
    public String getNextToken(){
        String token = null;
        StringBuilder tokenBuilder = new StringBuilder();
        
        while(true){
            if(index >= text.length){
                break;
            }
            
            char tokenc = text[index];
            
            if(SEPARATOR_SET.contains(tokenc)){
                if(tokenBuilder.length() == 0){
                    //区切り文字だけの場合
                    tokenBuilder.append(tokenc);
                    index++;
                    break;
                }else{
                    break;
                }
            }else{
                //区切り文字じゃなければトークンの文字として追加
                tokenBuilder.append(tokenc);
            }
            
            index++;
        }
        
        //トークンがあればStringにして返す
        if(tokenBuilder.length() > 0){
            token = tokenBuilder.toString();
        }
        
        return token;
    }
}

次にEditorView。initColorMapでキーワードとそのキーワードの色をMapに追加している。drawTextでTokenizerを使ってテキストを分割し、drawStringでキーワードの色分けを行い描画をしている

package text.edit.view;

import java.util.Map;
import java.util.HashMap;

import android.view.View;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Canvas;
import android.text.TextPaint;

import text.edit.document.TextDocument;
import text.edit.token.TextTokenizer;
import text.edit.token.JavaTokenizer;

public class EditorView extends View{
    private int fontSize;
    private TextDocument document;
    private Map<String, Integer> colorMap;
    
    public EditorView(Context context){
        super(context);
        
        fontSize = 16;
        document = new TextDocument();
        setBackgroundColor(Color.WHITE);
        initColorMap();
    }
    
    private void initColorMap(){
        colorMap = new HashMap<String, Integer>();
        
        //キーワードと色の関連付け
        colorMap.put("import", Color.BLUE);
        colorMap.put("public", Color.BLUE);
        colorMap.put("class", Color.BLUE);
        colorMap.put("static", Color.RED);
        colorMap.put("String", 0xFF008000);
        colorMap.put("void", 0xFF008000);
    }
    
    public void setText(String text){
        document.setText(text);
        invalidate();
    }
    
    @Override
    protected void onDraw(Canvas canvas){        
        drawText(canvas, document.getText());
    }
    
    //テキストを描画する
    private void drawText(Canvas canvas, String text){
        TextPaint paint = new TextPaint();
        paint.setTextSize(fontSize);
        
        String token;
        TextTokenizer tokenizer = new JavaTokenizer(text.toCharArray());
        int drawX = 0, drawY = fontSize;
        
        while((token = tokenizer.getNextToken()) != null){
            if(token.equals("\n")){
                drawX = 0;
                drawY += fontSize;
            }else{
                drawX = drawString(canvas, token, drawX, drawY, paint);
            }
        }        
    }
    
    //文字列を描画して次の文字列を描画する位置を返す
    private int drawString(Canvas canvas, String text, int drawX, int drawY, TextPaint paint){
        int srcColor = paint.getColor();
        
        //渡された文字列がキーワードに登録されてるなら
        //関連付けられた色で描画する
        if(colorMap.containsKey(text)){
            paint.setColor(colorMap.get(text));
        }else{
            paint.setColor(Color.BLACK);
        }
        
        for(char c : text.toCharArray()){
            drawX = drawCharacter(canvas, c, drawX, drawY, paint);
        }
        
        paint.setColor(srcColor);
        
        return drawX;
    }
    
    //一文字描画して次の文字の描画位置を返す
    private int drawCharacter(Canvas canvas, char c, int drawX, int drawY, TextPaint paint){        
        String drawChar = Character.toString(c);
        canvas.drawText(drawChar, 0, 1, drawX, drawY, paint);
        drawX += (int)paint.measureText(drawChar, 0, 1);        
        
        return drawX;
    }
}

最後にActivity。ただ、表示する文字列を変えただけ

package text.edit;

import android.app.Activity;
import android.os.Bundle;

import text.edit.view.EditorView;

public class TextEditorTest extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        EditorView editor = new EditorView(this);

        StringBuilder text = new StringBuilder();
        text.append("import java.util.List;\n\n");
        text.append("public class Main{\n");
        text.append("    public static void main(String[] args){\n");
        text.append("    }\n");
        text.append("}");
        
        editor.setText(text.toString());
        
        setContentView(editor);
    }
}