/** Reloads color info. */
  @Override
  public void loadSkin() {
    super.loadSkin();

    TAB_HIGHLIGHT_FOREGROUND_COLOR =
        new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_HIGHLIGHT"));

    TAB_SELECTED_FOREGROUND_COLOR =
        new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_SELECTED"));
  }
/** Panel that shows the content of an X509Certificate. */
public class X509CertificatePanel extends TransparentPanel {
  private static final long serialVersionUID = -8368302061995971947L;

  private final JEditorPane infoTextPane = new JEditorPane();

  private final ResourceManagementService R = DesktopUtilActivator.getResources();

  /**
   * Constructs a X509 certificate panel from a single certificate. If a chain is available instead
   * use the second constructor. This constructor is kept for backwards compatibility and for
   * convenience when there is only one certificate of interest.
   *
   * @param certificate <tt>X509Certificate</tt> object
   */
  public X509CertificatePanel(Certificate certificate) {
    this(new Certificate[] {certificate});
  }

  /**
   * Constructs a X509 certificate panel.
   *
   * @param certificates <tt>X509Certificate</tt> objects
   */
  public X509CertificatePanel(Certificate[] certificates) {
    setLayout(new BorderLayout(5, 5));

    // Certificate chain list
    TransparentPanel topPanel = new TransparentPanel(new BorderLayout());
    topPanel.add(
        new JLabel(
            "<html><body><b>"
                + R.getI18NString("service.gui.CERT_INFO_CHAIN")
                + "</b></body></html>"),
        BorderLayout.NORTH);

    DefaultMutableTreeNode top = new DefaultMutableTreeNode();
    DefaultMutableTreeNode previous = top;
    for (int i = certificates.length - 1; i >= 0; i--) {
      Certificate cert = certificates[i];
      DefaultMutableTreeNode next = new DefaultMutableTreeNode(cert);
      previous.add(next);
      previous = next;
    }
    JTree tree = new JTree(top);
    tree.setBorder(new BevelBorder(BevelBorder.LOWERED));
    tree.setRootVisible(false);
    tree.setExpandsSelectedPaths(true);
    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    tree.setCellRenderer(
        new DefaultTreeCellRenderer() {

          @Override
          public Component getTreeCellRendererComponent(
              JTree tree,
              Object value,
              boolean sel,
              boolean expanded,
              boolean leaf,
              int row,
              boolean hasFocus) {
            JLabel component =
                (JLabel)
                    super.getTreeCellRendererComponent(
                        tree, value, sel, expanded, leaf, row, hasFocus);
            if (value instanceof DefaultMutableTreeNode) {
              Object o = ((DefaultMutableTreeNode) value).getUserObject();
              if (o instanceof X509Certificate) {
                component.setText(getSimplifiedName((X509Certificate) o));
              } else {
                // We don't know how to represent this certificate type,
                // let's use the first 20 characters
                String text = o.toString();
                if (text.length() > 20) {
                  text = text.substring(0, 20);
                }
                component.setText(text);
              }
            }
            return component;
          }
        });
    tree.getSelectionModel()
        .addTreeSelectionListener(
            new TreeSelectionListener() {

              @Override
              public void valueChanged(TreeSelectionEvent e) {
                valueChangedPerformed(e);
              }
            });
    tree.setSelectionPath(
        new TreePath((((DefaultTreeModel) tree.getModel()).getPathToRoot(previous))));
    topPanel.add(tree, BorderLayout.CENTER);

    add(topPanel, BorderLayout.NORTH);

    // Certificate details pane
    Caret caret = infoTextPane.getCaret();
    if (caret instanceof DefaultCaret) {
      ((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
    }

    /*
     * Make JEditorPane respect our default font because we will be using it
     * to just display text.
     */
    infoTextPane.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, true);

    infoTextPane.setOpaque(false);
    infoTextPane.setEditable(false);
    infoTextPane.setContentType("text/html");
    infoTextPane.setText(toString(certificates[0]));

    final JScrollPane certScroll = new JScrollPane(infoTextPane);
    certScroll.setPreferredSize(new Dimension(300, 500));
    add(certScroll, BorderLayout.CENTER);
  }

  /**
   * Creates a String representation of the given object.
   *
   * @param certificate to print
   * @return the String representation
   */
  private String toString(Object certificate) {
    final StringBuilder sb = new StringBuilder();
    sb.append("<html><body>\n");

    if (certificate instanceof X509Certificate) {
      renderX509(sb, (X509Certificate) certificate);
    } else {
      sb.append("<pre>\n");
      sb.append(certificate.toString());
      sb.append("</pre>\n");
    }

    sb.append("</body></html>");
    return sb.toString();
  }

  /**
   * Appends an HTML representation of the given X509Certificate.
   *
   * @param sb StringBuilder to append to
   * @param certificate to print
   */
  private void renderX509(StringBuilder sb, X509Certificate certificate) {
    X500Principal issuer = certificate.getIssuerX500Principal();
    X500Principal subject = certificate.getSubjectX500Principal();

    sb.append("<table cellspacing='1' cellpadding='1'>\n");

    // subject
    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_TO"));
    try {
      for (Rdn name : new LdapName(subject.getName()).getRdns()) {
        String nameType = name.getType();
        String lblKey = "service.gui.CERT_INFO_" + nameType;
        String lbl = R.getI18NString(lblKey);

        if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType;

        final String value;
        Object nameValue = name.getValue();

        if (nameValue instanceof byte[]) {
          byte[] nameValueAsByteArray = (byte[]) nameValue;

          value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")";
        } else value = nameValue.toString();

        addField(sb, lbl, value);
      }
    } catch (InvalidNameException ine) {
      addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), subject.getName());
    }

    // issuer
    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_ISSUED_BY"));
    try {
      for (Rdn name : new LdapName(issuer.getName()).getRdns()) {
        String nameType = name.getType();
        String lblKey = "service.gui.CERT_INFO_" + nameType;
        String lbl = R.getI18NString(lblKey);

        if ((lbl == null) || ("!" + lblKey + "!").equals(lbl)) lbl = nameType;

        final String value;
        Object nameValue = name.getValue();

        if (nameValue instanceof byte[]) {
          byte[] nameValueAsByteArray = (byte[]) nameValue;

          value = getHex(nameValueAsByteArray) + " (" + new String(nameValueAsByteArray) + ")";
        } else value = nameValue.toString();

        addField(sb, lbl, value);
      }
    } catch (InvalidNameException ine) {
      addField(sb, R.getI18NString("service.gui.CERT_INFO_CN"), issuer.getName());
    }

    // validity
    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_VALIDITY"));
    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_ISSUED_ON"),
        certificate.getNotBefore().toString());
    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_EXPIRES_ON"),
        certificate.getNotAfter().toString());

    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_FINGERPRINTS"));
    try {
      String sha1String = getThumbprint(certificate, "SHA1");
      String md5String = getThumbprint(certificate, "MD5");

      addField(sb, "SHA1:", sha1String);
      addField(sb, "MD5:", md5String);
    } catch (CertificateException e) {
      // do nothing as we cannot show this value
    }

    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_CERT_DETAILS"));

    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_SER_NUM"),
        certificate.getSerialNumber().toString());

    addField(
        sb, R.getI18NString("service.gui.CERT_INFO_VER"), String.valueOf(certificate.getVersion()));

    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_SIGN_ALG"),
        String.valueOf(certificate.getSigAlgName()));

    addTitle(sb, R.getI18NString("service.gui.CERT_INFO_PUB_KEY_INFO"));

    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_ALG"),
        certificate.getPublicKey().getAlgorithm());

    if (certificate.getPublicKey().getAlgorithm().equals("RSA")) {
      RSAPublicKey key = (RSAPublicKey) certificate.getPublicKey();

      addField(
          sb,
          R.getI18NString("service.gui.CERT_INFO_PUB_KEY"),
          R.getI18NString(
              "service.gui.CERT_INFO_KEY_BYTES_PRINT",
              new String[] {
                String.valueOf(key.getModulus().toByteArray().length - 1),
                key.getModulus().toString(16)
              }));

      addField(
          sb, R.getI18NString("service.gui.CERT_INFO_EXP"), key.getPublicExponent().toString());

      addField(
          sb,
          R.getI18NString("service.gui.CERT_INFO_KEY_SIZE"),
          R.getI18NString(
              "service.gui.CERT_INFO_KEY_BITS_PRINT",
              new String[] {String.valueOf(key.getModulus().bitLength())}));
    } else if (certificate.getPublicKey().getAlgorithm().equals("DSA")) {
      DSAPublicKey key = (DSAPublicKey) certificate.getPublicKey();

      addField(sb, "Y:", key.getY().toString(16));
    }

    addField(
        sb,
        R.getI18NString("service.gui.CERT_INFO_SIGN"),
        R.getI18NString(
            "service.gui.CERT_INFO_KEY_BYTES_PRINT",
            new String[] {
              String.valueOf(certificate.getSignature().length), getHex(certificate.getSignature())
            }));

    sb.append("</table>\n");
  }

  /**
   * Add a title.
   *
   * @param sb StringBuilder to append to
   * @param title to print
   */
  private void addTitle(StringBuilder sb, String title) {
    sb.append("<tr><td colspan='2'")
        .append(" style='margin-top: 5pt; white-space: nowrap'><p><b>")
        .append(title)
        .append("</b></p></td></tr>\n");
  }

  /**
   * Add a field.
   *
   * @param sb StringBuilder to append to
   * @param field name of the certificate field
   * @param value to print
   */
  private void addField(StringBuilder sb, String field, String value) {
    sb.append("<tr>")
        .append("<td style='margin-left: 5pt; margin-right: 25pt;")
        .append(" white-space: nowrap'>")
        .append(field)
        .append("</td>")
        .append("<td>")
        .append(value)
        .append("</td>")
        .append("</tr>\n");
  }

  /**
   * Converts the byte array to hex string.
   *
   * @param raw the data.
   * @return the hex string.
   */
  private String getHex(byte[] raw) {
    if (raw == null) return null;

    StringBuilder hex = new StringBuilder(2 * raw.length);
    Formatter f = new Formatter(hex);
    try {
      for (byte b : raw) f.format("%02x", b);
    } finally {
      f.close();
    }
    return hex.toString();
  }

  /**
   * Calculates the hash of the certificate known as the "thumbprint" and returns it as a string
   * representation.
   *
   * @param cert The certificate to hash.
   * @param algorithm The hash algorithm to use.
   * @return The SHA-1 hash of the certificate.
   * @throws CertificateException
   */
  private static String getThumbprint(X509Certificate cert, String algorithm)
      throws CertificateException {
    MessageDigest digest;
    try {
      digest = MessageDigest.getInstance(algorithm);
    } catch (NoSuchAlgorithmException e) {
      throw new CertificateException(e);
    }
    byte[] encodedCert = cert.getEncoded();
    StringBuilder sb = new StringBuilder(encodedCert.length * 2);
    Formatter f = new Formatter(sb);
    try {
      for (byte b : digest.digest(encodedCert)) f.format("%02x", b);
    } finally {
      f.close();
    }
    return sb.toString();
  }

  /**
   * Construct a "simplified name" based on the subject DN from the certificate. The purpose is to
   * have something shorter to display in the list. The name used is one of the following DN parts,
   * if available, otherwise the complete DN: 'CN', 'OU' or else 'O'.
   *
   * @param cert to read subject DN from
   * @return the simplified name
   */
  private static String getSimplifiedName(X509Certificate cert) {
    final HashMap<String, String> parts = new HashMap<String, String>();
    try {
      for (Rdn name : new LdapName(cert.getSubjectX500Principal().getName()).getRdns()) {
        if (name.getType() != null && name.getValue() != null) {
          parts.put(name.getType(), name.getValue().toString());
        }
      }
    } catch (InvalidNameException ignored) // NOPMD
    {
    }

    String result = parts.get("CN");
    if (result == null) {
      result = parts.get("OU");
    }
    if (result == null) {
      result = parts.get("O");
    }
    if (result == null) {
      result = cert.getSubjectX500Principal().getName();
    }
    return result;
  }

  /**
   * Called when the selection changed in the tree. Loads the selected certificate.
   *
   * @param e the event
   */
  private void valueChangedPerformed(TreeSelectionEvent e) {
    Object o = e.getNewLeadSelectionPath().getLastPathComponent();
    if (o instanceof DefaultMutableTreeNode) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
      infoTextPane.setText(toString(node.getUserObject()));
    }
  }
}
/**
 * This UI displays a different interface, which is independent from the look and feel.
 *
 * @author David Bismut, [email protected]
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class SIPCommTabbedPaneEnhancedUI extends SIPCommTabbedPaneUI implements Skinnable {
  private static Color TAB_HIGHLIGHT_FOREGROUND_COLOR =
      new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_HIGHLIGHT"));

  private static Color TAB_SELECTED_FOREGROUND_COLOR =
      new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_SELECTED"));

  private static final int TAB_OVERLAP =
      Integer.parseInt(
          DesktopUtilActivator.getResources().getSettingsString("impl.gui.TAB_OVERLAP"));

  private static final int PREFERRED_WIDTH = 150;

  /**
   * The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a selected tab.
   */
  private static final String SELECTED_TAB_LEFT_BG = "service.gui.lookandfeel.SELECTED_TAB_LEFT_BG";

  /**
   * The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a selected tab.
   */
  private static final String SELECTED_TAB_MIDDLE_BG =
      "service.gui.lookandfeel.SELECTED_TAB_MIDDLE_BG";

  /**
   * The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a selected tab.
   */
  private static final String SELECTED_TAB_RIGHT_BG =
      "service.gui.lookandfeel.SELECTED_TAB_RIGHT_BG";

  /** The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a tab. */
  private static final String TAB_LEFT_BG = "service.gui.lookandfeel.TAB_LEFT_BG";

  /** The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a tab. */
  private static final String TAB_MIDDLE_BG = "service.gui.lookandfeel.TAB_MIDDLE_BG";

  /** The image used in the <tt>SIPCommLookAndFeel</tt> to paint the background of a tab. */
  private static final String TAB_RIGHT_BG = "service.gui.lookandfeel.TAB_RIGHT_BG";

  protected final java.util.List<Integer> highlightedTabs = new Vector<Integer>();

  public static ComponentUI createUI(JComponent c) {
    return new SIPCommTabbedPaneEnhancedUI();
  }

  @Override
  protected void paintFocusIndicator(
      Graphics g,
      int tabPlacement,
      Rectangle[] rects,
      int tabIndex,
      Rectangle iconRect,
      Rectangle textRect,
      boolean isSelected) {}

  /** Overriden to paint nothing. */
  @Override
  protected void paintTabBorder(
      Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {}

  @Override
  protected void paintContentBorderTopEdge(
      Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    if (tabPane.getTabCount() < 1) return;

    g.setColor(shadow);
    g.drawLine(x, y, x + w - 2, y);
  }

  @Override
  protected void paintContentBorderLeftEdge(
      Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    if (tabPane.getTabCount() < 1) return;

    g.setColor(shadow);

    g.drawLine(x, y, x, y + h - 3);
  }

  @Override
  protected void paintContentBorderBottomEdge(
      Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    if (tabPane.getTabCount() < 1) return;

    g.setColor(shadow);
    g.drawLine(x + 1, y + h - 3, x + w - 2, y + h - 3);
    g.drawLine(x + 1, y + h - 2, x + w - 2, y + h - 2);
    g.setColor(shadow.brighter());
    g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1);
  }

  @Override
  protected void paintContentBorderRightEdge(
      Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    if (tabPane.getTabCount() < 1) return;

    g.setColor(shadow);

    g.drawLine(x + w - 3, y + 1, x + w - 3, y + h - 3);
    g.drawLine(x + w - 2, y + 1, x + w - 2, y + h - 3);
    g.setColor(shadow.brighter());
    g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 2);
  }

  @Override
  protected void paintTabBackground(
      Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
    g = g.create();
    try {
      internalPaintTabBackground(g, tabPlacement, tabIndex, x, y, w, h, isSelected);
    } finally {
      g.dispose();
    }
  }

  private void internalPaintTabBackground(
      Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
    BufferedImage leftImg = null;
    BufferedImage middleImg = null;
    BufferedImage rightImg = null;

    Graphics2D g2 = (Graphics2D) g;

    AntialiasingManager.activateAntialiasing(g2);

    int tabOverlap = 0;

    if (isSelected) {
      if (tabPane.isEnabledAt(tabIndex)) {
        leftImg = DesktopUtilActivator.getImage(SELECTED_TAB_LEFT_BG);
        middleImg = DesktopUtilActivator.getImage(SELECTED_TAB_MIDDLE_BG);
        rightImg = DesktopUtilActivator.getImage(SELECTED_TAB_RIGHT_BG);

        tabOverlap = TAB_OVERLAP;
      } else {
        leftImg = DesktopUtilActivator.getImage(TAB_LEFT_BG);
        middleImg = DesktopUtilActivator.getImage(TAB_MIDDLE_BG);
        rightImg = DesktopUtilActivator.getImage(TAB_RIGHT_BG);
      }
    } else {
      leftImg = DesktopUtilActivator.getImage(TAB_LEFT_BG);
      middleImg = DesktopUtilActivator.getImage(TAB_MIDDLE_BG);
      rightImg = DesktopUtilActivator.getImage(TAB_RIGHT_BG);
    }

    // Remove the existing gap between the tabs and the panel, which is due
    // to the removal of the tabbed pane border.
    y++;
    // If the current tab is not the selected tab we paint it 2 more pixels
    // to the bottom in order to create the disabled look.
    if (!isSelected) y += 2;

    g2.drawImage(leftImg, x, y, null);
    g2.drawImage(
        middleImg,
        x + leftImg.getWidth(),
        y,
        w - leftImg.getWidth() - rightImg.getWidth() + tabOverlap,
        leftImg.getHeight(),
        null);
    g2.drawImage(rightImg, x + w - rightImg.getWidth() + tabOverlap, y, null);
  }

  @Override
  protected void paintText(
      Graphics g,
      int tabPlacement,
      Font font,
      FontMetrics metrics,
      int tabIndex,
      String title,
      Rectangle textRect,
      boolean isSelected) {
    g.setFont(font);

    int titleWidth = SwingUtilities.computeStringWidth(metrics, title);

    int preferredWidth = 0;
    if (isOneActionButtonEnabled()) {
      preferredWidth = calculateTabWidth(tabPlacement, tabIndex, metrics) - WIDTHDELTA - 15;

      if (isCloseEnabled()) preferredWidth -= BUTTONSIZE;

      if (isMaxEnabled()) preferredWidth -= BUTTONSIZE;
    } else {
      preferredWidth = titleWidth;
    }

    while (titleWidth > preferredWidth) {
      if (title.endsWith("...")) title = title.substring(0, title.indexOf("...") - 1).concat("...");
      else title = title.substring(0, title.length() - 4).concat("...");

      titleWidth = SwingUtilities.computeStringWidth(metrics, title);
    }

    textRect.width = titleWidth;

    View v = getTextViewForTab(tabIndex);
    if (v != null) {
      // html
      v.paint(g, textRect);
    } else {
      // plain text
      int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);

      if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
        if (isSelected) g.setColor(TAB_SELECTED_FOREGROUND_COLOR);
        else {
          if (this.isTabHighlighted(tabIndex)) {
            g.setColor(TAB_HIGHLIGHT_FOREGROUND_COLOR);
          } else g.setColor(tabPane.getForegroundAt(tabIndex));
        }

        BasicGraphicsUtils.drawString(
            g, title, mnemIndex, textRect.x, textRect.y + metrics.getAscent());
      } else { // tab disabled
        g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
        BasicGraphicsUtils.drawStringUnderlineCharAt(
            g, title, mnemIndex, textRect.x, textRect.y + metrics.getAscent());

        g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
        BasicGraphicsUtils.drawStringUnderlineCharAt(
            g, title, mnemIndex, textRect.x - 1, textRect.y + metrics.getAscent() - 1);
      }
    }
  }

  protected class ScrollableTabButton extends SIPCommTabbedPaneUI.ScrollableTabButton {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    public ScrollableTabButton(int direction) {
      super(direction);
      setRolloverEnabled(true);
    }

    @Override
    public Dimension getPreferredSize() {
      return new Dimension(16, calculateMaxTabHeight(0));
    }

    @Override
    public void paint(Graphics g) {
      Color origColor;
      boolean isPressed, isRollOver, isEnabled;
      int w, h, size;

      w = getWidth();
      h = getHeight();
      origColor = g.getColor();
      isPressed = getModel().isPressed();
      isRollOver = getModel().isRollover();
      isEnabled = isEnabled();

      g.setColor(getBackground());
      g.fillRect(0, 0, w, h);

      g.setColor(shadow);
      // Using the background color set above
      if (direction == WEST) {
        g.drawLine(0, 0, 0, h - 1); // left
        g.drawLine(w - 1, 0, w - 1, 0); // right
      } else g.drawLine(w - 2, h - 1, w - 2, 0); // right

      g.drawLine(0, 0, w - 2, 0); // top

      if (isRollOver) {
        // do highlights or shadows
        Color color1;
        Color color2;

        if (isPressed) {
          color2 = Color.WHITE;
          color1 = shadow;
        } else {
          color1 = Color.WHITE;
          color2 = shadow;
        }

        g.setColor(color1);

        if (direction == WEST) {
          g.drawLine(1, 1, 1, h - 1); // left
          g.drawLine(1, 1, w - 2, 1); // top
          g.setColor(color2);
          g.drawLine(w - 1, h - 1, w - 1, 1); // right
        } else {
          g.drawLine(0, 1, 0, h - 1);
          g.drawLine(0, 1, w - 3, 1); // top
          g.setColor(color2);
          g.drawLine(w - 3, h - 1, w - 3, 1); // right
        }
      }

      // g.drawLine(0, h - 1, w - 1, h - 1); //bottom

      // If there's no room to draw arrow, bail
      if (h < 5 || w < 5) {
        g.setColor(origColor);
        return;
      }

      if (isPressed) {
        g.translate(1, 1);
      }

      // Draw the arrow
      size = Math.min((h - 4) / 3, (w - 4) / 3);
      size = Math.max(size, 2);

      boolean highlight = false;

      if (!highlightedTabs.isEmpty()
          && ((direction == WEST && tabScroller.scrollBackwardButton.isEnabled())
              || (direction == EAST && tabScroller.scrollForwardButton.isEnabled()))) {
        Rectangle viewRect = tabScroller.viewport.getViewRect();

        if (direction == WEST) {
          int leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);

          for (int i = 0; i < leadingTabIndex; i++) {
            if (highlightedTabs.contains(i) && !isScrollTabVisible(i)) {
              highlight = true;
              break;
            }
          }
        } else {
          int leadingTabIndex = getClosestTab(viewRect.x + viewRect.y, viewRect.y);

          for (int i = leadingTabIndex; i < tabPane.getTabCount(); i++) {
            if (highlightedTabs.contains(i) && !isScrollTabVisible(i)) {
              highlight = true;
              break;
            }
          }
        }

        if (highlight) {
          Image img =
              DesktopUtilActivator.getImage(
                  direction == WEST
                      ? "service.gui.icons.TAB_UNREAD_BACKWARD_ICON"
                      : "service.gui.icons.TAB_UNREAD_FORWARD_ICON");

          int wi = img.getWidth(null);

          g.drawImage(img, (w - wi) / 2, (h - size) / 2 - 2 /* 2 borders 1px width*/, null);
        }
      }

      if (!highlight) paintTriangle(g, (w - size) / 2, (h - size) / 2, size, direction, isEnabled);

      // Reset the Graphics back to it's original settings
      if (isPressed) {
        g.translate(-1, -1);
      }
      g.setColor(origColor);
    }
  }

  /**
   * Checks whether the <tt>tabIndex</tt> is visible in the scrollable tabs list.
   *
   * @param tabIndex the tab index to check.
   * @return whether <tt>tabIndex</tt> is visible in the list of scrollable tabs.
   */
  private boolean isScrollTabVisible(int tabIndex) {
    Rectangle tabRect = rects[tabIndex];
    Rectangle viewRect = tabScroller.viewport.getViewRect();
    if ((tabRect.x + tabRect.width - BUTTONSIZE < viewRect.x)
        || (tabRect.x + BUTTONSIZE > viewRect.x + viewRect.width)) {
      return false;
    } else {
      return true;
    }
  }

  @Override
  protected SIPCommTabbedPaneUI.ScrollableTabButton createScrollableTabButton(int direction) {
    return new ScrollableTabButton(direction);
  }

  @Override
  protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
    int width = super.calculateTabWidth(tabPlacement, tabIndex, metrics);

    if (isOneActionButtonEnabled()) {
      if (width > PREFERRED_WIDTH) width = PREFERRED_WIDTH;
    }

    return width + WIDTHDELTA;
  }

  public void tabAddHightlight(int tabIndex) {
    this.highlightedTabs.add(tabIndex);
  }

  public void tabRemoveHighlight(int tabIndex) {
    Iterator<Integer> highlightedIter = highlightedTabs.iterator();

    while (highlightedIter.hasNext()) {
      if (highlightedIter.next().intValue() == tabIndex) {
        highlightedIter.remove();
        break;
      }
    }
  }

  public boolean isTabHighlighted(int tabIndex) {
    return highlightedTabs.contains(tabIndex);
  }

  /** Reloads color info. */
  @Override
  public void loadSkin() {
    super.loadSkin();

    TAB_HIGHLIGHT_FOREGROUND_COLOR =
        new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_HIGHLIGHT"));

    TAB_SELECTED_FOREGROUND_COLOR =
        new Color(DesktopUtilActivator.getResources().getColor("service.gui.TAB_TITLE_SELECTED"));
  }
}