2013년 12월 25일 수요일

SWT - JFace의 텍스트(TEXT) 지원




JFace의 텍스트(TEXT) 지원


org.eclipse.jface.text 에 있는 주요 클래스를 참고하세요.


JFace 텍스트 패키지 획득 


프로젝트의 Build Path - Configure Build Path... 에서 
Add External JARs 를 클릭하여 이클립스 설치폴더 $ECLIPSE_HOME/plugins/ 에서 
org.eclipse.text_x.y.z 와 
org.eclipse.jface.text_x.y.z 의 jar 파일을 추가합니다.
















TextViewer와 Document 


  IDocumentITextViewer 인터페이스는 JFace 텍스트를 지원하며, JFace에서 기본 구현을 제공합니다. 


  IDocument 의 인터페이스는 편집하고 있는 실제 텍스트를 보유합니다. 그리고 텍스트를 설정하고 검색하기 위한 표준 메서드와 더불어 리스너가 IDocumentListener를 통해서 내용 편집에 대한 이벤트를 받을 수 있도록 합니다.

  그 외 여러 고급 기능들도 지원하는데, 텍스트 영역에 Position 객체를 할당하여 문서의 지정한 위치로 바로 이동할 수 있는 북마크 기능을 구현 할 수 있고, 문서 분할자(Document Partitioner)를 활용하여 컨텐트 타입을 구분하며,  search() 메서드를 통한 검색기능을 제공합니다. 

  ITextViewer 는 표준 텍스트 위젯을 문서 기반 텍스트 위젯으로 바꾸기 위한 것입니다. 이를 기본 구현한 것은 TextViewer로, StyledText를 사용하여 데이터를 보여줍니다. 또한 위젯의 동작을 바꾸는 다양한 플러그인 유형을 지원합니다.

  • IUndoManager : 되살리기
  • ITextDoubleClickStrategy : 더블클릭
  • IAutoIndentStrategy  : 자동들여쓰기    등 등.


  각 플러그 인을 사용하려면 이런 인터페이스의 인스턴스를 텍스트 뷰어에 할당하고 나서 activatePluins()를 호출합니다.

  
org.eclipse.jface.text 의 다양한 하위패키지는 유용한 확장기능을 제공합니다.


org.eclipse.jface.text 하위패키지의 다양한 고급기능 제공




JFace 예제 


  다음 예제는 TextViewer의 기능을 사용한 간단한 텍스트 편집기 예제입니다.

  이 편집기는 사용자가 타이핑할 때마다 각 단어들을 추적합니다. 사용자가 F1키를 누르면 현재 타이핑하고 있는 단어를 완성할 수 있도록 목록을 보여주는데, 이 목록은 현재 커서가 있는 곳까지 사용자가 타이핑한 모든 단어에서 가져옵니다. 


  이 기능을 구현하기 위해서 org.eclipse.jface.text.contentassist 패키지에 있는 클래스를 이용할 것입니다.

  만들어질 클래스는 WordTracker RecentWordContentAssistProcessor, Ch5CompletionEditor이며 다음과 같은 역할을 합니다.


  • WordTracker : 사용자가 최근에 타이핑한 단어를 추적하는 역할을 담당하고, 해당 문자열의 완성을 제안할 수 있습니다.
  • RecentWordContentAssistProcessor : IContentAssistProcessor의 인스턴스이며, 프레임워크에게 가능한 완성 문자열을 보여줍니다.
  • Ch5CompletionEditor : 메인클래스



WorkTracker는 단어의 목록을 유지하고 검색하는 데 사용하는 유틸리티 클래스입니다. List에 단어를 추가한 후, 단어를 제안하는 작업이 필요할 때 List를 순회하면서 조건에 맞는 항목들을 찾습니다.



<예제코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.swtjface.ch5;
 
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
 
/**
 * 2010.06.28
 * 단어의 목록을 유지하고 검색하는 데 사용한다.
 * @author cremazer
 *
 */
public class WordTracker {
    
 private int maxQueueSize;
 private List<String> wordBuffer;
 private Map<StringString> knowWords = new HashMap<StringString>();
 
 /*
  * 생성자
  */
 public WordTracker(int queueSize){
  maxQueueSize = queueSize;
  wordBuffer = new LinkedList<String>();
 }
 
 public int getWordCount(){
  return wordBuffer.size();
 }
 
 /*
  * 추가될 단어가 있는지 없는지 확인 하고,
  * 없으면, 큐가 가득찼는지 확인하고 가득찼으면 마지막 데이터를 제거한다.
  * 그리고 새 단어를 등록한다.
  */
 public void add(String word){
  if(wordIsNotKnown(word)){
   flushOldestWord();
   insertNewWord(word);
  }
 }
 
 /*
  * 새 단어 등록
  */
 private void insertNewWord(String word){
  wordBuffer.add(0, word);
  knowWords.put(word, word);
 }
 
 /*
  * 큐 상태 확인 및 데이터 제거
  */
 private void flushOldestWord(){
  if(wordBuffer.size() == maxQueueSize){
   String removeWord = (String) wordBuffer.remove(maxQueueSize - 1);
   knowWords.remove(removeWord);
  }
 }
 
 /*
  * 단어가 존재하는지 확인
  */
 private boolean wordIsNotKnown(String word){
  return knowWords.get(word) == null;
 }
 
 /*
  * 해당 단어를 제안하면, List로부터 단어를 찾아 목록을 구성하여
  * 목록을 리턴한다.
  */
 public List<String> suggest(String word){
  List<String> suggestions = new LinkedList<String>();
  for(Iterator<String> i = wordBuffer.iterator(); i.hasNext();){
   String currWord = (String) i.next();
   if(currWord.startsWith(word)){
    suggestions.add(currWord);
   }
  }
  return suggestions;
 }
}





ContentAssistant는 사용자에게 완성하기 위해 가능한 단어 목록을 제안하는 역할을 합니다. TextViewer가 단어 제안을 요청하면 ContentAssistant가 보조 프로세서에게 위임하는 것이 문서의 현재 영역의 컨텐트 타입을 위해 적절합니다. 이 프로세서의 주요 역할은 computeCompletionProposals()를 호출하면 가능한 단어 완성 목록의 배열을 제공하는 것입니다. 



<예제코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.swtjface.ch5;
 
import java.util.Iterator;
import java.util.List;
 
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContextInformationValidator;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
 
/**
 * 2010.06.28
 * 프레임워크에서 가능한 완성 문자열을 보여준다.
 * @author cremazer
 *
 */
public class RecentWordContentAssistProcessor implements IContentAssistProcessor{
 
 private String lastError = null;
 private IContextInformationValidator contextInfoValidator;
 private WordTracker wordTracker;
 
 /*
  * 생성자
  */
 public RecentWordContentAssistProcessor(WordTracker tracker){
  super();
  contextInfoValidator = new ContextInformationValidator(this);
  wordTracker = tracker;
 }
 
 @Override
 public ICompletionProposal[] computeCompletionProposals(ITextViewer textViewer,
   int documentOffset) {
  
  IDocument document = textViewer.getDocument();
  int currOffset = documentOffset - 1;
  
  try{
   String currWord = "";
   char currChar;   
   
   /*
    * 커서의 현재 오프셋을 가지고 현재 단어 조각을 알아내기위해
    * 문서에서 한 번에 한 문자씩 뒤로 움직이면서 첫 번째 공백을 찾는다.
    */
   while( currOffset > 0 && 
     !Character.isWhitespace(
       currChar = document.getChar(currOffset))){
    currWord = currChar + currWord;
    currOffset--;
   }   
   
   /*
    * 현재 단어를 알게되면, wordTracker로부터 단어 완성 목록을 요청하고
    * 그것을 사용하여 buildProposals() 메서드에 있는 ICompletionProposal 배열을
    * 인스턴스화 한다.
    */
   List<String> suggestions = wordTracker.suggest(currWord);
   ICompletionProposal[] proposals = null;
   
   if( suggestions.size() > 0 ){
    proposals = buildProposals(suggestions, currWord, 
      documentOffset - currWord.length());
    lastError = null;
   }
   
   return proposals;
   
  }catch(BadLocationException ble){
   ble.printStackTrace();
   lastError = ble.getMessage();
   return null;
  }
 }
 
 private ICompletionProposal[] buildProposals(List<String> suggestions, 
   String replacedWord, 
   int offset){
  ICompletionProposal[] proposals = new ICompletionProposal[suggestions.size()];
  
  /*
   * 각 제안사항은 텍스트에서 삽입할 위치에 대한 오프셋과 대체할 문자의 수,
   * 커서를 움직여야 할 위치를 갖는 제안 텍스트를 구성한다.
   */
  int index = 0;
  for(Iterator<String> i = suggestions.iterator(); i.hasNext(); ){
   String currSuggestion = (String) i.next();
   proposals[index] = new CompletionProposal(
     currSuggestion, 
     offset, 
     replacedWord.length(), 
     currSuggestion.length());
   index++;
  }  
  
  return proposals;
 }
 
 @Override
 public IContextInformation[] computeContextInformation(ITextViewer textViewer,
   int documentOffset) {
  lastError = "No Context Information available";
  return null;
 }
 
 @Override
 public char[] getCompletionProposalAutoActivationCharacters() {
  //사용자가 명시적으로 제안을 요청할 때까지 기다린다.
  return null;
 }
 
 @Override
 public char[] getContextInformationAutoActivationCharacters() {
  //컨텍스트 관련 정보가 없다.
  return null;
 }
 
 @Override
 public IContextInformationValidator getContextInformationValidator() {
  return contextInfoValidator;
 }
 
 @Override
 public String getErrorMessage() {
  return lastError;
 }
 
}





Ch5CompletionEditor는 위의 구성요소를 통합합니다.

buildControls()에서 TextViewer를 인스턴스화하고 설정합니다.



<예제코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package com.swtjface.ch5;
 
import java.util.StringTokenizer;
 
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
 
/**
 * 2010.06.28
 * 메인 클래스
 * @author cremazer
 *
 */
public class Ch5CompletionEditor extends Composite {
 
 private TextViewer textViewer;
 private WordTracker wordTracker;
 
 private static final int MAX_QUEUE_SIZE = 200;
 
 public Ch5CompletionEditor(Composite parent) {
  super(parent, SWT.NULL);
  wordTracker = new WordTracker(MAX_QUEUE_SIZE);
  buildControls();
 }
 
 private void buildControls(){
  setLayout(new FillLayout());
  textViewer = new TextViewer(this, SWT.MULTI | SWT.V_SCROLL);
  
  /*
   * IDocument 인스턴스 할당
   * 각 TextViewer는 텍스트를 저장하기 위해서 IDocument를 필요로 한다.
   */
  textViewer.setDocument(new Document());
  
  
  /*
   * ContentAssistant 생성 및 보조프로세서 설정
   */
  final ContentAssistant assistant = new ContentAssistant();
  assistant.setContentAssistProcessor(
    new RecentWordContentAssistProcessor(wordTracker), 
    IDocument.DEFAULT_CONTENT_TYPE); //컨텐트 타입 할당  
  
  //뷰어에 설치
  assistant.install(textViewer);  
  
  /*
   * 단어 제안 목록을 보여주기 위한 키 입력(F1)이 들어오면,
   * 코드 상에서 컨텐트 보조 프로세서를 호출한다.
   */
  textViewer.getControl().addKeyListener(new KeyAdapter() {
   public void keyPressed(KeyEvent e){
    switch(e.keyCode){
     case SWT.F1:
      assistant.showPossibleCompletions(); //단어 제안 목록 보여주기
      break;
     default:
      //나머지는 무시
    }
   }
  });  
  
  /*
   * 편집에 대한 이벤트리스너 설정.
   * 공백이 입력되면 wordTracker에 단어를 추가한다.
   */
  textViewer.addTextListener(new ITextListener() {
   @Override
   public void textChanged(TextEvent e) {
    if( isWhitespaceString(e.getText()) ){
     wordTracker.add(findMostRecentWord(e.getOffset() - 1)); //새 단어 추가
    }
   }
  });
  
 }
 
 protected String findMostRecentWord(int startSearchOffset){
  int currOffset = startSearchOffset;
  char currChar;
  String word = "";
  
  try{
   while( currOffset > 0 
     && !Character.isWhitespace(
       currChar = textViewer.getDocument()
       .getChar(currOffset))){
    word = currChar + word;
    currOffset--;
   }
   
   return word;
  }catch(BadLocationException ble){
   ble.printStackTrace();
   return null;
  }
 }
 
 protected boolean isWhitespaceString(String string){
  StringTokenizer tokenizer = new StringTokenizer(string);
  //만약 토큰이 한개 이상 있다면, 이 문자열은 공백 문자가 아니다.
  return !tokenizer.hasMoreTokens();
 }
}





<테스트 예제코드>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.swtjface.ch5;
 
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
 
/**
 * 2010.06.28
 * 문서편집 테스트 
 * @author cremazer
 */
public class WidgetWindow extends ApplicationWindow {
    
 public WidgetWindow(){
  super(null);
 }
 
 protected Control createContents(Composite parent){
  Ch5CompletionEditor ce = new Ch5CompletionEditor(parent);  
  
  getShell().setText("Widget Window");
  return parent;
 }
 
 public static void main(String[] args) {
  WidgetWindow wwin = new WidgetWindow();
  wwin.setBlockOnOpen(true);
  wwin.open();
  Display.getCurrent().dispose();
 }
}


<결과>





  위의 그림 처럼 단어를 입력하고 공백을 입력하면, 입력된 단어 목록중에서 입력하려는 단어로 시작되는 단어 목록을 보여줍니다. 


  단어를 입력하다보면 아래와 같은 에러가 발생하는데, 텍스트 박스가 편집될 때마다 호출하는 메서드와 관련된 문제라고 생각하는데 정확한 원인은 파악하지 못했습니다.



<에러>

java.lang.NoClassDefFoundError: Could not initialize class org.eclipse.jface.text.TextSelection
 at org.eclipse.jface.text.TextViewer.firePostSelectionChanged(TextViewer.java:2677)
 at org.eclipse.jface.text.TextViewer$5.run(TextViewer.java:2658)
 at org.eclipse.swt.widgets.Display.runTimer(Display.java:3973)
 at org.eclipse.swt.widgets.Display.messageProc(Display.java:3165)
 at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method)
 at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:2411)
 at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3501)
 at org.eclipse.jface.window.Window.runEventLoop(Window.java:825)
 at org.eclipse.jface.window.Window.open(Window.java:801)
 at com.swtjface.ch5.WidgetWindow.main(WidgetWindow.java:28)



참고 서적 : SWT/JFace in Action


댓글 없음:

댓글 쓰기