/** * @version $Id: FrequentTermPage.java 1840 2014-04-16 05:38:34Z yukihiro-kinjyo $ * * 2011/09/29 16:10:40 * @author takayuki-matsumoto * * Copyright 2011-2014 TIDAコンソーシアム All Rights Reserved. */ package com.tida_okinawa.corona.ui.editors; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.progress.UIJob; import com.tida_okinawa.corona.correction.frequent.FrequentRecord; import com.tida_okinawa.corona.internal.ui.component.CompositeUtil; import com.tida_okinawa.corona.internal.ui.util.Pair; import com.tida_okinawa.corona.internal.ui.views.model.IUIProduct; import com.tida_okinawa.corona.internal.ui.views.model.impl.CoronaModel; import com.tida_okinawa.corona.io.IoActivator; import com.tida_okinawa.corona.io.model.ICoronaProduct; import com.tida_okinawa.corona.io.model.ICoronaProject; import com.tida_okinawa.corona.io.model.dic.DicType; import com.tida_okinawa.corona.io.model.dic.ICoronaDic; import com.tida_okinawa.corona.io.model.dic.ITerm; import com.tida_okinawa.corona.io.model.dic.IUserDic; import com.tida_okinawa.corona.io.model.dic.TermClass; import com.tida_okinawa.corona.io.model.dic.TermPart; import com.tida_okinawa.corona.ui.UIActivator; import com.tida_okinawa.corona.ui.editors.user.ComboItem; import com.tida_okinawa.corona.ui.editors.user.PrivateTermClass; import com.tida_okinawa.corona.ui.editors.user.PrivateTermPart; /** * @author takayuki-matsumoto */ public class FrequentTermPage extends FormPage { /* * TODO 未定義語を辞書に保存できるようにする */ protected String formTitle; protected boolean doSaveFlg = false; /** * 登録先辞書一覧に表示しない辞書一覧 */ public// List<IUserDic> jumanDics; // Memo unused List<FrequentRecord> errorItems = new ArrayList<FrequentRecord>(); /** * @param editor */ TermPart[] termParts; public FrequentTermPage(FormEditor editor) { this(editor, "FrequentTermPage.UniqueIdentifier", "登録済み用語"); } /** * @param editor * @param id * @param title */ public FrequentTermPage(FormEditor editor, String id, String title) { super(editor, id, title); this.formTitle = "頻出用語抽出フォーム(登録済み用語)"; if (UIActivator.isAlpha()) { termParts = new TermPart[] { TermPart.NOUN }; } else { // TODO なしは抜かなきゃ termParts = TermPart.values(); } } /* **************************************** * 保存 */ /** * doSaveでDBに保存されたアイテム<br /> * <編集用アイテム, <保存した用語, 用語を保存した辞書>> */ Map<FrequentRecord, Pair<ITerm, ICoronaDic>> committedItems = new HashMap<FrequentRecord, Pair<ITerm, ICoronaDic>>(); @Override public void doSave(IProgressMonitor monitor) { super.doSave(monitor); /* 未定義ページをまだ開いていないとき、保存する意味がないので */ if (!uiCreated) { return; } /* 初期化処理 */ errorItems.clear(); doSaveFlg = false; for (Iterator<FrequentRecord> itr = uncommittedItems.iterator(); itr.hasNext();) { FrequentRecord rec = itr.next(); if (isValid(rec)) { String value = rec.getGenkei(); String reading = rec.getYomi(); /* #477 よみ自動入力(よみが空だった場合に原形の文字列を入れる) */ if (reading.isEmpty()) { reading = value; } String termPart = rec.getHinshi(); String termClass = rec.getHinshiSaibunrui(); String cform = rec.getCform(); Pair<ITerm, ICoronaDic> pair = committedItems.get(rec); ICoronaDic newDic = rec.getDestDictionary(); if (pair == null) { ITerm item = IoActivator.getDicFactory().createTerm(value, reading, termPart, termClass, cform, ""); if (newDic != null) { newDic.addItem(item); committedItems.put(rec, new Pair<ITerm, ICoronaDic>(item, newDic)); } } else { ITerm item = pair.getValue1(); // 辞書が違っていたら、前の辞書から消して今の辞書に入れる ICoronaDic oldDic = pair.getValue2(); if (newDic == null) { // 登録先辞書が空白にされた。消す oldDic.removeItem(item); committedItems.remove(rec); } else { // 登録先辞書が同じだろうが違かろうが(辞書内のアイテムとはインスタンスが異なるため)。消して oldDic.removeItem(item); // 入れる(前のデータを使いまわすと、IDがあって入らない) ITerm newItem = IoActivator.getDicFactory().createTerm(value, reading, termPart, termClass, cform, ""); newDic.addItem(newItem); committedItems.put(rec, new Pair<ITerm, ICoronaDic>(newItem, newDic)); // 保存した辞書を更新するために置き換える } } itr.remove(); } else { if (rec.getDestDictionary() != null) { errorItems.add(rec); } else { // 対象外のデータはuncommittedItemsより抜く /* 自身のfor文の中でremoveしちゃだめだよー */ // uncommittedItems.remove(rec); itr.remove(); // こっちを使うこと! } } } Collection<ICoronaDic> commitDics = getDictionaries(); for (ICoronaDic dic : commitDics) { if (dic.isDirty()) { dic.commit(monitor); doSaveFlg = true; /* 保存処理実行済み */ } } if (errorItems.size() == 0) { // エラーがない場合のみフラグを解除 dirtyChanged(); } } boolean isValid(FrequentRecord data) { /* ユーザ用語辞書で、単語の文字数は64文字制限しているので、ここでも制限 */ if (data.getGenkei().length() > 64) { return false; } /* 品詞、品詞詳細が空白  */ if (data.getGenkei().equals("") || data.getHinshi().equals("") || data.getHinshiSaibunrui().equals("")) { return false; } /* 登録できる品詞以外を選択  */ boolean findName = false; for (TermPart part : termParts) { if (data.getHinshi().equals(part.getName())) { findName = true; break; } } if (!findName) { return false; } return true; } @Override public boolean isDirty() { return !uncommittedItems.isEmpty(); } public void dirtyChanged() { getEditor().editorDirtyStateChanged(); } /* **************************************** * UI構築 */ private boolean uiCreated = false; public TableViewer viewer; FrequentRecord[] inputData; /** * ソート(回数) */ protected ViewerSorter sortCount = new ViewerSorter() { @Override public int compare(Viewer viewer, Object e1, Object e2) { assert viewer instanceof TableViewer; Table tbl = ((TableViewer) viewer).getTable(); if (tbl.getSortDirection() == SWT.DOWN) { if (!(e2 instanceof FrequentRecord)) { return -1; } if (!(e1 instanceof FrequentRecord)) { return 1; } return ((FrequentRecord) e2).getCount() - ((FrequentRecord) e1).getCount(); } else { if (!(e1 instanceof FrequentRecord)) { return -1; } if (!(e2 instanceof FrequentRecord)) { return 1; } return ((FrequentRecord) e1).getCount() - ((FrequentRecord) e2).getCount(); } } }; /** * ソート(原形) */ protected ViewerSorter sortHeader = new ViewerSorter() { @Override public int compare(Viewer viewer, Object e1, Object e2) { assert viewer instanceof TableViewer; Table tbl = ((TableViewer) viewer).getTable(); if (tbl.getSortDirection() == SWT.DOWN) { if (!(e2 instanceof FrequentRecord)) { return -1; } if (!(e1 instanceof FrequentRecord)) { return 1; } return ((FrequentRecord) e2).getGenkei().compareTo(((FrequentRecord) e1).getGenkei()); } else { if (!(e1 instanceof FrequentRecord)) { return -1; } if (!(e2 instanceof FrequentRecord)) { return 1; } return ((FrequentRecord) e1).getGenkei().compareTo(((FrequentRecord) e2).getGenkei()); } } }; @Override protected void createFormContent(IManagedForm managedForm) { ScrolledForm form = managedForm.getForm(); form.setText(formTitle); form.getBody().setLayout(new GridLayout()); form.getBody().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Composite composite = CompositeUtil.defaultComposite(form.getBody(), 1); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); viewer = new TableViewer(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION); /* カラム設定 */ Table tbl = viewer.getTable(); tbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); tbl.setHeaderVisible(true); createTable(tbl); FrequentTermEditorInput input = (FrequentTermEditorInput) getEditorInput(); ViewerFilter vf = createFilter(); if (vf != null) { viewer.setFilters(new ViewerFilter[] { vf }); } inputData = input.getItems().toArray(new FrequentRecord[input.getItems().size()]); viewer.setInput(inputData); uiCreated = true; setEditKeyListener(viewer, 2); } protected void createTable(Table tbl) { createColumnWithSort(tbl, "用語", 130, sortHeader); tbl.setSortColumn(createColumnWithSort(tbl, "回数", 80, sortCount)); tbl.setSortDirection(SWT.DOWN); createColumnWithSort(tbl, "登録先辞書", 120, null); createColumnWithSort(tbl, "品詞", 120, null); createColumnWithSort(tbl, "品詞詳細", 120, null); createColumnWithSort(tbl, "登録元辞書", 120, null); viewer.setContentProvider(new ArrayContentProvider()); viewer.setLabelProvider(new FrequentLabelProvider()); viewer.setSorter(sortCount); setCellEditor(); } private static void setEditKeyListener(final TableViewer viewer, final int targetColumn) { viewer.getTable().addKeyListener(new KeyListener() { @Override public void keyReleased(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { switch (e.character) { case SWT.CR: EditorUtil.editMode(viewer, targetColumn); break; default: break; } } }); } /** * * @param parent * @param title * @param width * @param sortField */ protected TableColumn createColumnWithSort(final Table parent, String title, int width, final ViewerSorter sortField) { final TableColumn column = CompositeUtil.createColumn(parent, title, width); if (sortField != null) { column.addSelectionListener(new SelectionAdapter() { private boolean asc = false; @Override public void widgetSelected(SelectionEvent e) { asc = !asc; parent.setSortColumn(column); parent.setSortDirection((asc) ? SWT.UP : SWT.DOWN); viewer.setSorter(sortField); viewer.setInput(inputData); } }); } return column; } /** * 表示アイテムのフィルタを返す。 * * @return フィルタ。使わない場合、null */ protected ViewerFilter createFilter() { ViewerFilter vf = new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof FrequentRecord) { FrequentRecord fr = (FrequentRecord) element; fr.getRegisteredDics(); return TermPart.NOUN.getName().equals(fr.getHinshi()); } return true; } }; return vf; } /* ******************** * 編集機能 */ public static final String PROP_ORG = "org"; public static final String PROP_COUNT = "count"; public static final String PROP_DIC = "dic"; public static final String PROP_PART = "part"; public static final String PROP_DETAIL = "detail"; private static final String PROP_SRCDIC = "srcdic"; protected static final String[] properties = new String[] { PROP_ORG, PROP_COUNT, PROP_DIC, PROP_PART, PROP_DETAIL, PROP_SRCDIC }; protected ComboBoxCellEditor dicCellEditor; protected ComboBoxCellEditor detailComboEditor; private void setCellEditor() { Table tbl = viewer.getTable(); dicCellEditor = new ComboBoxCellEditor(tbl, new String[0]); ComboBoxCellEditor partCellEditor = new ComboBoxCellEditor(tbl, getPartItems().getNames()); detailComboEditor = new ComboBoxCellEditor(tbl, new String[0]); final CellEditor[] editors = new CellEditor[] { null, null, dicCellEditor, partCellEditor, detailComboEditor, null, null }; viewer.setColumnProperties(properties); viewer.setCellEditors(editors); viewer.setCellModifier(new FrequentRegisterCellModifier()); EditorUtil.setFocusMoveListener(editors, viewer, 2, 3, 4); } protected ComboItem<TermPart> getPartItems() { PrivateTermPart ret = new PrivateTermPart(termParts, false); return ret; } /* ******************** * 登録元辞書Job */ UpdateJob dicsJob; public void startJob() { if (dicsJob == null) { /* 登録元辞書を探すJobを開始する */ dicsJob = new UpdateJob(viewer, inputData); dicsJob.setUser(false); dicsJob.setSystem(true); } if (uiCreated) { if (dicsJob.getState() != Job.RUNNING) { dicsJob.schedule(); } } } public void cancelJob() { if (dicsJob != null) { dicsJob.cancel(); } } private class UpdateJob extends Job { TableViewer viewer_uj; FrequentRecord[] records; private int currentIndex = 0; public UpdateJob(TableViewer viewer, FrequentRecord[] records) { super("登録元辞書を探すJob"); setSystem(true); this.viewer_uj = viewer; Object[] filtered = records; for (ViewerFilter filter : viewer.getFilters()) { filtered = filter.filter(viewer, new Object(), filtered); } viewer.getSorter().sort(viewer, filtered); this.records = new FrequentRecord[filtered.length]; System.arraycopy(filtered, 0, this.records, 0, filtered.length); } @Override protected void canceling() { /* * エディタが非アクティブになったり、タブが未定義語に切り替わった時に処理される。 * キャンセルのタイミングによっては、表示が更新される前に処理が中断してしまうことに対応 */ updateViewer(updateWaitElements.clone()); } FrequentRecord[] updateWaitElements = new FrequentRecord[0]; void updateViewer(FrequentRecord[] updateElements) { /* Jobがキャンセルされた場合、要素の末尾がnullのままのことがあるので取り除く */ int cnt = 0; for (FrequentRecord rec : updateElements) { if (rec != null) { cnt++; } } FrequentRecord[] dest = new FrequentRecord[cnt]; System.arraycopy(updateElements, 0, dest, 0, cnt); viewer_uj.update(dest, null); } @Override public IStatus run(IProgressMonitor monitor) { if (viewer_uj.getTable().isDisposed()) { return Status.CANCEL_STATUS; } if (records.length == 0) { return Status.OK_STATUS; } /* 辞書件数が多くなると検索に時間がかかるため、10件ずつしか処理しない */ int iterations = Math.min(10, records.length - currentIndex); updateWaitElements = new FrequentRecord[iterations]; for (int i = 0; i < iterations; i++) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } try { records[currentIndex].createRegisteredDics(((FrequentTermEditorInput) getEditorInput()).getDics()); } catch (SQLException e) { /* * 本来は、forを抜けて更新処理そのものも止めるべきだが、今は手を入れない */ System.err.println("データベース接続がないため、登録元辞書を取得できません。"); } updateWaitElements[i] = records[currentIndex++]; } if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } final FrequentRecord[] updateElements = updateWaitElements.clone(); Job uiJob = new UIJob("") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { updateViewer(updateElements); return Status.OK_STATUS; } }; uiJob.setSystem(true); uiJob.schedule(); if (currentIndex < records.length) { /* 表示更新を挟ませるため、ディレイを設定(長いほど他の処理は早くなるが、この処理は遅くなる) */ schedule(100); } else { // } return Status.OK_STATUS; } } /** * 変更されたけど、まだdoSaveでDBに保存されていないアイテム。保存されたアイテムが編集された場合もここで管理する */ public Set<FrequentRecord> uncommittedItems = new HashSet<FrequentRecord>(); public class FrequentRegisterCellModifier implements ICellModifier { @Override public boolean canModify(Object element, String property) { if (property.equals(PROP_ORG) || property.equals(PROP_COUNT)) { return false; } return true; } @Override public Object getValue(Object element, String property) { FrequentRecord data = (FrequentRecord) element; if (property.equals(PROP_DIC)) { ICoronaDic destDic = data.getDestDictionary(); DicNameItem destDics = getDestDicItem(); dicCellEditor.setItems(destDics.getNames()); return destDics.getIndex(destDic); } else if (property.equals(PROP_PART)) { ComboItem<TermPart> termParts = getPartItems(); return termParts.getIndex(TermPart.valueOfName(data.getHinshi())); } else if (property.equals(PROP_DETAIL)) { /* 品詞詳細アイテム設定 */ TermPart termPart = TermPart.valueOfName(data.getHinshi()); ComboItem<TermClass> termClass = createTermClass(termPart.getIntValue()); String[] classes = termClass.getNames(); detailComboEditor.setItems(classes); return termClass.getIndex(TermClass.valueOfName(data.getHinshiSaibunrui())); } return null; } @Override public void modify(Object element, String property, Object value) { // ユーザが編集を確定した時に呼ばれる // 値を、モデルに反映させる。 boolean modify = false; FrequentRecord data = (FrequentRecord) ((TableItem) element).getData(); if (property.equals(PROP_DIC)) { Integer index = (Integer) value; IUserDic newDic = (IUserDic) getDestDicItem().get(index); // Memo もし遅いようなら getValueからmodiryの間だけフィールドに持つ modify = !equals(data.getDestDictionary(), newDic); data.setDestDictionary(newDic); } else if (property.equals(PROP_PART)) { /* 品詞設定 */ Integer index = (Integer) value; TermPart newValue = getPartItems().get(index); if (newValue == null) { modify = !equals(data.getHinshi(), ""); data.setHinshi(""); newValue = TermPart.NONE; } else if (!newValue.getName().equals(data.getHinshi())) { modify = true; data.setHinshi(newValue.getName()); } if (modify) { /* 品詞の値が変わったとき */ String[] detailItems = createTermClass(newValue.getIntValue()).getNames(); detailComboEditor.setItems(detailItems); data.setHinshiSaibunrui(detailItems[0]); } } else if (property.equals(PROP_DETAIL)) { Integer index = (Integer) value; int partId = TermPart.valueOfName(data.getHinshi()).getIntValue(); TermClass tc = createTermClass(partId).get(index); String termClass; if (tc != null) { termClass = tc.getName(); } else { termClass = ""; } modify = !equals(data.getHinshiSaibunrui(), termClass); data.setHinshiSaibunrui(termClass); } viewer.update(data, null); /* element のisDirty をチェックする */ if (modify) { uncommittedItems.add(data); dirtyChanged(); } } private boolean equals(Object o1, Object o2) { if (o1 == null) { if (o2 == null) { return true; } else { return false; } } return o1.equals(o2); } /** * @return 共通辞書と辞書フォルダからJuman辞書を除いたユーザ辞書を取ってくる */ private DicNameItem getDestDicItem() { Collection<ICoronaDic> ret = getDictionaries(); for (Iterator<ICoronaDic> itr = ret.iterator(); itr.hasNext();) { ICoronaDic dic = itr.next(); if (DicType.JUMAN.equals(((IUserDic) dic).getDicType())) { itr.remove(); } } return new DicNameItem(ret.toArray(new ICoronaDic[ret.size()])); } private class DicNameItem extends ComboItem<ICoronaDic> { public DicNameItem(ICoronaDic[] items) { super(items, true); } @Override protected String toName(ICoronaDic item) { return item.getName(); } } private PrivateTermClass createTermClass(int partId) { return new PrivateTermClass(TermClass.values(partId).toArray(new TermClass[TermClass.values(partId).size()])); } } /** * @return 今現在有効な辞書 */ public Collection<ICoronaDic> getDictionaries() { IUIProduct uiProduct = ((FrequentTermEditorInput) getEditorInput()).getUIProduct(); ICoronaProduct product = uiProduct.getObject(); ICoronaProject project = CoronaModel.INSTANCE.getProject(uiProduct).getObject(); Set<ICoronaDic> ret = new TreeSet<ICoronaDic>(new Comparator<ICoronaDic>() { @Override public int compare(ICoronaDic o1, ICoronaDic o2) { if (o2 == null) { return -1; } if (o1 == null) { return 1; } return o1.getName().compareTo(o2.getName()); } }); ret.addAll(product.getDictionarys(IUserDic.class)); ret.addAll(project.getDictionarys(IUserDic.class)); // JUMAN辞書は除く for (Iterator<ICoronaDic> itr = ret.iterator(); itr.hasNext();) { ICoronaDic dic = itr.next(); if (DicType.JUMAN.equals(((IUserDic) dic).getDicType())) { itr.remove(); } } return ret; } }