package logbook.gui;
import java.text.ParseException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import logbook.config.AppConfig;
import logbook.gui.listener.SaveWindowLocationAdapter;
import logbook.gui.listener.TableKeyShortcutAdapter;
import logbook.gui.listener.TableToClipboardAdapter;
import logbook.gui.listener.TableToCsvSaveAdapter;
import logbook.gui.logic.LayoutLogic;
import logbook.gui.logic.TableItemCreator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
/**
* テーブルで構成されるダイアログの基底クラス
*
*/
public abstract class AbstractTableDialog extends Dialog {
/** タイマー */
protected Timer timer;
/** ヘッダー */
protected String[] header = this.getTableHeader();
/** テーブルに表示しているボディー */
protected List<String[]> body;
/** ソート順序 */
protected final boolean[] orderflgs = new boolean[this.header.length];
/** シェル */
protected Shell shell;
/** メニューバー */
protected Menu menubar;
/** [ファイル]メニュー */
protected Menu filemenu;
/** [操作]メニュー */
protected Menu opemenu;
/** テーブル */
protected Table table;
/** テーブルのメニュー */
protected Menu tablemenu;
/** テーブルソート */
protected final TableComparator comparator = new TableComparator();
private Display display;
/**
* コンストラクター
*/
public AbstractTableDialog(Shell parent) {
super(parent, SWT.SHELL_TRIM | SWT.MODELESS);
}
/**
* Open the dialog.
*/
public void open() {
// シェルを作成
this.shell = new Shell(this.getParent(), this.getStyle());
this.shell.setSize(this.getSize());
// ウインドウ位置を復元
LayoutLogic.applyWindowLocation(this.getClass(), this.shell);
// 閉じた時にウインドウ位置を保存
this.shell.addShellListener(new SaveWindowLocationAdapter(this.getClass()));
this.shell.setText(this.getTitle());
this.shell.setLayout(new FillLayout());
// メニューバー
this.menubar = new Menu(this.shell, SWT.BAR);
this.shell.setMenuBar(this.menubar);
// テーブルより前に作成する必要があるコンポジットを作成
this.createContentsBefore();
// テーブル
this.table = new Table(this.getTableParent(), SWT.FULL_SELECTION | SWT.MULTI);
this.table.addKeyListener(new TableKeyShortcutAdapter(this.header, this.table));
this.table.setLinesVisible(true);
this.table.setHeaderVisible(true);
// メニューバーのメニュー
MenuItem fileroot = new MenuItem(this.menubar, SWT.CASCADE);
fileroot.setText("&File");
this.filemenu = new Menu(fileroot);
fileroot.setMenu(this.filemenu);
MenuItem savecsv = new MenuItem(this.filemenu, SWT.NONE);
savecsv.setText("&Save to CSV\tCtrl+S");
savecsv.setAccelerator(SWT.CTRL + 'S');
savecsv.addSelectionListener(new TableToCsvSaveAdapter(this.shell, this.getTitle(), this.getTableHeader(),
this.table));
MenuItem operoot = new MenuItem(this.menubar, SWT.CASCADE);
operoot.setText("&Action");
this.opemenu = new Menu(operoot);
operoot.setMenu(this.opemenu);
MenuItem reload = new MenuItem(this.opemenu, SWT.NONE);
reload.setText("&Refresh\tF5");
reload.setAccelerator(SWT.F5);
reload.addSelectionListener(new TableReloadAdapter());
MenuItem cyclicReload = new MenuItem(this.opemenu, SWT.CHECK);
cyclicReload.setText("Refresh every 3s (&A)\tCtrl+F5");
cyclicReload.setAccelerator(SWT.CTRL + SWT.F5);
cyclicReload.addSelectionListener(new CyclicReloadAdapter(cyclicReload));
MenuItem selectVisible = new MenuItem(this.opemenu, SWT.NONE);
selectVisible.setText("&Select Columns...");
selectVisible.addSelectionListener(new SelectVisibleColumnAdapter());
new MenuItem(this.opemenu, SWT.SEPARATOR);
// テーブル右クリックメニュー
this.tablemenu = new Menu(this.table);
this.table.setMenu(this.tablemenu);
MenuItem sendclipbord = new MenuItem(this.tablemenu, SWT.NONE);
sendclipbord.addSelectionListener(new TableToClipboardAdapter(this.header, this.table));
sendclipbord.setText("Copy (&C)");
MenuItem reloadtable = new MenuItem(this.tablemenu, SWT.NONE);
reloadtable.setText("Refresh (&R)");
reloadtable.addSelectionListener(new TableReloadAdapter());
// テーブルにヘッダーをセット
this.setTableHeader();
// テーブルに内容をセット
this.updateTableBody();
this.setTableBody();
// 列幅を整える
this.packTableHeader();
this.createContents();
this.shell.open();
this.shell.layout();
this.display = this.getParent().getDisplay();
while (!this.shell.isDisposed()) {
if (!this.display.readAndDispatch()) {
this.display.sleep();
}
}
// タイマーの終了
if (this.timer != null) {
this.timer.cancel();
}
}
/**
* テーブルをリロードする
*/
protected void reloadTable() {
this.shell.setRedraw(false);
TableColumn sortColumn = this.table.getSortColumn();
int topindex = this.table.getTopIndex();
int[] selection = this.table.getSelectionIndices();
this.table.setSortColumn(null);
this.disposeTableBody();
this.updateTableBody();
if (this.comparator.getHasSetConfig()) {
Collections.sort(this.body, this.comparator);
}
this.setTableBody();
this.packTableHeader();
this.table.setSortColumn(sortColumn);
this.table.setSelection(selection);
this.table.setTopIndex(topindex);
this.shell.setRedraw(true);
}
/**
* テーブルヘッダーをセットする
*/
private void setTableHeader() {
SelectionListener listener = this.getHeaderSelectionListener();
for (int i = 0; i < this.header.length; i++) {
TableColumn col = new TableColumn(this.table, SWT.LEFT);
col.setText(this.header[i]);
col.addSelectionListener(listener);
}
this.packTableHeader();
}
/**
* テーブルボディーをセットする
*/
protected void setTableBody() {
TableItemCreator creator = this.getTableItemCreator();
creator.init();
for (int i = 0; i < this.body.size(); i++) {
String[] line = this.body.get(i);
creator.create(this.table, line, i);
}
}
/**
* テーブルボディーをクリアする
*/
protected void disposeTableBody() {
TableItem[] items = this.table.getItems();
for (int i = 0; i < items.length; i++) {
items[i].dispose();
}
}
/**
* テーブルヘッダーの幅を調節する
*/
protected void packTableHeader() {
boolean[] visibles = AppConfig.get().getVisibleColumnMap().get(this.getClass().getName());
TableColumn[] columns = this.table.getColumns();
// 列の表示・非表示設定のサイズがカラム数と異なっている場合は破棄する
if (visibles != null) {
if (visibles.length != columns.length) {
AppConfig.get().getVisibleColumnMap().remove(this.getClass().getName());
visibles = null;
}
}
for (int i = 0; i < columns.length; i++) {
if ((visibles == null) || visibles[i]) {
columns[i].pack();
} else {
columns[i].setWidth(0);
}
}
}
/**
* テーブルの親コンポジット
* @return テーブルの親コンポジットを取得します
*/
protected Composite getTableParent() {
return this.shell;
}
/**
* Create contents of the dialog.
*/
protected void createContentsBefore() {
}
/**
* Create contents of the dialog.
*/
protected abstract void createContents();
/**
* タイトルを返します
* @return String
*/
protected abstract String getTitle();
/**
* ウインドウサイズを返します
* @return Point
*/
protected abstract Point getSize();
/**
* テーブルヘッダーを返します
* @return String[]
*/
protected abstract String[] getTableHeader();
/**
* テーブルボディーをアップデートします
*/
protected abstract void updateTableBody();
/**
* テーブル行を作成するクリエイターを返します
*
* @return TableItemCreator
*/
protected abstract TableItemCreator getTableItemCreator();
/**
* テーブルヘッダーの{@link org.eclipse.swt.events.SelectionListener}です
* @return SelectionListener
*/
protected abstract SelectionListener getHeaderSelectionListener();
/**
* テーブルをソートします
*
* @param headerColumn ソートするカラム
*/
protected void sortTableItems(TableColumn headerColumn) {
int index = 0;
for (int i = 0; i < this.header.length; i++) {
if (this.header[i].equals(headerColumn.getText())) {
index = i;
break;
}
}
this.sortTableItems(index, headerColumn);
}
/**
* テーブルをソートします
*
* @param index カラムインデックス
* @param headerColumn ソートするカラム
*/
protected void sortTableItems(int index, TableColumn headerColumn) {
this.shell.setRedraw(false);
this.disposeTableBody();
final boolean orderflg = !this.orderflgs[index];
for (int i = 0; i < this.orderflgs.length; i++) {
this.orderflgs[i] = false;
}
this.orderflgs[index] = orderflg;
if (orderflg) {
this.table.setSortColumn(headerColumn);
this.table.setSortDirection(SWT.UP);
} else {
this.table.setSortColumn(headerColumn);
this.table.setSortDirection(SWT.DOWN);
}
this.comparator.setIndex(index);
this.comparator.setOrder(orderflg);
Collections.sort(this.body, this.comparator);
this.setTableBody();
this.shell.setRedraw(true);
}
/**
* テーブルをソートする{@link java.util.Comparator}です。
*/
protected class TableComparator implements Comparator<String[]> {
/** ソート設定済みフラグ */
private boolean confflg;
/** 列位置 */
private int index;
/** 昇順・降順フラグ */
private boolean order;
@Override
public final int compare(String[] o1, String[] o2) {
String t1 = o1[this.index];
String t2 = o2[this.index];
if (StringUtils.isEmpty(t1) && StringUtils.isEmpty(t2)) {
return 0;
}
if (StringUtils.isEmpty(t1)) {
return 1;
}
if (StringUtils.isEmpty(t2)) {
return -1;
}
if (StringUtils.isNumeric(t1) && StringUtils.isNumeric(t2)) {
// 数値文字列の場合
Long o1l = Long.valueOf(t1);
Long o2l = Long.valueOf(t2);
return this.compareTo(o1l, o2l, this.order);
} else if (t1.matches("(?:\\d+d)?(?:\\d+h)?(?:\\d+m)?(?:\\d+s)?")) {
try {
// 時刻文字列の場合
// SimpleDateFormatは24時間超えるような時刻でも正しく?パースしてくれる
Date o1date = DateUtils.parseDate(t1, "ss's'", "mm'm'ss's'", "HH'h'mm'm'", "dd'd'HH'h'mm'm'");
Date o2date = DateUtils.parseDate(t2, "ss's'", "mm'm'ss's'", "HH'h'mm'm'", "dd'd'HH'h'mm'm'");
return this.compareTo(o1date, o2date, this.order);
} catch (ParseException e) {
e.printStackTrace();
}
}
// 文字列の場合
return this.compareTo(t1, t2, this.order);
}
/**
* 列位置をセットする
* @param index
*/
public final void setIndex(int index) {
this.index = index;
this.confflg = true;
}
/**
* 昇順・降順フラグをセットする
* @param order
*/
public final void setOrder(boolean order) {
this.order = order;
this.confflg = true;
}
/**
* ソート設定済みフラグ
* @return
*/
public final boolean getHasSetConfig() {
return this.confflg;
}
/**
* 比較する
*
* @param o1
* @param o2
* @param order
* @return
*/
private <T extends Comparable<? super T>> int compareTo(T o1, T o2, boolean order) {
if (this.order) {
return o1.compareTo(o2);
} else {
return o2.compareTo(o1);
}
}
}
/**
* テーブルを再読み込みするリスナーです
*/
protected class TableReloadAdapter extends SelectionAdapter {
@Override
public void widgetSelected(SelectionEvent e) {
AbstractTableDialog.this.reloadTable();
}
}
/**
* テーブルの列を表示・非表示選択するダイアログを表示する
*/
protected class SelectVisibleColumnAdapter extends SelectionAdapter {
@Override
public void widgetSelected(SelectionEvent e) {
new SelectVisibleColumnDialog(AbstractTableDialog.this.shell, AbstractTableDialog.this).open();
}
}
/**
* テーブルを定期的に再読み込みする
*/
protected class CyclicReloadAdapter extends SelectionAdapter {
private final MenuItem menuitem;
public CyclicReloadAdapter(MenuItem menuitem) {
this.menuitem = menuitem;
}
@Override
public void widgetSelected(SelectionEvent e) {
if (this.menuitem.getSelection()) {
// タイマーを作成
if (AbstractTableDialog.this.timer == null) {
AbstractTableDialog.this.timer = new Timer(true);
}
// 3秒毎に再読み込みするようにスケジュールする
AbstractTableDialog.this.timer.schedule(new CyclicReloadTask(AbstractTableDialog.this), 0,
TimeUnit.SECONDS.toMillis(3));
} else {
// タイマーを終了
if (AbstractTableDialog.this.timer != null) {
AbstractTableDialog.this.timer.cancel();
AbstractTableDialog.this.timer = null;
}
}
}
}
/**
* テーブルを定期的に再読み込みする
*/
protected static class CyclicReloadTask extends TimerTask {
private final AbstractTableDialog dialog;
public CyclicReloadTask(AbstractTableDialog dialog) {
this.dialog = dialog;
}
@Override
public void run() {
synchronized (this.dialog.shell) {
if (!this.dialog.shell.isDisposed()) {
this.dialog.display.asyncExec(new Runnable() {
@Override
public void run() {
CyclicReloadTask.this.dialog.reloadTable();
}
});
} else {
// ウインドウが消えていたらタスクをキャンセルする
this.cancel();
}
}
}
}
}