MemoryDumpList.java
     1: //========================================================================================
     2: //  MemoryDumpList.java
     3: //    en:Memory dump list
     4: //    ja:メモリダンプリスト
     5: //  Copyright (C) 2003-2024 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.*;  //BasicStroke,BorderLayout,BoxLayout,Color,Component,Container,Cursor,Desktop,Dimension,Font,FlowLayout,Frame,Graphics,Graphics2D,GraphicsDevice,GraphicsEnvironment,GridLayout,Image,Insets,Paint,Point,Rectangle,RenderingHints,Robot,Shape,Stroke,TexturePaint,Toolkit
    16: import java.awt.event.*;  //ActionEvent,ActionListener,ComponentAdapter,ComponentEvent,ComponentListener,FocusAdapter,FocusEvent,FocusListener,InputEvent,KeyAdapter,KeyEvent,KeyListener,MouseAdapter,MouseEvent,MouseListener,MouseMotionAdapter,MouseWheelEvent,WindowAdapter,WindowEvent,WindowListener,WindowStateListener
    17: import java.lang.*;  //Boolean,Character,Class,Comparable,Double,Exception,Float,IllegalArgumentException,Integer,Long,Math,Number,Object,Runnable,SecurityException,String,StringBuilder,System
    18: import java.util.*;  //ArrayList,Arrays,Calendar,GregorianCalendar,HashMap,Map,Map.Entry,TimeZone,Timer,TimerTask,TreeMap
    19: import javax.swing.*;  //AbstractSpinnerModel,Box,ButtonGroup,DefaultListModel,ImageIcon,JApplet,JButton,JCheckBox,JCheckBoxMenuItem,JDialog,JFileChooser,JFrame,JLabel,JList,JMenu,JMenuBar,JMenuItem,JPanel,JRadioButton,JScrollPane,JSpinner,JTextArea,JTextField,JTextPane,JViewport,ScrollPaneConstants,SpinnerListModel,SpinnerNumberModel,SwingConstants,SwingUtilities,UIManager,UIDefaults,UnsupportedLookAndFeelException
    20: import javax.swing.event.*;  //CaretEvent,CaretListener,ChangeEvent,ChangeListener,DocumentEvent,DocumentListener,ListSelectionListener
    21: import javax.swing.text.*;  //AbstractDocument,BadLocationException,DefaultCaret,Document,DocumentFilter,JTextComponent,ParagraphView,Style,StyleConstants,StyleContext,StyledDocument
    22: 
    23: public class MemoryDumpList {
    24: 
    25:   public static final int DMP_ITEM_SIZE = 0x00000010;  //項目の最小サイズ
    26:   public static final int DMP_PAGE_SIZE = 0x00000400;  //ページのサイズ
    27:   public static final int DMP_ITEM_MASK = -DMP_ITEM_SIZE;
    28:   public static final int DMP_PAGE_MASK = -DMP_PAGE_SIZE;
    29:   public static final int DMP_MAX_ITEMS = DMP_PAGE_SIZE / DMP_ITEM_SIZE + 2;  //ページの最大項目数。先頭と末尾の番兵を含む
    30: 
    31:   public static final char[] DMP_BASE = (
    32:     //         11111111112222222222333333333344444444445555555555666666666677777777778
    33:     //12345678901234567890123456789012345678901234567890123456789012345678901234567890
    34:     "xxxxxxxx  xx xx xx xx  xx xx xx xx  xx xx xx xx  xx xx xx xx ").toCharArray ();
    35:   public static final int DMP_DATA_START = 10;  //行頭からデータの開始位置までのオフセット
    36:   public static final int[] DMP_DATA_ADDRESS = {
    37:     0,   1, -1,  2,  3, -1,  4,  5, -1,  6,  7, -1, -1,
    38:     8,   9, -1, 10, 11, -1, 12, 13, -1, 14, 15, -1, -1,
    39:     16, 17, -1, 18, 19, -1, 20, 21, -1, 22, 23, -1, -1,
    40:     24, 25, -1, 26, 27, -1, 28, 29, -1, 30, 31,
    41:   };  //データの開始位置からのオフセット→データのアドレス*2+(0=上位,1=下位)。-1=空白
    42:   public static final int[] DMP_DATA_OFFSET = {
    43:     0,   1,  3,  4,  6,  7,  9, 10,
    44:     13, 14, 16, 17, 19, 20, 22, 23,
    45:     26, 27, 29, 30, 32, 33, 35, 36,
    46:     39, 40, 42, 43, 45, 46, 48, 49,
    47:   };  //データのアドレス*2+(0=上位,1=下位)→データの開始位置からのオフセット
    48: 
    49:   public static int dmpItemCount;  //ページに含まれる項目の数。先頭と末尾の番兵を含む。0=構築前または再構築要求
    50:   public static int dmpItemIndex;  //キャレットがある項目の番号
    51:   public static int dmpPageAddress;  //ページの先頭アドレス
    52:   public static final int[] dmpAddressArray = new int[DMP_MAX_ITEMS];  //項目の先頭アドレスの配列。先頭は前のページの末尾、末尾は次のページの先頭。スピナーのヒント
    53:   public static final int[] dmpSplitArray = new int[DMP_MAX_ITEMS];  //項目を区切る位置の配列。先頭は0
    54:   public static final int[] dmpCaretArray = new int[DMP_MAX_ITEMS];  //項目が選択されたときキャレットを移動させる位置の配列。行の手前にヘッダやラベルなどを挿入しないときはdmpSplitArrayと同じ
    55: 
    56:   public static JFrame dmpFrame;  //ウインドウ
    57:   public static ScrollTextArea dmpBoard;  //スクロールテキストエリア
    58:   public static JTextArea dmpTextArea;  //テキストエリア
    59:   public static Hex8Spinner dmpSpinner;  //スピナー
    60: 
    61:   public static int dmpSupervisorMode;  //0=ユーザモード,0以外=スーパーバイザモード
    62:   public static JCheckBox dmpSupervisorCheckBox;  //ユーザ/スーパーバイザチェックボックス
    63:   public static boolean dmpSecondBridge;  //true=行末からはみ出して書いた文字を行頭にも書く
    64:   public static Color dmpCellophaneColor;  //dmpSecondBridgeではみ出した部分に被せるセロファンの色
    65: 
    66:   //dmpInit ()
    67:   //  初期化
    68:   public static void dmpInit () {
    69: 
    70:     dmpItemCount = 0;  //構築前
    71:     dmpItemIndex = 0;
    72:     dmpPageAddress = 0;
    73:     dmpSupervisorMode = 1;
    74:     //dmpAddressArray = new int[DMP_MAX_ITEMS];
    75:     //dmpSplitArray = new int[DMP_MAX_ITEMS];
    76:     //dmpCaretArray = new int[DMP_MAX_ITEMS];
    77: 
    78:     dmpFrame = null;
    79: 
    80:   }  //dmpInit()
    81: 
    82:   //dmpStart ()
    83:   public static void dmpStart () {
    84:     if (RestorableFrame.rfmGetOpened (Settings.SGS_DMP_FRAME_KEY)) {
    85:       dmpOpen (-1, -1, true);
    86:     }
    87:   }  //dmpStart()
    88: 
    89:   //dmpOpen (address, supervisor, forceUpdate)
    90:   //  メモリダンプリストウインドウを開く
    91:   //  既に開いているときは手前に持ってくる
    92:   public static void dmpOpen (int address, int supervisor, boolean forceUpdate) {
    93:     if (dmpFrame == null) {
    94:       dmpMake ();
    95:     }
    96:     dmpUpdate (address, supervisor, forceUpdate);
    97:     dmpFrame.setVisible (true);
    98:     XEiJ.dbgVisibleMask |= XEiJ.DBG_DMP_VISIBLE_MASK;
    99:   }  //dmpOpen(int,int,boolean)
   100: 
   101:   static class MemoryDumpTextArea extends ScrollTextArea {
   102:     @Override public void paintAfterText (JTextArea textArea, Graphics2D g2) {
   103:       if (MemoryDumpList.dmpSecondBridge) {
   104:         try {
   105:           g2.setPaint (MemoryDumpList.dmpCellophaneColor);
   106:           Rectangle r0 = textArea.modelToView2D (61).getBounds ();  //0の位置
   107:           Rectangle r1 = textArea.modelToView2D (62).getBounds ();  //1の位置
   108:           g2.fillRect (r0.x - (r1.x - r0.x), r0.y,
   109:                        r1.x - r0.x, r0.height * DMP_MAX_ITEMS);  //左側
   110:           g2.fillRect (r0.x + (r1.x - r0.x) * 16, r0.y,
   111:                        r1.x - r0.x, r0.height * DMP_MAX_ITEMS);  //右側
   112:         } catch (BadLocationException ble) {
   113:         }
   114:       }
   115:     }
   116:   }
   117: 
   118:   //dmpMake ()
   119:   //  メモリダンプリストウインドウを作る
   120:   public static void dmpMake () {
   121: 
   122:     //スクロールテキストエリア
   123:     dmpBoard = ComponentFactory.setPreferredSize (
   124:       ComponentFactory.setFont (new MemoryDumpTextArea (), LnF.lnfMonospacedFont),
   125:       500, 400);
   126:     dmpBoard.setMargin (new Insets (2, 4, 2, 4));
   127:     dmpBoard.setHighlightCursorOn (true);
   128:     dmpTextArea = dmpBoard.getTextArea ();
   129:     dmpTextArea.setEditable (false);
   130: 
   131:     //スピナー
   132:     dmpSpinner = new Hex8Spinner (dmpPageAddress, DMP_ITEM_MASK, true);
   133: 
   134:     //スピナーのチェンジリスナー
   135:     //  スピナーが操作されたとき、そのアドレスの行にテキストエリアのキャレットを移動させる
   136:     //  ページの範囲外になったときはテキストエリアを再構築する
   137:     //  ページの構築中に呼び出されたときは何もしない
   138:     dmpSpinner.addChangeListener (new ChangeListener () {
   139:       @Override public void stateChanged (ChangeEvent ce) {
   140:         if (XEiJ.dbgEventMask == 0) {  //テキストは構築済みでsetTextの中ではない
   141:           dmpUpdate (dmpSpinner.getIntValue (), dmpSupervisorMode, false);
   142:         }
   143:       }
   144:     });
   145: 
   146:     //テキストエリアのキャレットリスナー
   147:     //  テキストエリアがクリックされてキャレットが動いたとき、その行のアドレスをスピナーに設定する
   148:     //  クリックでテキストエリアに移ってしまったフォーカスをスピナーに戻す
   149:     //  ページの構築中に呼び出されたときは何もしない
   150:     //    setText→キャレットリスナー→スピナーのチェンジリスナー→setTextとなるとsetTextの二重呼び出しでエラーが出る
   151:     ComponentFactory.addListener (
   152:       dmpTextArea,
   153:       new CaretListener () {
   154:         @Override public void caretUpdate (CaretEvent ce) {
   155:           if (XEiJ.dbgEventMask == 0) {  //テキストは構築済みでsetTextの中ではない
   156:             int p = ce.getDot ();  //キャレットの位置
   157:             if (p == ce.getMark ()) {  //選択範囲がない
   158:               int i = Arrays.binarySearch (dmpSplitArray, 1, dmpItemCount, p + 1);  //項目の先頭のときも次の項目を検索してから1つ戻る
   159:               i = (i >> 31 ^ i) - 1;  //キャレットがある位置を含む項目の番号
   160:               dmpSpinner.setHintIndex (i);
   161:             }
   162:           }
   163:         }
   164:       });
   165: 
   166:     //テキストエリアのマウスリスナー
   167:     ComponentFactory.addListener (
   168:       dmpTextArea,
   169:       new MouseAdapter () {
   170:         @Override public void mousePressed (MouseEvent me) {
   171:           if (XEiJ.mpuTask == null && me.isPopupTrigger ()) {
   172:             XEiJ.dbgShowPopup (me, dmpTextArea, false);
   173:           }
   174:         }
   175:         @Override public void mouseReleased (MouseEvent me) {
   176:           if (XEiJ.mpuTask == null && me.isPopupTrigger ()) {
   177:             XEiJ.dbgShowPopup (me, dmpTextArea, false);
   178:           }
   179:         }
   180:       });
   181: 
   182:     //テキストエリアのフォーカスリスナー
   183:     ComponentFactory.addListener (
   184:       dmpBoard,
   185:       new FocusAdapter () {
   186:         @Override public void focusGained (FocusEvent fe) {
   187:           dmpBoard.setCaretVisible (true);
   188:         }
   189:         @Override public void focusLost (FocusEvent fe) {
   190:           dmpBoard.setCaretVisible (false);
   191:         }
   192:       });
   193: 
   194:     //テキストエリアのキーリスナー
   195:     ComponentFactory.addListener (
   196:       dmpBoard,
   197:       new KeyAdapter () {
   198:         @Override public void keyTyped (KeyEvent ke) {
   199:           if (XEiJ.dbgEventMask == 0) {  //テキストは構築済みでsetTextの中ではない
   200:             int x = Character.digit (ke.getKeyChar (), 16);
   201:             if (x >= 0) {  //16進数のキーが押された
   202:               int p = dmpTextArea.getCaretPosition ();  //キャレットの位置
   203:               int i = Arrays.binarySearch (dmpSplitArray, 1, dmpItemCount, p + 1);
   204:               i = (i >> 31 ^ i) - 1;  //キャレットがある位置を含む項目の番号
   205:               int t = p - dmpCaretArray[i] - DMP_DATA_START;  //最初のデータの位置からのオフセット
   206:               if (t >= 0 && t < DMP_DATA_ADDRESS.length) {
   207:                 t = DMP_DATA_ADDRESS[t];
   208:                 if (t >= 0) {  //データがある
   209:                   int a = dmpAddressArray[i] + (t >> 1);  //アドレス
   210:                   XEiJ.dbgEventMask++;  //キャレットが動くがキャレットリスナーが反応しないようにする
   211:                   if ((t & 1) == 0) {  //上位
   212:                     try {
   213:                       MC68060.mmuPokeByteData (a, x << 4 | Character.digit (dmpTextArea.getText (p + 1, 1).charAt (0), 16), XEiJ.regSRS);  //書き込む
   214:                     } catch (BadLocationException ble) {
   215:                     }
   216:                     dmpTextArea.replaceRange (XEiJ.fmtHex2 (MC68060.mmuPeekByteZeroData (a, XEiJ.regSRS)), p, p + 2);  //読み出す
   217:                     dmpTextArea.setCaretPosition (p + 1);  //下位の位置
   218:                   } else {  //下位
   219:                     try {
   220:                       MC68060.mmuPokeByteData (a, Character.digit (dmpTextArea.getText (p - 1, 1).charAt (0), 16) << 4 | x, XEiJ.regSRS);  //書き込む
   221:                     } catch (BadLocationException ble) {
   222:                     }
   223:                     dmpTextArea.replaceRange (XEiJ.fmtHex2 (MC68060.mmuPeekByteZeroData (a, XEiJ.regSRS)), p - 1, p + 1);  //読み出す
   224:                     if (t < 31) {
   225:                       dmpTextArea.setCaretPosition (dmpCaretArray[i] + DMP_DATA_START + DMP_DATA_OFFSET[t + 1]);  //次のアドレスの上位の位置
   226:                     }
   227:                   }
   228:                   XEiJ.dbgEventMask--;
   229:                 }
   230:               }
   231:             }
   232:           }
   233:         }
   234:       });
   235: 
   236:     //ボタンのアクションリスナー
   237:     ActionListener listener = new ActionListener () {
   238:       @Override public void actionPerformed (ActionEvent ae) {
   239:         Object source = ae.getSource ();
   240:         switch (ae.getActionCommand ()) {
   241:         case "Reload":
   242:           dmpItemCount = 0;  //再構築要求
   243:           dmpUpdate (dmpAddressArray[dmpItemIndex], dmpSupervisorMode, true);
   244:           break;
   245:         case "User/Supervisor":  //ユーザ/スーパーバイザ
   246:           if (XEiJ.dbgEventMask == 0) {
   247:             dmpUpdate (dmpAddressArray[dmpItemIndex], ((JCheckBox) ae.getSource ()).isSelected () ? 1 : 0, true);
   248:           }
   249:           break;
   250:         case "Second bridge":  //セカンドブリッジ
   251:           dmpSecondBridge = ((JCheckBox) ae.getSource ()).isSelected ();
   252:           dmpItemCount = 0;  //再構築要求
   253:           dmpUpdate (dmpAddressArray[dmpItemIndex], dmpSupervisorMode, true);
   254:           break;
   255:         }
   256:       }
   257:     };
   258: 
   259:     //再読み込みボタン
   260:     JButton reloadButton =
   261:       Multilingual.mlnToolTipText (
   262:         ComponentFactory.createImageButton (
   263:           XEiJ.createImage (
   264:             20, 14,
   265:             "11111111111111111111" +
   266:             "1..................1" +
   267:             "1.......1111.......1" +
   268:             "1......111111.1....1" +
   269:             "1.....11....111....1" +
   270:             "1....11.....111....1" +
   271:             "1....11....1111....1" +
   272:             "1....11............1" +
   273:             "1....11............1" +
   274:             "1.....11....11.....1" +
   275:             "1......111111......1" +
   276:             "1.......1111.......1" +
   277:             "1..................1" +
   278:             "11111111111111111111",
   279:             LnF.lnfRGB[0],
   280:             LnF.lnfRGB[12]),
   281:           "Reload", listener),
   282:         "ja", "再読み込み");
   283: 
   284:     //スーパーバイザチェックボックス
   285:     dmpSupervisorCheckBox =
   286:       Multilingual.mlnToolTipText (
   287:         ComponentFactory.createIconCheckBox (
   288:           dmpSupervisorMode != 0,
   289:           XEiJ.createImage (
   290:             20, 14,
   291:             "22222222222222222222" +
   292:             "2..................2" +
   293:             "2..................2" +
   294:             "2.....11....11.....2" +
   295:             "2.....11....11.....2" +
   296:             "2.....11....11.....2" +
   297:             "2.....11....11.....2" +
   298:             "2.....11....11.....2" +
   299:             "2.....11....11.....2" +
   300:             "2.....11111111.....2" +
   301:             "2.....11111111.....2" +
   302:             "2..................2" +
   303:             "2..................2" +
   304:             "22222222222222222222",
   305:             LnF.lnfRGB[0],
   306:             LnF.lnfRGB[12],
   307:             LnF.lnfRGB[12]),
   308:           XEiJ.createImage (
   309:             20, 14,
   310:             "22222222222222222222" +
   311:             "2..................2" +
   312:             "2..................2" +
   313:             "2.....11111111.....2" +
   314:             "2.....11111111.....2" +
   315:             "2.....11...........2" +
   316:             "2.....11111111.....2" +
   317:             "2.....11111111.....2" +
   318:             "2...........11.....2" +
   319:             "2.....11111111.....2" +
   320:             "2.....11111111.....2" +
   321:             "2..................2" +
   322:             "2..................2" +
   323:             "22222222222222222222",
   324:             LnF.lnfRGB[0],
   325:             LnF.lnfRGB[12],
   326:             LnF.lnfRGB[12]),
   327:           "User/Supervisor", listener),
   328:         "ja", "ユーザ/スーパーバイザ");
   329: 
   330:     //セカンドブリッジチェックボックス
   331:     dmpSecondBridge = false;
   332:     dmpCellophaneColor = new Color ((LnF.lnfRGB[5] & 0x00ffffff) | 0xcc000000, true);
   333:     JCheckBox secondBridgeCheckBox =
   334:       Multilingual.mlnToolTipText (
   335:         ComponentFactory.createIconCheckBox (
   336:           dmpSecondBridge,
   337:           XEiJ.createImage (
   338:             20, 14,
   339:             "22222222222222222222" +
   340:             "2..................2" +
   341:             "2...........11111..2" +
   342:             "2...........11111..2" +
   343:             "2....111111111111..2" +
   344:             "2...........11111..2" +
   345:             "2...........11111..2" +
   346:             "2..................2" +
   347:             "2..................2" +
   348:             "2....1111111111....2" +
   349:             "2..................2" +
   350:             "2..................2" +
   351:             "2..................2" +
   352:             "22222222222222222222",
   353:             LnF.lnfRGB[0],
   354:             LnF.lnfRGB[12],
   355:             LnF.lnfRGB[12]),
   356:           XEiJ.createImage (
   357:             20, 14,
   358:             "22222222222222222222" +
   359:             "2..................2" +
   360:             "2...........11111..2" +
   361:             "2...........11111..2" +
   362:             "2....111111111111..2" +
   363:             "2...........11111..2" +
   364:             "2...........11111..2" +
   365:             "2..11111...........2" +
   366:             "2..11111...........2" +
   367:             "2..111111111111....2" +
   368:             "2..11111...........2" +
   369:             "2..11111...........2" +
   370:             "2..................2" +
   371:             "22222222222222222222",
   372:             LnF.lnfRGB[0],
   373:             LnF.lnfRGB[12],
   374:             LnF.lnfRGB[12]),
   375:           "Second bridge", listener),
   376:         "ja", "セカンドブリッジ");
   377: 
   378:     //ウインドウ
   379:     dmpFrame = Multilingual.mlnTitle (
   380:       ComponentFactory.createRestorableSubFrame (
   381:         Settings.SGS_DMP_FRAME_KEY,
   382:         "Memory dump list",
   383:         null,
   384:         ComponentFactory.createBorderPanel (
   385:           dmpBoard,
   386:           ComponentFactory.createHorizontalBox (
   387:             dmpSpinner,
   388:             reloadButton,
   389:             dmpSupervisorCheckBox,
   390:             secondBridgeCheckBox,
   391:             Box.createHorizontalGlue ()
   392:             )
   393:           )
   394:         ),
   395:       "ja", "メモリダンプリスト");
   396:     ComponentFactory.addListener (
   397:       dmpFrame,
   398:       new WindowAdapter () {
   399:         @Override public void windowClosing (WindowEvent we) {
   400:           XEiJ.dbgVisibleMask &= ~XEiJ.DBG_DMP_VISIBLE_MASK;
   401:         }
   402:       });
   403: 
   404:   }  //dmpMake()
   405: 
   406:   //dmpUpdate (address, supervisor, forceUpdate)
   407:   //  メモリダンプリストウインドウを更新する
   408:   public static void dmpUpdate (int address, int supervisor, boolean forceUpdate) {
   409: 
   410:     XEiJ.dbgEventMask++;  //構築開始
   411: 
   412:     if (address == -1) {  //spを表示
   413:       address = XEiJ.regRn[15];
   414:       forceUpdate = true;
   415:     }
   416: 
   417:     if (supervisor == -1) {
   418:       supervisor = XEiJ.regSRS;
   419:       forceUpdate = true;
   420:     }
   421: 
   422:     if ((dmpSupervisorMode != 0) != (supervisor != 0)) {  //ユーザ/スーパーバイザが一致しない
   423:       dmpSupervisorMode = supervisor;
   424:       forceUpdate = true;
   425:       if (dmpSupervisorCheckBox.isSelected () != (supervisor != 0)) {
   426:         dmpSupervisorCheckBox.setSelected (supervisor != 0);
   427:       }
   428:     }
   429: 
   430:     if (forceUpdate) {  //再構築要求
   431:       dmpItemCount = 0;
   432:     }
   433: 
   434:     if (dmpItemCount != 0) {  //構築前または再構築要求のいずれでもない
   435:       int i = Arrays.binarySearch (dmpAddressArray, 1, dmpItemCount, address + 1);  //項目の先頭のときも次の項目を検索してから1つ戻る
   436:       i = (i >> 31 ^ i) - 1;  //目的のアドレスを含む項目の番号
   437:       if (0 < i && i < dmpItemCount - 1 &&  //ページの内側
   438:           dmpAddressArray[i] == address) {  //項目の先頭
   439: 
   440:         //再構築しない
   441: 
   442:         if (dmpItemIndex != i) {  //キャレットがある項目を変更する必要がある
   443:           dmpItemIndex = i;
   444:           dmpTextArea.setCaretPosition (dmpCaretArray[i]);
   445:         }
   446: 
   447:         XEiJ.dbgEventMask--;  //構築終了
   448: 
   449:         return;
   450:       }
   451:     }
   452: 
   453:     //再構築する
   454: 
   455:     //構築前または再構築要求または先頭または末尾の番兵が選択された
   456:     //  0x00000000の境界を跨ぐとき反対側を指すことがあるので先頭と末尾の番兵を区別しない
   457:     address &= DMP_ITEM_MASK;  //目的のアドレスを含む項目の先頭アドレス
   458:     dmpPageAddress = address & DMP_PAGE_MASK;  //ページの先頭アドレス
   459:     int pageEndAddress = dmpPageAddress + DMP_PAGE_SIZE;  //ページの末尾アドレス。0になることがある
   460: 
   461:     //先頭の番兵
   462:     dmpAddressArray[0] = dmpPageAddress - DMP_ITEM_SIZE;  //昇順を維持するためマスクしない
   463:     dmpSplitArray[0] = 0;
   464:     dmpCaretArray[0] = 0;
   465:     StringBuilder sb = new StringBuilder (
   466:       //         1111111111222222222233333333334444444444555555555566666666667777777777
   467:       //1234567890123456789012345678901234567890123456789012345678901234567890123456789
   468:       //xxxxxxx  xx xx xx xx  xx xx xx xx  xx xx xx xx  xx xx xx xx  ................
   469:       "▲        +0 +1 +2 +3  +4 +5 +6 +7  +8 +9 +A +B  +C +D +E +F  0123456789ABCDEF\n");
   470:     int itemCount = 1;  //項目数
   471:     int itemAddress = dmpPageAddress;  //項目の先頭アドレス
   472:     boolean bridge = false;  //1=先頭はSJISの2バイトコードの2バイト目
   473:     for (int k = 1; k <= 200; k++) {  //最大100文字200バイトまで遡る。長い文章でもSJISの2バイトコードの1バイト目ばかりが続くわけではないので十分だろう
   474:       int h = MC68060.mmuPeekByteZeroData (itemAddress - k, supervisor);
   475:       if ((0x81 <= h && h <= 0x9f) ||
   476:           (0xe0 <= h && h <= 0xef)) {  //SJISの2バイトコードの1バイト目
   477:         bridge = !bridge;
   478:       } else {
   479:         break;
   480:       }
   481:     }
   482: 
   483:     do {
   484:       int itemEndAddress = itemAddress + DMP_ITEM_SIZE;  //項目の末尾アドレス
   485:       //項目の開始
   486:       if (itemAddress == address) {
   487:         dmpItemIndex = itemCount;  //目的のアドレスを含む項目の番号
   488:       }
   489:       dmpAddressArray[itemCount] = itemAddress;  //項目の先頭アドレス
   490:       dmpSplitArray[itemCount] = sb.length ();  //項目を区切る位置
   491:       dmpCaretArray[itemCount] = sb.length ();  //項目が選択されたときキャレットを移動させる位置
   492:       //アドレス
   493:       XEiJ.fmtHex8 (DMP_BASE,  0, itemAddress);
   494:       //データ
   495:       XEiJ.fmtHex2 (DMP_BASE, 10, MC68060.mmuPeekByteZeroData (itemAddress     , supervisor));
   496:       XEiJ.fmtHex2 (DMP_BASE, 13, MC68060.mmuPeekByteZeroData (itemAddress +  1, supervisor));
   497:       XEiJ.fmtHex2 (DMP_BASE, 16, MC68060.mmuPeekByteZeroData (itemAddress +  2, supervisor));
   498:       XEiJ.fmtHex2 (DMP_BASE, 19, MC68060.mmuPeekByteZeroData (itemAddress +  3, supervisor));
   499:       XEiJ.fmtHex2 (DMP_BASE, 23, MC68060.mmuPeekByteZeroData (itemAddress +  4, supervisor));
   500:       XEiJ.fmtHex2 (DMP_BASE, 26, MC68060.mmuPeekByteZeroData (itemAddress +  5, supervisor));
   501:       XEiJ.fmtHex2 (DMP_BASE, 29, MC68060.mmuPeekByteZeroData (itemAddress +  6, supervisor));
   502:       XEiJ.fmtHex2 (DMP_BASE, 32, MC68060.mmuPeekByteZeroData (itemAddress +  7, supervisor));
   503:       XEiJ.fmtHex2 (DMP_BASE, 36, MC68060.mmuPeekByteZeroData (itemAddress +  8, supervisor));
   504:       XEiJ.fmtHex2 (DMP_BASE, 39, MC68060.mmuPeekByteZeroData (itemAddress +  9, supervisor));
   505:       XEiJ.fmtHex2 (DMP_BASE, 42, MC68060.mmuPeekByteZeroData (itemAddress + 10, supervisor));
   506:       XEiJ.fmtHex2 (DMP_BASE, 45, MC68060.mmuPeekByteZeroData (itemAddress + 11, supervisor));
   507:       XEiJ.fmtHex2 (DMP_BASE, 49, MC68060.mmuPeekByteZeroData (itemAddress + 12, supervisor));
   508:       XEiJ.fmtHex2 (DMP_BASE, 52, MC68060.mmuPeekByteZeroData (itemAddress + 13, supervisor));
   509:       XEiJ.fmtHex2 (DMP_BASE, 55, MC68060.mmuPeekByteZeroData (itemAddress + 14, supervisor));
   510:       XEiJ.fmtHex2 (DMP_BASE, 58, MC68060.mmuPeekByteZeroData (itemAddress + 15, supervisor));
   511:       sb.append (DMP_BASE);
   512:       //キャラクタ
   513:       boolean nextBridge = false;
   514:       int a;
   515:       if (!bridge) {  //行末からはみ出して書いた文字はない
   516:         sb.append (' ');
   517:         a = itemAddress;
   518:       } else if (itemAddress == dmpPageAddress ||  //1行目は常に
   519:                  dmpSecondBridge) {  //2行目以降は設定に従い、行末からはみ出して書いた文字を行頭にも書く
   520:         a = itemAddress - 1;
   521:       } else {  //行末からはみ出して書いた文字を行頭には書かない
   522:         sb.append ("  ");
   523:         a = itemAddress + 1;
   524:       }
   525:       for (; a < itemEndAddress; a++) {
   526:         int h = MC68060.mmuPeekByteZeroData (a, supervisor);
   527:         int c;
   528:         if ((0x81 <= h && h <= 0x9f) ||
   529:             (0xe0 <= h && h <= 0xef)) {  //SJISの2バイトコードの1バイト目
   530:           int l = MC68060.mmuPeekByteZeroData (a + 1, supervisor);  //これは範囲外になる場合がある
   531:           if (0x40 <= l && l <= 0xfc && l != 0x7f) {  //SJISの2バイトコードの2バイト目
   532:             c = CharacterCode.chrSJISToChar[h << 8 | l];  //2バイトで変換する
   533:             if (c == 0) {  //対応する文字がない
   534:               c = '※';
   535:             } else if (c == 0x3000) {  //全角空白
   536:               c = '\u2b1a';  //U+2B1A Dotted Square
   537:             }
   538:             a++;
   539:             if (a == itemEndAddress) {  //行末からはみ出した
   540:               nextBridge = true;
   541:             }
   542:           } else {  //SJISの2バイトコードの2バイト目ではない
   543:             c = '.';  //SJISの2バイトコードの1バイト目ではなかった
   544:           }
   545:         } else {  //SJISの2バイトコードの1バイト目ではない
   546:           c = CharacterCode.chrSJISToChar[h];  //1バイトで変換する
   547:           if (c < 0x20 || c == 0x7f) {  //対応する文字がないまたは制御コード
   548:             c = '.';
   549:           }
   550:         }
   551:         sb.append ((char) c);
   552:       }  //for a
   553:       sb.append ('\n');
   554:       //項目の終了
   555:       itemCount++;
   556:       itemAddress = itemEndAddress;
   557:       bridge = nextBridge;
   558:     } while (itemAddress < pageEndAddress);
   559: 
   560:     //末尾の番兵
   561:     dmpAddressArray[itemCount] = itemAddress;  //昇順を維持するためマスクしない
   562:     dmpSplitArray[itemCount] = sb.length ();
   563:     dmpCaretArray[itemCount] = sb.length ();
   564:     sb.append (
   565:       //         1111111111222222222233333333334444444444555555555566666666667777777777
   566:       //1234567890123456789012345678901234567890123456789012345678901234567890123456789
   567:       "▼        +0 +1 +2 +3  +4 +5 +6 +7  +8 +9 +A +B  +C +D +E +F  0123456789ABCDEF");
   568:     itemCount++;
   569:     dmpItemCount = itemCount;
   570: 
   571:     //テキスト
   572:     dmpTextArea.setText (sb.toString ());
   573:     dmpTextArea.setCaretPosition (dmpCaretArray[dmpItemIndex]);
   574: 
   575:     //スピナー
   576:     dmpSpinner.setHintArray (dmpAddressArray, itemCount);
   577:     dmpSpinner.setHintIndex (dmpItemIndex);
   578: 
   579:     XEiJ.dbgEventMask--;  //構築終了
   580: 
   581:   }  //dmpUpdate(int,int,boolean)
   582: 
   583: }  //class MemoryDumpList
   584: 
   585: 
   586: