1 package charactermanaj.ui;
3 import static java.lang.Math.*;
5 import java.awt.BorderLayout;
7 import java.awt.Component;
8 import java.awt.Cursor;
9 import java.awt.Dimension;
10 import java.awt.FontMetrics;
11 import java.awt.Graphics;
12 import java.awt.Graphics2D;
13 import java.awt.GridBagConstraints;
14 import java.awt.GridBagLayout;
15 import java.awt.Insets;
16 import java.awt.Point;
17 import java.awt.Rectangle;
18 import java.awt.RenderingHints;
19 import java.awt.Toolkit;
20 import java.awt.event.ActionEvent;
21 import java.awt.event.ActionListener;
22 import java.awt.event.MouseAdapter;
23 import java.awt.event.MouseEvent;
24 import java.awt.event.MouseMotionAdapter;
25 import java.awt.event.MouseMotionListener;
26 import java.awt.event.MouseWheelEvent;
27 import java.awt.event.MouseWheelListener;
28 import java.awt.geom.Rectangle2D;
29 import java.awt.image.BufferedImage;
30 import java.beans.PropertyChangeEvent;
31 import java.beans.PropertyChangeListener;
32 import java.util.ArrayList;
33 import java.util.EventObject;
34 import java.util.LinkedList;
35 import java.util.List;
37 import java.util.Properties;
38 import java.util.TreeSet;
39 import java.util.concurrent.Semaphore;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
43 import javax.swing.AbstractButton;
44 import javax.swing.BorderFactory;
45 import javax.swing.Box;
46 import javax.swing.Icon;
47 import javax.swing.JButton;
48 import javax.swing.JCheckBox;
49 import javax.swing.JComboBox;
50 import javax.swing.JLabel;
51 import javax.swing.JLayeredPane;
52 import javax.swing.JPanel;
53 import javax.swing.JScrollBar;
54 import javax.swing.JScrollPane;
55 import javax.swing.JSlider;
56 import javax.swing.JTextField;
57 import javax.swing.JToolBar;
58 import javax.swing.JViewport;
59 import javax.swing.OverlayLayout;
60 import javax.swing.SwingUtilities;
61 import javax.swing.Timer;
62 import javax.swing.UIManager;
63 import javax.swing.border.Border;
64 import javax.swing.event.ChangeEvent;
65 import javax.swing.event.ChangeListener;
66 import javax.swing.plaf.basic.BasicComboBoxEditor;
68 import charactermanaj.Main;
69 import charactermanaj.graphics.filters.BackgroundColorFilter;
70 import charactermanaj.graphics.filters.BackgroundColorFilter.BackgroundColorMode;
71 import charactermanaj.model.AppConfig;
72 import charactermanaj.ui.util.ScaleSupport;
73 import charactermanaj.ui.util.ScrollPaneDragScrollSupport;
74 import charactermanaj.util.LocalizedResourcePropertyLoader;
75 import charactermanaj.util.UIHelper;
82 public class PreviewPanel extends JPanel {
84 private static final long serialVersionUID = 1L;
86 protected static final String STRINGS_RESOURCE = "languages/previewpanel";
90 * プレビューパネルの上部ツールバーの通知を受けるリスナ
94 public interface PreviewPanelListener {
101 void savePicture(PreviewPanelEvent e);
108 void copyPicture(PreviewPanelEvent e);
115 void changeBackgroundColor(PreviewPanelEvent e);
122 void showInformation(PreviewPanelEvent e);
129 void addFavorite(PreviewPanelEvent e);
136 void flipHorizontal(PreviewPanelEvent e);
142 private final String indicatorText;
147 private final Timer timer;
152 private long indicatorDelay;
155 public void addNotify() {
157 if (!timer.isRunning()) {
163 public void removeNotify() {
164 if (timer.isRunning()) {
167 super.removeNotify();
170 public static class PreviewPanelEvent extends EventObject {
172 private static final long serialVersionUID = 1L;
174 private int modifiers;
176 public PreviewPanelEvent(Object src, ActionEvent e) {
177 this(src, (e == null) ? 0 : e.getModifiers());
180 public PreviewPanelEvent(Object src, int modifiers) {
182 this.modifiers = modifiers;
185 public int getModifiers() {
189 public boolean isShiftKeyPressed() {
190 return (modifiers & ActionEvent.SHIFT_MASK) != 0;
194 private final Object lock = new Object();
196 private long loadingTicket;
198 private long loadedTicket;
200 private long firstWaitingTimestamp;
202 private boolean indicatorShown;
204 private String title;
206 private JLabel lblTitle;
208 private JLayeredPane layeredPane;
210 private CheckInfoLayerPanel checkInfoLayerPanel;
212 private PreviewImagePanel previewImgPanel;
214 private JScrollPane previewImgScrollPane;
216 private ScrollPaneDragScrollSupport scrollSupport;
218 private PreviewControlPanel previewControlPanel;
220 private double latestToggleZoom = 2.;
222 private LinkedList<PreviewPanelListener> listeners = new LinkedList<PreviewPanelListener>();
225 public PreviewPanel(ScaleSupport scaleSupport) {
226 setLayout(new BorderLayout());
228 final AppConfig appConfig = AppConfig.getInstance();
229 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
230 .getLocalizedProperties(STRINGS_RESOURCE);
232 // 画像をロード中であることを示すインジケータの確認サイクル.
233 timer = new Timer(100, new ActionListener() {
234 public void actionPerformed(ActionEvent e) {
239 indicatorText = strings.getProperty("indicatorText");
240 indicatorDelay = appConfig.getPreviewIndicatorDelay();
242 UIHelper uiUtl = UIHelper.getInstance();
243 JButton saveBtn = uiUtl.createIconButton("icons/save.png");
244 JButton copyBtn = uiUtl.createIconButton("icons/copy.png");
245 JButton colorBtn = uiUtl.createIconButton("icons/color.png");
246 JButton informationBtn = uiUtl.createIconButton("icons/information.png");
247 JButton favoriteBtn = uiUtl.createIconButton("icons/favorite.png");
248 JButton flipHolizontalBtn = uiUtl.createIconButton("icons/flip.png");
250 saveBtn.addActionListener(new ActionListener() {
251 public void actionPerformed(ActionEvent e) {
252 savePicture(new PreviewPanelEvent(PreviewPanel.this, e));
255 copyBtn.addActionListener(new ActionListener() {
256 public void actionPerformed(ActionEvent e) {
257 copyPicture(new PreviewPanelEvent(PreviewPanel.this, e));
260 colorBtn.addActionListener(new ActionListener() {
261 public void actionPerformed(ActionEvent e) {
262 changeBackgroundColor(new PreviewPanelEvent(PreviewPanel.this, e));
265 informationBtn.addActionListener(new ActionListener() {
266 public void actionPerformed(ActionEvent e) {
267 showInformation(new PreviewPanelEvent(PreviewPanel.this, e));
270 favoriteBtn.addActionListener(new ActionListener() {
271 public void actionPerformed(ActionEvent e) {
272 addFavorite(new PreviewPanelEvent(PreviewPanel.this, e));
275 flipHolizontalBtn.addActionListener(new ActionListener() {
276 public void actionPerformed(ActionEvent e) {
277 flipHolizontal(new PreviewPanelEvent(PreviewPanel.this, e));
281 saveBtn.setToolTipText(strings.getProperty("tooltip.save"));
282 copyBtn.setToolTipText(strings.getProperty("tooltip.copy"));
283 colorBtn.setToolTipText(strings.getProperty("tooltip.changeBgColor"));
284 informationBtn.setToolTipText(strings.getProperty("tooltip.showInformation"));
285 favoriteBtn.setToolTipText(strings.getProperty("tooltip.registerFavorites"));
286 flipHolizontalBtn.setToolTipText(strings.getProperty("tooltip.flipHorizontal"));
288 final JToolBar toolBar = new JToolBar();
289 toolBar.setFloatable(false);
290 toolBar.add(flipHolizontalBtn);
291 toolBar.add(copyBtn);
292 toolBar.add(saveBtn);
293 toolBar.add(Box.createHorizontalStrut(8));
294 toolBar.add(colorBtn);
295 toolBar.add(Box.createHorizontalStrut(4));
296 toolBar.add(favoriteBtn);
297 toolBar.add(informationBtn);
299 lblTitle = new JLabel() {
300 private static final long serialVersionUID = 1L;
302 public Dimension getPreferredSize() {
303 Dimension dim = super.getPreferredSize();
304 int maxWidth = getParent().getWidth() - toolBar.getWidth();
305 if (dim.width > maxWidth) {
306 dim.width = maxWidth;
311 public Dimension getMaximumSize() {
312 return getPreferredSize();
315 public Dimension getMinimumSize() {
316 Dimension dim = getPreferredSize();
322 lblTitle.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3));
324 JPanel previewPaneHeader = new JPanel();
325 previewPaneHeader.setLayout(new BorderLayout());
326 previewPaneHeader.add(lblTitle, BorderLayout.WEST);
327 previewPaneHeader.add(toolBar, BorderLayout.EAST);
329 previewImgPanel = new PreviewImagePanel();
332 previewImgScrollPane = new JScrollPane(previewImgPanel);
333 previewImgScrollPane.setAutoscrolls(false);
334 previewImgScrollPane.setWheelScrollingEnabled(false);
335 previewImgScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
336 previewImgScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
338 scrollSupport = new ScrollPaneDragScrollSupport(previewImgScrollPane) {
340 protected void setCursor(Cursor cursor) {
341 PreviewPanel.this.setCursor(cursor);
345 add(previewPaneHeader, BorderLayout.NORTH);
347 layeredPane = new JLayeredPane();
348 layeredPane.setLayout(new OverlayLayout(layeredPane));
350 layeredPane.add(previewImgScrollPane, JLayeredPane.DEFAULT_LAYER);
352 checkInfoLayerPanel = new CheckInfoLayerPanel();
353 layeredPane.add(checkInfoLayerPanel, JLayeredPane.POPUP_LAYER);
354 checkInfoLayerPanel.setVisible(false);
356 add(layeredPane, BorderLayout.CENTER);
358 previewControlPanel = new PreviewControlPanel();
359 Dimension dim = previewControlPanel.getPreferredSize();
360 Dimension prevDim = previewImgScrollPane.getPreferredSize();
361 dim.width = prevDim.width;
362 previewControlPanel.setPreferredSize(dim);
364 add(previewControlPanel, BorderLayout.SOUTH);
365 previewControlPanel.setPinned(appConfig.isEnableZoomPanel());
368 previewControlPanel.addPropertyChangeListener("zoomFactorInt", new PropertyChangeListener() {
369 public void propertyChange(PropertyChangeEvent evt) {
370 Integer newValue = (Integer) evt.getNewValue();
371 zoomWithCenterPosition(newValue.doubleValue() / 100., null);
375 previewControlPanel.addPropertyChangeListener("backgroundColorMode", new PropertyChangeListener() {
376 public void propertyChange(PropertyChangeEvent evt) {
377 BackgroundColorMode bgColorMode = (BackgroundColorMode) evt.getNewValue();
378 previewImgPanel.setBackgroundColorMode(bgColorMode);
379 if (bgColorMode != BackgroundColorMode.ALPHABREND
380 && appConfig.isEnableCheckInfoTooltip() ) {
382 checkInfoLayerPanel.setMessage(null);
383 checkInfoLayerPanel.setVisible(true);
387 checkInfoLayerPanel.setVisible(false);
392 previewImgScrollPane.addMouseMotionListener(new MouseMotionAdapter() {
394 public void mouseMoved(MouseEvent e) {
395 Rectangle rct = previewImgScrollPane.getBounds();
397 UIHelper uiUtl = UIHelper.getInstance();
398 if (y > rct.height - (int) (appConfig.getZoomPanelActivationArea() * uiUtl.getScaleY())) {
399 previewControlPanel.setVisible(true);
401 if ( !previewControlPanel.isPinned()) {
402 previewControlPanel.setVisible(false);
409 for (final MouseWheelListener listener : previewImgScrollPane.getMouseWheelListeners()) {
410 previewImgScrollPane.removeMouseWheelListener(listener);
413 previewImgScrollPane.addMouseWheelListener(new MouseWheelListener() {
414 public void mouseWheelMoved(MouseWheelEvent e) {
415 if ((Main.isMacOSX() && e.isAltDown()) ||
416 ( !Main.isMacOSX() && e.isControlDown())) {
417 // Mac OS XならOptionキー、それ以外はコントロールキーとともにホイールスクロールの場合
420 // ズーム以外のホイール操作はスクロールとする.
424 updateCheckInfoMessage(e.getPoint());
428 previewImgScrollPane.addMouseListener(new MouseAdapter() {
430 public void mousePressed(MouseEvent e) {
431 if (e.getClickCount() == 2) {
433 // (正確に2回目。3回目以降はダブルクリック + シングルクリック)
434 toggleZoom(e.getPoint());
436 scrollSupport.drag(true, e.getPoint());
440 public void mouseReleased(MouseEvent e) {
441 scrollSupport.drag(false, e.getPoint());
445 previewImgScrollPane.addMouseMotionListener(new MouseMotionListener() {
447 public void mouseMoved(MouseEvent e) {
448 updateCheckInfoMessage(e.getPoint());
451 public void mouseDragged(MouseEvent e) {
452 scrollSupport.dragging(e.getPoint());
455 updateCheckInfoMessage(e.getPoint());
463 protected void toggleZoom(Point mousePos) {
464 if (previewImgPanel.isDefaultZoom()) {
466 zoomWithCenterPosition(latestToggleZoom, mousePos);
469 // 等倍でなければ現在の倍率を記憶して等倍にする.
470 double currentZoomFactor = previewImgPanel.getZoomFactor();
471 latestToggleZoom = currentZoomFactor;
472 zoomWithCenterPosition(1., mousePos);
477 * マウス位置に対して画像情報のツールチップを表示する
479 * @param mousePosition
482 protected void updateCheckInfoMessage(Point mousePosition) {
483 if ( !checkInfoLayerPanel.isVisible()) {
488 if (mousePosition != null) {
489 Point panelPt = SwingUtilities.convertPoint(previewImgScrollPane,
490 mousePosition, previewImgPanel);
491 imgPos = previewImgPanel.getImagePosition(panelPt);
493 if (imgPos != null) {
494 // 画像位置があれば、その位置の情報を取得する.
495 int argb = previewImgPanel.getImageARGB(imgPos);
496 int a = (argb >> 24) & 0xff;
497 int r = (argb >> 16) & 0xff;
498 int g = (argb >> 8) & 0xff;
500 int y = (int) (0.298912f * r + 0.586611f * g + 0.114478f * b);
501 String text = String.format(
502 "(%3d,%3d)¥nA:%3d, Y:%3d¥nR:%3d, G:%3d, B:%3d", imgPos.x,
503 imgPos.y, a, y, r, g, b);
504 checkInfoLayerPanel.setMessage(text);
505 checkInfoLayerPanel.setPotision(mousePosition);
508 // 画像位置がなければツールチップは空にする.
509 checkInfoLayerPanel.setMessage(null);
514 * マウス座標単位で指定したオフセット分スクロールする.
521 protected void scroll(int diff_x, int diff_y) {
522 scrollSupport.scroll(diff_x, diff_y);
526 * マウスホイールによる水平・垂直スクロール.<br>
527 * シフトキーで水平、それ以外は垂直とする.<br>
532 protected void scrollByWheel(final MouseWheelEvent e) {
533 scrollSupport.scrollByWheel(e);
541 * ホイールの量は関係なく、方向だけで判定する.<br>
542 * プラットフォームごとに修飾キーの判定が異なるので、 呼び出しもとであらかじめ切り分けて呼び出すこと.<br>
547 protected void zoomByWheel(final MouseWheelEvent e) {
548 int wheelRotation = e.getWheelRotation();
549 double currentZoom = previewImgPanel.getZoomFactor();
551 if (wheelRotation < 0) {
553 zoomFactor = currentZoom * 1.1;
555 } else if (wheelRotation > 0){
557 zoomFactor = currentZoom * 0.9;
564 zoomWithCenterPosition(zoomFactor, e.getPoint());
571 * ズームスライダまたはコンボのいずれかの値を更新すると、他方からも更新通知があがるため 二重処理を防ぐためのセマフォ.<br>
573 private Semaphore zoomLock = new Semaphore(1);
576 * プレビューに表示する画像の倍率を更新する.<br>
577 * 指定した座標が拡大縮小の中心点になるようにスクロールを試みる.<br>
578 * 座標がnullの場合は現在表示されている中央を中心とするようにスクロールを試みる.<br>
579 * (スクロールバーが表示されていない、もしくは十分にスクロールできない場合は必ずしも中心とはならない.)<br>
580 * コントロールパネルの表示値も更新する.<br>
581 * コントロールパネルからの更新通知をうけて再入しないように、 同時に一つしか実行されないようにしている.<br>
584 * 倍率、範囲外のものは範囲内に補正される.
586 * スクロールペイン上のマウス座標、もしくはnull(nullの場合は表示中央)
588 protected void zoomWithCenterPosition(double zoomFactor, Point mousePos) {
589 if ( !zoomLock.tryAcquire()) {
594 if (zoomFactor < 0.2) {
596 } else if (zoomFactor > 8.) {
600 JViewport vp = previewImgScrollPane.getViewport();
603 if (mousePos != null) {
604 // スクロールペインのマウス座標を表示パネルの位置に換算する.
605 viewCenter = SwingUtilities.convertPoint(this, mousePos, previewImgPanel);
608 // 表示パネル上の現在表示しているビューポートの中央の座標を求める
609 Rectangle viewRect = vp.getViewRect();
610 viewCenter = new Point(
611 (viewRect.x + viewRect.width / 2),
612 (viewRect.y + viewRect.height / 2)
616 // 現在のビューサイズ(余白があれば余白も含む)
617 Dimension viewSize = previewImgPanel.getScaledSize(true);
620 previewControlPanel.setZoomFactor(zoomFactor);
621 previewImgPanel.setZoomFactor(zoomFactor);
623 // 新しいのビューサイズ(余白があれば余白も含む)
624 Dimension viewSizeAfter = previewImgPanel.getScaledSize(true);
625 Dimension visibleSize = vp.getExtentSize();
627 if (viewSize != null && viewSizeAfter != null &&
628 viewSizeAfter.width > 0 && viewSizeAfter.height > 0 &&
629 viewSizeAfter.width > visibleSize.width &&
630 viewSizeAfter.height > visibleSize.height) {
631 // 新しいビューの大きさよりも表示可能領域が小さい場合のみ
632 vp.setViewSize(viewSizeAfter);
634 // スクロールペインに表示されている画面サイズを求める.
635 // スクロールバーがある方向は、コンテンツの最大と等しいが
636 // スクロールバーがない場合は画面サイズのほうが大きいため、
637 // 倍率変更による縦横の移動比は、それぞれ異なる.
638 int visible_width = max(visibleSize.width, viewSize.width);
639 int visible_height = max(visibleSize.height, viewSize.height);
640 int visible_width_after = max(visibleSize.width, viewSizeAfter.width);
641 int visible_height_after = max(visibleSize.height, viewSizeAfter.height);
645 // また、画像は縦横同率であるが表示ウィンドウはスクロールバー有無により同率とは限らない.
646 double zoomDiffX = (double) visible_width_after / (double) visible_width;
647 double zoomDiffY = (double) visible_height_after / (double) visible_height;
650 Point viewCenterAfter = new Point();
651 viewCenterAfter.x = (int) round(viewCenter.x * zoomDiffX);
652 viewCenterAfter.y = (int) round(viewCenter.y * zoomDiffY);
655 int diff_x = viewCenterAfter.x - viewCenter.x;
656 int diff_y = viewCenterAfter.y - viewCenter.y;
659 scroll(diff_x, diff_y);
662 // スクロールの単位を画像1ドットあたりの表示サイズに変更する.
664 JScrollBar vsb = previewImgScrollPane.getVerticalScrollBar();
665 JScrollBar hsb = previewImgScrollPane.getHorizontalScrollBar();
666 vsb.setUnitIncrement(max(1, (int) ceil(zoomFactor)));
667 hsb.setUnitIncrement(max(1, (int) ceil(zoomFactor)));
678 public Point getViewPosition() {
679 JViewport vp = previewImgScrollPane.getViewport();
680 return vp.getViewPosition();
684 * 指定した座標が中央となるようにスクロールする。
685 * まだ画像が表示されていない場合は次に画像を設定したときに行う。
688 public void setViewPosition(Point viewPt) {
689 JViewport vp = previewImgScrollPane.getViewport();
690 if (previewImgPanel.getPreviewImage() != null) {
691 if (viewPt != null) {
692 vp.setViewPosition(viewPt);
694 requestViewPt = null;
696 requestViewPt = viewPt;
700 private Point requestViewPt;
703 * プレビューに表示するタイトル.<br>
708 public void setTitle(String title) {
712 if (!title.equals(this.title)) {
714 lblTitle.setText(title + (indicatorShown ? indicatorText : ""));
715 lblTitle.setToolTipText(title);
719 public String getTitle() {
724 * ロードに時間がかかっているか判定し、 インジケータを表示するためのタイマーイベントハンドラ.<br>
726 protected void onTimer() {
729 synchronized (lock) {
730 waiting = isWaiting();
731 firstRequest = firstWaitingTimestamp;
733 boolean indicatorShown = (waiting && ((System.currentTimeMillis() - firstRequest) > indicatorDelay));
734 if (this.indicatorShown != indicatorShown) {
735 this.indicatorShown = indicatorShown;
736 lblTitle.setText(title + (indicatorShown ? indicatorText : ""));
741 * チケットの状態が、ロード完了待ち状態であるか?<br>
742 * ロード中のチケットが、ロード完了のチケットより新しければロード中と見なす.<br>
744 * @return 完了待ちであればtrue、そうでなければfalse
746 protected boolean isWaiting() {
747 synchronized (lock) {
748 return loadingTicket > loadedTicket;
753 * ロード要求が出されるたびに、そのロード要求チケットを登録する.<br>
754 * チケットは要求されるたびに増加するシーケンスとする.<br>
759 public void setLoadingRequest(long ticket) {
760 synchronized (lock) {
761 if ( !isWaiting() && this.loadedTicket < ticket) {
762 // 現在認識しているチケットの状態がロード完了であり、
763 // それよりも新しいチケットが要求されたならば、
764 // 今回のチケットから待ち時間の計測を開始する.
765 this.firstWaitingTimestamp = System.currentTimeMillis();
767 this.loadingTicket = ticket;
772 * ロード完了するたびに呼び出される.<br>
777 public void setLoadingComplete(long ticket) {
778 synchronized (lock) {
779 this.loadedTicket = ticket;
789 public void setPreviewImage(BufferedImage previewImg) {
790 previewImgPanel.setPreviewImage(previewImg);
791 if (requestViewPt != null) {
792 // 画像設定前にスクロール位置の要求があれば、再適用を試みる
793 setViewPosition(requestViewPt);
798 * 表示されている画像を取得する.<br>
799 * 表示画像が設定されていなければnull.<br>
801 * @return 表示画像、もしくはnull
803 public BufferedImage getPreviewImage() {
804 return previewImgPanel.getPreviewImage();
808 * 表示している画面イメージそのままを取得する.
812 public BufferedImage getScreenImage() {
813 JViewport vp = previewImgScrollPane.getViewport();
814 Dimension dim = vp.getExtentSize();
815 BufferedImage img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
816 Graphics2D g = img.createGraphics();
829 * @param wallpaperImg
832 public void setWallpaper(Wallpaper wallpaper) {
833 previewImgPanel.setWallpaper(wallpaper);
838 * 壁紙が未設定の場合は空の壁紙インスタンスが返される.<br>
842 public Wallpaper getWallpaper() {
843 return previewImgPanel.getWallpaper();
851 public double getZoomFactor() {
852 return previewControlPanel.getZoomFactor();
861 public void setZoomFactor(double zoomFactor) {
862 previewControlPanel.setZoomFactor(zoomFactor);
871 public void setVisibleZoomBox(boolean visible) {
872 previewControlPanel.setPinned(visible);
878 * @return ピン留めされていればtrue
880 public boolean isVisibleZoomBox() {
881 return previewControlPanel.isPinned();
884 public void addPreviewPanelListener(PreviewPanelListener listener) {
885 if (listener == null) {
886 throw new IllegalArgumentException();
888 listeners.add(listener);
891 public void removePreviewPanelListener(PreviewPanelListener listener) {
892 listeners.remove(listener);
895 protected void savePicture(PreviewPanelEvent e) {
896 for (PreviewPanelListener listener : listeners) {
897 listener.savePicture(e);
901 protected void flipHolizontal(PreviewPanelEvent e) {
902 for (PreviewPanelListener listener : listeners) {
903 listener.flipHorizontal(e);
907 protected void copyPicture(PreviewPanelEvent e) {
908 for (PreviewPanelListener listener : listeners) {
909 listener.copyPicture(e);
913 protected void changeBackgroundColor(PreviewPanelEvent e) {
914 for (PreviewPanelListener listener : listeners) {
915 listener.changeBackgroundColor(e);
919 protected void showInformation(PreviewPanelEvent e) {
920 for (PreviewPanelListener listener : listeners) {
921 listener.showInformation(e);
925 protected void addFavorite(PreviewPanelEvent e) {
926 for (PreviewPanelListener listener : listeners) {
927 listener.addFavorite(e);
933 * チェック情報の表示用レイヤーパネル.<br>
937 class CheckInfoLayerPanel extends JPanel {
938 private static final long serialVersionUID = 1L;
943 private static final Logger logger = Logger.getLogger(CheckInfoLayerPanel.class.getName());
948 private Insets padding = new Insets(3, 3, 3, 3);
953 private Point pos = new Point();
960 private String message = "";
967 private String[] messageLines;
972 private int fontHeight;
976 * 次回描画前に消去する必要のある領域.<br>
977 * まだ一度も描画してなければnull.<br>
979 private Rectangle eraseRect;
985 private Rectangle requestRect;
988 * 画面に関連づけられていない状態でのテキスト表示サイズは 計算できないため、画面追加時に再計算させるための 予約フラグ.<br>
990 private boolean requestRecalcOnAdd;
993 * フォントのためのデスクトップヒント.(あれば)
995 @SuppressWarnings("rawtypes")
996 private Map desktopHintsForFont;
999 * 透明コンポーネントとして構築する.<br>
1001 @SuppressWarnings("rawtypes")
1002 public CheckInfoLayerPanel() {
1005 Toolkit tk = Toolkit.getDefaultToolkit();
1006 desktopHintsForFont = (Map) tk.getDesktopProperty("awt.font.desktophints");
1007 logger.log(Level.CONFIG, "awt.font.desktophints=" + desktopHintsForFont);
1011 * 指定エリアに情報を描画する.<br>
1014 protected void paintComponent(Graphics g0) {
1015 Graphics2D g = (Graphics2D) g0;
1016 super.paintComponent(g);
1019 Rectangle clip = g.getClipBounds();
1020 // System.out.println("clip:" + clip + " /eraseRect:" + eraseRect + " /drawRect:" + requestRect);
1022 // 削除すべき領域が描画範囲に含まれているか?
1023 // (含まれていれば、その領域は消去済みである.)
1024 if (clip == null || (eraseRect != null && clip.contains(eraseRect))) {
1029 if (requestRect == null || requestRect.isEmpty()
1030 || !(clip != null && clip.intersects(requestRect))) {
1031 // 表示すべき領域が存在しないか、描画要求範囲にない.
1034 if (messageLines == null || messageLines.length == 0) {
1035 // 表示するものがなければ何もしない.
1040 if (desktopHintsForFont != null) {
1041 g.addRenderingHints(desktopHintsForFont);
1045 g.setColor(new Color(255, 255, 255, 192));
1046 g.fillRect(requestRect.x, requestRect.y, requestRect.width, requestRect.height);
1047 g.setColor(Color.GRAY);
1048 g.drawRect(requestRect.x, requestRect.y, requestRect.width - 1, requestRect.height - 1);
1051 g.setColor(Color.BLACK);
1052 int oy = fontHeight;
1053 for (String messageLine : messageLines) {
1054 g.drawString(messageLine, requestRect.x + padding.left, requestRect.y + padding.top - 1 + oy);
1058 // 描画された領域を次回消去領域として記憶する.
1059 if (eraseRect == null || eraseRect.isEmpty()) {
1060 // 消去済みであれば、今回分のみを次回消去領域とする.
1061 eraseRect = (Rectangle) requestRect.clone();
1064 // 消去済みエリアが未消去で残っている場合は
1066 eraseRect.add(requestRect);
1071 * 画面にアタッチされた場合、描画領域の再計算が 必要であれば計算する.<br>
1074 public void addNotify() {
1076 if (requestRecalcOnAdd) {
1077 requestRecalcOnAdd = false;
1083 * 要求されたプロパティから、フォント高さによる表示領域を計算し、 その領域の再描画を要求する.(描画する内容がなれば、描画要求しない.)<br>
1084 * 前回表示領域があれば、消去するために、そのエリアも再描画を要求する.<br>
1085 * それ以外のエリアは描画要求しない.(描画の最適化による負荷軽減策)<br>
1086 * フォントサイズを求めるためにグラフィクスへのアクセスが必要となるが、 まだ取得できない場合は{@link #addNotify()}の呼び出し時に
1087 * 再計算するようにフラグを立てておく.<br>
1089 protected void calcRepaint() {
1090 Graphics2D g = (Graphics2D) getGraphics();
1092 requestRecalcOnAdd = true;
1096 // 前回描画領域のクリアのために呼び出す.
1097 if (eraseRect != null && !eraseRect.isEmpty()) {
1102 if (message.length() == 0) {
1107 FontMetrics fm = g.getFontMetrics();
1108 String[] messageLines = message.split("¥n");
1110 Rectangle2D rct = null;
1111 for (String messageLine : messageLines) {
1112 Rectangle2D tmp = fm.getStringBounds(messageLine, g);
1121 int fw = (int) rct.getWidth();
1122 int fh = (int) rct.getHeight();
1124 int w = fw + padding.left + padding.right;
1125 int h = fh * messageLines.length + padding.top + padding.bottom;
1132 int client_w = getWidth();
1133 int client_h = getHeight();
1135 if (x + w > client_w) {
1143 if (y + h > client_h) {
1144 y -= (y + h - client_h);
1148 this.requestRect = new Rectangle(x, y, w, h);
1149 this.messageLines = messageLines;
1150 this.fontHeight = fh;
1153 Rectangle paintRect = (Rectangle) requestRect.clone();
1161 public void setPotision(Point requestPt) {
1162 if (requestPt == null) {
1163 throw new IllegalArgumentException();
1165 if ( !requestPt.equals(pos)) {
1167 pos = (Point) requestPt.clone();
1169 firePropertyChange("position", oldpos, pos);
1173 public Point getPosition() {
1174 return (Point) pos.clone();
1177 public void setMessage(String message) {
1178 if (message == null) {
1181 message = message.replace("¥r¥n", "¥n");
1182 if ( !message.equals(this.message)) {
1183 String oldmes = this.message;
1184 this.message = message;
1186 firePropertyChange("message", oldmes, message);
1190 public String getMessage() {
1200 class PreviewImagePanel extends JPanel {
1201 private static final long serialVersionUID = 1L;
1206 private BackgroundColorMode bgColorMode;
1211 private Wallpaper wallpaper;
1216 private PropertyChangeListener wallpaperListener;
1221 private BufferedImage previewImg;
1224 * 表示用画像(背景モードによる調整あり).<br>
1225 * 事前に拡大縮小を適用済みの場合は、{@link #scaledZoomFactor}に 適用している倍率が設定される.<br>
1226 * 表示用に改めてイメージを生成する必要がない場合は、 透過オリジナルと同じインスタンスとなりえる.<br>
1228 private BufferedImage previewImgForDraw;
1231 * 表示用画像がスケール済みである場合、そのスケールが設定される.<br>
1232 * スケール済みでない場合はnullとなる.<br>
1234 private Double scaledZoomFactor;
1240 private double zoomFactor = 1.;
1245 private static final double TOLERANT = 0.001;
1251 public PreviewImagePanel() {
1255 bgColorMode = BackgroundColorMode.ALPHABREND;
1258 wallpaperListener = new PropertyChangeListener() {
1259 public void propertyChange(PropertyChangeEvent evt) {
1260 onChangeWallpaper();
1265 wallpaper = new Wallpaper();
1266 wallpaper.addPropertyChangeListener(wallpaperListener);
1273 protected void paintComponent(Graphics g0) {
1274 Graphics2D g = (Graphics2D) g0;
1275 super.paintComponent(g);
1277 if (previewImgForDraw == null) {
1281 // 倍率を適用した画像を画面の中央に配置できるように計算する.
1282 // (画像が倍率適用済みであれば1倍とする)
1283 Rectangle imgRct = adjustImageRectangle();
1285 // 表示用画像がスケール済みでない場合はレンダリングオプションを適用する.
1286 if (scaledZoomFactor == null) {
1287 Object renderingOption = getRenderingOption();
1288 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingOption);
1292 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1293 // 表示の最大範囲 (可視領域外も含む)
1295 int h = getHeight();
1296 wallpaper.drawWallpaper(g, w, h);
1300 g.drawImage(previewImgForDraw,
1302 imgRct.x + imgRct.width, imgRct.y + imgRct.height,
1304 previewImgForDraw.getWidth(), previewImgForDraw.getHeight(),
1307 // 通常モード以外のグリッド描画に該当するモードはグリッドを前景に描く
1308 AppConfig appConfig = AppConfig.getInstance();
1309 int drawGridMask = appConfig.getDrawGridMask();
1310 if ((drawGridMask & bgColorMode.mask()) != 0) {
1311 Color oldc = g.getColor();
1313 g.setColor(appConfig.getPreviewGridColor());
1314 drawGrid(g, imgRct.x, imgRct.y, appConfig.getPreviewGridSize());
1325 * 開始位置の-1単位位置から画像サイズの+1単位までがグリッド範囲となる。
1335 protected void drawGrid(Graphics2D g, int offset_x, int offset_y, int unit) {
1336 Rectangle clip = g.getClipBounds();
1338 int src_w = previewImg.getWidth();
1339 int src_h = previewImg.getHeight();
1340 int my = src_h / unit;
1341 int mx = src_w / unit;
1343 int st_x = offset_x + (int)(-1 * unit * zoomFactor);
1344 int en_x = offset_x + (int)((mx + 1) * unit * zoomFactor);
1345 int w = en_x - st_x + 1;
1347 for (int y = -1; y <= my + 1; y++) {
1349 Rectangle rct = new Rectangle(
1350 st_x, offset_y + (int)(y1 * zoomFactor),
1352 if (clip == null || clip.intersects(rct)) {
1353 g.drawLine(rct.x, rct.y, rct.x + rct.width, rct.y);
1357 int st_y = offset_y + (int)(-1 * unit * zoomFactor);
1358 int en_y = offset_y + (int)((my + 1) * unit * zoomFactor);
1359 int h = en_y - st_y + 1;
1361 for (int x = -1; x <= mx + 1; x++) {
1363 Rectangle rct = new Rectangle(
1364 offset_x + (int)(x1 * zoomFactor), st_y,
1366 g.drawLine(rct.x, rct.y, rct.x, rct.y + rct.height);
1371 * 現在の倍率に応じたレンダリングオプションを取得する.<br>
1373 * @return レンダリングオプション
1375 protected Object getRenderingOption() {
1376 AppConfig appConfig = AppConfig.getInstance();
1377 double rendringOptimizeThreshold;
1378 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1379 rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForNormal();
1381 rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForCheck();
1383 Object renderingHint;
1384 if (zoomFactor < rendringOptimizeThreshold) {
1385 // 補正を適用する最大倍率以内である場合
1386 if (zoomFactor <= 1. || !appConfig.isEnableInterpolationBicubic()) {
1387 // 縮小する場合、もしくはバイキュービックをサポートしない場合
1388 renderingHint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
1390 // 拡大する場合でバイキュービックをサポートしている場合
1391 renderingHint = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
1395 // 補正を適用する最大倍率を超えている場合は補正なし.
1396 renderingHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1398 return renderingHint;
1402 * 倍率と、画面のサイズと、表示するオリジナルの画像サイズをもとに、 倍率を適用した画像サイズを、画面に収まる位置に補正して返す.<br>
1403 * 返される矩形の幅と高さ(width, height)は拡大後の画像サイズに等しい.<br>
1404 * 拡大後の画像が画面よりも小さければセンタリングするように矩形の開始位置(x, y)がオフセットされる.<br>
1405 * そうでなければ矩形の開始位置(x, y)は0となる.<br>
1406 * 画像が設定されていなければ幅と高さがゼロの矩形が返される.<br>
1408 * @return 画像を表示するオフセットと大きさ、もしくは空の矩形
1410 public Rectangle adjustImageRectangle() {
1411 if (previewImg == null) {
1412 return new Rectangle(0, 0, 0, 0); // 幅・高さともにゼロ
1414 int client_w = getWidth();
1415 int client_h = getHeight();
1417 int src_w = previewImg.getWidth();
1418 int src_h = previewImg.getHeight();
1420 int w = (int) round(src_w * zoomFactor);
1421 int h = (int) round(src_h * zoomFactor);
1425 offset_x = (client_w - w) / 2;
1429 offset_y = (client_h - h) / 2;
1432 return new Rectangle(offset_x, offset_y, w, h);
1436 * パネルのマウス座標から、実寸の画像のピクセル位置を返す.<br>
1437 * 画像が表示されていないか、範囲外であればnullを返す.<br>
1441 * @return 画像の位置、もしくはnull
1443 public Point getImagePosition(Point pt) {
1444 if (pt == null || previewImg == null) {
1445 // プレビュー画像が設定されていなければnull
1449 Rectangle imgRct = adjustImageRectangle();
1451 if ( !imgRct.contains(pt.x, pt.y)) {
1457 Point ret = (Point) pt.clone();
1462 ret.x = (int) floor(ret.x / zoomFactor);
1463 ret.y = (int) floor(ret.y / zoomFactor);
1469 * 画像の位置から画面の位置を割り出す.<br>
1475 public Point getMousePosition(Point pt) {
1476 if (pt == null || previewImg == null) {
1477 // プレビュー画像が設定されていなければnull
1481 Rectangle imgRct = adjustImageRectangle();
1484 Point ret = (Point) pt.clone();
1485 ret.x = (int) ceil(ret.x * zoomFactor);
1486 ret.y = (int) ceil(ret.y * zoomFactor);
1496 * 指定した位置のRGB値を取得する.<br>
1497 * 範囲外の場合は0が返される.<br>
1501 * @return イメージのARGB値 (ビット順序は、A:24, R:16, G:8, B:0)
1503 public int getImageARGB(Point pt) {
1505 throw new IllegalArgumentException();
1508 return previewImg.getRGB(pt.x, pt.y);
1510 } catch (RuntimeException ex) {
1516 * 倍率を適用した画像パネルのサイズを計算し適用する.<br>
1517 * モードにより余白が加えられる.<br>
1519 protected void recalcScaledSize() {
1520 Dimension scaledSize = getScaledSize(true);
1521 if (scaledSize != null) {
1522 setPreferredSize(scaledSize);
1528 * 元画像の倍率適用後のサイズを返す.<br>
1529 * 元画像が設定されていなければnull.<br>
1530 * needOffsetがfalseであれば表示モードに関わらず、画像の拡大・縮小後の純粋なサイズ、
1531 * trueであれば余白が必要な表示モードの場合の余白が付与された場合のサイズが返される.<br>
1534 * 余白を加味したサイズが必要な場合はtrue
1535 * @return 倍率適用後のサイズ、もしくはnull
1537 protected Dimension getScaledSize(boolean needOffset) {
1538 if (previewImg == null) {
1541 int src_w = previewImg.getWidth();
1542 int src_h = previewImg.getHeight();
1544 int w = (int) round(src_w * zoomFactor);
1545 int h = (int) round(src_h * zoomFactor);
1547 Dimension scaledSize = new Dimension(w, h);
1549 if (bgColorMode != BackgroundColorMode.ALPHABREND) {
1550 // 通常モード以外は画像よりも少し大きめにすることで
1552 AppConfig appConfig = AppConfig.getInstance();
1553 int unfilledSpace = appConfig.getPreviewUnfilledSpaceForCheckMode();
1554 scaledSize.width += max(0, unfilledSpace * 2);
1555 scaledSize.height += max(0, unfilledSpace * 2);
1566 public void setPreviewImage(BufferedImage previewImg) {
1567 BufferedImage oldimg = this.previewImg;
1568 this.previewImg = previewImg;
1571 makeDrawImage(true);
1574 firePropertyChange("previewImage", oldimg, previewImg);
1577 public BufferedImage getPreviewImage() {
1586 public void setWallpaper(Wallpaper wallpaper) {
1587 if (wallpaper == null) {
1588 throw new IllegalArgumentException();
1590 if ( !this.wallpaper.equals(wallpaper)) {
1591 Wallpaper wallpaperOld = this.wallpaper;
1592 if (wallpaperOld != null) {
1593 wallpaperOld.removePropertyChangeListener(wallpaperListener);
1595 this.wallpaper = wallpaper;
1596 if (this.wallpaper != null) {
1597 this.wallpaper.addPropertyChangeListener(wallpaperListener);
1599 firePropertyChange("wallpaper", wallpaperOld, this.wallpaper);
1600 onChangeWallpaper();
1604 public Wallpaper getWallpaper() {
1608 protected void onChangeWallpaper() {
1613 * 背景モード調整済みの表示用画像を作成する.
1615 * @param changeImage
1618 protected void makeDrawImage(boolean changeImage) {
1619 if (previewImg == null) {
1621 this.previewImgForDraw = null;
1622 scaledZoomFactor = null;
1627 if (changeImage || scaledZoomFactor != null) {
1628 // 画像が変更されているか、スケール済みであれば
1630 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1631 // アルファブレンド通常モードは背景用にあえて作成する必要はない.
1635 // アルファブレンド通常モード以外は背景に作成する
1636 Color bgColor = wallpaper.getBackgroundColor();
1637 BackgroundColorFilter bgColorFilter = new BackgroundColorFilter(bgColorMode, bgColor);
1638 img = bgColorFilter.filter(previewImg, null);
1642 // 画像が変更されておらず、スケール済みでもなければ
1643 // すでに作成済みの画像が使用できる.
1644 img = previewImgForDraw;
1648 Object renderingOption = getRenderingOption();
1650 // バイキュービックでなければ、事前の拡大縮小は行わずに、表示時に行う.
1651 if ( !renderingOption.equals(RenderingHints.VALUE_INTERPOLATION_BICUBIC)) {
1652 previewImgForDraw = img;
1653 scaledZoomFactor = null;
1657 // バイキュービックの場合、倍率を適用したサイズに予め加工しておく.
1658 Dimension scaledSize = getScaledSize(false);
1659 BufferedImage offscreen = new BufferedImage(
1660 scaledSize.width, scaledSize.height, BufferedImage.TYPE_INT_ARGB);
1661 Graphics2D g = offscreen.createGraphics();
1663 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1664 RenderingHints.VALUE_INTERPOLATION_BICUBIC);
1667 0, 0, scaledSize.width, scaledSize.height,
1668 0, 0, img.getWidth(), img.getHeight(),
1674 previewImgForDraw = offscreen;
1675 scaledZoomFactor = Double.valueOf(zoomFactor);
1678 public void setBackgroundColorMode(BackgroundColorMode bgColorMode) {
1679 if (bgColorMode == null) {
1680 throw new IllegalArgumentException();
1682 if (this.bgColorMode != bgColorMode) {
1683 BackgroundColorMode oldcm = bgColorMode;
1684 this.bgColorMode = bgColorMode;
1686 makeDrawImage(true);
1690 firePropertyChange("backgroundColorMode", oldcm, bgColorMode);
1694 public BackgroundColorMode setBackgroundColorMode() {
1698 public void setZoomFactor(double zoomFactor) {
1699 if (abs(zoomFactor - this.zoomFactor) > TOLERANT) {
1700 // 0.001未満の差異は誤差とみなして反映しない.
1701 double oldzoom = this.zoomFactor;
1702 this.zoomFactor = zoomFactor;
1705 makeDrawImage(false);
1708 firePropertyChange("zoomFactor", oldzoom, zoomFactor);
1712 public double getZoomFactor() {
1719 * @return 100%であればtrue
1721 public boolean isDefaultZoom() {
1722 return zoomFactor - 1 < TOLERANT;
1727 * 倍率・背景モードを操作するための下部パネル用
1731 class PreviewControlPanel extends JPanel {
1732 private static final long serialVersionUID = 1L;
1734 private static final Logger logger = Logger.getLogger(PreviewControlPanel.class.getName());
1736 protected static final String STRINGS_RESOURCE = "languages/previewpanel";
1741 private JCheckBox chkPinning;
1746 private JCheckBox chkNoAlpha;
1751 private JCheckBox chkGrayscale;
1756 private JSlider zoomSlider;
1761 private JComboBox zoomCombo;
1767 private static final int MIN_INDEX = -170;
1772 private static final int MAX_INDEX = 219;
1777 private double minimumZoomFactor;
1782 private double maximumZoomFactor;
1788 private int currentZoomFactorInt;
1793 private BackgroundColorMode backgroundColorMode;
1797 * 任意の底Aをもつ対数 logA(N)を計算して返す.
1805 private static double logN(double a, double x) {
1806 return log(x) / log(a);
1810 * 倍率(等倍を1とする)に対するスライダのインデックス値を返す.<br>
1811 * スライダは10ステップごとに前のステップの10%づつ増減する.(複利式)<br>
1817 private static int zoomFactorToIndex(double zoomFactor) {
1818 return (int) round(logN((1. + 0.1), zoomFactor) * 10);
1822 * スライダのインデックス値から倍率(等倍を1とする)を返す.<br>
1823 * 10ステップごとに10%づつ増減する.(複利式)<br>
1827 * @return 倍率(1を等倍とする)
1829 private static double zoomFactorFromIndex(int index) {
1830 return pow(1. + 0.1, index / 10.);
1837 public PreviewControlPanel() {
1838 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1839 .getLocalizedProperties(STRINGS_RESOURCE);
1841 final UIHelper uiHelper = UIHelper.getInstance();
1844 Icon pinIcon = uiHelper.createTwoStateIcon(
1845 "icons/pin-icon1.png", "icons/pin-icon2.png");
1848 chkPinning = new JCheckBox(pinIcon);
1849 chkPinning.setToolTipText(strings.getProperty("tooltip.zoompanel.pinning"));
1851 // 円ボタン型チェックボックス用アイコンの実装
1852 final Icon stateIcon = new Icon() {
1853 public int getIconHeight() {
1854 return (int)(12 * uiHelper.getScaleX());
1856 public int getIconWidth() {
1857 return (int)(6 * uiHelper.getScaleY());
1859 public void paintIcon(Component c, Graphics g, int x, int y) {
1861 if (c instanceof AbstractButton) {
1862 AbstractButton btn = (AbstractButton) c;
1863 sw = btn.isSelected();
1866 int w = getIconWidth();
1867 int h = getIconHeight();
1881 AppConfig appConfig = AppConfig.getInstance();
1882 Color fillColor = appConfig.getSelectedItemBgColor();
1883 g.setColor(fillColor);
1884 g.fillOval(x + ox, y + oy, s, w);
1886 g.setColor(Color.GRAY);
1887 g.drawOval(x + ox, y + oy, s, s);
1891 // アルファ確認とグレースケール確認用のチェックボックス
1892 chkNoAlpha = new JCheckBox(stateIcon);
1893 chkGrayscale = new JCheckBox(stateIcon);
1895 chkNoAlpha.setToolTipText(strings.getProperty("tooltip.zoompanel.checkalpha"));
1896 chkGrayscale.setToolTipText(strings.getProperty("tooltip.zoompanel.checkgrayscale"));
1898 backgroundColorMode = BackgroundColorMode.ALPHABREND;
1900 final ChangeListener chkAlphaGrayChangeListener = new ChangeListener() {
1901 public void stateChanged(ChangeEvent e) {
1902 onChangeCheckAlphaGray();
1905 chkNoAlpha.addChangeListener(chkAlphaGrayChangeListener);
1906 chkGrayscale.addChangeListener(chkAlphaGrayChangeListener);
1909 zoomSlider = new JSlider(JSlider.HORIZONTAL, MIN_INDEX, MAX_INDEX, 0);
1910 zoomSlider.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_slider"));
1913 zoomCombo = new JComboBox();
1914 zoomCombo.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_combo"));
1916 // 倍率の既定リストの設定と、最大・最小値の算定
1917 minimumZoomFactor = zoomFactorFromIndex(zoomSlider.getMinimum());
1918 maximumZoomFactor = zoomFactorFromIndex(zoomSlider.getMaximum());
1920 int minZoomRange = (int) round(minimumZoomFactor * 100.);
1921 int maxZoomRange = (int) round(maximumZoomFactor * 100.);
1923 List<Integer> predefinedZoomRanges = getPredefinedZoomRanges();
1924 for (int zoomRange : predefinedZoomRanges) {
1925 if (zoomRange < minZoomRange) {
1926 minZoomRange = zoomRange;
1928 if (zoomRange > maxZoomRange) {
1929 maxZoomRange = zoomRange;
1931 zoomCombo.addItem(Integer.toString(zoomRange));
1933 final int[] zoomRanges = {minZoomRange, maxZoomRange};
1935 currentZoomFactorInt = 100;
1936 zoomCombo.setSelectedItem(Integer.toString(currentZoomFactorInt));
1937 zoomCombo.setEditable(true);
1938 if ( !Main.isMacOSX()) {
1939 // Windows環境だとデフォルトで9桁分のテキストフィールドが作成され
1940 // それがレイアウトの推奨サイズとして実際に使われてしまうため、
1941 // 明示的に3桁にとどめておくようにオーバーライドする.
1943 zoomCombo.setEditor(new BasicComboBoxEditor() {
1945 editor = new JTextField(3) {
1946 private static final long serialVersionUID = 1L;
1948 public void setBorder(Border border) {
1951 public void setText(String s) {
1952 if (getText().equals(s)) {
1962 // スライダを変更することによりコンボボックスを変更する、
1963 // もしくはコンボボックスを変更することでスライダを変更するが、
1964 // 互いに通知を呼び合うことになるため他方を無視するためのセマフォ
1965 final Semaphore changeLock = new Semaphore(1);
1967 zoomCombo.addActionListener(new ActionListener() {
1968 public void actionPerformed(ActionEvent e) {
1969 boolean adjusted = false;
1970 String value = (String) zoomCombo.getSelectedItem();
1973 zoomFactorInt = Integer.parseInt(value);
1974 if (zoomFactorInt < zoomRanges[0]) {
1975 zoomFactorInt = zoomRanges[0];
1978 } else if (zoomFactorInt > zoomRanges[1]) {
1979 zoomFactorInt = zoomRanges[1];
1983 } catch (RuntimeException ex) {
1984 zoomFactorInt = 100;
1988 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));
1989 Toolkit tk = Toolkit.getDefaultToolkit();
1992 if (changeLock.tryAcquire()) {
1994 zoomSlider.setValue(zoomFactorToIndex(zoomFactorInt / 100.));
1997 changeLock.release();
2000 fireZoomFactorChange(zoomFactorInt);
2004 zoomSlider.addChangeListener(new ChangeListener() {
2005 public void stateChanged(ChangeEvent e) {
2006 int index = zoomSlider.getValue();
2007 double zoomFactor = zoomFactorFromIndex(index);
2008 int zoomFactorInt = (int) round(zoomFactor * 100);
2010 if (changeLock.tryAcquire()) {
2012 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));
2015 changeLock.release();
2017 fireZoomFactorChange(zoomFactorInt);
2024 GridBagLayout gbl = new GridBagLayout();
2027 GridBagConstraints gbc = new GridBagConstraints();
2034 gbc.fill = GridBagConstraints.NONE;
2035 gbc.anchor = GridBagConstraints.CENTER;
2036 gbc.insets = new Insets(0, 0, 0, 5);
2040 add(chkPinning, gbc);
2044 gbc.insets = new Insets(0, 0, 0, 0);
2045 add(chkGrayscale, gbc);
2049 gbc.insets = new Insets(0, 0, 0, 5);
2050 add(chkNoAlpha, gbc);
2054 gbc.fill = GridBagConstraints.HORIZONTAL;
2055 add(zoomSlider, gbc);
2059 gbc.insets = new Insets(3, 0, 3, 0);
2060 gbc.fill = GridBagConstraints.VERTICAL;
2061 add(zoomCombo, gbc);
2063 Integer scrollbarWidth = (Integer) UIManager.get("ScrollBar.width");
2064 logger.log(Level.CONFIG, "ScrollBar.width=" + scrollbarWidth);
2065 if (scrollbarWidth == null) {
2066 scrollbarWidth = Integer.parseInt(
2067 strings.getProperty("uiconstraint.scrollbar.width"));
2072 gbc.anchor = GridBagConstraints.WEST;
2073 gbc.insets = new Insets(0, 0, 0, scrollbarWidth);
2074 add(new JLabel("%"), gbc);
2078 * アプリケーション設定より事前定義済みの倍率候補を取得する
2080 * @return 倍率候補のリスト(順序あり)
2082 protected List<Integer> getPredefinedZoomRanges() {
2083 AppConfig appConfig = AppConfig.getInstance();
2084 String strs = appConfig.getPredefinedZoomRanges();
2085 TreeSet<Integer> ranges = new TreeSet<Integer>();
2086 for (String str : strs.split(",")) {
2088 if (str.length() > 0) {
2090 int zoomFactor = Integer.parseInt(str);
2091 ranges.add(Integer.valueOf(zoomFactor));
2093 } catch (RuntimeException ex) {
2098 ranges.add(Integer.valueOf(100)); // 等倍は常に設定する.
2099 return new ArrayList<Integer>(ranges);
2105 protected void fireZoomFactorChange(int newZoomFactor) {
2106 if (currentZoomFactorInt != newZoomFactor) {
2107 int oldValue = currentZoomFactorInt;
2108 currentZoomFactorInt = newZoomFactor;
2109 firePropertyChange("zoomFactorInt", oldValue, newZoomFactor);
2113 private Semaphore changeChkLock = new Semaphore(1);
2115 protected void onChangeCheckAlphaGray() {
2116 changeChkLock.tryAcquire();
2118 BackgroundColorMode backgroundColorMode = BackgroundColorMode.valueOf(
2119 chkNoAlpha.isSelected(),
2120 chkGrayscale.isSelected()
2122 setBackgroundColorMode(backgroundColorMode);
2125 changeChkLock.release();
2129 public BackgroundColorMode getBackgroundColorMode() {
2130 return this.backgroundColorMode;
2133 public void setBackgroundColorMode(BackgroundColorMode backgroundColorMode) {
2134 if (backgroundColorMode == null) {
2135 throw new IllegalArgumentException();
2138 BackgroundColorMode oldcm = this.backgroundColorMode;
2139 if (oldcm != backgroundColorMode) {
2140 this.backgroundColorMode = backgroundColorMode;
2141 changeChkLock.tryAcquire();
2143 chkNoAlpha.setSelected(backgroundColorMode.isNoAlphaChannel());
2144 chkGrayscale.setSelected(backgroundColorMode.isGrayscale());
2147 changeChkLock.release();
2149 firePropertyChange("backgroundColorMode", oldcm, backgroundColorMode);
2153 public boolean isPinned() {
2154 return chkPinning.isSelected();
2157 public void setPinned(boolean pinned) {
2158 chkPinning.setSelected(pinned);
2159 if (isDisplayable()) {
2164 public double getZoomFactor() {
2165 return (double) currentZoomFactorInt / 100.;
2168 public void setZoomFactor(double zoomFactor) {
2169 if (zoomFactor < minimumZoomFactor) {
2170 zoomFactor = minimumZoomFactor;
2172 if (zoomFactor > maximumZoomFactor) {
2173 zoomFactor = maximumZoomFactor;
2175 int zoomFactorInt = (int) round(zoomFactor * 100.);
2176 if (zoomFactorInt != currentZoomFactorInt) {
2177 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));