package logbook.gui; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import logbook.config.AppConfig; import logbook.constants.AppConstants; 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.TableItemDecorator; import logbook.gui.logic.TableWrapper; import logbook.thread.ThreadManager; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; 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.wb.swt.SWTResourceManager; /** * 1つ以上の複数のテーブルで構成されるダイアログの基底クラス * */ public abstract class AbstractTableDialogEx<T> extends Dialog { /** Beanクラス */ protected Class<T> clazz; /** スケジューリングされた再読み込みタスク */ protected ScheduledFuture<?> future; /** シェル */ protected Shell shell; /** メニューバー */ protected Menu menubar; /** [ファイル]メニュー */ protected Menu filemenu; /** [操作]メニュー */ protected Menu opemenu; /** テーブル */ protected List<TableWrapper<T>> tables = new ArrayList<>(); /** * コンストラクター */ public AbstractTableDialogEx(Shell parent, Class<T> clazz) { super(parent, SWT.SHELL_TRIM | SWT.MODELESS); this.clazz = clazz; } /** * 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); // メニューバーのメニュー MenuItem fileroot = new MenuItem(this.menubar, SWT.CASCADE); fileroot.setText("ファイル"); this.filemenu = new Menu(fileroot); fileroot.setMenu(this.filemenu); MenuItem savecsv = new MenuItem(this.filemenu, SWT.NONE); savecsv.setText("CSVファイルに保存(&S)\tCtrl+S"); savecsv.setAccelerator(SWT.CTRL + 'S'); savecsv.addSelectionListener(new TableToCsvSaveAdapter(this.shell, this.getTitle(), () -> this.getSelectionTable().getTable())); MenuItem operoot = new MenuItem(this.menubar, SWT.CASCADE); operoot.setText("操作"); this.opemenu = new Menu(operoot); operoot.setMenu(this.opemenu); MenuItem reload = new MenuItem(this.opemenu, SWT.NONE); reload.setText("再読み込み(&R)\tF5"); reload.setAccelerator(SWT.F5); reload.addSelectionListener(new TableReloadAdapter()); Boolean isCyclicReload = AppConfig.get().getCyclicReloadMap().get(this.getClass().getName()); MenuItem cyclicReload = new MenuItem(this.opemenu, SWT.CHECK); cyclicReload.setText("定期的に再読み込み(&A)\tCtrl+F5"); cyclicReload.setAccelerator(SWT.CTRL + SWT.F5); if ((isCyclicReload != null) && isCyclicReload.booleanValue()) { cyclicReload.setSelection(true); } CyclicReloadAdapter adapter = new CyclicReloadAdapter(cyclicReload); cyclicReload.addSelectionListener(adapter); adapter.setCyclicReload(cyclicReload); MenuItem selectVisible = new MenuItem(this.opemenu, SWT.NONE); selectVisible.setText("列の表示・非表示(&V)"); selectVisible.addSelectionListener(new SelectVisibleColumnAdapter()); new MenuItem(this.opemenu, SWT.SEPARATOR); // 閉じた時に設定を保存 this.shell.addShellListener(new ShellAdapter() { @Override public void shellClosed(ShellEvent e) { AppConfig.get().getCyclicReloadMap() .put(AbstractTableDialogEx.this.getClass().getName(), cyclicReload.getSelection()); } }); this.createContents(); this.shell.open(); this.shell.layout(); Display display = this.getParent().getDisplay(); while (!this.shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } // タスクがある場合キャンセル if (this.future != null) { this.future.cancel(false); } } /** * テーブルを追加します * * @param parent テーブルの親コンポジット * @return TableWrapper */ protected TableWrapper<T> addTable(Composite parent) { // テーブル Table table = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI); table.setLinesVisible(true); table.setHeaderVisible(true); table.addKeyListener(new TableKeyShortcutAdapter(table)); // テーブル右クリックメニュー Menu tablemenu = new Menu(table); table.setMenu(tablemenu); MenuItem sendclipbord = new MenuItem(tablemenu, SWT.NONE); sendclipbord.addSelectionListener(new TableToClipboardAdapter(table)); sendclipbord.setText("クリップボードにコピー(&C)"); MenuItem reloadtable = new MenuItem(tablemenu, SWT.NONE); reloadtable.setText("再読み込み(&R)"); reloadtable.addSelectionListener(new TableReloadAdapter()); TableWrapper<T> wrapper = new TableWrapper<T>(table, this.clazz) .setDialogClass(this.getClass()) .setDecorator(TableItemDecorator.stripe(SWTResourceManager.getColor(AppConstants.ROW_BACKGROUND))); this.tables.add(wrapper); return wrapper; } /** * 選択されているテーブルを取得する */ protected TableWrapper<T> getSelectionTable() { return this.tables.get(0); } /** * テーブルをリロードする */ protected void reload() { this.getSelectionTable() .reload() .update(); } /** * ウインドウサイズを返します * @return Point */ protected Point getSize() { return new Point(600, 350); } /** * タイトルを返します * @return String */ protected abstract String getTitle(); /** * Create contents of the dialog.<br> * 1つの空のテーブルをダイアログに追加するにはcreateContents()に次のように記述します * <pre>{@code * this.addTable(this.shell) * .setContentSupplier(() -> Stream.empty()) // 空のStreamを返すサプライヤーをセット * .reload() * .update(); * }</pre> */ protected abstract void createContents(); /** * テーブルを再読み込みするリスナーです */ protected class TableReloadAdapter extends SelectionAdapter { @Override public void widgetSelected(SelectionEvent e) { AbstractTableDialogEx.this.reload(); } } /** * テーブルの列を表示・非表示選択するダイアログを表示する */ protected class SelectVisibleColumnAdapter extends SelectionAdapter { @Override public void widgetSelected(SelectionEvent e) { AbstractTableDialogEx<T> thiz = AbstractTableDialogEx.this; Shell shell = thiz.shell; Table table = thiz.getSelectionTable().getTable(); Class<?> clazz = thiz.getClass(); // ダイアログ表示 new SelectVisibleColumnDialog(shell, table, clazz).open(); // テーブル再読み込み thiz.reload(); } } /** * テーブルを定期的に再読み込みする */ protected class CyclicReloadAdapter extends SelectionAdapter { private final MenuItem menuitem; public CyclicReloadAdapter(MenuItem menuitem) { this.menuitem = menuitem; } @Override public void widgetSelected(SelectionEvent e) { this.setCyclicReload(this.menuitem); } private void setCyclicReload(MenuItem menuitem) { if (menuitem.getSelection()) { Runnable command = () -> { if (!AbstractTableDialogEx.this.shell.isDisposed()) { Display.getDefault().asyncExec(() -> { if (!AbstractTableDialogEx.this.shell.isDisposed()) { AbstractTableDialogEx.this.reload(); } }); } else { // ウインドウが消えていたらタスクをキャンセルする throw new ThreadDeath(); } }; // タスクがある場合キャンセル if (AbstractTableDialogEx.this.future != null) { AbstractTableDialogEx.this.future.cancel(false); } // 再読み込みするようにスケジュールする AbstractTableDialogEx.this.future = ThreadManager.getExecutorService() .scheduleWithFixedDelay(command, 1, 1, TimeUnit.SECONDS); } else { // タスクがある場合キャンセル if (AbstractTableDialogEx.this.future != null) { AbstractTableDialogEx.this.future.cancel(false); } } } } }