JColorChooser - Locale setzen

beastofchaos

Bekanntes Mitglied
Hallo Leute,
Ich hab in meinem Programm die I18N eingeführt und es funktioniert in vielerlei Hinsicht. Abgesehen davon, dass ich für die Sprache in meinen Dialogen extra Dialog basteln musste, hab ich nun das Problem, dass mein JFileChooser, der in einem Panel am Rand, der wiederum Teil eines SplitPane ist, liegt, die Sprache nicht verändern will. Es gibt da nämlich drei Möglichkeiten die Farbe zu bestimmen ("Muster", "HSV", "RGB"). "Muster" wird eigentlich automatisch in eine andere Sprache umgewandelt, wenn ich die Locale z.B. auf "fr" setze. Bei mir passiert das erst, wenn ich ieinen Dialog aufrufe.

Hier mal der Konstruktor, in dem auch der JColorChooser aufgebaut wird.

Java:
    public EastPanel(Main target){
		main = target;
		
        diaChooser.setPreviewPanel(new CustomPanel());          // eigene Vorschau
        diaChooser.setColor(Color.black);
        
		setLayout(new BorderLayout());
        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.add(main.paintPanel);
        splitPane.add(diaChooser);
        splitPane.setDividerSize(10);
        splitPane.setOneTouchExpandable(true);
		add(splitPane, BorderLayout.CENTER);
	}

Wie schaffe ich es nun, eigenständig den JColorChooser zu aktualisieren?
Versucht hab ich es bisher mit "repaint()" (invalidate würde dann sicher genauso wenig bringen), "SwingUtilities.updateComponentTreeUI(diaChooser)" und "diaChooser.setLocale(new Locale("fr"))".

Gruß, Thomas
 
:wuerg: Irgendein Bastard hat den JColorChooser von vorn bis hinten versaut. Zumindest das BasicColorChooserUI ist vollkommen ohnmächtig… Man schaue sich diese Überprüfung an:
Java:
layoutSize = new BorderLayout().minimumLayoutSize(previewPanel);
if ((previewPanelHolder != null) && (chooser != null) &&
 (layoutSize.getWidth() + layoutSize.getHeight() == 0)) {
  chooser.remove(previewPanelHolder);
  return;
}
layoutSize wird ausschließlich dann != 0x0 sein, wenn das previewPanel Insets ungleich 0,0,0,0 hat. Dafür ein BorderLayout zu erzeugen ist schon ein Verbrechen und hat mit dem was man möchte nichts zu tun.

Man erzeuge einen JColorChooser (mit BasicColorChooserUI) und frage sein PreviewPanel ab. Dieses ist nicht null. Man setze darauf hin ein anderes Panel. Dann setze man den PreviewPanel von zuvor und er wird nicht angezeigt. Dafür bekommt man ein frisch erzeugtes PreviewPanel angezeigt, wenn man [c]colorChooser.setPreviewPanel(null)[/c] sagt. Alles klar.

Das führt im weiteren dazu, dass [c]JColorChooser.updateUI()[/c] kaputt ist. Man muss es wohl austricksen (siehe nächster Post; dauert noch nen Moment). Grmbl.

So. Genug gemotzt.

Ebenius
 
Okay. Noch schwergewichtiger kann's kaum werden. Wenn ich die Entwickler in die Finger bekomme, …

Aber so geht's. Ich hoffe die Kommentare erklären alles. Ausführbares Beispiel:
Java:
/* (@)JColorChooserLocaleSwitchTestGUI.java */

/* Copyright 2011 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Locale;

import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.colorchooser.AbstractColorChooserPanel;

/**
 * That {@link JColorChooser} beast is really resistant against any wish to
 * change the locale. Needs a lot of cheating, see below.
 * 
 * @author Sebastian Haufe
 */
public class JColorChooserLocaleSwitchTestGUI {

  /** Creates the GUI. Call on EDT, only! */
  static void createAndShowGui() {
    final JColorChooser colorChooser = new JColorChooser();

    final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
    contentPane.add(new JButton(new AbstractAction(
          "Toggle Locale (de_DE/en_US)") {

      private static final long serialVersionUID = 1L;

      public void actionPerformed(ActionEvent e) {
        if (Locale.US.equals(colorChooser.getLocale())) {
          colorChooser.setLocale(Locale.GERMANY);
        } else {
          colorChooser.setLocale(Locale.US);
        }

        System.out.println("Set locale: " + colorChooser.getLocale());
        Locale.setDefault(colorChooser.getLocale());
        final String oldRecentStr =
              UIManager.getString("ColorChooser.swatchesRecentText");

        /* 1) Buggy BasicColorChooserUI bastard! To get the tabbed pane titles
           reset to now locale settings, you need to reinstall them on the
           color chooser component. The AbstractColorChooserPanel instances
           created by that UI implementation read their display name from the
           L&F defaults; of course ignoring the locale of the components.
           That's why we need to set the default locale on the L&F defaults.*/

        UIManager.getLookAndFeelDefaults().setDefaultLocale(
              colorChooser.getLocale());

        /* 2) Buggy BasicColorChooserUI bastard! If the UI is installed on the
           component while preview panel is not null, it removes the preview
           panel holder, unless the preview panel has non-zero insets. If you
           try to unset the preview panel (null) the UI immediately creates a
           default preview panel and resets it to the component (which has
           zero insets again). Basically, there are a two workarounds
           possible:

           a) Create a dummy panel with non-zero insets, set that panel as
           preview panel, then update the UI and then set the preview panel
           to null (to have it recreated, immediately).

           b) Unset the UI, unset the preview panel and then update the UI.

           Choice a) would possibly break other UI implementations, but b)
           should be save; so we go for that one.                           */

        colorChooser.setUI(null);
        colorChooser.setPreviewPanel(null);

        SwingUtilities.updateComponentTreeUI(colorChooser);

        /* 3) Buggy BasicColorChooserUI bastard! The text of the "Recent"
              label in the DefaultSwatchChooser is assigned to a
              private static String field on class instantiation only. The
              label is not accessible in any way. So we find it in the
              component hierarchy by comparing the text. Of course the text is
              assigned in somewhere below installUI(). So we need to do the
              voodoo after updateUI()! Damn it!                             */

        AbstractColorChooserPanel[] panels = colorChooser.getChooserPanels();
        for (AbstractColorChooserPanel cp : panels) {
          if ("javax.swing.colorchooser.DefaultSwatchChooserPanel".equals(cp
                .getClass().getName())) {
            final String recentStr =
                  UIManager.getString("ColorChooser.swatchesRecentText");
            for (Component c : flattenChildHierarchy(cp,
                  new LinkedList<Component>())) {
              if (c instanceof JLabel) {
                final String txt = ((JLabel) c).getText();
                if ((oldRecentStr == null && txt == null)
                      || (oldRecentStr != null && oldRecentStr.equals(txt))) {
                  ((JLabel) c).setText(recentStr);
                }
              } else if ("javax.swing.colorchooser.RecentSwatchPanel"
                    .equals(c)) {
                ((JComponent) c).putClientProperty(
                      AccessibleContext.ACCESSIBLE_NAME_PROPERTY, recentStr);
              }
            }
          }
        }
      }

      private Collection<Component> flattenChildHierarchy(
            Container parent,
            Collection<Component> collector) {
        for (int i = 0; i < parent.getComponentCount(); i++) {
          final Component c = parent.getComponent(i);
          collector.add(c);
          if (c instanceof Container) {
            flattenChildHierarchy((Container) c, collector);
          }
        }
        return collector;
      }

    }), BorderLayout.SOUTH);
    contentPane.add(colorChooser);

    final JFrame f =
          new JFrame("Test Frame: JColorChooserLocaleSwitchTestGUI"); //$NON-NLS-1$
    f.setContentPane(contentPane);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }

  /** @param args ignored */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

      public void run() {
        createAndShowGui();
      }
    });
  }
}

Ebenius
 
So, dankeschön für den Code. Ich habe mal versucht, das für mich relevante rauszuholen, indem ich allesmögliche verkommentiert habe. So kam ich darauf, dass der für mich relevante Part aus drei/vier Zeilen besteht. In meinem Programm steht nun als Hinzugefügt in meiner refresh()-Methode:

Java:
        diaChooser.setLocale(Locale.getDefault());
        UIManager.getLookAndFeelDefaults().setDefaultLocale( diaChooser.getLocale());
        diaChooser.setUI(null);
        diaChooser.setPreviewPanel(null);
   
        SwingUtilities.updateComponentTreeUI(diaChooser);
        diaChooser.setPreviewPanel(new CustomPanel());

Also ich musste einfach UI und PreviewPanel auf null setzen, damit er erst reagiert 🙂 Wenn eines von beidem nicht null ist, verschwindet die Vorschau. Offensichtlich will der sich das einfach wieder neu basteln, sonst will er es auch nicht aktualisieren 😛.

Nochmal vielen Danke, Gruß Thomas
 

Zurück
Oben