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.ScrollPaneDragScrollSupport;
73 import charactermanaj.util.LocalizedResourcePropertyLoader;
74 import charactermanaj.util.UIHelper;
81 public class PreviewPanel extends JPanel {
83 private static final long serialVersionUID = 1L;
85 protected static final String STRINGS_RESOURCE = "languages/previewpanel";
89 * プレビューパネルの上部ツールバーの通知を受けるリスナ
93 public interface PreviewPanelListener {
100 void savePicture(PreviewPanelEvent e);
107 void copyPicture(PreviewPanelEvent e);
114 void changeBackgroundColor(PreviewPanelEvent e);
121 void showInformation(PreviewPanelEvent e);
128 void addFavorite(PreviewPanelEvent e);
135 void flipHorizontal(PreviewPanelEvent e);
141 private final String indicatorText;
146 private final Timer timer;
151 private long indicatorDelay;
154 public void addNotify() {
156 if (!timer.isRunning()) {
162 public void removeNotify() {
163 if (timer.isRunning()) {
166 super.removeNotify();
169 public static class PreviewPanelEvent extends EventObject {
171 private static final long serialVersionUID = 1L;
173 private int modifiers;
175 public PreviewPanelEvent(Object src, ActionEvent e) {
176 this(src, (e == null) ? 0 : e.getModifiers());
179 public PreviewPanelEvent(Object src, int modifiers) {
181 this.modifiers = modifiers;
184 public int getModifiers() {
188 public boolean isShiftKeyPressed() {
189 return (modifiers & ActionEvent.SHIFT_MASK) != 0;
193 private final Object lock = new Object();
195 private long loadingTicket;
197 private long loadedTicket;
199 private long firstWaitingTimestamp;
201 private boolean indicatorShown;
203 private String title;
205 private JLabel lblTitle;
207 private JLayeredPane layeredPane;
209 private CheckInfoLayerPanel checkInfoLayerPanel;
211 private PreviewImagePanel previewImgPanel;
213 private JScrollPane previewImgScrollPane;
215 private ScrollPaneDragScrollSupport scrollSupport;
217 private PreviewControlPanel previewControlPanel;
219 private double latestToggleZoom = 2.;
221 private LinkedList<PreviewPanelListener> listeners = new LinkedList<PreviewPanelListener>();
224 public PreviewPanel() {
225 setLayout(new BorderLayout());
227 final AppConfig appConfig = AppConfig.getInstance();
228 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
229 .getLocalizedProperties(STRINGS_RESOURCE);
231 // 画像をロード中であることを示すインジケータの確認サイクル.
232 timer = new Timer(100, new ActionListener() {
233 public void actionPerformed(ActionEvent e) {
238 indicatorText = strings.getProperty("indicatorText");
239 indicatorDelay = appConfig.getPreviewIndicatorDelay();
241 UIHelper uiUtl = UIHelper.getInstance();
242 JButton saveBtn = uiUtl.createIconButton("icons/save.png");
243 JButton copyBtn = uiUtl.createIconButton("icons/copy.png");
244 JButton colorBtn = uiUtl.createIconButton("icons/color.png");
245 JButton informationBtn = uiUtl.createIconButton("icons/information.png");
246 JButton favoriteBtn = uiUtl.createIconButton("icons/favorite.png");
247 JButton flipHolizontalBtn = uiUtl.createIconButton("icons/flip.png");
249 saveBtn.addActionListener(new ActionListener() {
250 public void actionPerformed(ActionEvent e) {
251 savePicture(new PreviewPanelEvent(PreviewPanel.this, e));
254 copyBtn.addActionListener(new ActionListener() {
255 public void actionPerformed(ActionEvent e) {
256 copyPicture(new PreviewPanelEvent(PreviewPanel.this, e));
259 colorBtn.addActionListener(new ActionListener() {
260 public void actionPerformed(ActionEvent e) {
261 changeBackgroundColor(new PreviewPanelEvent(PreviewPanel.this, e));
264 informationBtn.addActionListener(new ActionListener() {
265 public void actionPerformed(ActionEvent e) {
266 showInformation(new PreviewPanelEvent(PreviewPanel.this, e));
269 favoriteBtn.addActionListener(new ActionListener() {
270 public void actionPerformed(ActionEvent e) {
271 addFavorite(new PreviewPanelEvent(PreviewPanel.this, e));
274 flipHolizontalBtn.addActionListener(new ActionListener() {
275 public void actionPerformed(ActionEvent e) {
276 flipHolizontal(new PreviewPanelEvent(PreviewPanel.this, e));
280 saveBtn.setToolTipText(strings.getProperty("tooltip.save"));
281 copyBtn.setToolTipText(strings.getProperty("tooltip.copy"));
282 colorBtn.setToolTipText(strings.getProperty("tooltip.changeBgColor"));
283 informationBtn.setToolTipText(strings.getProperty("tooltip.showInformation"));
284 favoriteBtn.setToolTipText(strings.getProperty("tooltip.registerFavorites"));
285 flipHolizontalBtn.setToolTipText(strings.getProperty("tooltip.flipHorizontal"));
287 final JToolBar toolBar = new JToolBar();
288 toolBar.setFloatable(false);
289 toolBar.add(flipHolizontalBtn);
290 toolBar.add(copyBtn);
291 toolBar.add(saveBtn);
292 toolBar.add(Box.createHorizontalStrut(8));
293 toolBar.add(colorBtn);
294 toolBar.add(Box.createHorizontalStrut(4));
295 toolBar.add(favoriteBtn);
296 toolBar.add(informationBtn);
298 lblTitle = new JLabel() {
299 private static final long serialVersionUID = 1L;
301 public Dimension getPreferredSize() {
302 Dimension dim = super.getPreferredSize();
303 int maxWidth = getParent().getWidth() - toolBar.getWidth();
304 if (dim.width > maxWidth) {
305 dim.width = maxWidth;
310 public Dimension getMaximumSize() {
311 return getPreferredSize();
314 public Dimension getMinimumSize() {
315 Dimension dim = getPreferredSize();
321 lblTitle.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3));
323 JPanel previewPaneHeader = new JPanel();
324 previewPaneHeader.setLayout(new BorderLayout());
325 previewPaneHeader.add(lblTitle, BorderLayout.WEST);
326 previewPaneHeader.add(toolBar, BorderLayout.EAST);
328 previewImgPanel = new PreviewImagePanel();
331 previewImgScrollPane = new JScrollPane(previewImgPanel);
332 previewImgScrollPane.setAutoscrolls(false);
333 previewImgScrollPane.setWheelScrollingEnabled(false);
334 previewImgScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
335 previewImgScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
337 scrollSupport = new ScrollPaneDragScrollSupport(previewImgScrollPane) {
339 protected void setCursor(Cursor cursor) {
340 PreviewPanel.this.setCursor(cursor);
344 add(previewPaneHeader, BorderLayout.NORTH);
346 layeredPane = new JLayeredPane();
347 layeredPane.setLayout(new OverlayLayout(layeredPane));
349 layeredPane.add(previewImgScrollPane, JLayeredPane.DEFAULT_LAYER);
351 checkInfoLayerPanel = new CheckInfoLayerPanel();
352 layeredPane.add(checkInfoLayerPanel, JLayeredPane.POPUP_LAYER);
353 checkInfoLayerPanel.setVisible(false);
355 add(layeredPane, BorderLayout.CENTER);
357 previewControlPanel = new PreviewControlPanel();
358 Dimension dim = previewControlPanel.getPreferredSize();
359 Dimension prevDim = previewImgScrollPane.getPreferredSize();
360 dim.width = prevDim.width;
361 previewControlPanel.setPreferredSize(dim);
363 add(previewControlPanel, BorderLayout.SOUTH);
364 previewControlPanel.setPinned(appConfig.isEnableZoomPanel());
367 previewControlPanel.addPropertyChangeListener("zoomFactorInt", new PropertyChangeListener() {
368 public void propertyChange(PropertyChangeEvent evt) {
369 Integer newValue = (Integer) evt.getNewValue();
370 zoomWithCenterPosition(newValue.doubleValue() / 100., null);
374 previewControlPanel.addPropertyChangeListener("backgroundColorMode", new PropertyChangeListener() {
375 public void propertyChange(PropertyChangeEvent evt) {
376 BackgroundColorMode bgColorMode = (BackgroundColorMode) evt.getNewValue();
377 previewImgPanel.setBackgroundColorMode(bgColorMode);
378 if (bgColorMode != BackgroundColorMode.ALPHABREND
379 && appConfig.isEnableCheckInfoTooltip() ) {
381 checkInfoLayerPanel.setMessage(null);
382 checkInfoLayerPanel.setVisible(true);
386 checkInfoLayerPanel.setVisible(false);
391 previewImgScrollPane.addMouseMotionListener(new MouseMotionAdapter() {
393 public void mouseMoved(MouseEvent e) {
394 Rectangle rct = previewImgScrollPane.getBounds();
396 if (y > rct.height - appConfig.getZoomPanelActivationArea()) {
397 previewControlPanel.setVisible(true);
399 if ( !previewControlPanel.isPinned()) {
400 previewControlPanel.setVisible(false);
407 for (final MouseWheelListener listener : previewImgScrollPane.getMouseWheelListeners()) {
408 previewImgScrollPane.removeMouseWheelListener(listener);
411 previewImgScrollPane.addMouseWheelListener(new MouseWheelListener() {
412 public void mouseWheelMoved(MouseWheelEvent e) {
413 if ((Main.isMacOSX() && e.isAltDown()) ||
414 ( !Main.isMacOSX() && e.isControlDown())) {
415 // Mac OS XならOptionキー、それ以外はコントロールキーとともにホイールスクロールの場合
418 // ズーム以外のホイール操作はスクロールとする.
422 updateCheckInfoMessage(e.getPoint());
426 previewImgScrollPane.addMouseListener(new MouseAdapter() {
428 public void mousePressed(MouseEvent e) {
429 if (e.getClickCount() == 2) {
431 // (正確に2回目。3回目以降はダブルクリック + シングルクリック)
432 toggleZoom(e.getPoint());
434 scrollSupport.drag(true, e.getPoint());
438 public void mouseReleased(MouseEvent e) {
439 scrollSupport.drag(false, e.getPoint());
443 previewImgScrollPane.addMouseMotionListener(new MouseMotionListener() {
445 public void mouseMoved(MouseEvent e) {
446 updateCheckInfoMessage(e.getPoint());
449 public void mouseDragged(MouseEvent e) {
450 scrollSupport.dragging(e.getPoint());
453 updateCheckInfoMessage(e.getPoint());
461 protected void toggleZoom(Point mousePos) {
462 if (previewImgPanel.isDefaultZoom()) {
464 zoomWithCenterPosition(latestToggleZoom, mousePos);
467 // 等倍でなければ現在の倍率を記憶して等倍にする.
468 double currentZoomFactor = previewImgPanel.getZoomFactor();
469 latestToggleZoom = currentZoomFactor;
470 zoomWithCenterPosition(1., mousePos);
475 * マウス位置に対して画像情報のツールチップを表示する
477 * @param mousePosition
480 protected void updateCheckInfoMessage(Point mousePosition) {
481 if ( !checkInfoLayerPanel.isVisible()) {
486 if (mousePosition != null) {
487 Point panelPt = SwingUtilities.convertPoint(previewImgScrollPane,
488 mousePosition, previewImgPanel);
489 imgPos = previewImgPanel.getImagePosition(panelPt);
491 if (imgPos != null) {
492 // 画像位置があれば、その位置の情報を取得する.
493 int argb = previewImgPanel.getImageARGB(imgPos);
494 int a = (argb >> 24) & 0xff;
495 int r = (argb >> 16) & 0xff;
496 int g = (argb >> 8) & 0xff;
498 int y = (int) (0.298912f * r + 0.586611f * g + 0.114478f * b);
499 String text = String.format(
500 "(%3d,%3d)¥nA:%3d, Y:%3d¥nR:%3d, G:%3d, B:%3d", imgPos.x,
501 imgPos.y, a, y, r, g, b);
502 checkInfoLayerPanel.setMessage(text);
503 checkInfoLayerPanel.setPotision(mousePosition);
506 // 画像位置がなければツールチップは空にする.
507 checkInfoLayerPanel.setMessage(null);
512 * マウス座標単位で指定したオフセット分スクロールする.
519 protected void scroll(int diff_x, int diff_y) {
520 scrollSupport.scroll(diff_x, diff_y);
524 * マウスホイールによる水平・垂直スクロール.<br>
525 * シフトキーで水平、それ以外は垂直とする.<br>
530 protected void scrollByWheel(final MouseWheelEvent e) {
531 scrollSupport.scrollByWheel(e);
539 * ホイールの量は関係なく、方向だけで判定する.<br>
540 * プラットフォームごとに修飾キーの判定が異なるので、 呼び出しもとであらかじめ切り分けて呼び出すこと.<br>
545 protected void zoomByWheel(final MouseWheelEvent e) {
546 int wheelRotation = e.getWheelRotation();
547 double currentZoom = previewImgPanel.getZoomFactor();
549 if (wheelRotation < 0) {
551 zoomFactor = currentZoom * 1.1;
553 } else if (wheelRotation > 0){
555 zoomFactor = currentZoom * 0.9;
562 zoomWithCenterPosition(zoomFactor, e.getPoint());
569 * ズームスライダまたはコンボのいずれかの値を更新すると、他方からも更新通知があがるため 二重処理を防ぐためのセマフォ.<br>
571 private Semaphore zoomLock = new Semaphore(1);
574 * プレビューに表示する画像の倍率を更新する.<br>
575 * 指定した座標が拡大縮小の中心点になるようにスクロールを試みる.<br>
576 * 座標がnullの場合は現在表示されている中央を中心とするようにスクロールを試みる.<br>
577 * (スクロールバーが表示されていない、もしくは十分にスクロールできない場合は必ずしも中心とはならない.)<br>
578 * コントロールパネルの表示値も更新する.<br>
579 * コントロールパネルからの更新通知をうけて再入しないように、 同時に一つしか実行されないようにしている.<br>
582 * 倍率、範囲外のものは範囲内に補正される.
584 * スクロールペイン上のマウス座標、もしくはnull(nullの場合は表示中央)
586 protected void zoomWithCenterPosition(double zoomFactor, Point mousePos) {
587 if ( !zoomLock.tryAcquire()) {
592 if (zoomFactor < 0.2) {
594 } else if (zoomFactor > 8.) {
598 JViewport vp = previewImgScrollPane.getViewport();
601 if (mousePos != null) {
602 // スクロールペインのマウス座標を表示パネルの位置に換算する.
603 viewCenter = SwingUtilities.convertPoint(this, mousePos, previewImgPanel);
606 // 表示パネル上の現在表示しているビューポートの中央の座標を求める
607 Rectangle viewRect = vp.getViewRect();
608 viewCenter = new Point(
609 (viewRect.x + viewRect.width / 2),
610 (viewRect.y + viewRect.height / 2)
614 // 現在のビューサイズ(余白があれば余白も含む)
615 Dimension viewSize = previewImgPanel.getScaledSize(true);
618 previewControlPanel.setZoomFactor(zoomFactor);
619 previewImgPanel.setZoomFactor(zoomFactor);
621 // 新しいのビューサイズ(余白があれば余白も含む)
622 Dimension viewSizeAfter = previewImgPanel.getScaledSize(true);
623 Dimension visibleSize = vp.getExtentSize();
625 if (viewSize != null && viewSizeAfter != null &&
626 viewSizeAfter.width > 0 && viewSizeAfter.height > 0 &&
627 viewSizeAfter.width > visibleSize.width &&
628 viewSizeAfter.height > visibleSize.height) {
629 // 新しいビューの大きさよりも表示可能領域が小さい場合のみ
630 vp.setViewSize(viewSizeAfter);
632 // スクロールペインに表示されている画面サイズを求める.
633 // スクロールバーがある方向は、コンテンツの最大と等しいが
634 // スクロールバーがない場合は画面サイズのほうが大きいため、
635 // 倍率変更による縦横の移動比は、それぞれ異なる.
636 int visible_width = max(visibleSize.width, viewSize.width);
637 int visible_height = max(visibleSize.height, viewSize.height);
638 int visible_width_after = max(visibleSize.width, viewSizeAfter.width);
639 int visible_height_after = max(visibleSize.height, viewSizeAfter.height);
643 // また、画像は縦横同率であるが表示ウィンドウはスクロールバー有無により同率とは限らない.
644 double zoomDiffX = (double) visible_width_after / (double) visible_width;
645 double zoomDiffY = (double) visible_height_after / (double) visible_height;
648 Point viewCenterAfter = new Point();
649 viewCenterAfter.x = (int) round(viewCenter.x * zoomDiffX);
650 viewCenterAfter.y = (int) round(viewCenter.y * zoomDiffY);
653 int diff_x = viewCenterAfter.x - viewCenter.x;
654 int diff_y = viewCenterAfter.y - viewCenter.y;
657 scroll(diff_x, diff_y);
660 // スクロールの単位を画像1ドットあたりの表示サイズに変更する.
662 JScrollBar vsb = previewImgScrollPane.getVerticalScrollBar();
663 JScrollBar hsb = previewImgScrollPane.getHorizontalScrollBar();
664 vsb.setUnitIncrement(max(1, (int) ceil(zoomFactor)));
665 hsb.setUnitIncrement(max(1, (int) ceil(zoomFactor)));
676 public Point getViewPosition() {
677 JViewport vp = previewImgScrollPane.getViewport();
678 return vp.getViewPosition();
682 * 指定した座標が中央となるようにスクロールする。
683 * まだ画像が表示されていない場合は次に画像を設定したときに行う。
686 public void setViewPosition(Point viewPt) {
687 JViewport vp = previewImgScrollPane.getViewport();
688 if (previewImgPanel.getPreviewImage() != null) {
689 if (viewPt != null) {
690 vp.setViewPosition(viewPt);
692 requestViewPt = null;
694 requestViewPt = viewPt;
698 private Point requestViewPt;
701 * プレビューに表示するタイトル.<br>
706 public void setTitle(String title) {
710 if (!title.equals(this.title)) {
712 lblTitle.setText(title + (indicatorShown ? indicatorText : ""));
713 lblTitle.setToolTipText(title);
717 public String getTitle() {
722 * ロードに時間がかかっているか判定し、 インジケータを表示するためのタイマーイベントハンドラ.<br>
724 protected void onTimer() {
727 synchronized (lock) {
728 waiting = isWaiting();
729 firstRequest = firstWaitingTimestamp;
731 boolean indicatorShown = (waiting && ((System.currentTimeMillis() - firstRequest) > indicatorDelay));
732 if (this.indicatorShown != indicatorShown) {
733 this.indicatorShown = indicatorShown;
734 lblTitle.setText(title + (indicatorShown ? indicatorText : ""));
739 * チケットの状態が、ロード完了待ち状態であるか?<br>
740 * ロード中のチケットが、ロード完了のチケットより新しければロード中と見なす.<br>
742 * @return 完了待ちであればtrue、そうでなければfalse
744 protected boolean isWaiting() {
745 synchronized (lock) {
746 return loadingTicket > loadedTicket;
751 * ロード要求が出されるたびに、そのロード要求チケットを登録する.<br>
752 * チケットは要求されるたびに増加するシーケンスとする.<br>
757 public void setLoadingRequest(long ticket) {
758 synchronized (lock) {
759 if ( !isWaiting() && this.loadedTicket < ticket) {
760 // 現在認識しているチケットの状態がロード完了であり、
761 // それよりも新しいチケットが要求されたならば、
762 // 今回のチケットから待ち時間の計測を開始する.
763 this.firstWaitingTimestamp = System.currentTimeMillis();
765 this.loadingTicket = ticket;
770 * ロード完了するたびに呼び出される.<br>
775 public void setLoadingComplete(long ticket) {
776 synchronized (lock) {
777 this.loadedTicket = ticket;
787 public void setPreviewImage(BufferedImage previewImg) {
788 previewImgPanel.setPreviewImage(previewImg);
789 if (requestViewPt != null) {
790 // 画像設定前にスクロール位置の要求があれば、再適用を試みる
791 setViewPosition(requestViewPt);
796 * 表示されている画像を取得する.<br>
797 * 表示画像が設定されていなければnull.<br>
799 * @return 表示画像、もしくはnull
801 public BufferedImage getPreviewImage() {
802 return previewImgPanel.getPreviewImage();
806 * 表示している画面イメージそのままを取得する.
810 public BufferedImage getScreenImage() {
811 JViewport vp = previewImgScrollPane.getViewport();
812 Dimension dim = vp.getExtentSize();
813 BufferedImage img = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB);
814 Graphics2D g = img.createGraphics();
827 * @param wallpaperImg
830 public void setWallpaper(Wallpaper wallpaper) {
831 previewImgPanel.setWallpaper(wallpaper);
836 * 壁紙が未設定の場合は空の壁紙インスタンスが返される.<br>
840 public Wallpaper getWallpaper() {
841 return previewImgPanel.getWallpaper();
849 public double getZoomFactor() {
850 return previewControlPanel.getZoomFactor();
859 public void setZoomFactor(double zoomFactor) {
860 previewControlPanel.setZoomFactor(zoomFactor);
869 public void setVisibleZoomBox(boolean visible) {
870 previewControlPanel.setPinned(visible);
876 * @return ピン留めされていればtrue
878 public boolean isVisibleZoomBox() {
879 return previewControlPanel.isPinned();
882 public void addPreviewPanelListener(PreviewPanelListener listener) {
883 if (listener == null) {
884 throw new IllegalArgumentException();
886 listeners.add(listener);
889 public void removePreviewPanelListener(PreviewPanelListener listener) {
890 listeners.remove(listener);
893 protected void savePicture(PreviewPanelEvent e) {
894 for (PreviewPanelListener listener : listeners) {
895 listener.savePicture(e);
899 protected void flipHolizontal(PreviewPanelEvent e) {
900 for (PreviewPanelListener listener : listeners) {
901 listener.flipHorizontal(e);
905 protected void copyPicture(PreviewPanelEvent e) {
906 for (PreviewPanelListener listener : listeners) {
907 listener.copyPicture(e);
911 protected void changeBackgroundColor(PreviewPanelEvent e) {
912 for (PreviewPanelListener listener : listeners) {
913 listener.changeBackgroundColor(e);
917 protected void showInformation(PreviewPanelEvent e) {
918 for (PreviewPanelListener listener : listeners) {
919 listener.showInformation(e);
923 protected void addFavorite(PreviewPanelEvent e) {
924 for (PreviewPanelListener listener : listeners) {
925 listener.addFavorite(e);
931 * チェック情報の表示用レイヤーパネル.<br>
935 class CheckInfoLayerPanel extends JPanel {
936 private static final long serialVersionUID = 1L;
941 private static final Logger logger = Logger.getLogger(CheckInfoLayerPanel.class.getName());
946 private Insets padding = new Insets(3, 3, 3, 3);
951 private Point pos = new Point();
958 private String message = "";
965 private String[] messageLines;
970 private int fontHeight;
974 * 次回描画前に消去する必要のある領域.<br>
975 * まだ一度も描画してなければnull.<br>
977 private Rectangle eraseRect;
983 private Rectangle requestRect;
986 * 画面に関連づけられていない状態でのテキスト表示サイズは 計算できないため、画面追加時に再計算させるための 予約フラグ.<br>
988 private boolean requestRecalcOnAdd;
991 * フォントのためのデスクトップヒント.(あれば)
993 @SuppressWarnings("rawtypes")
994 private Map desktopHintsForFont;
997 * 透明コンポーネントとして構築する.<br>
999 @SuppressWarnings("rawtypes")
1000 public CheckInfoLayerPanel() {
1003 Toolkit tk = Toolkit.getDefaultToolkit();
1004 desktopHintsForFont = (Map) tk.getDesktopProperty("awt.font.desktophints");
1005 logger.log(Level.CONFIG, "awt.font.desktophints=" + desktopHintsForFont);
1009 * 指定エリアに情報を描画する.<br>
1012 protected void paintComponent(Graphics g0) {
1013 Graphics2D g = (Graphics2D) g0;
1014 super.paintComponent(g);
1017 Rectangle clip = g.getClipBounds();
1018 // System.out.println("clip:" + clip + " /eraseRect:" + eraseRect + " /drawRect:" + requestRect);
1020 // 削除すべき領域が描画範囲に含まれているか?
1021 // (含まれていれば、その領域は消去済みである.)
1022 if (clip == null || (eraseRect != null && clip.contains(eraseRect))) {
1027 if (requestRect == null || requestRect.isEmpty()
1028 || !(clip != null && clip.intersects(requestRect))) {
1029 // 表示すべき領域が存在しないか、描画要求範囲にない.
1032 if (messageLines == null || messageLines.length == 0) {
1033 // 表示するものがなければ何もしない.
1038 if (desktopHintsForFont != null) {
1039 g.addRenderingHints(desktopHintsForFont);
1043 g.setColor(new Color(255, 255, 255, 192));
1044 g.fillRect(requestRect.x, requestRect.y, requestRect.width, requestRect.height);
1045 g.setColor(Color.GRAY);
1046 g.drawRect(requestRect.x, requestRect.y, requestRect.width - 1, requestRect.height - 1);
1049 g.setColor(Color.BLACK);
1050 int oy = fontHeight;
1051 for (String messageLine : messageLines) {
1052 g.drawString(messageLine, requestRect.x + padding.left, requestRect.y + padding.top - 1 + oy);
1056 // 描画された領域を次回消去領域として記憶する.
1057 if (eraseRect == null || eraseRect.isEmpty()) {
1058 // 消去済みであれば、今回分のみを次回消去領域とする.
1059 eraseRect = (Rectangle) requestRect.clone();
1062 // 消去済みエリアが未消去で残っている場合は
1064 eraseRect.add(requestRect);
1069 * 画面にアタッチされた場合、描画領域の再計算が 必要であれば計算する.<br>
1072 public void addNotify() {
1074 if (requestRecalcOnAdd) {
1075 requestRecalcOnAdd = false;
1081 * 要求されたプロパティから、フォント高さによる表示領域を計算し、 その領域の再描画を要求する.(描画する内容がなれば、描画要求しない.)<br>
1082 * 前回表示領域があれば、消去するために、そのエリアも再描画を要求する.<br>
1083 * それ以外のエリアは描画要求しない.(描画の最適化による負荷軽減策)<br>
1084 * フォントサイズを求めるためにグラフィクスへのアクセスが必要となるが、 まだ取得できない場合は{@link #addNotify()}の呼び出し時に
1085 * 再計算するようにフラグを立てておく.<br>
1087 protected void calcRepaint() {
1088 Graphics2D g = (Graphics2D) getGraphics();
1090 requestRecalcOnAdd = true;
1094 // 前回描画領域のクリアのために呼び出す.
1095 if (eraseRect != null && !eraseRect.isEmpty()) {
1100 if (message.length() == 0) {
1105 FontMetrics fm = g.getFontMetrics();
1106 String[] messageLines = message.split("¥n");
1108 Rectangle2D rct = null;
1109 for (String messageLine : messageLines) {
1110 Rectangle2D tmp = fm.getStringBounds(messageLine, g);
1119 int fw = (int) rct.getWidth();
1120 int fh = (int) rct.getHeight();
1122 int w = fw + padding.left + padding.right;
1123 int h = fh * messageLines.length + padding.top + padding.bottom;
1130 int client_w = getWidth();
1131 int client_h = getHeight();
1133 if (x + w > client_w) {
1141 if (y + h > client_h) {
1142 y -= (y + h - client_h);
1146 this.requestRect = new Rectangle(x, y, w, h);
1147 this.messageLines = messageLines;
1148 this.fontHeight = fh;
1151 Rectangle paintRect = (Rectangle) requestRect.clone();
1159 public void setPotision(Point requestPt) {
1160 if (requestPt == null) {
1161 throw new IllegalArgumentException();
1163 if ( !requestPt.equals(pos)) {
1165 pos = (Point) requestPt.clone();
1167 firePropertyChange("position", oldpos, pos);
1171 public Point getPosition() {
1172 return (Point) pos.clone();
1175 public void setMessage(String message) {
1176 if (message == null) {
1179 message = message.replace("¥r¥n", "¥n");
1180 if ( !message.equals(this.message)) {
1181 String oldmes = this.message;
1182 this.message = message;
1184 firePropertyChange("message", oldmes, message);
1188 public String getMessage() {
1198 class PreviewImagePanel extends JPanel {
1199 private static final long serialVersionUID = 1L;
1204 private BackgroundColorMode bgColorMode;
1209 private Wallpaper wallpaper;
1214 private PropertyChangeListener wallpaperListener;
1219 private BufferedImage previewImg;
1222 * 表示用画像(背景モードによる調整あり).<br>
1223 * 事前に拡大縮小を適用済みの場合は、{@link #scaledZoomFactor}に 適用している倍率が設定される.<br>
1224 * 表示用に改めてイメージを生成する必要がない場合は、 透過オリジナルと同じインスタンスとなりえる.<br>
1226 private BufferedImage previewImgForDraw;
1229 * 表示用画像がスケール済みである場合、そのスケールが設定される.<br>
1230 * スケール済みでない場合はnullとなる.<br>
1232 private Double scaledZoomFactor;
1238 private double zoomFactor = 1.;
1243 private static final double TOLERANT = 0.001;
1249 public PreviewImagePanel() {
1253 bgColorMode = BackgroundColorMode.ALPHABREND;
1256 wallpaperListener = new PropertyChangeListener() {
1257 public void propertyChange(PropertyChangeEvent evt) {
1258 onChangeWallpaper();
1263 wallpaper = new Wallpaper();
1264 wallpaper.addPropertyChangeListener(wallpaperListener);
1271 protected void paintComponent(Graphics g0) {
1272 Graphics2D g = (Graphics2D) g0;
1273 super.paintComponent(g);
1275 if (previewImgForDraw == null) {
1279 // 倍率を適用した画像を画面の中央に配置できるように計算する.
1280 // (画像が倍率適用済みであれば1倍とする)
1281 Rectangle imgRct = adjustImageRectangle();
1283 // 表示用画像がスケール済みでない場合はレンダリングオプションを適用する.
1284 if (scaledZoomFactor == null) {
1285 Object renderingOption = getRenderingOption();
1286 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, renderingOption);
1290 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1291 // 表示の最大範囲 (可視領域外も含む)
1293 int h = getHeight();
1294 wallpaper.drawWallpaper(g, w, h);
1298 g.drawImage(previewImgForDraw,
1300 imgRct.x + imgRct.width, imgRct.y + imgRct.height,
1302 previewImgForDraw.getWidth(), previewImgForDraw.getHeight(),
1305 // 通常モード以外のグリッド描画に該当するモードはグリッドを前景に描く
1306 AppConfig appConfig = AppConfig.getInstance();
1307 int drawGridMask = appConfig.getDrawGridMask();
1308 if ((drawGridMask & bgColorMode.mask()) != 0) {
1309 Color oldc = g.getColor();
1311 g.setColor(new Color(appConfig.getPreviewGridColor(), true));
1312 drawGrid(g, imgRct.x, imgRct.y, appConfig.getPreviewGridSize());
1323 * 開始位置の-1単位位置から画像サイズの+1単位までがグリッド範囲となる。
1333 protected void drawGrid(Graphics2D g, int offset_x, int offset_y, int unit) {
1334 Rectangle clip = g.getClipBounds();
1336 int src_w = previewImg.getWidth();
1337 int src_h = previewImg.getHeight();
1338 int my = src_h / unit;
1339 int mx = src_w / unit;
1341 int st_x = offset_x + (int)(-1 * unit * zoomFactor);
1342 int en_x = offset_x + (int)((mx + 1) * unit * zoomFactor);
1343 int w = en_x - st_x + 1;
1345 for (int y = -1; y <= my + 1; y++) {
1347 Rectangle rct = new Rectangle(
1348 st_x, offset_y + (int)(y1 * zoomFactor),
1350 if (clip == null || clip.intersects(rct)) {
1351 g.drawLine(rct.x, rct.y, rct.x + rct.width, rct.y);
1355 int st_y = offset_y + (int)(-1 * unit * zoomFactor);
1356 int en_y = offset_y + (int)((my + 1) * unit * zoomFactor);
1357 int h = en_y - st_y + 1;
1359 for (int x = -1; x <= mx + 1; x++) {
1361 Rectangle rct = new Rectangle(
1362 offset_x + (int)(x1 * zoomFactor), st_y,
1364 g.drawLine(rct.x, rct.y, rct.x, rct.y + rct.height);
1369 * 現在の倍率に応じたレンダリングオプションを取得する.<br>
1371 * @return レンダリングオプション
1373 protected Object getRenderingOption() {
1374 AppConfig appConfig = AppConfig.getInstance();
1375 double rendringOptimizeThreshold;
1376 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1377 rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForNormal();
1379 rendringOptimizeThreshold = appConfig.getRenderingOptimizeThresholdForCheck();
1381 Object renderingHint;
1382 if (zoomFactor < rendringOptimizeThreshold) {
1383 // 補正を適用する最大倍率以内である場合
1384 if (zoomFactor <= 1. || !appConfig.isEnableInterpolationBicubic()) {
1385 // 縮小する場合、もしくはバイキュービックをサポートしない場合
1386 renderingHint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
1388 // 拡大する場合でバイキュービックをサポートしている場合
1389 renderingHint = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
1393 // 補正を適用する最大倍率を超えている場合は補正なし.
1394 renderingHint = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1396 return renderingHint;
1400 * 倍率と、画面のサイズと、表示するオリジナルの画像サイズをもとに、 倍率を適用した画像サイズを、画面に収まる位置に補正して返す.<br>
1401 * 返される矩形の幅と高さ(width, height)は拡大後の画像サイズに等しい.<br>
1402 * 拡大後の画像が画面よりも小さければセンタリングするように矩形の開始位置(x, y)がオフセットされる.<br>
1403 * そうでなければ矩形の開始位置(x, y)は0となる.<br>
1404 * 画像が設定されていなければ幅と高さがゼロの矩形が返される.<br>
1406 * @return 画像を表示するオフセットと大きさ、もしくは空の矩形
1408 public Rectangle adjustImageRectangle() {
1409 if (previewImg == null) {
1410 return new Rectangle(0, 0, 0, 0); // 幅・高さともにゼロ
1412 int client_w = getWidth();
1413 int client_h = getHeight();
1415 int src_w = previewImg.getWidth();
1416 int src_h = previewImg.getHeight();
1418 int w = (int) round(src_w * zoomFactor);
1419 int h = (int) round(src_h * zoomFactor);
1423 offset_x = (client_w - w) / 2;
1427 offset_y = (client_h - h) / 2;
1430 return new Rectangle(offset_x, offset_y, w, h);
1434 * パネルのマウス座標から、実寸の画像のピクセル位置を返す.<br>
1435 * 画像が表示されていないか、範囲外であればnullを返す.<br>
1439 * @return 画像の位置、もしくはnull
1441 public Point getImagePosition(Point pt) {
1442 if (pt == null || previewImg == null) {
1443 // プレビュー画像が設定されていなければnull
1447 Rectangle imgRct = adjustImageRectangle();
1449 if ( !imgRct.contains(pt.x, pt.y)) {
1455 Point ret = (Point) pt.clone();
1460 ret.x = (int) floor(ret.x / zoomFactor);
1461 ret.y = (int) floor(ret.y / zoomFactor);
1467 * 画像の位置から画面の位置を割り出す.<br>
1473 public Point getMousePosition(Point pt) {
1474 if (pt == null || previewImg == null) {
1475 // プレビュー画像が設定されていなければnull
1479 Rectangle imgRct = adjustImageRectangle();
1482 Point ret = (Point) pt.clone();
1483 ret.x = (int) ceil(ret.x * zoomFactor);
1484 ret.y = (int) ceil(ret.y * zoomFactor);
1494 * 指定した位置のRGB値を取得する.<br>
1495 * 範囲外の場合は0が返される.<br>
1499 * @return イメージのARGB値 (ビット順序は、A:24, R:16, G:8, B:0)
1501 public int getImageARGB(Point pt) {
1503 throw new IllegalArgumentException();
1506 return previewImg.getRGB(pt.x, pt.y);
1508 } catch (RuntimeException ex) {
1514 * 倍率を適用した画像パネルのサイズを計算し適用する.<br>
1515 * モードにより余白が加えられる.<br>
1517 protected void recalcScaledSize() {
1518 Dimension scaledSize = getScaledSize(true);
1519 if (scaledSize != null) {
1520 setPreferredSize(scaledSize);
1526 * 元画像の倍率適用後のサイズを返す.<br>
1527 * 元画像が設定されていなければnull.<br>
1528 * needOffsetがfalseであれば表示モードに関わらず、画像の拡大・縮小後の純粋なサイズ、
1529 * trueであれば余白が必要な表示モードの場合の余白が付与された場合のサイズが返される.<br>
1532 * 余白を加味したサイズが必要な場合はtrue
1533 * @return 倍率適用後のサイズ、もしくはnull
1535 protected Dimension getScaledSize(boolean needOffset) {
1536 if (previewImg == null) {
1539 int src_w = previewImg.getWidth();
1540 int src_h = previewImg.getHeight();
1542 int w = (int) round(src_w * zoomFactor);
1543 int h = (int) round(src_h * zoomFactor);
1545 Dimension scaledSize = new Dimension(w, h);
1547 if (bgColorMode != BackgroundColorMode.ALPHABREND) {
1548 // 通常モード以外は画像よりも少し大きめにすることで
1550 AppConfig appConfig = AppConfig.getInstance();
1551 int unfilledSpace = appConfig.getPreviewUnfilledSpaceForCheckMode();
1552 scaledSize.width += max(0, unfilledSpace * 2);
1553 scaledSize.height += max(0, unfilledSpace * 2);
1564 public void setPreviewImage(BufferedImage previewImg) {
1565 BufferedImage oldimg = this.previewImg;
1566 this.previewImg = previewImg;
1569 makeDrawImage(true);
1572 firePropertyChange("previewImage", oldimg, previewImg);
1575 public BufferedImage getPreviewImage() {
1584 public void setWallpaper(Wallpaper wallpaper) {
1585 if (wallpaper == null) {
1586 throw new IllegalArgumentException();
1588 if ( !this.wallpaper.equals(wallpaper)) {
1589 Wallpaper wallpaperOld = this.wallpaper;
1590 if (wallpaperOld != null) {
1591 wallpaperOld.removePropertyChangeListener(wallpaperListener);
1593 this.wallpaper = wallpaper;
1594 if (this.wallpaper != null) {
1595 this.wallpaper.addPropertyChangeListener(wallpaperListener);
1597 firePropertyChange("wallpaper", wallpaperOld, this.wallpaper);
1598 onChangeWallpaper();
1602 public Wallpaper getWallpaper() {
1606 protected void onChangeWallpaper() {
1611 * 背景モード調整済みの表示用画像を作成する.
1613 * @param changeImage
1616 protected void makeDrawImage(boolean changeImage) {
1617 if (previewImg == null) {
1619 this.previewImgForDraw = null;
1620 scaledZoomFactor = null;
1625 if (changeImage || scaledZoomFactor != null) {
1626 // 画像が変更されているか、スケール済みであれば
1628 if (bgColorMode == BackgroundColorMode.ALPHABREND) {
1629 // アルファブレンド通常モードは背景用にあえて作成する必要はない.
1633 // アルファブレンド通常モード以外は背景に作成する
1634 Color bgColor = wallpaper.getBackgroundColor();
1635 BackgroundColorFilter bgColorFilter = new BackgroundColorFilter(bgColorMode, bgColor);
1636 img = bgColorFilter.filter(previewImg, null);
1640 // 画像が変更されておらず、スケール済みでもなければ
1641 // すでに作成済みの画像が使用できる.
1642 img = previewImgForDraw;
1646 Object renderingOption = getRenderingOption();
1648 // バイキュービックでなければ、事前の拡大縮小は行わずに、表示時に行う.
1649 if ( !renderingOption.equals(RenderingHints.VALUE_INTERPOLATION_BICUBIC)) {
1650 previewImgForDraw = img;
1651 scaledZoomFactor = null;
1655 // バイキュービックの場合、倍率を適用したサイズに予め加工しておく.
1656 Dimension scaledSize = getScaledSize(false);
1657 BufferedImage offscreen = new BufferedImage(
1658 scaledSize.width, scaledSize.height, BufferedImage.TYPE_INT_ARGB);
1659 Graphics2D g = offscreen.createGraphics();
1661 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1662 RenderingHints.VALUE_INTERPOLATION_BICUBIC);
1665 0, 0, scaledSize.width, scaledSize.height,
1666 0, 0, img.getWidth(), img.getHeight(),
1672 previewImgForDraw = offscreen;
1673 scaledZoomFactor = Double.valueOf(zoomFactor);
1676 public void setBackgroundColorMode(BackgroundColorMode bgColorMode) {
1677 if (bgColorMode == null) {
1678 throw new IllegalArgumentException();
1680 if (this.bgColorMode != bgColorMode) {
1681 BackgroundColorMode oldcm = bgColorMode;
1682 this.bgColorMode = bgColorMode;
1684 makeDrawImage(true);
1688 firePropertyChange("backgroundColorMode", oldcm, bgColorMode);
1692 public BackgroundColorMode setBackgroundColorMode() {
1696 public void setZoomFactor(double zoomFactor) {
1697 if (abs(zoomFactor - this.zoomFactor) > TOLERANT) {
1698 // 0.001未満の差異は誤差とみなして反映しない.
1699 double oldzoom = this.zoomFactor;
1700 this.zoomFactor = zoomFactor;
1703 makeDrawImage(false);
1706 firePropertyChange("zoomFactor", oldzoom, zoomFactor);
1710 public double getZoomFactor() {
1717 * @return 100%であればtrue
1719 public boolean isDefaultZoom() {
1720 return zoomFactor - 1 < TOLERANT;
1725 * 倍率・背景モードを操作するための下部パネル用
1729 class PreviewControlPanel extends JPanel {
1730 private static final long serialVersionUID = 1L;
1732 private static final Logger logger = Logger.getLogger(PreviewControlPanel.class.getName());
1734 protected static final String STRINGS_RESOURCE = "languages/previewpanel";
1739 private JCheckBox chkPinning;
1744 private JCheckBox chkNoAlpha;
1749 private JCheckBox chkGrayscale;
1754 private JSlider zoomSlider;
1759 private JComboBox zoomCombo;
1765 private static final int MIN_INDEX = -170;
1770 private static final int MAX_INDEX = 219;
1775 private double minimumZoomFactor;
1780 private double maximumZoomFactor;
1786 private int currentZoomFactorInt;
1791 private BackgroundColorMode backgroundColorMode;
1795 * 任意の底Aをもつ対数 logA(N)を計算して返す.
1803 private static double logN(double a, double x) {
1804 return log(x) / log(a);
1808 * 倍率(等倍を1とする)に対するスライダのインデックス値を返す.<br>
1809 * スライダは10ステップごとに前のステップの10%づつ増減する.(複利式)<br>
1815 private static int zoomFactorToIndex(double zoomFactor) {
1816 return (int) round(logN((1. + 0.1), zoomFactor) * 10);
1820 * スライダのインデックス値から倍率(等倍を1とする)を返す.<br>
1821 * 10ステップごとに10%づつ増減する.(複利式)<br>
1825 * @return 倍率(1を等倍とする)
1827 private static double zoomFactorFromIndex(int index) {
1828 return pow(1. + 0.1, index / 10.);
1835 public PreviewControlPanel() {
1836 final Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
1837 .getLocalizedProperties(STRINGS_RESOURCE);
1839 UIHelper uiHelper = UIHelper.getInstance();
1842 Icon pinIcon = uiHelper.createTwoStateIcon(
1843 "icons/pin-icon1.png", "icons/pin-icon2.png");
1846 chkPinning = new JCheckBox(pinIcon);
1847 chkPinning.setToolTipText(strings.getProperty("tooltip.zoompanel.pinning"));
1849 // 円ボタン型チェックボックス用アイコンの実装
1850 final Icon stateIcon = new Icon() {
1851 public int getIconHeight() {
1854 public int getIconWidth() {
1857 public void paintIcon(Component c, Graphics g, int x, int y) {
1859 if (c instanceof AbstractButton) {
1860 AbstractButton btn = (AbstractButton) c;
1861 sw = btn.isSelected();
1864 int w = getIconWidth();
1865 int h = getIconHeight();
1879 AppConfig appConfig = AppConfig.getInstance();
1880 Color fillColor = appConfig.getSelectedItemBgColor();
1881 g.setColor(fillColor);
1882 g.fillOval(x + ox, y + oy, s, w);
1884 g.setColor(Color.GRAY);
1885 g.drawOval(x + ox, y + oy, s, s);
1889 // アルファ確認とグレースケール確認用のチェックボックス
1890 chkNoAlpha = new JCheckBox(stateIcon);
1891 chkGrayscale = new JCheckBox(stateIcon);
1893 chkNoAlpha.setToolTipText(strings.getProperty("tooltip.zoompanel.checkalpha"));
1894 chkGrayscale.setToolTipText(strings.getProperty("tooltip.zoompanel.checkgrayscale"));
1896 backgroundColorMode = BackgroundColorMode.ALPHABREND;
1898 final ChangeListener chkAlphaGrayChangeListener = new ChangeListener() {
1899 public void stateChanged(ChangeEvent e) {
1900 onChangeCheckAlphaGray();
1903 chkNoAlpha.addChangeListener(chkAlphaGrayChangeListener);
1904 chkGrayscale.addChangeListener(chkAlphaGrayChangeListener);
1907 zoomSlider = new JSlider(JSlider.HORIZONTAL, MIN_INDEX, MAX_INDEX, 0);
1908 zoomSlider.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_slider"));
1911 zoomCombo = new JComboBox();
1912 zoomCombo.setToolTipText(strings.getProperty("tooltip.zoompanel.zoomfactor_combo"));
1914 // 倍率の既定リストの設定と、最大・最小値の算定
1915 minimumZoomFactor = zoomFactorFromIndex(zoomSlider.getMinimum());
1916 maximumZoomFactor = zoomFactorFromIndex(zoomSlider.getMaximum());
1918 int minZoomRange = (int) round(minimumZoomFactor * 100.);
1919 int maxZoomRange = (int) round(maximumZoomFactor * 100.);
1921 List<Integer> predefinedZoomRanges = getPredefinedZoomRanges();
1922 for (int zoomRange : predefinedZoomRanges) {
1923 if (zoomRange < minZoomRange) {
1924 minZoomRange = zoomRange;
1926 if (zoomRange > maxZoomRange) {
1927 maxZoomRange = zoomRange;
1929 zoomCombo.addItem(Integer.toString(zoomRange));
1931 final int[] zoomRanges = {minZoomRange, maxZoomRange};
1933 currentZoomFactorInt = 100;
1934 zoomCombo.setSelectedItem(Integer.toString(currentZoomFactorInt));
1935 zoomCombo.setEditable(true);
1936 if ( !Main.isMacOSX()) {
1937 // Windows環境だとデフォルトで9桁分のテキストフィールドが作成され
1938 // それがレイアウトの推奨サイズとして実際に使われてしまうため、
1939 // 明示的に3桁にとどめておくようにオーバーライドする.
1941 zoomCombo.setEditor(new BasicComboBoxEditor() {
1943 editor = new JTextField(3) {
1944 private static final long serialVersionUID = 1L;
1946 public void setBorder(Border border) {
1949 public void setText(String s) {
1950 if (getText().equals(s)) {
1960 // スライダを変更することによりコンボボックスを変更する、
1961 // もしくはコンボボックスを変更することでスライダを変更するが、
1962 // 互いに通知を呼び合うことになるため他方を無視するためのセマフォ
1963 final Semaphore changeLock = new Semaphore(1);
1965 zoomCombo.addActionListener(new ActionListener() {
1966 public void actionPerformed(ActionEvent e) {
1967 boolean adjusted = false;
1968 String value = (String) zoomCombo.getSelectedItem();
1971 zoomFactorInt = Integer.parseInt(value);
1972 if (zoomFactorInt < zoomRanges[0]) {
1973 zoomFactorInt = zoomRanges[0];
1976 } else if (zoomFactorInt > zoomRanges[1]) {
1977 zoomFactorInt = zoomRanges[1];
1981 } catch (RuntimeException ex) {
1982 zoomFactorInt = 100;
1986 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));
1987 Toolkit tk = Toolkit.getDefaultToolkit();
1990 if (changeLock.tryAcquire()) {
1992 zoomSlider.setValue(zoomFactorToIndex(zoomFactorInt / 100.));
1995 changeLock.release();
1998 fireZoomFactorChange(zoomFactorInt);
2002 zoomSlider.addChangeListener(new ChangeListener() {
2003 public void stateChanged(ChangeEvent e) {
2004 int index = zoomSlider.getValue();
2005 double zoomFactor = zoomFactorFromIndex(index);
2006 int zoomFactorInt = (int) round(zoomFactor * 100);
2008 if (changeLock.tryAcquire()) {
2010 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));
2013 changeLock.release();
2015 fireZoomFactorChange(zoomFactorInt);
2022 GridBagLayout gbl = new GridBagLayout();
2025 GridBagConstraints gbc = new GridBagConstraints();
2032 gbc.fill = GridBagConstraints.NONE;
2033 gbc.anchor = GridBagConstraints.CENTER;
2034 gbc.insets = new Insets(0, 0, 0, 5);
2038 add(chkPinning, gbc);
2042 gbc.insets = new Insets(0, 0, 0, 0);
2043 add(chkGrayscale, gbc);
2047 gbc.insets = new Insets(0, 0, 0, 5);
2048 add(chkNoAlpha, gbc);
2052 gbc.fill = GridBagConstraints.HORIZONTAL;
2053 add(zoomSlider, gbc);
2057 gbc.insets = new Insets(3, 0, 3, 0);
2058 gbc.fill = GridBagConstraints.VERTICAL;
2059 add(zoomCombo, gbc);
2061 Integer scrollbarWidth = (Integer) UIManager.get("ScrollBar.width");
2062 logger.log(Level.CONFIG, "ScrollBar.width=" + scrollbarWidth);
2063 if (scrollbarWidth == null) {
2064 scrollbarWidth = Integer.parseInt(
2065 strings.getProperty("uiconstraint.scrollbar.width"));
2070 gbc.anchor = GridBagConstraints.WEST;
2071 gbc.insets = new Insets(0, 0, 0, scrollbarWidth);
2072 add(new JLabel("%"), gbc);
2076 * アプリケーション設定より事前定義済みの倍率候補を取得する
2078 * @return 倍率候補のリスト(順序あり)
2080 protected List<Integer> getPredefinedZoomRanges() {
2081 AppConfig appConfig = AppConfig.getInstance();
2082 String strs = appConfig.getPredefinedZoomRanges();
2083 TreeSet<Integer> ranges = new TreeSet<Integer>();
2084 for (String str : strs.split(",")) {
2086 if (str.length() > 0) {
2088 int zoomFactor = Integer.parseInt(str);
2089 ranges.add(Integer.valueOf(zoomFactor));
2091 } catch (RuntimeException ex) {
2096 ranges.add(Integer.valueOf(100)); // 等倍は常に設定する.
2097 return new ArrayList<Integer>(ranges);
2103 protected void fireZoomFactorChange(int newZoomFactor) {
2104 if (currentZoomFactorInt != newZoomFactor) {
2105 int oldValue = currentZoomFactorInt;
2106 currentZoomFactorInt = newZoomFactor;
2107 firePropertyChange("zoomFactorInt", oldValue, newZoomFactor);
2111 private Semaphore changeChkLock = new Semaphore(1);
2113 protected void onChangeCheckAlphaGray() {
2114 changeChkLock.tryAcquire();
2116 BackgroundColorMode backgroundColorMode = BackgroundColorMode.valueOf(
2117 chkNoAlpha.isSelected(),
2118 chkGrayscale.isSelected()
2120 setBackgroundColorMode(backgroundColorMode);
2123 changeChkLock.release();
2127 public BackgroundColorMode getBackgroundColorMode() {
2128 return this.backgroundColorMode;
2131 public void setBackgroundColorMode(BackgroundColorMode backgroundColorMode) {
2132 if (backgroundColorMode == null) {
2133 throw new IllegalArgumentException();
2136 BackgroundColorMode oldcm = this.backgroundColorMode;
2137 if (oldcm != backgroundColorMode) {
2138 this.backgroundColorMode = backgroundColorMode;
2139 changeChkLock.tryAcquire();
2141 chkNoAlpha.setSelected(backgroundColorMode.isNoAlphaChannel());
2142 chkGrayscale.setSelected(backgroundColorMode.isGrayscale());
2145 changeChkLock.release();
2147 firePropertyChange("backgroundColorMode", oldcm, backgroundColorMode);
2151 public boolean isPinned() {
2152 return chkPinning.isSelected();
2155 public void setPinned(boolean pinned) {
2156 chkPinning.setSelected(pinned);
2157 if (isDisplayable()) {
2162 public double getZoomFactor() {
2163 return (double) currentZoomFactorInt / 100.;
2166 public void setZoomFactor(double zoomFactor) {
2167 if (zoomFactor < minimumZoomFactor) {
2168 zoomFactor = minimumZoomFactor;
2170 if (zoomFactor > maximumZoomFactor) {
2171 zoomFactor = maximumZoomFactor;
2173 int zoomFactorInt = (int) round(zoomFactor * 100.);
2174 if (zoomFactorInt != currentZoomFactorInt) {
2175 zoomCombo.setSelectedItem(Integer.toString(zoomFactorInt));