Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8227708

[macos] repaint() of a custom component in partially covered JInternalFrame swamps CPU

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      Mac OSX 10.14.4 but also happened in earlier versions.
      Java 12 but also happened in Java 8 and Java 10.


      A DESCRIPTION OF THE PROBLEM :
      I create two JInternalFrames in a JDesktopPane and put some custom checkboxes in one.
      A thread is causing the background of the boxes to change color like a drum sequencer.
      If the checkboxes are in the foreground then it works fine.
      If the second frame covers part of the first frame then the CPU usage goes over 100% and
      the UI becomes unusable.
      The front window needs to be selected for the bug to occur.



      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Launch a terminal window on Mac OS and enter: top
      Compile and run this program.
      Arrange the two windows so you can see both 'top' and this application.
      Let CPU usage settle for about 10 seconds.
      Note "java" CPU usage in top is < 20%
      Click on "patch2" window to select it.


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
       java CPU usage still < 20%
      ACTUAL -
      java CPU usage > 100%
      Java UI becomes very sluggish.

      ---------- BEGIN SOURCE ----------
      /*
       * Simple application that reproduces a possible bug in Java.
       *
       * Author: Phil Burk 2019
       * License: Free for all uses without restriction and without warranty of any kind.
       */

      package com.mobileer.bugbusypaint;

      import java.awt.Color;
      import java.awt.Dimension;
      import java.awt.Graphics;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      import java.awt.event.MouseAdapter;
      import java.awt.event.MouseEvent;
      import java.util.concurrent.CopyOnWriteArrayList;

      import javax.swing.BoxLayout;
      import javax.swing.JDesktopPane;
      import javax.swing.JFrame;
      import javax.swing.JInternalFrame;
      import javax.swing.JPanel;
      import javax.swing.SwingUtilities;


      public class BugBusyPaint
      {
          private final static int NUM_ROWS = 8;
          private final static int NUM_COLUMNS = 16;
          private final static int PATCH_WIDTH = 600;
          private final static int PATCH_HEIGHT = 500;

          private JFrame mainFrame;
          private JDesktopPane desktop;
          private Thread thread;
          BitMaskJackView[] maskViews = new BitMaskJackView[NUM_ROWS];

          public static void main(String[] args) {
           BugBusyPaint app = new BugBusyPaint();
              app.execute();
          }

          private void execute() {
              init();
              start();
          }

          private void init() {

              mainFrame = new JFrame("Test");
              mainFrame.setLocation(40, 50);
              mainFrame.setSize(800, 600);

              // Create a desktop to contain the internal frames.
              desktop = new JDesktopPane();
              desktop.setBackground(Color.ORANGE);
              desktop.setOpaque(true);
              mainFrame.setContentPane(desktop);

              // Create a patch frame with custom check boxes.
              JInternalFrame patch1 = new JInternalFrame("patch1",
               false, // resizable
               false, // closable
               false, // maximizable
               false);// iconifiable
              patch1.setLocation(50, 30);
              patch1.setSize(PATCH_WIDTH, PATCH_HEIGHT);
              patch1.setBackground(Color.CYAN);
              // Turning off shadows does not help.
              // patch1.putClientProperty("JInternalFrame.frameType", "normal");
              desktop.add(patch1);

              // Fill the first patch with check boxes
              JPanel panel = new JPanel();
              panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
              for (int i = 0; i < maskViews.length; i++) {
               maskViews[i] = new BitMaskJackView(NUM_COLUMNS);
               panel.add(maskViews[i]);
              }
              patch1.add( panel );

              // Create a second patch to partially cover the first one.
              JInternalFrame patch2 = new JInternalFrame("patch2",
               false, // resizable
               false, // closable
               false, // maximizable
               false);// iconifiable
              patch2.setLocation(100, 30);
              patch2.setSize(PATCH_WIDTH, PATCH_HEIGHT);
              patch2.setBackground(Color.DARK_GRAY);
              // Turning off shadows does not help.
              // patch2.putClientProperty("JInternalFrame.frameType", "normal");
              desktop.add(patch2);

              patch1.setVisible(true);
              patch2.setVisible(true);

              mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          }


          private void start() {
              mainFrame.setVisible(true);
              startSequencer();
          }

      private void startSequencer() {
      thread = new Thread() {
                  @Override
                  public void run() {
                      try {
                       int column = 0;
                          // Process commands from queue.
                          while (true) {
                           sleep(200);
                           final int j = column;
                              SwingUtilities.invokeLater(new Runnable() {
                                  @Override
                                  public void run() {
                                   for (BitMaskJackView maskView : maskViews) {
                               maskView.indexChanged(j);
                                   }
                                  }
                              });
                           if (++column >= 16) column = 0;
                          }
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              };
              thread.start();
      }

      }

      // =====================================================================

      @SuppressWarnings("serial")
      class BitMaskJackView extends JPanel {
          private BooleanBox[] mBoxes;
          private int previousIndex;

          public BitMaskJackView(int numColumns) {
              BoxLayout layout = new BoxLayout(this, BoxLayout.X_AXIS);
              setLayout(layout);
              mBoxes = new BooleanBox[numColumns];
              for (int i = 0; i < numColumns; i++) {
                  final BooleanBox checkbox = new BooleanBox();
                  add(checkbox);
                  mBoxes[i] = checkbox;
              }
          }

          public void indexChanged(int index) {
              mBoxes[previousIndex].setCurrent(false);
              mBoxes[index].setCurrent(true);
              if (mBoxes[previousIndex].isVisible()) {
                  mBoxes[previousIndex].repaint();
              }
              if (mBoxes[index].isVisible()) {
                  mBoxes[index].repaint();
              }
              previousIndex = index;
          }
      }

      //=====================================================================

      class BooleanBox extends JPanel {
          private boolean mSelected;
          private boolean mCurrent;
          private CopyOnWriteArrayList<ActionListener> mActionListeners = new CopyOnWriteArrayList<ActionListener>();
          private static final int WIDTH = 20;
          private static final int HEIGHT = 20;
          private static final int MARGIN = 4;

          private static int mPaintCounter = 0;

          public BooleanBox() {
              addMouseListener(new MyMouseListener());
              Dimension dim = new Dimension(WIDTH, HEIGHT);
              setMinimumSize(dim);
              setPreferredSize(dim);
          }

          @Override
          public void paintComponent(Graphics g) {
              super.paintComponent(g);

              // Draw background based on whether is it a currently active box.
              Color backColor = (mCurrent) ? Color.ORANGE : getBackground();
              g.setColor(backColor);
              g.fillRect(0, 0, getWidth(), getHeight());

              // Draw a circle.
              int x = MARGIN / 2;
              int y = MARGIN / 2;
              int w = getWidth() - MARGIN;
              int h = getHeight() - MARGIN;

              int diameter = (w < h) ? w : h; // min
              if (mSelected) {
                  g.setColor(Color.CYAN);
                  g.fillOval(x, y, diameter, diameter);
              }
              g.setColor(Color.BLACK);
              g.drawOval(x, y, diameter, diameter);
              // System.out.println("paint BooleanBox #" + mPaintCounter++);
          }

          private class MyMouseListener extends MouseAdapter {
              @Override
              public void mouseClicked(MouseEvent e) {
                  setSelected(!isSelected()); // toggle
                  fireActionListeners();
              }
          }

          public boolean isSelected() {
              return mSelected;
          }

          public boolean isCurrent() {
              return mCurrent;
          }

          public void setCurrent(boolean current) {
              this.mCurrent = current;
          }

          public void setSelected(boolean selected) {
              this.mSelected = selected;
              repaint();
          }

          public void fireActionListeners() {
              ActionEvent event = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "toggle");
              for (ActionListener actionListener : mActionListeners) {
                  actionListener.actionPerformed(event);
              }
          }

          public void addActionListener(ActionListener actionListener) {
              mActionListeners.add(actionListener);
          }

          public void removeActionListener(ActionListener actionListener) {
              mActionListeners.remove(actionListener);
          }
      }

      ---------- END SOURCE ----------

      FREQUENCY : always


            psadhukhan Prasanta Sadhukhan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: