/**
   * Rebuild the preview
   *
   * @param forceRepaint if true, a component repaint will be issued
   */
  private void rebuild(boolean forceRepaint) {
    try {
      Configuration configuration = myContext.getConfiguration();
      int minApiLevel =
          configuration.getTarget() != null
              ? configuration.getTarget().getVersion().getApiLevel()
              : Integer.MAX_VALUE;
      ThemePreviewBuilder builder =
          new ThemePreviewBuilder()
              .setBackgroundColor(getBackground())
              .addAllComponents(ThemePreviewBuilder.AVAILABLE_BASE_COMPONENTS)
              .addAllComponents(myCustomComponents)
              .addComponentFilter(new ThemePreviewBuilder.SearchFilter(mySearchTerm))
              .addComponentFilter(new ThemePreviewBuilder.ApiLevelFilter(minApiLevel))
              .addComponentFilter(myGroupFilter);

      myIsAppCompatTheme = isAppCompatTheme(configuration);
      if (myIsAppCompatTheme) {
        builder
            .addComponentFilter(mySupportReplacementsFilter)
            .addAllComponents(mySupportLibraryComponents);
      }
      myAndroidPreviewPanel.setDocument(builder.build());

      if (forceRepaint) {
        repaint();
      }
    } catch (ParserConfigurationException e) {
      LOG.error("Unable to generate dynamic theme preview", e);
    }
  }
 private void refreshConfiguration() {
   Configuration configuration = myContext.getConfiguration();
   myAndroidPreviewPanel.updateConfiguration(configuration);
   // We want the preview to remain the same size even when the device being used to render is
   // different.
   // Adjust the scale to the current config.
   if (configuration.getDeviceState() != null) {
     double scale =
         myConstantScalingFactor
             / configuration
                 .getDeviceState()
                 .getHardware()
                 .getScreen()
                 .getPixelDensity()
                 .getDpiValue();
     myAndroidPreviewPanel.setScale(scale);
   } else {
     LOG.error("Configuration getDeviceState returned null. Unable to set preview scale.");
   }
 }
 /** Tells the panel that it needs to reload its android content and repaint it. */
 public void invalidateGraphicsRenderer() {
   myAndroidPreviewPanel.invalidateGraphicsRenderer();
   myAndroidPreviewPanel.repaint();
 }
 @NotNull
 public Set<String> getUsedAttrs() {
   return myAndroidPreviewPanel.getUsedAttrs();
 }
  public AndroidThemePreviewPanel(@NotNull ThemeEditorContext context, @NotNull Color background) {
    super(BoxLayout.PAGE_AXIS);

    setOpaque(true);
    setMinimumSize(JBUI.size(200, 0));

    myContext = context;
    myAndroidPreviewPanel = new AndroidPreviewPanel(myContext.getConfiguration());
    myContext.addChangeListener(
        new ThemeEditorContext.ChangeListener() {
          @Override
          public void onNewConfiguration(ThemeEditorContext context) {
            refreshConfiguration();
          }
        });
    myAndroidPreviewPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));

    myBreadcrumbs = new NavigationComponent<Breadcrumb>();
    myBreadcrumbs.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL));

    myDumbService = DumbService.getInstance(context.getProject());

    myScrollPane =
        new JBScrollPane(
            myAndroidPreviewPanel,
            ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    myScrollPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
    myScrollPane.setBorder(null);
    myScrollPane.setViewportBorder(null);
    mySearchTextField = new SearchTextField(true);
    // Avoid search box stretching more than 1 line.
    mySearchTextField.setMaximumSize(
        new Dimension(Integer.MAX_VALUE, mySearchTextField.getPreferredSize().height));
    mySearchTextField.setBorder(IdeBorderFactory.createEmptyBorder(0, 30, 0, 30));
    final Runnable delayedUpdate =
        new Runnable() {
          @Override
          public void run() {
            rebuild();
          }
        };

    // We use a timer when we detect a change in the search field to avoid re-creating the preview
    // if it's not necessary.
    mySearchTextField.addDocumentListener(
        new DocumentAdapter() {
          @Override
          protected void textChanged(DocumentEvent e) {
            Document document = e.getDocument();
            try {
              String search = document.getText(0, document.getLength());

              // Only use search terms longer than 3 characters.
              String newSearchTerm = search.length() < 3 ? "" : search;
              if (newSearchTerm.equals(mySearchTerm)) {
                return;
              }
              if (myPendingSearch != null) {
                myPendingSearch.cancel(true);
              }
              mySearchTerm = newSearchTerm;
              myPendingSearch =
                  mySearchScheduler.schedule(delayedUpdate, 300, TimeUnit.MILLISECONDS);
            } catch (BadLocationException e1) {
              LOG.error(e1);
            }
          }
        });

    myBreadcrumbs.setRootItem(new Breadcrumb("All components"));

    add(Box.createRigidArea(new Dimension(0, 5)));
    add(myBreadcrumbs);
    add(Box.createRigidArea(new Dimension(0, 10)));
    add(mySearchTextField);
    add(Box.createRigidArea(new Dimension(0, 10)));
    add(myScrollPane);

    setBackground(background);
    reloadComponents();

    myBreadcrumbs.addItemListener(
        new NavigationComponent.ItemListener<Breadcrumb>() {
          @Override
          public void itemSelected(@NotNull Breadcrumb item) {
            myBreadcrumbs.goTo(item);
            rebuild();
          }
        });

    myAndroidPreviewPanel.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mouseClicked(MouseEvent e) {
            ViewInfo view = myAndroidPreviewPanel.findViewAtPoint(e.getPoint());

            if (view == null) {
              return;
            }

            mySearchTextField.setText("");

            Object cookie = view.getCookie();
            if (cookie instanceof MergeCookie) {
              cookie = ((MergeCookie) cookie).getCookie();
            }

            if (!(cookie instanceof Element)) {
              return;
            }

            NamedNodeMap attributes = ((Element) cookie).getAttributes();
            Node group =
                attributes.getNamedItemNS(
                    ThemePreviewBuilder.BUILDER_URI, ThemePreviewBuilder.BUILDER_ATTR_GROUP);

            if (group != null) {
              myBreadcrumbs.push(
                  new Breadcrumb(ThemePreviewBuilder.ComponentGroup.valueOf(group.getNodeValue())));
              rebuild();
            }
          }
        });

    myContext.addConfigurationListener(
        new ConfigurationListener() {
          @Override
          public boolean changed(int flags) {
            refreshConfiguration();

            if ((flags & ConfigurationListener.CFG_THEME) != 0) {
              boolean appCompatTheme = isAppCompatTheme(myContext.getConfiguration());
              if (appCompatTheme != myIsAppCompatTheme) {
                rebuild();
              }
            }

            return true;
          }
        });
  }