xeij/PaletteViewer.java
//========================================================================================
//  PaletteViewer.java
//    en:Palette viewer
//    ja:パレットビュア
//  Copyright (C) 2003-2026 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.*;
import java.awt.event.*;  //MouseEvent
import java.awt.font.*;  //TextLayout
import java.awt.geom.*;  //Rectangle2D
import java.awt.image.*;  //BufferedImage
import javax.swing.*;

public class PaletteViewer {

  public static final boolean PLV_ON = true;

  //                group
  //  +-------+-------+-------+-------+
  //  |       |       |       |       |
  //  | cell  | cell  | cell  | cell  |
  //  |       |       |       |       |
  //  +-------+-------+-------+-------+
  //  |       |       |       |       |
  //  | cell  | cell  | cell  | cell  |
  //  |       |       |       |       |
  //  +-------+-------+-------+-------+
  //  |       |       |       |       |
  //  | cell  | cell  | cell  | cell  |
  //  |       |       |       |       |
  //  +-------+-------+-------+-------+
  //  |       |       |       |       |
  //  | cell  | cell  | cell  | cell  |
  //  |       |       |       |       |
  //  +-------+-------+-------+-------+
  //                      page
  //  +-------------------------------------------+
  //  |                  header                   |
  //  |     +-------+ +-------+ +-------+ +-------+
  //  |     |       | |       | |       | |       |
  //  |     | group | | group | | group | | group |
  //  |     |       | |       | |       | |       |
  //  |     +-------+ +-------+ +-------+ +-------+
  //  |     +-------+ +-------+ +-------+ +-------+
  //  |     |       | |       | |       | |       |
  //  |  h  | group | | group | | group | | group |
  //  |  e  |       | |       | |       | |       |
  //  |  a  +-------+-+-------+ +-------+ +-------+
  //  |  d  +-------+ +-------+ +-------+ +-------+
  //  |  e  |       | |       | |       | |       |
  //  |  r  | group | | group | | group | | group |
  //  |     |       | |       | |       | |       |
  //  |     +-------+ +-------+ +-------+ +-------+
  //  |     +-------+ +-------+ +-------+ +-------+
  //  |     |       | |       | |       | |       |
  //  |     | group | | group | | group | | group |
  //  |     |       | |       | |       | |       |
  //  +-----+-------+-+-------+ +-------+ +-------+
  //                gap
  //                          arrgt
  //        0          1              2              3
  //    +-------+  +-------+  +-------+-------+  +-------+
  //    |       |  |       |  |       |       |  |       |
  //    |   G   |  |  TS   |  |   G   |  TS   |  |   G   |
  //    |       |  |       |  |       |       |  |       |
  //    +-------+  +-------+  +-------+-------+  +-------+
  //                                             |       |
  //                                             |  TS   |
  //                                             |       |
  //                                             +-------+

  //セル
  static final int PLV_CELL_WIDTH = 18;  //セルの幅。偶数
  static final int PLV_CELL_HEIGHT = 18;  //セルの高さ

  //グループ
  //  グループのサイズは4x4セルに固定
  static final int PLV_GROUP_GAP = 2;  //グループの間隔

  //ヘッダ
  static final String PLV_FONT_NAME = "Dialog";  //フォント名
  static final int PLV_FONT_STYLE = Font.BOLD;  //フォントスタイル
  static final int PLV_FONT_SIZE = 16;  //フォントサイズ
  static final int PLV_HEADER_WIDTH = PLV_FONT_SIZE * 2;  //左ヘッダの幅
  static final int PLV_HEADER_HEIGHT = PLV_FONT_SIZE * 2;  //上ヘッダの高さ

  //イメージ
  static int plvImageWidth;  //イメージの幅
  static int plvImageHeight;  //イメージの高さ
  static BufferedImage plvBufferedImage;  //イメージ
  static int[] plvBitmap;  //ビットマップ
  static ScrollCanvas plvCanvas;  //キャンバス

  //色
  static final int PLV_BACKGROUND_RGB = 0xff333333;  //背景色
  static final int PLV_FOREGROUND_RGB = 0xffcccccc;  //文字色

  //ページ
  static int plvCellOffset;  //開始セル番号
  static int plvColsBit;  //セル桁数のビット
  static int plvRowsBit;  //セル行数のビット
  static int plvColsMask;  //セル桁数のマスク
  static int plvRowsMask;  //セル行数のマスク
  static int plvWidth;  //幅
  static int plvHeight;  //高さ

  //配置
  //  0 1  2 3
  //  G TS H V
  static final int PLV_DEF_ARRGT = 2;
  static final int PLV_MIN_ARRGT = 0;
  static final int PLV_MAX_ARRGT = 3;
  static int plvArrgtNumber;  //配置番号
  static JComboBox<String> plvArrgtComboBox;  //配置ドロップダウンリスト
  static int plvArrgtLand;  //配置後の横方向のページ数。plvArrgtNumber==2?2:1
  static int plvArrgtPort;  //配置後の縦方向のページ数。plvArrgtNumber==3?2:1
  static int plvArrgtCols;  //配置後のセル桁数。plvArrgtLand<<plvColsBit
  static int plvArrgtRows;  //配置後のセル行数。plvArrgtPort<<plvRowsBit
  static int plvArrgtWidth;  //配置後の幅。plvWidth*plvArrgtLand
  static int plvArrgtHeight;  //配置後の高さ。plvHeight*plvArrgtPort

  //倍率
  //  -1  0 1 2 3 4
  //  1/2 1 2 4 8 16
  static final int PLV_DEF_SCALE = 0;
  static final int PLV_MIN_SCALE = -1;
  static final int PLV_MAX_SCALE = 4;
  static int plvScaleNumber;  //倍率番号
  static JComboBox<String> plvScaleComboBox;  //倍率ドロップダウンリスト

  //Hex
  //  off    on
  //  10進数 16進数
  static final boolean PLV_DEF_HEX = false;
  static boolean plvHex;

  //停止
  static boolean plvStopped;  //true=停止中
  static boolean plvStoppedRequest;
  //  コピーされた値
  static final int[] plvCopiedPalTbl = new int[65536];
  static final int[] plvCopiedPal16G8 = new int[256];
  static final int[] plvCopiedPal16TS = new int[256];
  static final int[] plvCopiedPal8G16L = new int[256];
  static final int[] plvCopiedPal8G16H = new int[256];
  //  参照する値。停止中はコピーされた値、さもなくば現在の値
  static int[] plvPalTbl;
  static int[] plvPal16G8;
  static int[] plvPal16TS;
  static int[] plvPal8G16L;
  static int[] plvPal8G16H;
  //
  static boolean plvG65536On;  //true=65536色表示あり

  //テキストフィールド
  static JTextField plvTextField;
  static boolean plvTextLocked;

  //ウインドウ
  static JFrame plvFrame;

  //ポップアップメニュー
  static JPopupMenu plvPopupMenu;
  static int plvPageToCopy;
  static int plvRowToCopy;

  //タイマー
  public static final int PLV_INTERVAL = 10;
  public static int plvTimer;

  //plvInit ()
  //  初期化
  public static void plvInit () {
    //パラメータ
    plvArrgtNumber = Settings.sgsGetInt ("plvarrgt", PLV_DEF_ARRGT, PLV_MIN_ARRGT, PLV_MAX_ARRGT);
    plvHex = Settings.sgsGetOnOff ("plvhex", PLV_DEF_HEX);
    plvScaleNumber = Settings.sgsGetInt ("plvscale", PLV_DEF_SCALE, PLV_MIN_SCALE, PLV_MAX_SCALE);
    //ページ
    plvCellOffset = 0;
    plvColsBit =
      plvRowsBit = 4;
    plvColsMask = (1 << plvColsBit) - 1;
    plvRowsMask = (1 << plvRowsBit) - 1;
    plvWidth = (PLV_HEADER_WIDTH +
                (PLV_CELL_WIDTH << plvColsBit) +
                (PLV_GROUP_GAP << (plvColsBit - 2)) - PLV_GROUP_GAP);
    plvHeight = (PLV_HEADER_HEIGHT +
                 (PLV_CELL_HEIGHT << plvRowsBit) +
                 (PLV_GROUP_GAP << (plvRowsBit - 2)) - PLV_GROUP_GAP);
    //イメージ
    plvImageWidth = plvWidth * 2;
    plvImageHeight = plvHeight * 2;
    //配置
    plvSetArrgt (plvArrgtNumber);
    //停止
    plvSetStoppedOff ();
    //ウインドウ
    plvFrame = null;
    //タイマー
    plvTimer = 0;
  }  //plvInit

  //plvTini ()
  //  後始末
  public static void plvTini () {
    //パラメータ
    Settings.sgsPutInt ("plvarrgt", plvArrgtNumber);
    Settings.sgsPutOnOff ("plvhex", plvHex);
    Settings.sgsPutInt ("plvscale", plvScaleNumber);
  }  //plvTini

  //plvSetArrgt (number)
  //  配置を設定する
  static void plvSetArrgt (int number) {
    plvArrgtNumber = number;
    plvArrgtLand = plvArrgtNumber == 2 ? 2 : 1;
    plvArrgtPort = plvArrgtNumber == 3 ? 2 : 1;
    plvArrgtCols = plvArrgtLand << plvColsBit;
    plvArrgtRows = plvArrgtPort << plvRowsBit;
    plvArrgtWidth = plvWidth * plvArrgtLand;
    plvArrgtHeight = plvHeight * plvArrgtPort;
    if (plvCanvas != null) {
      plvDrawHeader ();
      plvCanvas.setImage (plvArrgtWidth, plvArrgtHeight);
    }
  }  //plvSetArrgt

  //x = plvColToX (col)
  //  セル桁からX座標を求める
  static int plvColToX (int col) {
    int p = col >> plvColsBit;  //ページ
    col &= plvColsMask;  //ページ内セル桁
    int g = col >> 2;  //グループ
    col &= 3;  //グループ内セル桁
    return (plvWidth * p +
            PLV_HEADER_WIDTH +
            ((PLV_CELL_WIDTH << 2) + PLV_GROUP_GAP) * g +
            PLV_CELL_WIDTH * col);
  }  //plvColToX

  //y = plvRowToY (row)
  //  セル行からY座標を求める
  static int plvRowToY (int row) {
    int p = row >> plvRowsBit;  //ページ
    row &= plvRowsMask;  //ページ内セル行
    int g = row >> 2;  //グループ
    row &= 3;  //グループ内セル行
    return (plvHeight * p +
            PLV_HEADER_HEIGHT +
            ((PLV_CELL_HEIGHT << 2) + PLV_GROUP_GAP) * g +
            PLV_CELL_HEIGHT * row);
  }  //plvRowToY

  //((x << 16) | col) = plvXToXCol (x)
  //  X座標からセル内X座標とセル桁を求める。-1=範囲外
  static int plvXToXCol (int x) {
    if (x < 0 || plvArrgtWidth <= x) {  //表示範囲の外
      return -1;
    }
    int p = x / plvWidth;  //ページ
    x -= plvWidth * p;  //ページ内X座標
    if (x < PLV_HEADER_WIDTH) {  //左ヘッダ
      return -1;
    }
    x -= PLV_HEADER_WIDTH;
    int g = x / ((PLV_CELL_WIDTH << 2) + PLV_GROUP_GAP);  //グループ
    x -= ((PLV_CELL_WIDTH << 2) + PLV_GROUP_GAP) * g;  //グループ内X座標
    if ((PLV_CELL_WIDTH << 2) <= x) {  //グループの間隔
      return -1;
    }
    int col = x / PLV_CELL_WIDTH;  //グループ内セル桁
    x -= PLV_CELL_WIDTH * col;  //セル内X座標
    return (x << 16) | ((p << plvColsBit) + (g << 2) + col);
  }  //plvXToXCol

  //((y << 16) | row) = plvYToYRow (y)
  //  Y座標からセル内Y座標とセル行を求める。-1=範囲外
  static int plvYToYRow (int y) {
    if (y < 0 || plvArrgtHeight <= y) {  //表示範囲の外
      return -1;
    }
    int p = y / plvHeight;  //ページ
    y -= plvHeight * p;  //ページ内Y座標
    if (y < PLV_HEADER_HEIGHT) {  //上ヘッダ
      return -1;
    }
    y -= PLV_HEADER_HEIGHT;
    int g = y / ((PLV_CELL_HEIGHT << 2) + PLV_GROUP_GAP);  //グループ
    y -= ((PLV_CELL_HEIGHT << 2) + PLV_GROUP_GAP) * g;  //グループ内Y座標
    if ((PLV_CELL_HEIGHT << 2) <= y) {  //グループの間隔
      return -1;
    }
    int row = y / PLV_CELL_HEIGHT;  //グループ内セル行
    y -= PLV_CELL_HEIGHT * row;  //セル内Y座標
    return (y << 16) | ((p << plvRowsBit) + (g << 2) + row);
  }  //plvYToYRow

  //plvStart ()
  //  開始
  public static void plvStart () {
    if (RestorableFrame.rfmGetOpened (Settings.SGS_PLV_FRAME_KEY)) {
      plvOpen ();
    }
  }  //plvStart

  //plvOpen ()
  //  開く
  public static void plvOpen () {
    if (plvFrame == null) {
      plvMakeFrame ();
    } else {
      plvUpdateFrame ();
    }
    XEiJ.dbgVisibleMask |= XEiJ.DBG_PLV_VISIBLE_MASK;
    XEiJ.pnlExitFullScreen (false);
    plvFrame.setVisible (true);
  }  //plvOpen

  //plvMakeFrame ()
  //  作る
  //  ここでは開かない
  static void plvMakeFrame () {
    //イメージ
    plvBufferedImage = new BufferedImage (plvImageWidth, plvImageHeight, BufferedImage.TYPE_INT_ARGB);
    plvDrawHeader ();
    //ビットマップ
    plvBitmap = ((DataBufferInt) plvBufferedImage.getRaster ().getDataBuffer ()).getData ();
    //キャンバス
    plvCanvas = new ScrollCanvas (plvBufferedImage, plvArrgtWidth, plvArrgtHeight);
    plvCanvas.setMatColor (new Color (PLV_BACKGROUND_RGB));
    plvCanvas.setMinScaleShift (PLV_MIN_SCALE);
    plvCanvas.setMaxScaleShift (PLV_MAX_SCALE);
    plvCanvas.setScaleShift (plvScaleNumber);
    //アクションリスナー
    ActionListener listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Arrgt":
          plvSetArrgt (plvArrgtComboBox.getSelectedIndex () + PLV_MIN_ARRGT);
          if (XEiJ.mpuTask == null) {
            plvUpdateFrame ();
          }
          break;
        case "Scale":
          plvScaleNumber = plvScaleComboBox.getSelectedIndex () + PLV_MIN_SCALE;
          plvCanvas.setScaleShift (plvScaleNumber);
          break;
        case "Hex":
          plvHex = (((JCheckBox) source).isSelected ());
          plvDrawHeader ();
          plvUpdateFrame ();
          break;
        case "Stop":
          plvStoppedRequest = (((JCheckBox) source).isSelected ());
          if (XEiJ.mpuTask == null) {
            plvUpdateFrame ();
          }
          break;
        case "Copy as hexadecimal":
          if (0 <= plvPageToCopy) {
            StringBuilder sb = new StringBuilder ();
            for (int col = 0; col <= plvColsMask; col++) {
              if (col != 0) {
                sb.append (' ');
              }
              int p = col + (plvRowToCopy << plvColsBit);
              int c = (plvPageToCopy == 0 ? plvPal16G8 : plvPal16TS)[p];
              sb.append (String.format ("%04X", c));
            }
            XEiJ.clpCopy (sb.toString ());
            plvPageToCopy = -1;
          }
          break;
        default:
          System.out.println ("unknown action command " + command);
        }
      }
    };
    //テキストフィールド
    plvTextField = new JTextField ();
    plvTextField.setEditable (false);
    plvTextField.setHorizontalAlignment (JTextField.CENTER);
    //ウインドウ
    plvFrame = Multilingual.mlnTitle (
      ComponentFactory.createRestorableSubFrame (
        Settings.SGS_PLV_FRAME_KEY,
        "Palette Viewer",
        null,
        ComponentFactory.createBorderPanel (
          0, 0,
          //CENTER
          plvCanvas,
          //NORTH
          ComponentFactory.createVerticalBox (
            //1行目
            ComponentFactory.createHorizontalBox (
              Box.createHorizontalGlue (),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel ("Arrgt"),
                "ja", "配置"),
              plvArrgtComboBox = ComponentFactory.createComboBox (
                plvArrgtNumber - PLV_MIN_ARRGT,
                "Arrgt",
                listener,
                "G", "TS", "H", "V"),
              //
              Multilingual.mlnText (
                ComponentFactory.createLabel ("Scale"),
                "ja", "倍率"),
              plvScaleComboBox = ComponentFactory.createComboBox (
                plvScaleNumber - PLV_MIN_SCALE,
                "Scale",
                listener,
                "1/2", "1", "2", "4", "8", "16"),
              //
              Box.createHorizontalStrut (5),
              ComponentFactory.createCheckBox (plvHex, "Hex", listener),
              //
              Box.createHorizontalGlue ()
              ),  //HorizontalBox
            //2行目
            ComponentFactory.createHorizontalBox (
              Box.createHorizontalGlue (),
              //
              plvTextField,
              //
              Box.createHorizontalStrut (5),
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (plvStoppedRequest, "Stop", listener),
                "ja", "停止"),
              //
              Box.createHorizontalGlue ()
              )  //HorizontalBox
            )  //VerticalBox
          )  //BorderPanel
        ),  //SubFrame
      "ja", "パレットビュア");
    //スケールシフトリスナー
    plvCanvas.addScaleShiftListener (new ScrollCanvas.ScaleShiftListener () {
      @Override public void scaleShiftChanged (int scaleShift) {
        plvScaleNumber = scaleShift;
        plvScaleComboBox.setSelectedIndex (plvScaleNumber - PLV_MIN_SCALE);
      }
    });
    //ウインドウリスナー
    ComponentFactory.addListener (
      plvFrame,
      new WindowAdapter () {
        @Override public void windowClosing (WindowEvent we) {
          XEiJ.dbgVisibleMask &= ~XEiJ.DBG_PLV_VISIBLE_MASK;
        }
      });
    //ポップアップメニュー
    plvPopupMenu = ComponentFactory.createPopupMenu (
      Multilingual.mlnText (
        ComponentFactory.createMenuItem ("Copy as hexadecimal", 'C', listener),
        "ja", "16進数でコピー")
      );
    plvPageToCopy = -1;
    //マウスリスナー
    MouseAdapter ma = new MouseAdapter () {
      @Override public void mouseClicked (MouseEvent me) {
        plvTextLocked = !plvTextLocked;  //テキストフィールドのロックを反転する
      }  //mouseClicked
      @Override public void mouseMoved (MouseEvent me) {
        if (!plvTextLocked) {  //テキストフィールドがロックされていないとき
          MouseEvent2D me2D = (MouseEvent2D) me;
          int x = (int) me2D.getX2D ();
          int y = (int) me2D.getY2D ();
          String s = plvGetPixel (x, y);
          plvTextField.setText (s == null ? "" : s);  //テキストフィールドに表示する
        }
      }  //mouseMoved
      @Override public void mousePressed (MouseEvent me) {
        plvShowPopup (me);
      }  //mousePressed
      @Override public void mouseReleased (MouseEvent me) {
        plvShowPopup (me);
      }  //mouseReleased
    };
    plvCanvas.addMouseListener (ma);
    plvCanvas.addMouseMotionListener (ma);
  }  //plvMakeFrame

  //plvShowPopup (me)
  //  ポップアップメニューを表示する
  static void plvShowPopup (MouseEvent me) {
    if (!me.isPopupTrigger ()) {
      return;
    }
    MouseEvent2D me2D = (MouseEvent2D) me;
    int x = (int) me2D.getX2D ();
    int y = (int) me2D.getY2D ();
    int col = plvXToXCol (x);  //セル内X座標<<16|セル桁
    int row = plvYToYRow (y);  //セル内Y座標<<16|セル行
    if (col < 0 || row < 0) {
      return;
    }
    col = (char) col;  //セル桁
    row = (char) row;  //セル行
    if (col <= plvColsMask && row <= plvRowsMask && plvArrgtNumber != 1) {  //グラフィック
      plvPageToCopy = 0;
    } else {  //テキストとスプライト
      plvPageToCopy = 1;
    }
    plvRowToCopy = row & plvRowsMask;
    Point p = plvCanvas.getPopupPoint (me2D);
    plvPopupMenu.show (plvCanvas, p.x, p.y);
  }  //plvShowPopup

  //plvDrawHeader ()
  //  ヘッダを描く
  static void plvDrawHeader () {
    Graphics2D g2 = plvBufferedImage.createGraphics ();
    //背景
    g2.setColor (new Color (PLV_BACKGROUND_RGB));
    g2.fillRect (0, 0, plvArrgtWidth, plvArrgtHeight);
    //ヘッダ
    g2.setColor (new Color (PLV_FOREGROUND_RGB));
    g2.setFont (new Font (PLV_FONT_NAME, PLV_FONT_STYLE, PLV_FONT_SIZE));
    //  タイトル
    if (plvArrgtNumber != 1) {
      plvDrawString (g2,
                     (PLV_HEADER_WIDTH + plvWidth) / 2,  //X座標
                     plvRowToY (0) - PLV_FONT_SIZE * 13 / 12,  //Y座標
                     "Graphic Palette",  //文字列
                     SwingConstants.SOUTH);  //アンカー
    }
    if (plvArrgtNumber != 0) {
      plvDrawString (g2,
                     plvArrgtWidth - plvWidth + (PLV_HEADER_WIDTH + plvWidth) / 2,  //X座標
                     plvArrgtHeight - plvHeight + plvRowToY (0) - PLV_FONT_SIZE * 13 / 12,  //Y座標
                     "Text and Sprite Palette",  //文字列
                     SwingConstants.SOUTH);  //アンカー
    }
    //  上
    for (int row = 0; row < plvArrgtRows; row += (plvRowsMask + 1)) {
      for (int col = 0; col < plvArrgtCols; col++) {
        plvDrawString (g2,
                       plvColToX (col) + PLV_CELL_WIDTH / 2,  //X座標
                       plvRowToY (row) - PLV_FONT_SIZE / 12,  //Y座標
                       String.format (plvHex ? "%X" : "%d", col & plvColsMask),  //文字列
                       SwingConstants.SOUTH);  //アンカー
      }  //for col
    }  //for row
    //  左
    for (int col = 0; col < plvArrgtCols; col += (plvColsMask + 1)) {
      for (int row = 0; row < plvArrgtRows; row++) {
        plvDrawString (g2,
                       plvColToX (col) - PLV_FONT_SIZE / 6,  //X座標
                       plvRowToY (row) + PLV_CELL_HEIGHT / 2,  //Y座標
                       String.format (plvHex ? "%X" : "%d",
                                      plvCellOffset + ((row & plvRowsMask) << plvColsBit)),  //文字列
                       SwingConstants.EAST);  //アンカー
      }  //for row
    }  //for col
  }  //plvDrawHeader

  //plvDrawString (g, x, y, s, d)
  //  文字列を描く
  static void plvDrawString (Graphics2D g2, int x, int y, String s, int d) {
    Font f = g2.getFont ();
    FontRenderContext flc = g2.getFontRenderContext ();
    TextLayout tl = new TextLayout (s, f, flc);
    Rectangle2D r = tl.getBounds ();
    int rx = (int) Math.round (r.getX ());
    int ry = (int) Math.round (r.getY ());
    int rw = (int) Math.round (r.getWidth ());
    int rh = (int) Math.round (r.getHeight ());
    switch (d) {
    case SwingConstants.NORTH_WEST:
      g2.drawString (s, x - rx, y - ry);
      break;
    case SwingConstants.NORTH:
      g2.drawString (s, x - (rx + (rw >> 1)), y - ry);
      break;
    case SwingConstants.NORTH_EAST:
      g2.drawString (s, x - (rx + rw), y - ry);
      break;
    case SwingConstants.WEST:
      g2.drawString (s, x - rx, y - (ry + (rh >> 1)));
      break;
    case SwingConstants.CENTER:
      g2.drawString (s, x - (rx + (rw >> 1)), y - (ry + (rh >> 1)));
      break;
    case SwingConstants.EAST:
      g2.drawString (s, x - (rx + rw), y - (ry + (rh >> 1)));
      break;
    case SwingConstants.SOUTH_WEST:
      g2.drawString (s, x - rx, y - (ry + rh));
      break;
    case SwingConstants.SOUTH:
      g2.drawString (s, x - (rx + (rw >> 1)), y - (ry + rh));
      break;
    case SwingConstants.SOUTH_EAST:
      g2.drawString (s, x - (rx + rw), y - (ry + rh));
      break;
    }
  }  //plvDrawString

  //s = plvGetPixel (x, y)
  //  座標からピクセルの情報を求める
  static String plvGetPixel (int x, int y) {
    int col = plvXToXCol (x);  //セル内X座標<<16|セル桁
    int row = plvYToYRow (y);  //セル内Y座標<<16|セル行
    if (col < 0 || row < 0) {  //セル範囲外
      return null;
    }
    x = col >> 16;  //セル内X座標
    y = row >> 16;  //セル内Y座標
    col = (char) col;  //セル桁
    row = (char) row;  //セル行
    int p = (col & plvColsMask) + ((row & plvRowsMask) << plvColsBit);
    if (col <= plvColsMask && row <= plvRowsMask && plvArrgtNumber != 1) {  //グラフィック
      if (plvG65536On) {  //65536色表示あり
        if (x == 0 || x == PLV_CELL_WIDTH - 1 ||
            y == 0 || y == PLV_CELL_HEIGHT - 1 ||
            x == PLV_CELL_WIDTH / 2 - 1 ||
            x == PLV_CELL_WIDTH / 2) {  //パレット範囲外
          return null;
        }
        int h = x < PLV_CELL_WIDTH / 2 ? 0 : 1;  //左半分と右半分
        if ((p & 1) == 0) {  //下位
          p += h;
          int c = plvPal8G16L[p];
          return String.format ("G p=0x??%02X c=0x??%02X(%d,%d,%d,%d)",
                                p, c,
                                c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
        } else {  //上位
          p = (p & -2) + h;
          int c = plvPal8G16H[p];
          return String.format ("G p=0x%02X?? c=0x%02X??(%d,%d,%d,%d)",
                                p, c >> 8,
                                c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
        }
      } else {  //16色、256色、65536色表示なし
        if (x == 0 || x == PLV_CELL_WIDTH - 1 ||
            y == 0 || y == PLV_CELL_HEIGHT - 1) {  //パレット範囲外
          return null;
        }
        int c = plvPal16G8[p];
        return String.format ("G p=0x%02X c=0x%04X(%d,%d,%d,%d)",
                              p, c,
                              c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
      }
    } else {  //テキストとスプライト
      if (x == 0 || x == PLV_CELL_WIDTH - 1 ||
          y == 0 || y == PLV_CELL_HEIGHT - 1) {  //パレット範囲外
        return null;
      }
      int c = plvPal16TS[p];
      return String.format ("TS p=0x%02X c=0x%04X(%d,%d,%d,%d)",
                            p, c,
                            c >> 11, (c >> 6) & 31, (c >> 1) & 31, c & 1);
    }
  }  //plvGetPixel

  //plvUpdateFrame ()
  //  更新する
  static void plvUpdateFrame () {
    if (plvFrame == null) {  //未初期化
      return;
    }
    //停止/動作
    if (plvStopped != plvStoppedRequest) {
      if (plvStoppedRequest) {  //動作→停止
        plvSetStoppedOn ();
      } else {  //停止→動作
        plvSetStoppedOff ();
      }
    }
    if (!plvStopped) {
      plvG65536On = ((VideoController.vcnReg1Port & 2) != 0 &&  //65536色
                     (VideoController.vcnReg3Port & 15) != 0);  //表示あり
    }
    //パレットを表示する
    for (int row = 0; row < plvArrgtRows; row++) {
      int y = plvRowToY (row) + 1;
      for (int col = 0; col < plvArrgtCols; col++) {
        int x = plvColToX (col) + 1;
        int i = x + plvImageWidth * y;
        int p = (col & plvColsMask) + ((row & plvRowsMask) << plvColsBit);
        if (col <= plvColsMask && row <= plvRowsMask && plvArrgtNumber != 1) {  //グラフィック
          if (plvG65536On) {  //65536色表示あり
            for (int v = 0; v < PLV_CELL_HEIGHT - 2; v++) {  //中央の壁
              plvBitmap[i + PLV_CELL_WIDTH / 2 - 2] = PLV_BACKGROUND_RGB;
              plvBitmap[i + PLV_CELL_WIDTH / 2 - 1] = PLV_BACKGROUND_RGB;
              i += plvImageWidth;
            }
            i += -plvImageWidth * (PLV_CELL_HEIGHT - 2);
            for (int h = 0; h < 2; h++) {  //左半分と右半分
              int c = ((p & 1) == 0 ? plvPal8G16L : plvPal8G16H)[(p & -2) + h];
              int d = plvPalTbl[c];
              for (int v = 0; v < PLV_CELL_HEIGHT - 2; v++) {
                for (int u = 0; u < PLV_CELL_WIDTH / 2 - 2; u++) {
                  plvBitmap[i + u] = d;
                }
                i += plvImageWidth;
              }
              i += PLV_CELL_WIDTH / 2 - plvImageWidth * (PLV_CELL_HEIGHT - 2);
            }
          } else {  //16色、256色、65536色表示なし
            int c = plvPal16G8[p];
            int d = plvPalTbl[c];
            for (int v = 0; v < PLV_CELL_HEIGHT - 2; v++) {
              for (int u = 0; u < PLV_CELL_WIDTH - 2; u++) {
                plvBitmap[i + u] = d;
              }
              i += plvImageWidth;
            }
          }
        } else {  //テキストとスプライト
          int c = plvPal16TS[p];
          int d = plvPalTbl[c];
          for (int v = 0; v < PLV_CELL_HEIGHT - 2; v++) {
            for (int u = 0; u < PLV_CELL_WIDTH - 2; u++) {
              plvBitmap[i + u] = d;
            }
            i += plvImageWidth;
          }
        }
      }
    }
    plvCanvas.repaint ();
  }  //plvUpdateFrame

  //plvSetStoppedOn ()
  //  停止する
  static void plvSetStoppedOn () {
    plvStopped = true;
    plvStoppedRequest = true;
    //現在の値をコピーする
    System.arraycopy (VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15], 0, plvCopiedPalTbl, 0, 65536);
    System.arraycopy (VideoController.vcnPal16G8Port, 0, plvCopiedPal16G8, 0, 256);
    System.arraycopy (VideoController.vcnPal16TSPort, 0, plvCopiedPal16TS, 0, 256);
    System.arraycopy (VideoController.vcnPal8G16LPort, 0, plvCopiedPal8G16L, 0, 256);
    System.arraycopy (VideoController.vcnPal8G16HPort, 0, plvCopiedPal8G16H, 0, 256);
    //コピーされた値を参照する
    plvPalTbl = plvCopiedPalTbl;
    plvPal16G8 = plvCopiedPal16G8;
    plvPal16TS = plvCopiedPal16TS;
    plvPal8G16L = plvCopiedPal8G16L;
    plvPal8G16H = plvCopiedPal8G16H;
  }  //plvSetStoppedOn

  //plvSetStoppedOff ()
  //  停止を解除する
  static void plvSetStoppedOff () {
    plvStopped = false;
    //現在の値を参照する
    plvPalTbl = VideoController.vcnPalBase[VideoController.VCN_CONTRAST_SCALE * 15];
    plvPal16G8 = VideoController.vcnPal16G8Port;
    plvPal16TS = VideoController.vcnPal16TSPort;
    plvPal8G16L = VideoController.vcnPal8G16LPort;
    plvPal8G16H = VideoController.vcnPal8G16HPort;
  }  //plvSetStoppedOff

}  //class PaletteViewer