package open.dolphin.client;
import java.awt.Color;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import open.dolphin.ui.MyJSheet;
/**
*
* @author pns thx for masuda sensei
*/
public class FindAndView {
private static final Color SELECTED_COLOR = new Color(255,180,66); //カーソルがある部分の色
private static final String SELECTED_COLOR_HEX = "#FFB442";
private static final Color FOUND_COLOR = new Color(243,255,15); //黄色っぽい色
private static final String FOUND_COLOR_HEX = "#F3FF0F";
//private static final Color SELECTED_BORDER = new Color(255, 0, 153); //stampHolder の選択色
private String searchText;
private JPanel scrollerPanel; // 検索対象の Panel (KarteDocumentViewer からもってくる
private SimpleAttributeSet foundAttr = new SimpleAttributeSet(); // 見つかった
private SimpleAttributeSet onCursorAttr = new SimpleAttributeSet(); // 現在いるところ
private SimpleAttributeSet defaultAttr = new SimpleAttributeSet(); // もともとの背景色(panelに応じて変化)
private FoundDataList foundDataList;
private int row; // 現在の row
// StampHolder にマークするためのタグ
private final String FONT_END = "</font>";
private final String FONT_FOUND = "<font style=\"background-color:" + FOUND_COLOR_HEX + "\">";
private final String FONT_SELECTED = "<font style=\"background-color:" + SELECTED_COLOR_HEX + "\">";
public FindAndView() {
foundAttr.addAttribute(StyleConstants.Background, FOUND_COLOR);
onCursorAttr.addAttribute(StyleConstants.Background, SELECTED_COLOR);
}
/**
* findFirst 検索対象のパネルをスキャンして,検索結果を positions データベースに入れる
* さらに最初に見つかった部分を表示する
* @param text
* @param soaIsOn
* @param pIsOn
* @param panel
*/
public void showFirst(String text, boolean soaIsOn, boolean pIsOn, JPanel panel) {
foundDataList = new FoundDataList(); // 検索結果を入れるためのテーブル
searchText = text;
scrollerPanel = panel;
defaultAttr.addAttribute(StyleConstants.Background,
((KartePanel)panel.getComponent(0)).getSoaTextPane().getBackground());
int kpCount = panel.getComponentCount(); // panel に組み込まれている kartePanel の数
int kpHeight = 0; // 後でソートするために,scrollerPanel 上の y 座標を記録するとき使う
// 前回検索のマーキング全部クリアする
clearMarking(panel);
for (int i=0; i<kpCount; i++) {
KartePanel kp = (KartePanel) panel.getComponent(i);
JTextPane soaPane = kp.getSoaTextPane();
JTextPane pPane = kp.getPTextPane();
try {
// soa Pane の text 検索 --------------------------------------------
if (soaIsOn) {
// 最初の検索
int pos = soaPane.getText().indexOf(text);
// もし見つかったら,マーキングと positions データベース登録
while (pos != -1) {
// 見つかったテキストに,foundAttr をセットして,見つかった位置の y 座標を positions データベースに入れる
int y = kpHeight + soaPane.modelToView(pos).y;
foundDataList.addRow(y, true, soaPane, pos, null);
// 次の検索
pos = soaPane.getText().indexOf(text, pos+1);
}
}
// pPane の text, stamp 検索 ----------------------------------------
if (pIsOn) {
// まずは text の検索
int pos = pPane.getText().indexOf(text);
// もし見つかったら,マーキングと positions データベース登録
while (pos != -1) {
// 見つかったテキストに,foundAttr をセットして,見つかった位置の y 座標を positions データベースに入れる
int y = kpHeight + pPane.modelToView(pos).y;
foundDataList.addRow(y, true, pPane, pos, null);
// 次の検索
pos = pPane.getText().indexOf(text, pos+1);
}
// 次に stamp 検索
KarteStyledDocument kd = (KarteStyledDocument) pPane.getStyledDocument();
List<StampHolder> list = kd.getStampHolders();
for (StampHolder sh : list) {
// sh は JLabel で HTML で setText してあるので,searchText に HTML タグが
// 入ってたりすると大変なことになるが,めんどくさいので処理はしない
String stampText = sh.getText();
pos = stampText.indexOf(searchText);
if (pos != -1) { // 見つかった
int y = kpHeight + pPane.modelToView(sh.getStartPos()).y;
foundDataList.addRow(y, false, pPane, sh.getStartPos(), sh);
}
}
}
} catch (BadLocationException ex) {
System.out.println(ex);
}
kpHeight += kp.getHeight(); // kartePanel の高さ分だけずらす
}
if (!foundDataList.isEmpty()) {
foundDataList.sort();
// 全てマーキング
// for (int i=0; i<foundDataList.getRowCount(); i++) {
// if (foundDataList.isText(i)) {
// setFoundAttr(foundDataList.getPane(i), foundDataList.getPos(i));
// } else {
// setFoundAttr(foundDataList.getStamp(i));
// }
// }
// 最初に見つかった部分に移動
row = 0;
if (foundDataList.isText(row)) {
setOnCursorAttr(foundDataList.getPane(row), foundDataList.getPos(row));
} else {
setOnCursorAttr(foundDataList.getStamp(row));
}
scrollToCenter(panel, foundDataList.getPane(row), foundDataList.getPos(row));
panel.requestFocus();
} else showNotFoundDialog("検索", "はみつかりませんでした");
}
/**
* 検索結果データベース(positions)を元に見つかった部分を表示する
* @param panel
* @param next
*/
private void show (JPanel panel, boolean next) {
// すでに JSheet が出ている場合は,toFront してリターン
if (MyJSheet.isAlreadyShown(scrollerPanel)) {
SwingUtilities.getWindowAncestor(scrollerPanel).toFront();
return;
}
// 検索結果が1つだったらすぐリターン
if (foundDataList.getRowCount() <= 1) {
showNotFoundDialog(next? "次を検索" : "前を検索", "はこれだけです");
return;
}
// scrollerPanel が変化していなければ次を探す。変化していたらクリア。
if (panel == scrollerPanel) {
// 次の検索の前に onCursorAttr を foundAttr に戻す
// if (isText(row)) setFoundAttr(getPane(row), getPos(row));
// else setFoundAttr(getStamp(row));
if (foundDataList.isText(row)) {
removeAttr(foundDataList.getPane(row), foundDataList.getPos(row));
} else {
removeAttr(foundDataList.getStamp(row));
}
// findNext or findPrevious
if (next) {
row++;
if (row == foundDataList.getRowCount()) {
row = 0;
if (showConfirmDialog("文書の最後まで検索しました。最初からもう一度検索しますか?") == JOptionPane.NO_OPTION) {
return;
}
}
} else {
row --;
if (row < 0) {
row = foundDataList.getRowCount() - 1;
if (showConfirmDialog("文書の最初に戻りました。最後からもう一度検索しますか?") == JOptionPane.NO_OPTION) {
return;
}
}
}
// 次の見つかった部分を表示して onCursorAttr セット
if (foundDataList.isText(row)) {
setOnCursorAttr(foundDataList.getPane(row), foundDataList.getPos(row));
} else {
setOnCursorAttr(foundDataList.getStamp(row));
}
scrollToCenter(panel, foundDataList.getPane(row), foundDataList.getPos(row));
} else {
// scroller が変わってたら,マーキングを全てクリアする
clearMarking(panel);
}
}
/**
* 検索文字列をハイライト表示するための関連メソッド
* @param pane
* @param pos
*/
private void setOnCursorAttr(JTextPane pane, int pos) {
pane.getStyledDocument().setCharacterAttributes(pos, searchText.length(), onCursorAttr, false);
}
private void setOnCursorAttr(StampHolder sh) {
setAttr(sh, FONT_SELECTED);
}
private void setFoundAttr(JTextPane pane, int pos) {
pane.getStyledDocument().setCharacterAttributes(pos, searchText.length(), foundAttr, false);
}
private void setFoundAttr(StampHolder sh) {
setAttr(sh, FONT_FOUND);
}
private void setAttr(StampHolder sh, String fontTag) {
sh.setAttr(searchText, fontTag, FONT_END);
//sh.repaint();
}
private void removeAttr(JTextPane pane, int pos) {
pane.getStyledDocument().setCharacterAttributes(pos, searchText.length(), defaultAttr, false);
}
private void removeAttr(StampHolder sh) {
sh.removeAttr();
//sh.repaint();
}
public void showNext (JPanel panel) {
show(panel, true);
}
public void showPrevious (JPanel panel) {
show(panel, false);
}
/**
* 検索してカーソルがある部分をできるだけ画面の中央に表示する
* @param panel
* @param pane
* @param pos
*/
private void scrollToCenter(JPanel panel, JTextPane pane, int pos) {
try {
Rectangle r = pane.modelToView(pos);
int h = panel.getParent().getBounds().height; // viewport の高さ
r.y -= h / 2;
r.height = h;
// 上のようにすると,同じ画面に found text があっても,いちいちスクロールしてしまう
//r.height += 100;
pane.scrollRectToVisible(r);
} catch (BadLocationException ex) {
System.out.println(ex);
}
}
/**
* マーキングを全てクリアする
* @param panel
*/
private void clearMarking(JPanel panel) {
int kpCount = panel.getComponentCount(); // panel に組み込まれている kartePanel の数
for (int i=0; i<kpCount; i++) {
// text
KartePanel kp = (KartePanel) panel.getComponent(i);
JTextPane soaPane = kp.getSoaTextPane();
JTextPane pPane = kp.getPTextPane();
soaPane.getStyledDocument().setCharacterAttributes(0, soaPane.getText().length(), defaultAttr, false);
pPane.getStyledDocument().setCharacterAttributes(0, pPane.getText().length(), defaultAttr, false);
// stamp
KarteStyledDocument kd = (KarteStyledDocument) pPane.getStyledDocument();
for (int j=0; j<kd.getLength(); j++) {
StampHolder sh = (StampHolder) StyleConstants.getComponent(kd.getCharacterElement(j).getAttributes());
if (sh != null) removeAttr(sh);
}
}
}
private void showNotFoundDialog(String title, String message) {
// JOptionPane.showMessageDialog(scrollerPanel.getRootPane(),
MyJSheet.showMessageDialog(scrollerPanel,
"「" + searchText + "」" + message,
title,
JOptionPane.ERROR_MESSAGE);
}
private int showConfirmDialog(String message) {
return MyJSheet.showConfirmDialog( scrollerPanel,
message,
"",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
}
/**
* 検索で見つかったデータを入れておく model
*/
private class FoundDataList {
private List<Model> list;
public FoundDataList() {
list = new ArrayList<Model>();
}
public void addRow(int y, boolean isText, JTextPane pane, int pos, StampHolder sh) {
list.add(new Model(y, isText, pane, pos, sh));
}
public boolean isEmpty() {
return list.isEmpty();
}
public int getRowCount() {
return list.size();
}
public void sort() {
Collections.sort(list);
}
public int getY(int row) {
return list.get(row).y;
}
public boolean isText(int row) {
return list.get(row).isText;
}
public JTextPane getPane(int row) {
return list.get(row).pane;
}
public int getPos(int row) {
return list.get(row).pos;
}
public StampHolder getStamp(int row) {
return list.get(row).stampHolder;
}
private class Model implements Comparable {
public int y;
public boolean isText;
public JTextPane pane;
public int pos;
public StampHolder stampHolder;
private Model(int y, boolean isText, JTextPane pane, int pos, StampHolder sh){
this.y = y;
this.isText = isText;
this.pane = pane;
this.pos = pos;
stampHolder = sh;
}
@Override
public int compareTo(Object o) {
int test = ((Model)o).y;
if (test == y){
return 0;
} else if (test > y) {
return -1;
}
return 1;
}
}
}
}