xeij/ButtonFunction.java
//========================================================================================
//  ButtonFunction.java
//    en:F11/F12 and button function assignments
//    ja:F11/F12およびボタン機能割り当て
//  Copyright (C) 2003-2025 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.event.*;  //ActionListener
import java.util.*;  //ArrayList
import javax.swing.*;  //JMenu

//class ButtonFunction
//  F11/F12およびボタン機能割り当て
public class ButtonFunction {



  public static enum Button {
    F11 {
      @Override String en () {
        return "F11 key";
      }
      @Override String ja () {
        return "F11 キー";
      }
      @Override String paramK () {
        return "f11key";
      }
    },
    F12 {
      @Override String en () {
        return "F12 key";
      }
      @Override String ja () {
        return "F12 キー";
      }
      @Override String paramK () {
        return "f12key";
      }
    },
    WHEEL {
      @Override String en () {
        return "Wheel button";
      }
      @Override String ja () {
        return "ホイールボタン";
      }
      @Override String paramK () {
        return "wheel";
      }
    },
    WHEELUP {
      @Override String en () {
        return "Wheel scroll up";
      }
      @Override String ja () {
        return "ホイールスクロールアップ";
      }
      @Override String paramK () {
        return "wheelup";
      }
    },
    WHEELDOWN {
      @Override String en () {
        return "Wheel scroll down";
      }
      @Override String ja () {
        return "ホイールスクロールダウン";
      }
      @Override String paramK () {
        return "wheeldown";
      }
    },
    BUTTON4 {
      @Override String en () {
        return "Button 4";
      }
      @Override String ja () {
        return "ボタン 4";
      }
      @Override String paramK () {
        return "button4";
      }
    },
    BUTTON5 {
      @Override String en () {
        return "Button 5";
      }
      @Override String ja () {
        return "ボタン 5";
      }
      @Override String paramK () {
        return "button5";
      }
    };
    abstract String en ();
    abstract String ja ();
    abstract String paramK ();
  }  //enum Button

  static final Button[] BUTTON_ARRAY = Button.values ();
  static final int BUTTONS = BUTTON_ARRAY.length;



  public static enum Modifier {
    ONLY {
      @Override int mask () {
        return 0;
      }
      @Override String paramK () {
        return "";
      }
      @Override String en () {
        return "Only";
      }
      @Override String ja () {
        return "単独";
      }
    },
    SHIFT {
      @Override int mask () {
        return InputEvent.SHIFT_DOWN_MASK;
      }
      @Override String paramK () {
        return "shift";
      }
      @Override String en () {
        return "Shift";
      }
      @Override String ja () {
        return "Shift";
      }
    },
    CTRL {
      @Override int mask () {
        return InputEvent.CTRL_DOWN_MASK;
      }
      @Override String paramK () {
        return "ctrl";
      }
      @Override String en () {
        return "Ctrl";
      }
      @Override String ja () {
        return "Ctrl";
      }
    },
    ALT {
      @Override int mask () {
        return InputEvent.ALT_DOWN_MASK;
      }
      @Override String paramK () {
        return "alt";
      }
      @Override String en () {
        return "Alt";
      }
      @Override String ja () {
        return "Alt";
      }
    };
    abstract int mask ();
    abstract String paramK ();
    abstract String en ();
    abstract String ja ();
  }  //enum Modifier

  static final Modifier[] MODIFIER_ARRAY = Modifier.values ();
  static final int MODIFIERS = MODIFIER_ARRAY.length;



  public static enum Function {
    FULLSCREEN {
      @Override String en () {
        return "Toggle full screen";
      }
      @Override String ja () {
        return "全画面表示の切り替え";
      }
      @Override String paramV () {
        return "fullscreen";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.pnlToggleFullScreen ();
        }
        return true;
      }
    },
    MAXIMIZED {
      @Override String en () {
        return "Toggle maximized";
      }
      @Override String ja () {
        return "最大化の切り替え";
      }
      @Override String paramV () {
        return "maximized";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.pnlToggleMaximized ();
        }
        return true;
      }
    },
    SEAMLESS {
      @Override String en () {
        return "Toggle seamless mouse";
      }
      @Override String ja () {
        return "シームレスマウスの切り替え";
      }
      @Override String paramV () {
        return "seamless";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          Mouse.musSetSeamlessOn (!Mouse.musSeamlessOn);
        }
        return true;
      }
    },
    SCREENSHOT {
      @Override String en () {
        return "Take a screenshot";
      }
      @Override String ja () {
        return "スクリーンショットを撮る";
      }
      @Override String paramV () {
        return "screenshot";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          Keyboard.kbdDoCapture ();
        }
        return true;
      }
    },
    TEXTCOPY {
      @Override String en () {
        return "Text screen copy";
      }
      @Override String ja () {
        return "テキスト画面コピー";
      }
      @Override String paramV () {
        return "textcopy";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          TextCopy.txcCopy ();
        }
        return true;
      }
    },
    GIFANIMATION {
      @Override String en () {
        return "Start recording GIF animation";
      }
      @Override String ja () {
        return "GIF アニメーション録画開始";
      }
      @Override String paramV () {
        return "gifanimation";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          GIFAnimation.gifStartRecording ();
        }
        return true;
      }
    },
    STOPANDSTART {
      @Override String en () {
        return "Stop and start";
      }
      @Override String ja () {
        return "停止と再開";
      }
      @Override String paramV () {
        return "stopandstart";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuStopAndStart ();
        }
        return true;
      }
    },
    TRACE1 {
      @Override String en () {
        return "1 trace";
      }
      @Override String ja () {
        return "トレース 1 回";
      }
      @Override String paramV () {
        return "trace1";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuAdvance (1);
        }
        return true;
      }
    },
    TRACE10 {
      @Override String en () {
        return "10 traces";
      }
      @Override String ja () {
        return "トレース 10 回";
      }
      @Override String paramV () {
        return "trace10";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuAdvance (10);
        }
        return true;
      }
    },
    TRACE100 {
      @Override String en () {
        return "100 traces";
      }
      @Override String ja () {
        return "トレース 100 回";
      }
      @Override String paramV () {
        return "trace100";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuAdvance (100);
        }
        return true;
      }
    },
    STEP1 {
      @Override String en () {
        return "1 step";
      }
      @Override String ja () {
        return "ステップ 1 回";
      }
      @Override String paramV () {
        return "step1";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuStep (1);
        }
        return true;
      }
    },
    STEP10 {
      @Override String en () {
        return "10 steps";
      }
      @Override String ja () {
        return "ステップ 10 回";
      }
      @Override String paramV () {
        return "step10";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuStep (10);
        }
        return true;
      }
    },
    STEP100 {
      @Override String en () {
        return "100 steps";
      }
      @Override String ja () {
        return "ステップ 100 回";
      }
      @Override String paramV () {
        return "step100";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuStep (100);
        }
        return true;
      }
    },
    RETURN {
      @Override String en () {
        return "Step until return";
      }
      @Override String ja () {
        return "ステップアンティルリターン";
      }
      @Override String paramV () {
        return "return";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          XEiJ.mpuStepUntilReturn ();
        }
        return true;
      }
    },
    LEFTCLICK {
      @Override String en () {
        return "Left click";
      }
      @Override String ja () {
        return "左クリック";
      }
      @Override String paramV () {
        return "leftclick";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          Mouse.musWheelButton = 1;  //左クリック
          Mouse.musWheelReleaseTime = XEiJ.mpuClockTime + XEiJ.TMR_FREQ / 1000 * 100;  //下へ回転。左クリック100ms
        }
        return true;
      }
    },
    RIGHTCLICK {
      @Override String en () {
        return "Right click";
      }
      @Override String ja () {
        return "右クリック";
      }
      @Override String paramV () {
        return "rightclick";
      }
      @Override boolean execute (boolean pressed) {
        if (pressed) {
          Mouse.musWheelButton = 2;  //右クリック
          Mouse.musWheelReleaseTime = XEiJ.mpuClockTime + XEiJ.TMR_FREQ / 1000 * 100;  //上へ回転。右クリック100ms
        }
        return true;
      }
    },
    DONOTHING {
      @Override String en () {
        return "No function";
      }
      @Override String ja () {
        return "機能なし";
      }
      @Override String paramV () {
        return "donothing";
      }
      @Override boolean execute (boolean pressed) {
        return false;
      }
    };
    abstract String en ();
    abstract String ja ();
    abstract String paramV ();
    abstract boolean execute (boolean pressed);
  }  //enum Function

  static final Function[] FUNCTION_ARRAY = Function.values ();
  static final int FUNCTIONS = FUNCTION_ARRAY.length;



  //割り当て
  //  assignment[MODIFIERS * bi + mi] = fi
  final static int[] assignment = new int[MODIFIERS * BUTTONS];

  //ウインドウ
  public static JFrame bfnFrame;

  //bfnInit ()
  //  初期化
  public static void bfnInit () {
    //パラメータを復元する
    for (int bi = 0; bi < BUTTONS; bi++) {
      Button b = BUTTON_ARRAY[bi];
      for (int mi = 0; mi < MODIFIERS; mi++) {
        Modifier m = MODIFIER_ARRAY[mi];
        String k = m.paramK () + b.paramK ();
        String v = Settings.sgsGetString (k);
        int fi = Function.DONOTHING.ordinal ();
        for (int gi = 0; gi < FUNCTIONS; gi++) {
          Function g = FUNCTION_ARRAY[gi];
          if (g.paramV ().equals (v)) {
            fi = gi;
            break;
          }
        }
        assignment[MODIFIERS * bi + mi] = fi;
      }
    }
    switch (Settings.sgsGetString ("mousewheel")) {  //旧パラメータ。保存しない
    case "trace":
      //-wheelup=trace1
      //-shiftwheelup=trace10
      //-ctrlwheelup=trace100
      //-wheeldown=step1
      //-shiftwheeldown=step10
      //-ctrlwheeldown=step100
      //-altwheeldown=return
      assignment[MODIFIERS * Button.WHEELUP.ordinal () + Modifier.ONLY.ordinal ()] = Function.TRACE1.ordinal ();
      assignment[MODIFIERS * Button.WHEELUP.ordinal () + Modifier.SHIFT.ordinal ()] = Function.TRACE10.ordinal ();
      assignment[MODIFIERS * Button.WHEELUP.ordinal () + Modifier.CTRL.ordinal ()] = Function.TRACE100.ordinal ();
      assignment[MODIFIERS * Button.WHEELDOWN.ordinal () + Modifier.ONLY.ordinal ()] = Function.STEP1.ordinal ();
      assignment[MODIFIERS * Button.WHEELDOWN.ordinal () + Modifier.SHIFT.ordinal ()] = Function.STEP10.ordinal ();
      assignment[MODIFIERS * Button.WHEELDOWN.ordinal () + Modifier.CTRL.ordinal ()] = Function.STEP100.ordinal ();
      assignment[MODIFIERS * Button.WHEELDOWN.ordinal () + Modifier.ALT.ordinal ()] = Function.RETURN.ordinal ();
      break;
    case "click":
      //-wheelup=leftclick
      //-wheeldown=rightclick
      assignment[MODIFIERS * Button.WHEELUP.ordinal () + Modifier.ONLY.ordinal ()] = Function.LEFTCLICK.ordinal ();
      assignment[MODIFIERS * Button.WHEELDOWN.ordinal () + Modifier.ONLY.ordinal ()] = Function.RIGHTCLICK.ordinal ();
      break;
    }
  }  //bfnInit

  //bfnTini ()
  //  後始末
  public static void bfnTini () {
    //パラメータを保存する
    for (int bi = 0; bi < BUTTONS; bi++) {
      Button b = BUTTON_ARRAY[bi];
      for (int mi = 0; mi < MODIFIERS; mi++) {
        Modifier m = MODIFIER_ARRAY[mi];
        String k = m.paramK () + b.paramK ();
        int fi = assignment[MODIFIERS * bi + mi];
        Function f = FUNCTION_ARRAY[fi];
        String v = f.paramV ();
        Settings.sgsPutString (k, v);
      }
    }
    Settings.sgsPutString ("mousewheel", "");  //旧パラメータ。保存しない
  }  //bfnTini

  //bfnMakeMenuItem ()
  public static JMenuItem bfnMakeMenuItem () {
    return Multilingual.mlnText (
      ComponentFactory.createMenuItem (
        "F11/F12 and button function assignments",
        new ActionListener () {
          @Override public void actionPerformed (ActionEvent ae) {
            bfnOpen ();
          }
        }),
      "ja", "F11/F12 およびボタン機能割り当て");
  }  //bfnMakeMenuItem

  //bfnStart ()
  public static void bfnStart () {
    if (RestorableFrame.rfmGetOpened (Settings.SGS_BFN_FRAME_KEY)) {
      bfnOpen ();
    }
  }  //bfnStart

  //bfnOpen ()
  //  ウィンドウを開く
  public static void bfnOpen () {
    if (bfnFrame == null) {
      bfnMakeFrame ();
    }
    XEiJ.pnlExitFullScreen (false);
    bfnFrame.setVisible (true);
  }  //bfnOpen()

  //bfnMakeFrame ()
  //  ウィンドウを作る
  //  ここでは開かない
  public static void bfnMakeFrame () {
    ActionListener listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        int bmi = Integer.parseInt (ae.getActionCommand ());
        int fi = bmi / (MODIFIERS * BUTTONS);
        bmi -= MODIFIERS * BUTTONS * fi;
        assignment[bmi] = fi;
      }
    };
    //                                                                                                                     MODIFIERS*BUTTONS
    //                     0       1     2        3      4                                                                               |
    //              +-------------+--------------------------+---------+-------+-----------------+-------------------+----------+----------+
    //            0 |             |         F11 key          | F12 key | Wheel | Wheel scroll up | Wheel scroll down | Button 4 | Button 5 |
    //              |             +--+--------+-------+------+-+--+-+--+-+-+-+-+---+----+---+----+----+----+----+----+-+--+--+--+-+--+--+--+
    //            1 |             |  | Shift+ | Ctrl+ | Alt+ | |  | |  | | | | |   |    |   |    |    |    |    |    | |  |  |  | |  |  |  |
    //              +-------------+--+--------+-------+------+-+--+-+--+-+-+-+-+---+----+---+----+----+----+----+----+-+--+--+--+-+--+--+--+
    //            2 | fullscreen  |X |   X    |   X   |  X   |X|X |X|X |X|X|X|X| X | X  | X | X  | X  | X  | X  | X  |X|X |X |X |X|X |X |X |
    //              +-------------+--+--------+-------+------+-+--+-+--+-+-+-+-+---+----+---+----+----+----+----+----+-+--+--+--+-+--+--+--+
    //            :
    //              +-------------+--+--------+-------+------+-+--+-+--+-+-+-+-+---+----+---+----+----+----+----+----+-+--+--+--+-+--+--+--+
    //  1+FUNCTIONS | no function |X |   X    |   X   |  X   |X|X |X|X |X|X|X|X| X | X  | X | X  | X  | X  | X  | X  |X|X |X |X |X|X |X |X |
    //              +-------------+--+--------+-------+------+-+--+-+--+-+-+-+-+---+----+---+----+----+----+----+----+-+--+--+--+-+--+--+--+
    //  ※水平線の位置はfi={0,6,14,16}の上に固定
    //  各ボタンの左側に垂直線を加える
    int colCount = 1 + (1 + MODIFIERS) * BUTTONS;
    ArrayList<Object> cellList = new ArrayList<Object> ();
    cellList.add ("");
    for (int bi = 0; bi < BUTTONS; bi++) {
      Button b = BUTTON_ARRAY[bi];
      cellList.add (ComponentFactory.createVerticalSeparator ());
      cellList.add (Multilingual.mlnText (ComponentFactory.createLabel (b.en ()), "ja", b.ja ()));
    }
    for (int bi = 0; bi < BUTTONS; bi++) {
      for (int mi = 0; mi < MODIFIERS; mi++) {
        Modifier m = MODIFIER_ARRAY[mi];
        cellList.add (Multilingual.mlnText (ComponentFactory.createLabel (m.en ()), "ja", m.ja ()));
      }
    }
    ButtonGroup[] group = new ButtonGroup[MODIFIERS * BUTTONS];
    for (int fi = 0; fi < FUNCTIONS; fi++) {
      Function f = FUNCTION_ARRAY[fi];
      if (fi == 0 || fi == 6 || fi == 14 || fi == 16) {  //※
        cellList.add (ComponentFactory.createHorizontalSeparator ());
      }
      cellList.add (Multilingual.mlnText (ComponentFactory.createLabel (f.en ()), "ja", f.ja ()));
      for (int bi = 0; bi < BUTTONS; bi++) {
        cellList.add (ComponentFactory.createVerticalSeparator ());
        for (int mi = 0; mi < MODIFIERS; mi++) {
          int bmi = MODIFIERS * bi + mi;
          if (fi == 0) {
            group[bmi] = new ButtonGroup ();
          }
          cellList.add (
            ComponentFactory.setEnabled (
              ComponentFactory.setText (
                ComponentFactory.createRadioButton (
                  group[bmi],
                  assignment[bmi] == fi,
                  String.valueOf (MODIFIERS * BUTTONS * fi + bmi),  //commandをfbmiにする
                  listener),
                ""),  //textを""にする
              fi == Function.TEXTCOPY.ordinal () ? XEiJ.clpClipboard != null :  //テキスト画面コピーはクリップボードが必要
              true)
            );
        }
      }
    }
    String colStyles = "italic";
    String cellStyles = "rowSpan=2";
    for (int bi = 0; bi < BUTTONS; bi++) {
      colStyles += ";lengthen";
      for (int mi = 0; mi < MODIFIERS; mi++) {
        colStyles += ";";
      }
      cellStyles += ";rowSpan=2;colSpan=" + MODIFIERS;
    }
    bfnFrame = Multilingual.mlnTitle (
      ComponentFactory.createRestorableSubFrame (
        Settings.SGS_BFN_FRAME_KEY,
        "F11/F12 and button function assignments",
        null,
        ComponentFactory.setPreferredSize (
          new JScrollPane (
            ComponentFactory.setEmptyBorder (
              ComponentFactory.createGridPanel (
                colCount,  //colCount
                2 + FUNCTIONS + 4,  //rowCount
                "paddingLeft=3,paddingRight=3,center",   //gridStyles
                colStyles,  //colStyles
                "italic;italic" +
                ";colSpan=" + colCount + ",widen" +  //0の上
                ";;;;;;;colSpan=" + colCount + ",widen" +  //6の上※
                ";;;;;;;;;colSpan=" + colCount + ",widen" +  //14の上※
                ";;;colSpan=" + colCount + ",widen",  //16の上※ rowStyles
                cellStyles,  //cellStyles
                cellList.toArray (new Object[0])),
              6, 6, 6, 6)),
          600, 380)),
      "ja", "F11/F12 およびボタン機能割り当て");
  }  //bfnMakeFrame()

  //consumed = bfnExecute (b, modifiersEx, pressed, forcedF)
  //  ボタンが押されたまたは離された
  public static boolean bfnExecute (Button b, int modifiersEx, boolean pressed, Function forcedF) {
    int mask = modifiersEx & (InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK);
    for (int mi = 0; mi < MODIFIERS; mi++) {
      Modifier m = MODIFIER_ARRAY[mi];
      if (m.mask () == mask) {
        int bi = b.ordinal ();
        int bmi = MODIFIERS * bi + mi;
        int fi = assignment[bmi];
        Function f = FUNCTION_ARRAY[fi];
        if (forcedF != null) {
          f = forcedF;
        }
        return f.execute (pressed);
      }
    }
    return false;
  }  //bfnExecute

  //bfnFullScreenText ()
  //  全画面表示を解除するボタンを探して文字列で返す。なければnull
  public static String bfnFullScreenText () {
    int fi = Function.FULLSCREEN.ordinal ();  //全画面表示の切り替え
    int gi = Function.MAXIMIZED.ordinal ();  //最大化の切り替え。全画面表示のとき全画面表示を解除する
    for (int mi = 0; mi < MODIFIERS; mi++) {
      for (int bi = 0; bi < BUTTONS; bi++) {
        int a = assignment[MODIFIERS * bi + mi];
        if (a == fi || a == gi) {
          StringBuilder sb = new StringBuilder ();
          if (mi != 0) {
            sb.append (MODIFIER_ARRAY[mi].en ()).append ("+");
          }
          if (Multilingual.mlnJapanese) {
            sb.append (BUTTON_ARRAY[bi].ja ());
          } else {
            sb.append (BUTTON_ARRAY[bi].en ());
          }
          return sb.toString ();
        }
      }
    }
    return null;
  }  //bfnFullScreenText



}  //class ButtonFunction