001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017
018package org.opends.guitools.controlpanel.ui.components;
019
020import static org.opends.messages.AdminToolMessages.*;
021
022import java.awt.GridBagConstraints;
023import java.awt.GridBagLayout;
024import java.awt.event.ActionListener;
025import java.awt.event.KeyEvent;
026import java.text.ParseException;
027
028import org.forgerock.i18n.LocalizableMessage;
029import org.forgerock.i18n.slf4j.LocalizedLogger;
030
031import javax.swing.Box;
032import javax.swing.Icon;
033import javax.swing.ImageIcon;
034import javax.swing.JButton;
035import javax.swing.JLabel;
036import javax.swing.JPanel;
037import javax.swing.KeyStroke;
038
039import org.opends.guitools.controlpanel.browser.IconPool;
040import org.opends.guitools.controlpanel.datamodel.BinaryValue;
041import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
042import org.opends.guitools.controlpanel.util.Utilities;
043
044/**
045 * A simple panel used in the LDAP entry viewers to display a binary value.
046 * It does not allow to edit the binary value.  It is used for instance in the
047 * tables.
048 *
049 */
050public class BinaryCellPanel extends JPanel
051{
052  private static final long serialVersionUID = 6607973945986559802L;
053  private JButton iconButton;
054  private JLabel label;
055  private CellEditorButton editButton;
056  private CellEditorButton deleteButton;
057  private boolean displayDelete;
058  private JLabel lockLabel = Utilities.createDefaultLabel();
059
060  private ImageIcon lockIcon =
061    Utilities.createImageIcon(IconPool.IMAGE_PATH+"/field-locked.png");
062
063  private Object value;
064
065  private static final int THUMBNAIL_HEIGHT = 50;
066
067  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
068
069  /**
070   * Default constructor.
071   *
072   */
073  public BinaryCellPanel()
074  {
075    super(new GridBagLayout());
076    setOpaque(false);
077    GridBagConstraints gbc = new GridBagConstraints();
078    gbc.fill = GridBagConstraints.HORIZONTAL;
079    gbc.gridx = 0;
080    gbc.gridy = 0;
081    iconButton = Utilities.createButton(LocalizableMessage.EMPTY);
082    label = Utilities.createDefaultLabel(
083        INFO_CTRL_PANEL_NO_VALUE_SPECIFIED.get());
084    add(iconButton);
085    iconButton.setVisible(false);
086    gbc.weightx = 1.0;
087    gbc.gridx ++;
088    add(label, gbc);
089    add(Box.createHorizontalGlue(), gbc);
090    gbc.gridx ++;
091    editButton = new CellEditorButton(INFO_CTRL_PANEL_EDIT_BUTTON_LABEL.get());
092    editButton.setForeground(ColorAndFontConstants.buttonForeground);
093    editButton.setOpaque(false);
094    gbc.insets.left = 5;
095    gbc.weightx = 0.0;
096    add(editButton, gbc);
097
098    gbc.gridx ++;
099    deleteButton =
100      new CellEditorButton(INFO_CTRL_PANEL_DELETE_BUTTON_LABEL.get());
101    deleteButton.setForeground(ColorAndFontConstants.buttonForeground);
102    deleteButton.setOpaque(false);
103    deleteButton.setVisible(isDisplayDelete());
104    add(deleteButton, gbc);
105
106    gbc.insets.left = 5;
107    gbc.gridx ++;
108    add(lockLabel, gbc);
109    lockLabel.setVisible(false);
110  }
111
112  /**
113   * Returns the message describing the provided array of bytes.
114   * @param value the array of bytes.
115   * @param isImage whether the array of bytes represents an image or not.
116   * @return the message describing the provided array of bytes.
117   */
118  public LocalizableMessage getString(byte[] value, boolean isImage)
119  {
120    if (value == null)
121    {
122      return INFO_CTRL_PANEL_NO_VALUE_SPECIFIED.get();
123    }
124    else if (isImage)
125    {
126      return LocalizableMessage.EMPTY;
127    }
128    else
129    {
130      return INFO_CTRL_PANEL_BINARY_VALUE.get();
131    }
132  }
133
134  /**
135   * Updates the visibility of the lock icon.
136   * @param visible whether the lock icon is visible or not.
137   */
138  public void setLockIconVisible(boolean visible)
139  {
140    if (visible)
141    {
142      lockLabel.setIcon(lockIcon);
143      lockLabel.setVisible(true);
144    }
145    else
146    {
147      lockLabel.setIcon(null);
148      lockLabel.setVisible(false);
149    }
150  }
151
152  /**
153   * Sets the text of the edit button (for instance if this panel is displaying
154   * a read-only value, the user might set a value of 'View...' that launches
155   * a viewer).
156   * @param text the text of the button.
157   */
158  public void setEditButtonText(LocalizableMessage text)
159  {
160    editButton.setText(text.toString());
161  }
162
163  /**
164   * Returns the message describing the provided binary value.
165   * @param value the binary value.
166   * @param isImage whether the binary value represents an image or not.
167   * @return the message describing the provided binary value.
168   */
169  public LocalizableMessage getMessage(BinaryValue value, boolean isImage)
170  {
171    LocalizableMessage returnValue;
172    if (value == null)
173    {
174      returnValue = INFO_CTRL_PANEL_NO_VALUE_SPECIFIED.get();
175    }
176    else if (isImage)
177    {
178      returnValue = LocalizableMessage.EMPTY;
179    }
180    else if (value.getType() == BinaryValue.Type.BASE64_STRING)
181    {
182      returnValue = INFO_CTRL_PANEL_BINARY_VALUE.get();
183    }
184    else
185    {
186      returnValue = INFO_CTRL_PANEL_CONTENTS_OF_FILE.get(value.getFile());
187    }
188    return returnValue;
189  }
190
191  /**
192   * Sets the value to be displayed by this panel.
193   * @param value the binary value as an array of bytes.
194   * @param isImage whether the binary value represents an image or not.
195   */
196  public void setValue(byte[] value, boolean isImage)
197  {
198    label.setText(getString(value, isImage).toString());
199    deleteButton.setVisible(value != null && isDisplayDelete());
200    this.value = value;
201    if (!isImage)
202    {
203      label.setIcon(null);
204      label.setVisible(true);
205      iconButton.setVisible(false);
206    }
207    else
208    {
209      updateIcon(value);
210    }
211  }
212
213  /**
214   * Sets the value to be displayed by this panel.
215   * @param value the binary value as a BinaryValue object.
216   * @param isImage whether the binary value represents an image or not.
217   */
218  public void setValue(BinaryValue value, boolean isImage)
219  {
220    label.setText(getMessage(value, isImage).toString());
221    deleteButton.setVisible(value != null && isDisplayDelete());
222    this.value = value;
223    if (!isImage)
224    {
225      label.setIcon(null);
226      label.setVisible(true);
227      iconButton.setVisible(false);
228    }
229    else
230    {
231      try
232      {
233        updateIcon(value.getBytes());
234      }
235      catch (ParseException pe)
236      {
237        logger.warn(LocalizableMessage.raw("Error decoding base 64 value: "+pe, pe));
238        Utilities.setWarningLabel(label, ERR_LOADING_IMAGE.get());
239      }
240    }
241  }
242
243  private void updateIcon(byte[] value)
244  {
245    if (value == null)
246    {
247      label.setVisible(true);
248      iconButton.setVisible(false);
249    }
250    else
251    {
252      Icon icon = getIcon(value);
253      if (icon == null || icon.getIconHeight() <= 0)
254      {
255        Utilities.setWarningLabel(label, ERR_LOADING_IMAGE.get());
256        label.setVisible(true);
257        iconButton.setVisible(false);
258      }
259      else
260      {
261        iconButton.setVisible(true);
262        iconButton.setIcon(icon);
263        label.setVisible(false);
264      }
265    }
266  }
267
268  /**
269   * Returns the object represented by this panel.
270   * @return the object represented by this panel.
271   */
272  public Object getValue()
273  {
274    return value;
275  }
276
277  /**
278   * Explicitly request the focus for the edit button of this panel.
279   *
280   */
281  public void requestFocusForButton()
282  {
283    editButton.requestFocusInWindow();
284  }
285
286  /**
287   * Adds an action listener to this panel.  The action listener will be
288   * invoked when the user clicks on the 'Edit' button or the icon.
289   * @param listener the action listener.
290   */
291  public void addEditActionListener(ActionListener listener)
292  {
293    editButton.addActionListener(listener);
294    iconButton.addActionListener(listener);
295  }
296
297  /**
298   * Removes an action listener previously added with the method
299   * addEditActionListener.
300   * @param listener the action listener.
301   */
302  public void removeEditActionListener(ActionListener listener)
303  {
304    editButton.removeActionListener(listener);
305    iconButton.removeActionListener(listener);
306  }
307
308  /**
309   * Adds an action listener to this panel.  The action listener will be
310   * invoked when the user clicks on the 'Delete'.
311   * @param listener the action listener.
312   */
313  public void addDeleteActionListener(ActionListener listener)
314  {
315    deleteButton.addActionListener(listener);
316  }
317
318  /**
319   * Removes an action listener previously added with the method
320   * addDeleteActionListener.
321   * @param listener the action listener.
322   */
323  public void removeDeleteActionListener(ActionListener listener)
324  {
325    deleteButton.removeActionListener(listener);
326  }
327
328  /** {@inheritDoc} */
329  protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
330      int condition, boolean pressed)
331  {
332    // This method is used to transfer the key events to the button.
333    return editButton.processKeyBinding(ks, e, condition, pressed);
334  }
335
336  /**
337   * Tells whether the 'Delete' button is displayed or not.
338   * @return <CODE>true</CODE> if the 'Delete' button is visible and
339   * <CODE>false</CODE> otherwise.
340   */
341  public boolean isDisplayDelete()
342  {
343    return displayDelete;
344  }
345
346  /**
347   * Sets whether the 'Delete' button must be displayed or not.
348   * @param displayDelete whether the 'Delete' button must be displayed or not.
349   */
350  public void setDisplayDelete(boolean displayDelete)
351  {
352    this.displayDelete = displayDelete;
353  }
354
355  private Icon getIcon(byte[] bytes)
356  {
357    return Utilities.createImageIcon(bytes, THUMBNAIL_HEIGHT,
358        INFO_CTRL_PANEL_THUMBNAIL_DESCRIPTION.get(),
359        true);
360  }
361}