/** * 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; } }); }