package logbook.gui; import java.awt.Desktop; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import logbook.config.AppConfig; import logbook.constants.AppConstants; import logbook.gui.logic.LayoutLogic; import logbook.gui.twitter.TweetDialog; import logbook.gui.twitter.TwitterClient; import logbook.internal.LoggerHolder; import logbook.util.AwtUtils; import logbook.util.SwtUtils; import org.apache.commons.io.FilenameUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Spinner; import org.eclipse.swt.widgets.Text; import org.eclipse.wb.swt.SWTResourceManager; /** * キャプチャダイアログ * */ public final class CaptureDialog extends WindowBase { /** ロガー */ private static final LoggerHolder LOG = new LoggerHolder(CaptureDialog.class); private final Shell parent; private Shell shell; private Composite composite; private Text text; private Button capture; private Button twitter; private Button interval; private Spinner intervalms; /** キャプチャ範囲 */ private Rectangle rectangle; private Timer timer; private boolean isAlive; private Font font; /** Jpeg品質 */ private static final float QUALITY = 0.9f; /** 日付フォーマット(ファイル名) */ private final SimpleDateFormat fileNameFormat = new SimpleDateFormat(AppConstants.DATE_LONG_FORMAT); /** 日付フォーマット(ディレクトリ名) */ private final SimpleDateFormat dirNameFormat = new SimpleDateFormat(AppConstants.DATE_DAYS_FORMAT); /** トリム範囲 */ private java.awt.Rectangle trimRect; /** * Create the dialog. * @param parent */ public CaptureDialog(Shell parent, MenuItem menuItem) { super(menuItem); this.parent = parent; } /** * Open the dialog. */ @Override public void open() { // 初期化済みの場合 if (this.isWindowInitialized()) { // リロードして表示 this.setCaptureRect(intToRect(AppConfig.get().getCaptureRect())); this.setVisible(true); return; } this.createContents(); this.registerEvents(); this.setWindowInitialized(true); this.setVisible(true); } @Override protected boolean moveWithDrag() { return true; } /** * Create contents of the dialog. */ private void createContents() { // シェル super.createContents(this.parent, SWT.CLOSE | SWT.TITLE | SWT.RESIZE | SWT.TOOL, false); this.getShell().setText("キャプチャ"); this.shell = this.getShell(); // レイアウト GridLayout glShell = new GridLayout(2, false); glShell.horizontalSpacing = 1; glShell.marginHeight = 1; glShell.marginWidth = 1; glShell.verticalSpacing = 1; this.shell.setLayout(glShell); // 太字にするためのフォントデータを作成する FontData defaultfd = this.shell.getFont().getFontData()[0]; FontData fd = new FontData(defaultfd.getName(), defaultfd.getHeight(), SWT.BOLD); this.font = new Font(Display.getDefault(), fd); // コンポジット Composite rangeComposite = new Composite(this.shell, SWT.NONE); rangeComposite.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); rangeComposite.setLayout(new GridLayout(2, false)); // 範囲設定 this.text = new Text(rangeComposite, SWT.BORDER | SWT.READ_ONLY); GridData gdText = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); gdText.widthHint = SwtUtils.DPIAwareWidth(120); this.text.setLayoutData(gdText); this.text.setText("範囲が未設定です"); Button button = new Button(rangeComposite, SWT.NONE); button.setText("範囲を選択"); button.addSelectionListener(new SelectRectangleAdapter()); // コンポジット this.composite = new Composite(this.shell, SWT.NONE); this.composite.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); this.composite.setLayout(new GridLayout(3, false)); // 周期設定 this.interval = new Button(this.composite, SWT.CHECK); this.interval.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { CaptureDialog.this.capture.setText(getCaptureButtonText(false, CaptureDialog.this.interval.getSelection())); } }); this.interval.setText("周期"); this.intervalms = new Spinner(this.composite, SWT.BORDER); this.intervalms.setMaximum(60000); this.intervalms.setMinimum(100); this.intervalms.setSelection(1000); this.intervalms.setIncrement(100); Label label = new Label(this.composite, SWT.NONE); label.setText("ミリ秒"); Button openFolderButton = new Button(this.shell, SWT.NONE); openFolderButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); openFolderButton.setText("保存先を開く"); openFolderButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { CaptureDialog.this.openCaptureDir(); } }); Composite buttonComposite = new Composite(this.shell, SWT.NONE); buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); buttonComposite.setLayout(new GridLayout(2, true)); this.capture = new Button(buttonComposite, SWT.NONE); this.capture.setFont(this.font); GridData gdCapture = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); gdCapture.heightHint = SwtUtils.DPIAwareHeight(64); this.capture.setLayoutData(gdCapture); this.capture.setEnabled(false); this.capture.setText(getCaptureButtonText(false, this.interval.getSelection())); this.capture.addSelectionListener(new CaptureStartAdapter()); this.twitter = new Button(buttonComposite, SWT.NONE); this.twitter.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL)); this.twitter.setEnabled(false); this.twitter.addSelectionListener(new TwitterAdapter()); SwtUtils.setButtonImage(this.twitter, SWTResourceManager.getImage(WindowBase.class, AppConstants.TWITTER)); this.shell.addListener(SWT.Dispose, new Listener() { @Override public void handleEvent(Event event) { // タイマーを停止させる if (CaptureDialog.this.timer != null) { CaptureDialog.this.timer.cancel(); } // フォントを開放 if (CaptureDialog.this.font != null) { CaptureDialog.this.font.dispose(); } } }); // 選択する項目はドラックで移動できないようにする for (Control c : new Control[] { this.text, button, this.interval, this.intervalms, this.capture }) { c.setData("disable-drag-move", true); } // 設定を反映 this.setCaptureRect(intToRect(AppConfig.get().getCaptureRect())); this.shell.pack(); } private static Rectangle intToRect(int[] intRect) { if (intRect == null) return null; return new Rectangle(intRect[0], intRect[1], intRect[2], intRect[3]); } private static int[] rectToInt(Rectangle rect) { if (rect == null) return null; return new int[] { rect.x, rect.y, rect.width, rect.height }; } /** * キャプチャボタンの文字を取得します * * @param isrunning * @param interval * @return */ private static String getCaptureButtonText(boolean isrunning, boolean interval) { if (isrunning && interval) { return "停 止"; } else if (interval) { return "開 始"; } else { return "キャプチャ"; } } private void setCaptureRect(Rectangle rectangle) { if ((rectangle != null) && (rectangle.width > 1) && (rectangle.height > 1)) { this.rectangle = rectangle; this.trimRect = AwtUtils.getTrimSize(captureImage(rectangle, null)); AppConfig.get().setCaptureRect(rectToInt(rectangle)); this.text.setText("(" + rectangle.x + "," + rectangle.y + ")-(" + (rectangle.x + rectangle.width) + "," + (rectangle.y + rectangle.height) + ")"); this.capture.setEnabled(true); this.twitter.setEnabled(true); } } /** * 範囲の選択を押した時 * */ public final class SelectRectangleAdapter extends SelectionAdapter { /** ダイアログが完全に消えるまで待つ時間 */ private static final int WAIT = 250; @Override public void widgetSelected(SelectionEvent paramSelectionEvent) { try { Display display = Display.getDefault(); // ダイアログを非表示にする CaptureDialog.this.shell.setVisible(false); // 消えるまで待つ Thread.sleep(WAIT); // ディスプレイに対してGraphics Contextを取得する(フルスクリーンキャプチャ) GC gc = new GC(display); Rectangle rect = display.getBounds(); Image image = new Image(display, rect); gc.copyArea(image, rect.x, rect.y); gc.dispose(); try { // 範囲を取得する Rectangle rectangle = new FullScreenDialog(CaptureDialog.this.shell, image, display) .open(); CaptureDialog.this.setCaptureRect(rectangle); } finally { image.dispose(); } CaptureDialog.this.shell.setVisible(true); CaptureDialog.this.shell.setActive(); } catch (Exception e) { e.printStackTrace(); } } } /** * キャプチャボタンを押した時 * */ public final class CaptureStartAdapter extends SelectionAdapter { @Override public void widgetSelected(SelectionEvent e) { Timer timer = CaptureDialog.this.timer; boolean interval = CaptureDialog.this.interval.getSelection(); int intervalms = CaptureDialog.this.intervalms.getSelection(); if (timer != null) { // タイマーを停止させる timer.cancel(); timer = null; } if (CaptureDialog.this.isAlive) { CaptureDialog.this.capture.setText(getCaptureButtonText(false, interval)); LayoutLogic.enable(CaptureDialog.this.composite, true); CaptureDialog.this.isAlive = false; } else { timer = new Timer(true); if (interval) { // 固定レートで周期キャプチャ timer.scheduleAtFixedRate(new CaptureTask(), 0, intervalms); CaptureDialog.this.isAlive = true; } else { // 一回だけキャプチャ timer.schedule(new CaptureTask(), 0); } CaptureDialog.this.capture.setText(getCaptureButtonText(true, interval)); if (interval) { LayoutLogic.enable(CaptureDialog.this.composite, false); } } CaptureDialog.this.timer = timer; } } private BufferedImage captureImage() { return captureImage(this.rectangle, this.trimRect); } private static BufferedImage captureImage(Rectangle rectangle, java.awt.Rectangle trimRect) { // 範囲をキャプチャする BufferedImage image = AwtUtils.getCapture(rectangle); if (image != null) { if (trimRect != null) { return AwtUtils.trim(image, trimRect); } return image; } return null; } private File getSaveFile() throws IOException { // 時刻からファイル名を作成 Date now = new Date(); String dir = null; if (AppConfig.get().isCreateDateFolder()) { dir = FilenameUtils.concat(AppConfig.get().getCapturePath(), this.dirNameFormat.format(now)); } else { dir = AppConfig.get().getCapturePath(); } String fname = FilenameUtils.concat(dir, this.fileNameFormat.format(now) + "." + AppConfig.get().getImageFormat()); File file = new File(fname); if (file.exists()) { if (file.isDirectory()) { throw new IOException("File '" + file + "' exists but is a directory"); } if (!(file.canWrite())) throw new IOException("File '" + file + "' cannot be written to"); } else { File parent = file.getParentFile(); if ((parent != null) && (!(parent.mkdirs())) && (!(parent.isDirectory()))) { throw new IOException("Directory '" + parent + "' could not be created"); } } return file; } private void openCaptureDir() { try { File file = this.getSaveFile(); if (Desktop.isDesktopSupported()) { Desktop.getDesktop().open(new File(file.getParent())); } } catch (Exception e) { LOG.get().warn("保存先を開くで例外が発生しました", e); } } private void saveImageToFile(BufferedImage image, File file) throws IOException { ImageOutputStream ios = ImageIO.createImageOutputStream(file); try { ImageWriter writer = ImageIO.getImageWritersByFormatName(AppConfig.get().getImageFormat()) .next(); try { ImageWriteParam iwp = writer.getDefaultWriteParam(); if (iwp.canWriteCompressed()) { iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality(QUALITY); } writer.setOutput(ios); writer.write(null, new IIOImage(image, null, null), iwp); ApplicationMain.logPrint("キャプチャしました [" + file.getName() + "]"); } finally { writer.dispose(); } } finally { ios.close(); } } private File captureAndSave() throws IOException { File file = CaptureDialog.this.getSaveFile(); // 範囲をキャプチャする BufferedImage image = CaptureDialog.this.captureImage(); if (image != null) { CaptureDialog.this.saveImageToFile(image, file); } return file; } /** * 画面キャプチャスレッド * */ public final class CaptureTask extends TimerTask { @Override public void run() { try { CaptureDialog.this.captureAndSave(); } catch (Exception e) { LOG.get().warn("キャプチャ中に例外が発生しました", e); } } } /** * Twitterボタンを押した時 * */ public final class TwitterAdapter extends SelectionAdapter { @Override public void widgetSelected(SelectionEvent ev) { try { File file = CaptureDialog.this.captureAndSave(); if (TwitterClient.getInstance().prepareAccessToken(CaptureDialog.this)) { TweetDialog tweetDialog = new TweetDialog( CaptureDialog.this, file); tweetDialog.open(); } } catch (Exception e) { LOG.get().warn("つぶやく途中で例外が発生しました", e); } } } }