package logbook.gui; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import logbook.config.AppConfig; import logbook.constants.AppConstants; import logbook.dto.chart.Resource; import logbook.dto.chart.ResourceLog; import logbook.dto.chart.ResourceLog.SortableLog; import logbook.gui.listener.SaveWindowLocationAdapter; import logbook.gui.logic.CreateReportLogic; import logbook.gui.logic.LayoutLogic; import logbook.gui.logic.ResourceChart; import logbook.gui.logic.TableItemCreator; import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.ImageLoader; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; /** * 資材チャートのダイアログ * */ public final class ResourceChartDialog extends Dialog { /** ロガー */ private static final Logger LOG = LogManager.getLogger(ResourceChartDialog.class); /** スケールテキスト */ private static final String[] SCALE_TEXT = { "1d", "1w", "2w", "1m", "2m", "3m", "6m", "1y" }; /** スケールテキストに対応する日 */ private static final int[] SCALE_DAYS = { 1, 7, 14, 30, 60, 90, 180, 365 }; /** 資材テーブルに表示する資材のフォーマット */ private static final String DIFF_FORMAT = "{0,number,0}({1,number,+0;-0})"; /** シェル */ private Shell shell; /** メニューバー */ private Menu menubar; /** [ファイル]メニュー */ private Menu filemenu; /** スケール */ private Combo combo; /** グラフキャンバス */ private Canvas canvas; /** 資材ログ */ private ResourceLog log; /** 資材テーブル */ private Table table; /** 資材テーブルのヘッダ */ private final String[] header = Arrays.copyOfRange(CreateReportLogic.getMaterialHeader(), 1, 9); /** 資材テーブルのボディ */ private final List<String[]> body = new ArrayList<>(); /** * Create the dialog. * @param parent */ public ResourceChartDialog(Shell parent) { super(parent, SWT.SHELL_TRIM); this.setText("Resource Chart"); } /** * Open the dialog. * @return the result */ public void open() { this.createContents(); this.shell.open(); this.shell.layout(); Display display = this.getParent().getDisplay(); while (!this.shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * Create contents of the dialog. */ private void createContents() { this.shell = new Shell(this.getParent(), this.getStyle()); this.shell.setMinimumSize(450, 300); this.shell.setSize(800, 650); // ウインドウ位置を復元 LayoutLogic.applyWindowLocation(this.getClass(), this.shell); // 閉じた時にウインドウ位置を保存 this.shell.addShellListener(new SaveWindowLocationAdapter(this.getClass())); this.shell.setText(this.getText()); GridLayout glShell = new GridLayout(1, false); glShell.verticalSpacing = 2; glShell.marginWidth = 2; glShell.marginHeight = 2; glShell.horizontalSpacing = 2; this.shell.setLayout(glShell); this.menubar = new Menu(this.shell, SWT.BAR); this.shell.setMenuBar(this.menubar); MenuItem fileroot = new MenuItem(this.menubar, SWT.CASCADE); fileroot.setText("&File"); this.filemenu = new Menu(fileroot); fileroot.setMenu(this.filemenu); MenuItem save = new MenuItem(this.filemenu, SWT.NONE); save.setText("&Save as Image\tCtrl+S"); save.setAccelerator(SWT.CTRL + 'S'); SashForm sashForm = new SashForm(this.shell, SWT.SMOOTH | SWT.VERTICAL); sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); Composite compositeChart = new Composite(sashForm, SWT.NONE); GridLayout glCompositeChart = new GridLayout(2, false); glCompositeChart.verticalSpacing = 1; glCompositeChart.marginWidth = 1; glCompositeChart.marginHeight = 1; glCompositeChart.marginBottom = 1; glCompositeChart.horizontalSpacing = 1; compositeChart.setLayout(glCompositeChart); Label label = new Label(compositeChart, SWT.NONE); label.setText("Scale"); this.combo = new Combo(compositeChart, SWT.READ_ONLY); this.combo.setItems(SCALE_TEXT); this.combo.select(2); this.combo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ResourceChartDialog.this.canvas.redraw(); } }); this.canvas = new Canvas(compositeChart, SWT.NO_BACKGROUND); this.canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); Composite compositeTable = new Composite(sashForm, SWT.NONE); GridLayout glCompositeTable = new GridLayout(1, false); glCompositeTable.horizontalSpacing = 1; glCompositeTable.marginHeight = 1; glCompositeTable.marginWidth = 1; glCompositeTable.verticalSpacing = 1; compositeTable.setLayout(glCompositeTable); this.table = new Table(compositeTable, SWT.BORDER | SWT.FULL_SELECTION); this.table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); this.table.setHeaderVisible(true); this.table.setLinesVisible(true); sashForm.setWeights(new int[] { 3, 1 }); this.canvas.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { ResourceLog log = ResourceChartDialog.this.log; int scale = SCALE_DAYS[ResourceChartDialog.this.combo.getSelectionIndex()]; String scaleText = "Scale:" + ResourceChartDialog.this.combo.getText(); Point size = ResourceChartDialog.this.canvas.getSize(); int width = size.x - 1; int height = size.y - 1; if (log != null) { Image image = createImage(log, scale, scaleText, width, height); e.gc.drawImage(image, 0, 0); image.dispose(); } } }); // 資材ログ読み込み File report = new File(FilenameUtils.concat(AppConfig.get().getReportPath(), AppConstants.LOG_RESOURCE)); try { this.log = ResourceLog.getInstance(report); } catch (IOException e) { this.log = null; } // 画像ファイルとして保存のリスナー save.addSelectionListener(new SaveImageAdapter(this.shell, this.combo, this.canvas, this.log)); // 資材テーブルを表示する this.setTableHeader(); if (this.log != null) { createTableBody(this.log, this.body); this.setTableBody(); this.packTableHeader(); } } /** * テーブルヘッダーをセットする */ private void setTableHeader() { for (int i = 0; i < this.header.length; i++) { TableColumn col = new TableColumn(this.table, SWT.LEFT); col.setText(this.header[i]); } this.packTableHeader(); } /** * テーブルヘッダーの幅を調節する */ private void packTableHeader() { TableColumn[] columns = this.table.getColumns(); for (int i = 0; i < columns.length; i++) { columns[i].pack(); } } /** * テーブルボディーをセットする */ private void setTableBody() { TableItemCreator creator = CreateReportLogic.DEFAULT_TABLE_ITEM_CREATOR; creator.init(); for (int i = 0; i < this.body.size(); i++) { String[] line = this.body.get(i); creator.create(this.table, line, i); } } /** * 資材ログのグラフイメージを作成する * * @param log 資材ログ * @param scale 日単位のスケール * @param width 幅 * @param height 高さ * @return グラフイメージ */ private static Image createImage(ResourceLog log, int scale, String scaleText, int width, int height) { Image image = new Image(Display.getCurrent(), Math.max(width, 1), Math.max(height, 1)); try { GC gc = new GC(image); try { ResourceChart chart = new ResourceChart(log, scale, scaleText, width, height); chart.draw(gc); } finally { gc.dispose(); } } catch (Exception e) { image.dispose(); LOG.warn("An exception occurred while drawing the image", e); } return image; } /** * 資材テーブルのボディを作成する * * @param log 資材ログ * @param body テーブルボディ */ private static void createTableBody(ResourceLog log, List<String[]> body) { SimpleDateFormat format = new SimpleDateFormat(AppConstants.DATE_DAYS_FORMAT); format.setTimeZone(AppConstants.TIME_ZONE_MISSION); Map<String, SortableLog> resourceofday = new LinkedHashMap<>(); for (int i = 0; i < log.time.length; i++) { String key = format.format(new Date(log.time[i])); Resource[] r = log.resources; SortableLog slog = new SortableLog(); slog.time = log.time[i]; slog.fuel = r[ResourceLog.RESOURCE_FUEL].values[i]; slog.ammo = r[ResourceLog.RESOURCE_AMMO].values[i]; slog.metal = r[ResourceLog.RESOURCE_METAL].values[i]; slog.bauxite = r[ResourceLog.RESOURCE_BAUXITE].values[i]; slog.bucket = r[ResourceLog.RESOURCE_BUCKET].values[i]; slog.burner = r[ResourceLog.RESOURCE_BURNER].values[i]; slog.research = r[ResourceLog.RESOURCE_RESEARCH].values[i]; resourceofday.put(key, slog); } MessageFormat diffFormat = new MessageFormat(DIFF_FORMAT); SortableLog before = null; for (Entry<String, SortableLog> entry : resourceofday.entrySet()) { SortableLog val = entry.getValue(); int[] material = { val.fuel, val.ammo, val.metal, val.bauxite, val.bucket, val.burner, val.research }; int[] materialDiff = new int[material.length]; if (before != null) { int[] materialBefore = { before.fuel, before.ammo, before.metal, before.bauxite, before.bucket, before.burner, before.research }; for (int i = 0; i < material.length; i++) { materialDiff[i] = material[i] - materialBefore[i]; } } before = val; String[] line = new String[material.length + 1]; line[0] = entry.getKey(); for (int i = 0; i < material.length; i++) { line[i + 1] = diffFormat.format(new Object[] { material[i], materialDiff[i] }); } body.add(line); } Collections.reverse(body); } /** * 画像ファイルとして保存のリスナー * */ private static final class SaveImageAdapter extends SelectionAdapter { /** シェル */ private final Shell shell; /** スケール */ private final Combo combo; /** グラフキャンバス */ private final Canvas canvas; /** 資材ログ */ private final ResourceLog log; /** * コンストラクター */ public SaveImageAdapter(Shell shell, Combo scaleCombo, Canvas canvas, ResourceLog log) { this.shell = shell; this.combo = scaleCombo; this.canvas = canvas; this.log = log; } @Override public void widgetSelected(SelectionEvent e) { if (this.log != null) { SimpleDateFormat format = new SimpleDateFormat(AppConstants.DATE_DAYS_FORMAT); String name = "Resources_" + format.format(Calendar.getInstance().getTime()) + ".png"; FileDialog dialog = new FileDialog(this.shell, SWT.SAVE); dialog.setFileName(name); dialog.setFilterExtensions(new String[] { "*.png" }); String filename = dialog.open(); if (filename != null) { File file = new File(filename); if (file.exists()) { MessageBox messageBox = new MessageBox(this.shell, SWT.YES | SWT.NO); messageBox.setText("Confirmation"); messageBox.setMessage(filename + " already exists.\nDo you want to overwrite it?"); if (messageBox.open() == SWT.NO) { return; } } int scale = SCALE_DAYS[this.combo.getSelectionIndex()]; String scaleText = "Scale:" + this.combo.getText(); Point size = this.canvas.getSize(); int width = size.x - 1; int height = size.y - 1; try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { // イメージの生成 Image image = createImage(this.log, scale, scaleText, width, height); try { ImageLoader loader = new ImageLoader(); loader.data = new ImageData[] { image.getImageData() }; loader.save(out, SWT.IMAGE_PNG); } finally { image.dispose(); } } catch (Exception ex) { MessageBox messageBox = new MessageBox(this.shell, SWT.ICON_ERROR); messageBox.setText("Write error"); messageBox.setMessage(e.toString()); messageBox.open(); } } } } } }