/* Aseprite
 * Copyright (C) 2001-2013  David Capello
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "app/app.h"
#include "app/color.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/document_undo.h"
#include "app/file_selector.h"
#include "app/ini_file.h"
#include "app/modules/editors.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/settings/settings.h"
#include "app/settings/settings.h"
#include "app/ui/color_bar.h"
#include "app/ui/color_sliders.h"
#include "app/ui/editor/editor.h"
#include "app/ui/hex_color_entry.h"
#include "app/ui/palette_view.h"
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/status_bar.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "app/undo_transaction.h"
#include "app/undoers/set_palette_colors.h"
#include "base/bind.h"
#include "base/fs.h"
#include "base/path.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "gfx/size.h"
#include "raster/image.h"
#include "raster/palette.h"
#include "raster/quantization.h"
#include "raster/sprite.h"
#include "raster/stock.h"
#include "ui/graphics.h"
#include "ui/ui.h"

#include <cstdio>
#include <cstring>
#include <vector>

namespace app {

using namespace gfx;
using namespace ui;

class PaletteEntryEditor : public Window {
public:
  PaletteEntryEditor();

  void setColor(const app::Color& color);

protected:
  bool onProcessMessage(Message* msg) override;

  void onExit();
  void onCloseWindow();
  void onFgBgColorChange(const app::Color& color);
  void onColorSlidersChange(ColorSlidersChangeEvent& ev);
  void onColorHexEntryChange(const app::Color& color);
  void onColorTypeButtonClick(Event& ev);
  void onMoreOptionsClick(Event& ev);
  void onCopyColorsClick(Event& ev);
  void onPasteColorsClick(Event& ev);
  void onRampClick(Event& ev);
  void onQuantizeClick(Event& ev);

private:
  void selectColorType(app::Color::Type type);
  void setPaletteEntry(const app::Color& color);
  void setPaletteEntryChannel(const app::Color& color, ColorSliders::Channel channel);
  void setNewPalette(Palette* palette, const char* operationName);
  void updateCurrentSpritePalette(const char* operationName);
  void updateColorBar();
  void onPalChange();

  Box m_vbox;
  Box m_topBox;
  Box m_bottomBox;
  RadioButton m_rgbButton;
  RadioButton m_hsvButton;
  HexColorEntry m_hexColorEntry;
  Label m_entryLabel;
  Button m_moreOptions;
  RgbSliders m_rgbSliders;
  HsvSliders m_hsvSliders;
  Button m_copyButton;
  Button m_pasteButton;
  Button m_rampButton;
  Button m_quantizeButton;

  // This variable is used to avoid updating the m_hexColorEntry text
  // when the color change is generated from a
  // HexColorEntry::ColorChange signal. In this way we don't override
  // what the user is writting in the text field.
  bool m_disableHexUpdate;

  ui::Timer m_redrawTimer;
  bool m_redrawAll;

  // True if the palette change must be implant in the UndoHistory
  // (e.g. when two or more changes in the palette are made in short
  // time).
  bool m_implantChange;

  // True if the PaletteChange signal is generated by the same
  // PaletteEntryEditor instance.
  bool m_selfPalChange;

  ScopedConnection m_palChangeConn;

  // Internal-clipboard to copy & paste colors between palettes. It's
  // used in onCopy/PasteColorsClick.
  std::vector<uint32_t> m_clipboardColors;
};

static PaletteEntryEditor* g_window = NULL;

class PaletteEditorCommand : public Command {
public:
  PaletteEditorCommand();
  Command* clone() const override { return new PaletteEditorCommand(*this); }

protected:
  void onLoadParams(Params* params) override;
  void onExecute(Context* context) override;

private:
  bool m_open;
  bool m_close;
  bool m_switch;
  bool m_background;
};

PaletteEditorCommand::PaletteEditorCommand()
  : Command("PaletteEditor",
            "Palette Editor",
            CmdRecordableFlag)
{
  m_open = true;
  m_close = false;
  m_switch = false;
  m_background = false;
}

void PaletteEditorCommand::onLoadParams(Params* params)
{
  std::string target = params->get("target");
  if (target == "foreground") m_background = false;
  else if (target == "background") m_background = true;

  std::string open_str = params->get("open");
  if (open_str == "true") m_open = true;
  else m_open = false;

  std::string close_str = params->get("close");
  if (close_str == "true") m_close = true;
  else m_close = false;

  std::string switch_str = params->get("switch");
  if (switch_str == "true") m_switch = true;
  else m_switch = false;
}

void PaletteEditorCommand::onExecute(Context* context)
{
  // If this is the first time the command is execute...
  if (!g_window) {
    // If the command says "Close the palette editor" and it is not
    // created yet, we just do nothing.
    if (m_close)
      return;

    // If this is "open" or "switch", we have to create the frame.
    g_window = new PaletteEntryEditor();
  }
  // If the frame is already created and it's visible, close it (only in "switch" or "close" modes)
  else if (g_window->isVisible() && (m_switch || m_close)) {
    // Hide the frame
    g_window->closeWindow(NULL);
    return;
  }

  if (m_switch || m_open) {
    if (!g_window->isVisible()) {
      // Default bounds
      g_window->remapWindow();

      int width = MAX(g_window->getBounds().w, ui::display_w()/2);
      g_window->setBounds(Rect(
          ui::display_w() - width - ToolBar::instance()->getBounds().w,
          ui::display_h() - g_window->getBounds().h - StatusBar::instance()->getBounds().h,
          width, g_window->getBounds().h));

      // Load window configuration
      load_window_pos(g_window, "PaletteEditor");
    }

    // Run the frame in background.
    g_window->openWindow();
    ColorBar::instance()->setPaletteEditorButtonState(true);
  }

  // Show the specified target color
  {
    app::Color color =
      (m_background ? context->settings()->getBgColor():
                      context->settings()->getFgColor());

    g_window->setColor(color);
  }
}

//////////////////////////////////////////////////////////////////////
// PaletteEntryEditor implementation
//
// Based on ColorSelector class.

PaletteEntryEditor::PaletteEntryEditor()
  : Window(WithTitleBar, "Palette Editor (F4)")
  , m_vbox(JI_VERTICAL)
  , m_topBox(JI_HORIZONTAL)
  , m_bottomBox(JI_HORIZONTAL)
  , m_rgbButton("RGB", 1, kButtonWidget)
  , m_hsvButton("HSB", 1, kButtonWidget)
  , m_entryLabel("")
  , m_moreOptions("+")
  , m_copyButton("Copy")
  , m_pasteButton("Paste")
  , m_rampButton("Ramp")
  , m_quantizeButton("Quantize")
  , m_disableHexUpdate(false)
  , m_redrawTimer(250, this)
  , m_redrawAll(false)
  , m_implantChange(false)
  , m_selfPalChange(false)
{
  m_topBox.setBorder(gfx::Border(0));
  m_topBox.child_spacing = 0;
  m_bottomBox.setBorder(gfx::Border(0));

  setup_mini_look(&m_rgbButton);
  setup_mini_look(&m_hsvButton);
  setup_mini_look(&m_moreOptions);
  setup_mini_look(&m_copyButton);
  setup_mini_look(&m_pasteButton);
  setup_mini_look(&m_rampButton);
  setup_mini_look(&m_quantizeButton);

  // Top box
  m_topBox.addChild(&m_rgbButton);
  m_topBox.addChild(&m_hsvButton);
  m_topBox.addChild(&m_hexColorEntry);
  m_topBox.addChild(&m_entryLabel);
  m_topBox.addChild(new BoxFiller);
  m_topBox.addChild(&m_moreOptions);

  // Bottom box
  {
    Box* box = new Box(JI_HORIZONTAL);
    box->child_spacing = 0;
    box->addChild(&m_copyButton);
    box->addChild(&m_pasteButton);
    m_bottomBox.addChild(box);
  }
  m_bottomBox.addChild(&m_rampButton);
  m_bottomBox.addChild(&m_quantizeButton);

  // Main vertical box
  m_vbox.addChild(&m_topBox);
  m_vbox.addChild(&m_rgbSliders);
  m_vbox.addChild(&m_hsvSliders);
  m_vbox.addChild(&m_bottomBox);
  addChild(&m_vbox);

  // Hide (or show) the "More Options" depending the saved value in .cfg file
  m_bottomBox.setVisible(get_config_bool("PaletteEditor", "ShowMoreOptions", false));

  m_rgbButton.Click.connect(&PaletteEntryEditor::onColorTypeButtonClick, this);
  m_hsvButton.Click.connect(&PaletteEntryEditor::onColorTypeButtonClick, this);
  m_moreOptions.Click.connect(&PaletteEntryEditor::onMoreOptionsClick, this);
  m_copyButton.Click.connect(&PaletteEntryEditor::onCopyColorsClick, this);
  m_pasteButton.Click.connect(&PaletteEntryEditor::onPasteColorsClick, this);
  m_rampButton.Click.connect(&PaletteEntryEditor::onRampClick, this);
  m_quantizeButton.Click.connect(&PaletteEntryEditor::onQuantizeClick, this);

  m_rgbSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
  m_hsvSliders.ColorChange.connect(&PaletteEntryEditor::onColorSlidersChange, this);
  m_hexColorEntry.ColorChange.connect(&PaletteEntryEditor::onColorHexEntryChange, this);

  selectColorType(app::Color::RgbType);

  // We hook fg/bg color changes (by eyedropper mainly) to update the selected entry color
  ColorBar::instance()->FgColorChange.connect(&PaletteEntryEditor::onFgBgColorChange, this);
  ColorBar::instance()->BgColorChange.connect(&PaletteEntryEditor::onFgBgColorChange, this);

  // We hook the Window::Close event to save the frame position before closing it.
  this->Close.connect(Bind<void>(&PaletteEntryEditor::onCloseWindow, this));

  // We hook App::Exit signal to destroy the g_window singleton at exit.
  App::instance()->Exit.connect(&PaletteEntryEditor::onExit, this);

  // Hook for palette change to redraw the palette editor frame
  m_palChangeConn =
    App::instance()->PaletteChange.connect(&PaletteEntryEditor::onPalChange, this);

  initTheme();
}

void PaletteEntryEditor::setColor(const app::Color& color)
{
  m_rgbSliders.setColor(color);
  m_hsvSliders.setColor(color);
  if (!m_disableHexUpdate)
    m_hexColorEntry.setColor(color);

  PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
  PaletteView::SelectedEntries entries;
  palette_editor->getSelectedEntries(entries);
  int i, j, i2;

  // Find the first selected entry
  for (i=0; i<(int)entries.size(); ++i)
    if (entries[i])
      break;

  // Find the first unselected entry after i
  for (i2=i+1; i2<(int)entries.size(); ++i2)
    if (!entries[i2])
      break;

  // Find the last selected entry
  for (j=entries.size()-1; j>=0; --j)
    if (entries[j])
      break;

  if (i == j) {
    m_entryLabel.setTextf(" Entry: %d", i);
  }
  else if (j-i+1 == i2-i) {
    m_entryLabel.setTextf(" Range: %d-%d", i, j);
  }
  else if (i == entries.size()) {
    m_entryLabel.setText(" No Entry");
  }
  else {
    m_entryLabel.setText(" Multiple Entries");
  }

  m_topBox.layout();
}

bool PaletteEntryEditor::onProcessMessage(Message* msg)
{
  if (msg->type() == kTimerMessage &&
      static_cast<TimerMessage*>(msg)->timer() == &m_redrawTimer) {
    // Redraw all editors
    if (m_redrawAll) {
      m_redrawAll = false;
      m_implantChange = false;
      m_redrawTimer.stop();

      // Call all observers of PaletteChange event.
      m_selfPalChange = true;
      App::instance()->PaletteChange();
      m_selfPalChange = false;

      // Redraw all editors
      try {
        ContextWriter writer(UIContext::instance());
        Document* document(writer.document());
        if (document != NULL)
          document->notifyGeneralUpdate();
      }
      catch (...) {
        // Do nothing
      }
    }
    // Redraw just the current editor
    else {
      m_redrawAll = true;
      if (current_editor != NULL)
        current_editor->updateEditor();
    }
  }
  return Window::onProcessMessage(msg);
}

void PaletteEntryEditor::onExit()
{
  delete this;
}

void PaletteEntryEditor::onCloseWindow()
{
  // Save window configuration
  save_window_pos(this, "PaletteEditor");

  // Uncheck the "Edit Palette" button.
  ColorBar::instance()->setPaletteEditorButtonState(false);
}

void PaletteEntryEditor::onFgBgColorChange(const app::Color& color)
{
  if (color.isValid() && color.getType() == app::Color::IndexType) {
    setColor(color);
  }
}

void PaletteEntryEditor::onColorSlidersChange(ColorSlidersChangeEvent& ev)
{
  setColor(ev.getColor());
  setPaletteEntryChannel(ev.getColor(), ev.getModifiedChannel());
  updateCurrentSpritePalette("Color Change");
  updateColorBar();
}

void PaletteEntryEditor::onColorHexEntryChange(const app::Color& color)
{
  // Disable updating the hex entry so we don't override what the user
  // is writting in the text field.
  m_disableHexUpdate = true;

  setColor(color);
  setPaletteEntry(color);
  updateCurrentSpritePalette("Color Change");
  updateColorBar();

  m_disableHexUpdate = false;
}

void PaletteEntryEditor::onColorTypeButtonClick(Event& ev)
{
  RadioButton* source = static_cast<RadioButton*>(ev.getSource());

  if (source == &m_rgbButton) selectColorType(app::Color::RgbType);
  else if (source == &m_hsvButton) selectColorType(app::Color::HsvType);
}

void PaletteEntryEditor::onMoreOptionsClick(Event& ev)
{
  Size reqSize;

  if (m_bottomBox.isVisible()) {
    set_config_bool("PaletteEditor", "ShowMoreOptions", false);
    m_bottomBox.setVisible(false);

    // Get the required size of the "More options" panel
    reqSize = m_bottomBox.getPreferredSize();
    reqSize.h += 4;

    // Remove the space occupied by the "More options" panel
    moveWindow(gfx::Rect(getOrigin(), getSize() - gfx::Size(0, reqSize.h)));
  }
  else {
    set_config_bool("PaletteEditor", "ShowMoreOptions", true);
    m_bottomBox.setVisible(true);

    // Get the required size of the whole window
    reqSize = getPreferredSize();

    // Add space for the "more_options" panel
    if (getBounds().h < reqSize.h) {
      gfx::Rect rect(getOrigin(), gfx::Size(getBounds().w, reqSize.h));

      // Show the expanded area inside the screen
      if (rect.y2() > ui::display_h())
        rect.offset(0, ui::display_h() - rect.y2());

      moveWindow(rect);
    }
    else
      setBounds(getBounds()); // TODO layout() method is missing
  }

  // Redraw the window
  invalidate();
}

void PaletteEntryEditor::onCopyColorsClick(Event& ev)
{
  // Get the selected entries in the palette view.
  PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
  PaletteView::SelectedEntries selectedEntries;
  palette_editor->getSelectedEntries(selectedEntries);

  // Copy all selected entries into "m_clipboardColors" vector. These
  // entries then are copied back to the palette in the "paste"
  // operation (onPasteColorsClick).
  Palette* palette = get_current_palette();
  m_clipboardColors.clear();
  for (int i=0; i<(int)selectedEntries.size(); ++i)
    if (selectedEntries[i])
      m_clipboardColors.push_back(palette->getEntry(i));
}

void PaletteEntryEditor::onPasteColorsClick(Event& ev)
{
  // Get the selected entries in the palette view.
  PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
  PaletteView::SelectedEntries selectedEntries;
  palette_editor->getSelectedEntries(selectedEntries);

  // Count how many colors are selected so we can continue pasting
  // colors even if the current number of selected colors is less than
  // the number of colors into the clipboard.
  int selectedEntriesCount = 0;
  for (int i=0; i<(int)selectedEntries.size(); ++i)
    if (selectedEntries[i])
      selectedEntriesCount++;

  Palette* palette = get_current_palette();
  for (int i=0, j=0; i<(int)selectedEntries.size() && j<(int)m_clipboardColors.size(); ++i) {
    // The color is pasted if the entry is selected or if the
    // clipboard contains more entries than the current number of
    // selected palette entries.
    if (selectedEntries[i] || j >= selectedEntriesCount)
      palette->setEntry(i, m_clipboardColors[j++]);
  }

  updateCurrentSpritePalette("Paste Colors");
  updateColorBar();

  // Generate onPalChange() event so we update all sliders to the new
  // values.
  onPalChange();
}

void PaletteEntryEditor::onRampClick(Event& ev)
{
  PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
  int index1, index2;

  if (!palette_editor->getSelectedRange(index1, index2))
    return;

  Palette* src_palette = get_current_palette();
  Palette* dst_palette = new Palette(FrameNumber(0), 256);

  src_palette->copyColorsTo(dst_palette);
  dst_palette->makeHorzRamp(index1, index2);

  setNewPalette(dst_palette, "Color Ramp");
  delete dst_palette;
}

void PaletteEntryEditor::onQuantizeClick(Event& ev)
{
  Palette* palette = NULL;

  {
    const ContextReader reader(UIContext::instance());
    const Sprite* sprite(reader.sprite());

    if (sprite == NULL) {
      Alert::show("Error<<There is no sprite selected to quantize.||&OK");
      return;
    }

    if (sprite->pixelFormat() != IMAGE_RGB) {
      Alert::show("Error<<You can use this command only for RGB sprites||&OK");
      return;
    }

    palette = quantization::create_palette_from_rgb(
      sprite, reader.frame(), NULL);
  }

  setNewPalette(palette, "Quantize Palette");
  delete palette;
}

void PaletteEntryEditor::setPaletteEntry(const app::Color& color)
{
  PaletteView* palView = ColorBar::instance()->getPaletteView();
  PaletteView::SelectedEntries entries;
  palView->getSelectedEntries(entries);

  color_t new_pal_color = raster::rgba(color.getRed(),
                                       color.getGreen(),
                                       color.getBlue(), 255);

  Palette* palette = get_current_palette();
  for (int c=0; c<palette->size(); c++) {
    if (entries[c])
      palette->setEntry(c, new_pal_color);
  }
}

void PaletteEntryEditor::setPaletteEntryChannel(const app::Color& color, ColorSliders::Channel channel)
{
  PaletteView* palView = ColorBar::instance()->getPaletteView();
  PaletteView::SelectedEntries entries;
  palView->getSelectedEntries(entries);

  int begSel, endSel;
  if (!palView->getSelectedRange(begSel, endSel))
    return;

  uint32_t src_color;
  int r, g, b;

  Palette* palette = get_current_palette();
  for (int c=0; c<palette->size(); c++) {
    if (entries[c]) {
      // Get the current RGB values of the palette entry
      src_color = palette->getEntry(c);
      r = rgba_getr(src_color);
      g = rgba_getg(src_color);
      b = rgba_getb(src_color);

      switch (color.getType()) {

        case app::Color::RgbType:
          // Modify one entry
          if (begSel == endSel) {
            r = color.getRed();
            g = color.getGreen();
            b = color.getBlue();
          }
          // Modify one channel a set of entries
          else {
            // Setup the new RGB values depending of the modified channel.
            switch (channel) {
              case ColorSliders::Red:
                r = color.getRed();
              case ColorSliders::Green:
                g = color.getGreen();
                break;
              case ColorSliders::Blue:
                b = color.getBlue();
                break;
            }
          }
          break;

        case app::Color::HsvType:
          {
            Hsv hsv;

            // Modify one entry
            if (begSel == endSel) {
              hsv.hue(color.getHue());
              hsv.saturation(double(color.getSaturation()) / 100.0);
              hsv.value(double(color.getValue()) / 100.0);
            }
            // Modify one channel a set of entries
            else {
              // Convert RGB to HSV
              hsv = Hsv(Rgb(r, g, b));

              // Only modify the desired HSV channel
              switch (channel) {
                case ColorSliders::Hue:
                  hsv.hue(color.getHue());
                  break;
                case ColorSliders::Saturation:
                  hsv.saturation(double(color.getSaturation()) / 100.0);
                  break;
                case ColorSliders::Value:
                  hsv.value(double(color.getValue()) / 100.0);
                  break;
              }
            }

            // Convert HSV back to RGB
            Rgb rgb(hsv);
            r = rgb.red();
            g = rgb.green();
            b = rgb.blue();
          }
          break;
      }

      palette->setEntry(c, raster::rgba(r, g, b, 255));
    }
  }
}

void PaletteEntryEditor::selectColorType(app::Color::Type type)
{
  m_rgbSliders.setVisible(type == app::Color::RgbType);
  m_hsvSliders.setVisible(type == app::Color::HsvType);

  switch (type) {
    case app::Color::RgbType: m_rgbButton.setSelected(true); break;
    case app::Color::HsvType: m_hsvButton.setSelected(true); break;
  }

  m_vbox.layout();
  m_vbox.invalidate();
}

void PaletteEntryEditor::setNewPalette(Palette* palette, const char* operationName)
{
  // Copy the palette
  palette->copyColorsTo(get_current_palette());

  // Set the palette calling the hooks
  set_current_palette(palette, false);

  // Update the sprite palette
  updateCurrentSpritePalette(operationName);

  // Redraw the entire screen
  ui::Manager::getDefault()->invalidate();
}

void PaletteEntryEditor::updateCurrentSpritePalette(const char* operationName)
{
  if (UIContext::instance()->activeDocument() &&
      UIContext::instance()->activeDocument()->sprite()) {
    try {
      ContextWriter writer(UIContext::instance());
      Document* document(writer.document());
      Sprite* sprite(writer.sprite());
      Palette* newPalette = get_current_palette(); // System current pal
      FrameNumber frame = writer.frame();
      Palette* currentSpritePalette = sprite->getPalette(frame); // Sprite current pal
      int from, to;

      // Check differences between current sprite palette and current system palette
      from = to = -1;
      currentSpritePalette->countDiff(newPalette, &from, &to);

      if (from >= 0 && to >= from) {
        DocumentUndo* undo = document->getUndo();

        // Add undo information to save the range of pal entries that will be modified.
        if (undo->isEnabled()) {
          if (m_implantChange && strcmp(undo->getNextUndoLabel(), operationName) == 0) {
            undo->implantUndoerInLastGroup
              (new undoers::SetPaletteColors(undo->getObjects(),
                                             sprite, currentSpritePalette,
                                             frame, from, to));
          }
          else {
            UndoTransaction undoTransaction(writer.context(), operationName, undo::ModifyDocument);
            undoTransaction.pushUndoer
              (new undoers::SetPaletteColors(undoTransaction.getObjects(),
                                             sprite, currentSpritePalette,
                                             frame, from, to));
            undoTransaction.commit();
          }
        }

        // Change the sprite palette
        sprite->setPalette(newPalette, false);
      }
    }
    catch (base::Exception& e) {
      Console::showException(e);
    }
  }

  PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
  palette_editor->invalidate();

  if (!m_redrawTimer.isRunning())
    m_redrawTimer.start();

  m_redrawAll = false;
  m_implantChange = true;
}

void PaletteEntryEditor::updateColorBar()
{
  ColorBar::instance()->invalidate();
}

void PaletteEntryEditor::onPalChange()
{
  if (!m_selfPalChange) {
    PaletteView* palette_editor = ColorBar::instance()->getPaletteView();
    int index = palette_editor->getSelectedEntry();
    if (index >= 0)
      setColor(app::Color::fromIndex(index));

    // Redraw the window
    invalidate();
  }
}

Command* CommandFactory::createPaletteEditorCommand()
{
  return new PaletteEditorCommand;
}

} // namespace app
