ScrollTextArea.java
     1: //========================================================================================
     2: //  ScrollTextArea.java
     3: //    en:Text area with scroll bars -- It is a modified JScrollPage that has a JTextArea as the view.
     4: //    ja:スクロールバー付きテキストエリア -- JTextAreaをビューに持つJScrollPaneです。
     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: //----------------------------------------------------------------------------------------
    14: //  追加機能
    15: //    背景にグリッドを表示できる
    16: //    ハイライトエリアを表示できる
    17: //    ハイライトカーソルを表示できる
    18: //    アンダーラインカーソルを表示できる
    19: //----------------------------------------------------------------------------------------
    20: 
    21: package xeij;
    22: 
    23: import java.awt.*;  //BasicStroke,BorderLayout,BoxLayout,Color,Component,Container,Cursor,Desktop,Dimension,Font,Frame,Graphics,Graphics2D,GraphicsDevice,GraphicsEnvironment,GridLayout,Image,Insets,Paint,Point,Rectangle,RenderingHints,Robot,Shape,Stroke,TexturePaint,Toolkit
    24: 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
    25: import java.awt.geom.*;  //AffineTransform,GeneralPath,Point2D,Rectangle2D
    26: import java.awt.image.*;  //BufferedImage,DataBuffer,DataBufferByte,DataBufferInt,IndexColorModel
    27: 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
    28: import javax.swing.event.*;  //CaretListener,ChangeEvent,ChangeListener,DocumentEvent,DocumentListener,ListSelectionListener
    29: import javax.swing.text.*;  //AbstractDocument,BadLocationException,DefaultCaret,Document,DocumentFilter,JTextComponent,ParagraphView,Style,StyleConstants,StyleContext,StyledDocument
    30: 
    31: public class ScrollTextArea extends JScrollPane {
    32: 
    33:   private boolean opaqueOn;  //true=背景を表示する
    34:   private boolean gridOn;  //true=グリッドを表示する
    35:   private boolean highlightCursorOn;  //true=ハイライトカーソルを表示する
    36:   private boolean underlineCursorOn;  //true=アンダーラインカーソルを表示する
    37: 
    38:   private Color foregroundColor;  //文字の色
    39:   private Color backgroundColor;  //背景の色
    40:   private Color rowGridColor;  //行グリッドの色
    41:   private Color columnGridColor;  //列グリッドの色
    42:   private Color tabColumnGridColor;  //タブ列グリッドの色
    43:   private Color highlightAreaColor;  //ハイライトエリアの色
    44:   private Color highlightCursorColor;  //ハイライトカーソルの色
    45:   private Color underlineCursorColor;  //アンダーラインカーソルの色
    46: 
    47:   private Paint gridPaint;  //グリッドのテクスチャペイント
    48:   private int highlightAreaStart;  //ハイライトエリアの開始位置
    49:   private int highlightAreaEnd;  //ハイライトエリアの終了位置。-1=ハイライトエリアなし
    50:   private Stroke underlineCursorStroke;  //アンダーラインカーソルのストローク
    51: 
    52:   private JTextArea textArea;
    53: 
    54:   private int fontWidth;
    55:   private int fontHeight;
    56:   private int marginTop;
    57:   private int marginLeft;
    58: 
    59:   private DefaultCaret caret;
    60: 
    61:   //コンストラクタ
    62:   //  super.setOpaque (false)で警告this-escapeが出るので@SuppressWarnings ("this-escape")を追加
    63:   @SuppressWarnings ("this-escape") public ScrollTextArea () {
    64:     super ();
    65: 
    66:     opaqueOn = true;
    67:     gridOn = true;
    68:     highlightCursorOn = false;
    69:     underlineCursorOn = false;
    70: 
    71:     foregroundColor      = new Color (LnF.lnfRGB[14]);
    72:     backgroundColor      = new Color (LnF.lnfRGB[0]);
    73:     rowGridColor         = new Color (LnF.lnfRGB[2]);
    74:     columnGridColor      = new Color (LnF.lnfRGB[1]);
    75:     tabColumnGridColor   = new Color (LnF.lnfRGB[2]);
    76:     highlightAreaColor   = new Color (LnF.lnfRGB[3]);
    77:     highlightCursorColor = new Color (LnF.lnfRGB[6]);
    78:     underlineCursorColor = new Color (LnF.lnfRGB[10]);
    79: 
    80:     gridPaint = backgroundColor;
    81:     highlightAreaStart = highlightAreaEnd = -1;
    82:     underlineCursorStroke = new BasicStroke (1.0F,
    83:                                              BasicStroke.CAP_SQUARE,
    84:                                              BasicStroke.JOIN_MITER,
    85:                                              10.0F,
    86:                                              new float[] { 0.0F, 2.0F },
    87:                                              0.0F);
    88: 
    89:     textArea = new JTextArea () {
    90:       //描画
    91:       @Override public void paintComponent (Graphics g) {
    92:         Graphics2D g2 = (Graphics2D) g;
    93:         int width = getWidth ();
    94:         int height = getHeight ();
    95:         //背景
    96:         if (opaqueOn) {
    97:           g2.setPaint (gridOn ? gridPaint : backgroundColor);
    98:           g2.fillRect (0, 0, width, height);
    99:         }
   100:         paintAfterBackground (this, g2);
   101:         //ハイライトエリア
   102:         if (0 <= highlightAreaEnd) {
   103:           try {
   104:             //Rectangle startRect = modelToView (highlightAreaStart).getBounds ();  //modelToView2Dは9から
   105:             Rectangle startRect = modelToView2D (highlightAreaStart).getBounds ();  //modelToView2Dは9から
   106:             //Rectangle endRect = modelToView (highlightAreaEnd).getBounds ();  //modelToView2Dは9から
   107:             Rectangle endRect = modelToView2D (highlightAreaEnd).getBounds ();  //modelToView2Dは9から
   108:             Insets margin = getMargin ();
   109:             g2.setPaint (highlightAreaColor);
   110:             g2.fillRect (margin.left, startRect.y,
   111:                          width - margin.left - margin.right, endRect.y - startRect.y + endRect.height);
   112:           } catch (BadLocationException ble) {
   113:           }
   114:         }
   115:         //ラインカーソル
   116:         if (highlightCursorOn || underlineCursorOn) {
   117:           try {
   118:             //Rectangle caretRect = modelToView (getCaretPosition ()).getBounds ();  //modelToView2Dは9から
   119:             Rectangle caretRect = modelToView2D (getCaretPosition ()).getBounds ();  //modelToView2Dは9から
   120:             Insets margin = getMargin ();
   121:             if (highlightCursorOn) {
   122:               g2.setPaint (highlightCursorColor);
   123:               g2.fillRect (margin.left, caretRect.y,
   124:                            width - margin.left - margin.right, caretRect.height);
   125:             }
   126:             if (underlineCursorOn) {
   127:               g2.setPaint (underlineCursorColor);
   128:               g2.setStroke (underlineCursorStroke);
   129:               g2.drawLine (margin.left, caretRect.y + caretRect.height - 1,
   130:                            width - margin.right - 1, caretRect.y + caretRect.height - 1);
   131:             }
   132:           } catch (BadLocationException ble) {
   133:           }
   134:         }
   135:         //テキスト
   136:         super.paintComponent (g);
   137:         paintAfterText (this, g2);
   138:       }
   139:     };
   140:     textArea.setOpaque (false);  //背景色を塗らせない
   141:     super.setOpaque (false);
   142: 
   143:     textArea.setForeground (foregroundColor);
   144:     textArea.setSelectionColor (new Color (LnF.lnfRGB[7]));  //選択領域の背景の色
   145:     //textArea.setSelectedTextColor (new Color (LnF.lnfRGB[14]));  //選択領域のテキストの色
   146: 
   147:     Font font = textArea.getFont ();
   148:     fontWidth = font.getSize () + 1 >> 1;
   149:     fontHeight = textArea.getFontMetrics (font).getHeight ();
   150:     Insets margin = textArea.getMargin ();
   151:     marginTop = margin.top;
   152:     marginLeft = margin.left;
   153: 
   154:     createGrid ();
   155: 
   156:     caret = new DefaultCaret () {
   157:       @Override protected void damage (Rectangle r) {
   158:         if (r != null) {
   159:           if (highlightCursorOn || underlineCursorOn) {
   160:             x = 0;
   161:             y = r.y;
   162:             width = textArea.getWidth ();
   163:             height = r.height;
   164:             textArea.repaint ();
   165:           } else {
   166:             super.damage (r);
   167:           }
   168:         }
   169:       }
   170:     };
   171:     caret.setBlinkRate (500);
   172:     textArea.setCaret (caret);
   173: 
   174:     getViewport ().setView (textArea);
   175:   }
   176: 
   177:   //paintAfterBackground (textArea, g2)
   178:   //  背景を描画した後に呼び出される
   179:   public void paintAfterBackground (JTextArea textArea, Graphics2D g2) {
   180:   }
   181: 
   182:   //paintAfterText (textArea, g2)
   183:   //  テキストを描画した後に呼び出される
   184:   public void paintAfterText (JTextArea textArea, Graphics2D g2) {
   185:   }
   186: 
   187:   //グリッドを作る
   188:   //  フォントの右下にグリッドを表示する
   189:   //  フォント(6,13),マージン(3,3,3,3)のとき
   190:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   191:   //  ................................................
   192:   //  r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.r.
   193:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   194:   //  ................................................
   195:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   196:   //  ................................................
   197:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   198:   //  ................................................
   199:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   200:   //  ................................................
   201:   //  ..t.....c.....c.....c.....c.....c.....c.....c...
   202:   //  ................................................
   203:   private void createGrid () {
   204:     int backgroundRGB = backgroundColor.getRGB ();
   205:     int rowGridRGB = rowGridColor.getRGB ();
   206:     int columnGridRGB = columnGridColor.getRGB ();
   207:     int tabColumnGridRGB = tabColumnGridColor.getRGB ();
   208:     int imageWidth = fontWidth * 8;
   209:     BufferedImage image = new BufferedImage (imageWidth, fontHeight, BufferedImage.TYPE_INT_RGB);
   210:     int[] bitmap = ((DataBufferInt) image.getRaster ().getDataBuffer ()).getData ();
   211:     for (int y = 0; y < fontHeight; y++) {
   212:       for (int x = 0; x < imageWidth; x++) {
   213:         bitmap[(marginLeft + x) % imageWidth + imageWidth * ((marginTop + y) % fontHeight)] =
   214:           (((y ^ fontHeight) & 1) == 0 ? backgroundRGB :
   215:            y == fontHeight - 1 ? ((x % fontWidth ^ fontWidth) & 1) != 0 ? rowGridRGB : backgroundRGB :
   216:            x == imageWidth - 1 ? tabColumnGridRGB : x % fontWidth == fontWidth - 1 ? columnGridRGB : backgroundRGB);
   217:       }
   218:     }
   219:     gridPaint = new TexturePaint (image, new Rectangle (0, 0, imageWidth, fontHeight));
   220:   }
   221: 
   222:   //サイズ
   223:   @Override public void setMaximumSize (Dimension size) {
   224:     super.setMaximumSize (size);
   225:     getViewport ().setMaximumSize (size);
   226:   }
   227:   @Override public void setMinimumSize (Dimension size) {
   228:     super.setMinimumSize (size);
   229:     getViewport ().setMinimumSize (size);
   230:   }
   231:   @Override public void setPreferredSize (Dimension size) {
   232:     super.setPreferredSize (size);
   233:     getViewport ().setPreferredSize (size);
   234:   }
   235: 
   236:   //マージン
   237:   public Insets getMargin () {
   238:     return textArea.getMargin ();
   239:   }
   240:   public void setMargin (Insets m) {
   241:     textArea.setMargin (m);
   242:     int t = m.top;
   243:     int l = m.left;
   244:     if (marginTop != t || marginLeft != l) {
   245:       marginTop = t;
   246:       marginLeft = l;
   247:       createGrid ();
   248:     }
   249:   }
   250: 
   251:   //背景
   252:   @Override public boolean isOpaque () {
   253:     return opaqueOn;
   254:   }
   255:   @Override public void setOpaque (boolean opaque) {
   256:     opaqueOn = opaque;
   257:   }
   258: 
   259:   //色
   260:   @Override public Color getForeground () {
   261:     return foregroundColor;
   262:   }
   263:   @Override public void setForeground (Color color) {
   264:     foregroundColor = color;
   265:     if (textArea != null) {  //スーパークラスのコンストラクタからの呼び出しではない
   266:       textArea.setForeground (color);
   267:     }
   268:   }
   269:   @Override public Color getBackground () {
   270:     return backgroundColor;
   271:   }
   272:   @Override public void setBackground (Color color) {
   273:     backgroundColor = color;
   274:     if (textArea != null) {  //スーパークラスのコンストラクタからの呼び出しではない
   275:       createGrid ();
   276:     }
   277:   }
   278: 
   279:   //フォント
   280:   @Override public Font getFont () {
   281:     if (textArea != null) {  //スーパークラスのコンストラクタからの呼び出しではない
   282:       return textArea.getFont ();
   283:     }
   284:     return super.getFont ();
   285:   }
   286:   @Override public void setFont (Font font) {
   287:     if (textArea != null) {  //スーパークラスのコンストラクタからの呼び出しではない
   288:       textArea.setFont (font);
   289:       int w = font.getSize () + 1 >> 1;
   290:       int h = textArea.getFontMetrics (font).getHeight ();
   291:       if (fontWidth != w || fontHeight != h) {
   292:         fontWidth = w;
   293:         fontHeight = h;
   294:         createGrid ();
   295:       }
   296:     }
   297:   }
   298: 
   299:   //イベント
   300:   public void addCaretListener (CaretListener listener) {
   301:     textArea.addCaretListener (listener);
   302:   }
   303:   public void removeCaretListener (CaretListener listener) {
   304:     textArea.removeCaretListener (listener);
   305:   }
   306:   public void addDocumentListener (DocumentListener listener) {
   307:     textArea.getDocument ().addDocumentListener (listener);
   308:   }
   309:   public void removeDocumentListener (DocumentListener listener) {
   310:     textArea.getDocument ().removeDocumentListener (listener);
   311:   }
   312:   @Override public void addKeyListener (KeyListener listener) {
   313:     textArea.addKeyListener (listener);
   314:   }
   315:   @Override public void removeKeyListener (KeyListener listener) {
   316:     textArea.removeKeyListener (listener);
   317:   }
   318:   @Override public void addMouseListener (MouseListener listener) {
   319:     textArea.addMouseListener (listener);
   320:   }
   321:   @Override public void removeMouseListener (MouseListener listener) {
   322:     textArea.removeMouseListener (listener);
   323:   }
   324:   @Override public void addFocusListener (FocusListener listener) {
   325:     textArea.addFocusListener (listener);
   326:   }
   327:   @Override public void removeFocusListener (FocusListener listener) {
   328:     textArea.removeFocusListener (listener);
   329:   }
   330: 
   331:   //折り返し
   332:   public boolean getLineWrap () {
   333:     return textArea.getLineWrap ();
   334:   }
   335:   public void setLineWrap (boolean wrap) {
   336:     textArea.setLineWrap (wrap);
   337:   }
   338: 
   339:   //キャレット
   340:   public Color getCaretColor () {
   341:     return textArea.getCaretColor ();
   342:   }
   343:   public void setCaretColor (Color color) {
   344:     textArea.setCaretColor (color);
   345:   }
   346:   public int getCaretPosition () {
   347:     return textArea.getCaretPosition ();
   348:   }
   349:   public void setCaretPosition (int pos) {
   350:     textArea.setCaretPosition (pos);
   351:   }
   352:   public boolean isCaretVisible () {
   353:     return caret.isVisible ();
   354:   }
   355:   public void setCaretVisible (boolean visible) {
   356:     caret.setVisible (visible);
   357:   }
   358: 
   359:   //テキスト
   360:   public JTextArea getTextArea () {
   361:     return textArea;
   362:   }
   363:   public String getText () {
   364:     return textArea.getText ();
   365:   }
   366:   public void setText (String text) {
   367:     highlightAreaEnd = -1;
   368:     textArea.setText (text);
   369:   }
   370: 
   371:   //編集
   372:   public void append (String text) {
   373:     highlightAreaEnd = -1;
   374:     textArea.append (text);
   375:   }
   376:   public void insert (String text, int pos) {
   377:     highlightAreaEnd = -1;
   378:     textArea.insert (text, pos);
   379:   }
   380:   public void replaceRange (String text, int start, int end) {
   381:     highlightAreaEnd = -1;
   382:     textArea.replaceRange (text, start, end);
   383:   }
   384: 
   385:   //選択
   386:   public void selectAll () {
   387:     textArea.selectAll ();
   388:   }
   389:   public String getSelectedText () {
   390:     return textArea.getSelectedText ();
   391:   }
   392:   public int getSelectionStart () {
   393:     return textArea.getSelectionStart ();
   394:   }
   395:   public int getSelectionEnd () {
   396:     return textArea.getSelectionEnd ();
   397:   }
   398: 
   399:   //モード
   400:   public boolean isEditable () {
   401:     return textArea.isEditable ();
   402:   }
   403:   public void setEditable (boolean editable) {
   404:     textArea.setEditable (editable);
   405:   }
   406: 
   407:   //グリッド
   408:   public boolean isGridOn () {
   409:     return gridOn;
   410:   }
   411:   public void setGridOn (boolean on) {
   412:     gridOn = on;
   413:   }
   414:   public Color getRowGridColor () {
   415:     return rowGridColor;
   416:   }
   417:   public void setRowGridColor (Color color) {
   418:     rowGridColor = color;
   419:     createGrid ();
   420:   }
   421:   public Color getColumnGridColor () {
   422:     return columnGridColor;
   423:   }
   424:   public void setColumnGridColor (Color color) {
   425:     columnGridColor = color;
   426:     createGrid ();
   427:   }
   428:   public Color getTabColumnGridColor () {
   429:     return tabColumnGridColor;
   430:   }
   431:   public void setTabColumnGridColor (Color color) {
   432:     tabColumnGridColor = color;
   433:     createGrid ();
   434:   }
   435: 
   436:   //ハイライトエリア
   437:   public void setHighlightArea (int start, int end) {
   438:     highlightAreaStart = start;
   439:     highlightAreaEnd = end;
   440:   }
   441: 
   442:   //ハイライトカーソル
   443:   public boolean isHighlightCursorOn () {
   444:     return highlightCursorOn;
   445:   }
   446:   public void setHighlightCursorOn (boolean on) {
   447:     highlightCursorOn = on;
   448:   }
   449:   public Color getHighlightCursorColor () {
   450:     return highlightCursorColor;
   451:   }
   452:   public void setHighlightCursorColor (Color color) {
   453:     highlightCursorColor = color;
   454:   }
   455: 
   456:   //アンダーラインカーソル
   457:   public boolean isUnderlineCursorOn () {
   458:     return underlineCursorOn;
   459:   }
   460:   public void setUnderlineCursorOn (boolean on) {
   461:     underlineCursorOn = on;
   462:   }
   463:   public Color getUnderlineCursorColor () {
   464:     return underlineCursorColor;
   465:   }
   466:   public void setUnderlineCursorColor (Color color) {
   467:     underlineCursorColor = color;
   468:   }
   469: 
   470: }  //class ScrollTextArea
   471: 
   472: 
   473: