1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #define COLOR_BAR_TRACE(...)
8 
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12 
13 #include "app/ui/color_bar.h"
14 
15 #include "app/app.h"
16 #include "app/app_menus.h"
17 #include "app/cmd/remap_colors.h"
18 #include "app/cmd/replace_image.h"
19 #include "app/cmd/set_palette.h"
20 #include "app/cmd/set_transparent_color.h"
21 #include "app/cmd_sequence.h"
22 #include "app/color.h"
23 #include "app/commands/command.h"
24 #include "app/commands/commands.h"
25 #include "app/commands/params.h"
26 #include "app/commands/quick_command.h"
27 #include "app/console.h"
28 #include "app/context_access.h"
29 #include "app/doc_undo.h"
30 #include "app/doc_api.h"
31 #include "app/i18n/strings.h"
32 #include "app/ini_file.h"
33 #include "app/modules/editors.h"
34 #include "app/modules/gui.h"
35 #include "app/modules/palettes.h"
36 #include "app/pref/preferences.h"
37 #include "app/transaction.h"
38 #include "app/ui/color_spectrum.h"
39 #include "app/ui/color_tint_shade_tone.h"
40 #include "app/ui/color_wheel.h"
41 #include "app/ui/editor/editor.h"
42 #include "app/ui/hex_color_entry.h"
43 #include "app/ui/input_chain.h"
44 #include "app/ui/keyboard_shortcuts.h"
45 #include "app/ui/palette_popup.h"
46 #include "app/ui/skin/skin_theme.h"
47 #include "app/ui/status_bar.h"
48 #include "app/ui/timeline/timeline.h"
49 #include "app/ui_context.h"
50 #include "app/ui_context.h"
51 #include "app/util/clipboard.h"
52 #include "base/bind.h"
53 #include "base/scoped_value.h"
54 #include "doc/cel.h"
55 #include "doc/cels_range.h"
56 #include "doc/image.h"
57 #include "doc/image_impl.h"
58 #include "doc/palette.h"
59 #include "doc/primitives.h"
60 #include "doc/remap.h"
61 #include "doc/rgbmap.h"
62 #include "doc/sort_palette.h"
63 #include "doc/sprite.h"
64 #include "she/surface.h"
65 #include "ui/alert.h"
66 #include "ui/graphics.h"
67 #include "ui/menu.h"
68 #include "ui/message.h"
69 #include "ui/paint_event.h"
70 #include "ui/separator.h"
71 #include "ui/splitter.h"
72 #include "ui/system.h"
73 #include "ui/tooltips.h"
74 
75 #include <cstring>
76 #include <limits>
77 
78 namespace app {
79 
80 enum class PalButton {
81   EDIT,
82   SORT,
83   PRESETS,
84   OPTIONS,
85   MAX
86 };
87 
88 using namespace app::skin;
89 using namespace ui;
90 
91 class ColorBar::WarningIcon : public ui::Button {
92 public:
WarningIcon()93   WarningIcon() : ui::Button(std::string()) {
94     initTheme();
95   }
96 protected:
onInitTheme(ui::InitThemeEvent & ev)97   void onInitTheme(ui::InitThemeEvent& ev) {
98     ui::Button::onInitTheme(ev);
99     setStyle(skin::SkinTheme::instance()->styles.warningBox());
100   }
101 };
102 
103 //////////////////////////////////////////////////////////////////////
104 // ColorBar::ScrollableView class
105 
ScrollableView()106 ColorBar::ScrollableView::ScrollableView()
107 {
108   initTheme();
109 }
110 
onInitTheme(InitThemeEvent & ev)111 void ColorBar::ScrollableView::onInitTheme(InitThemeEvent& ev)
112 {
113   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
114   setStyle(theme->styles.colorbarView());
115 
116   horizontalBar()->setStyle(theme->styles.miniScrollbar());
117   verticalBar()->setStyle(theme->styles.miniScrollbar());
118   horizontalBar()->setThumbStyle(theme->styles.miniScrollbarThumb());
119   verticalBar()->setThumbStyle(theme->styles.miniScrollbarThumb());
120 }
121 
122 //////////////////////////////////////////////////////////////////////
123 // ColorBar class
124 
125 ColorBar* ColorBar::m_instance = NULL;
126 
ColorBar(int align)127 ColorBar::ColorBar(int align)
128   : Box(align)
129   , m_buttons(int(PalButton::MAX))
130   , m_splitter(Splitter::ByPercentage, VERTICAL)
131   , m_paletteView(true, PaletteView::FgBgColors, this, 16)
132   , m_remapButton("Remap")
133   , m_selector(ColorSelector::NONE)
134   , m_tintShadeTone(nullptr)
135   , m_spectrum(nullptr)
136   , m_wheel(nullptr)
137   , m_fgColor(app::Color::fromRgb(255, 255, 255), IMAGE_RGB, ColorBarButtonsOptions())
138   , m_bgColor(app::Color::fromRgb(0, 0, 0), IMAGE_RGB, ColorBarButtonsOptions())
139   , m_fgWarningIcon(new WarningIcon)
140   , m_bgWarningIcon(new WarningIcon)
141   , m_fromPalView(false)
142   , m_fromPref(false)
143   , m_fromFgButton(false)
144   , m_fromBgButton(false)
145   , m_lastDocument(nullptr)
146   , m_ascending(true)
147   , m_lastButtons(kButtonLeft)
148   , m_editMode(false)
149   , m_redrawTimer(250, this)
150   , m_redrawAll(false)
151   , m_implantChange(false)
152   , m_selfPalChange(false)
153 {
154   m_instance = this;
155 
156   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
157 
158   m_buttons.addItem(theme->parts.palEdit());
159   m_buttons.addItem(theme->parts.palSort());
160   m_buttons.addItem(theme->parts.palPresets());
161   m_buttons.addItem(theme->parts.palOptions());
162 
163   m_paletteView.setColumns(8);
164 
165   setup_mini_look(m_scrollableView.horizontalBar());
166   setup_mini_look(m_scrollableView.verticalBar());
167 
168   m_scrollableView.attachToView(&m_paletteView);
169   m_scrollableView.setExpansive(true);
170 
171   m_remapButton.setVisible(false);
172 
173   m_palettePlaceholder.addChild(&m_scrollableView);
174   m_palettePlaceholder.addChild(&m_remapButton);
175   m_splitter.setId("palette_spectrum_splitter");
176   m_splitter.setPosition(80);
177   m_splitter.setExpansive(true);
178   m_splitter.addChild(&m_palettePlaceholder);
179   m_splitter.addChild(&m_selectorPlaceholder);
180 
181   setColorSelector(
182     Preferences::instance().colorBar.selector());
183 
184   addChild(&m_buttons);
185   addChild(&m_splitter);
186 
187   HBox* fgBox = new HBox;
188   HBox* bgBox = new HBox;
189   fgBox->addChild(&m_fgColor);
190   fgBox->addChild(m_fgWarningIcon);
191   bgBox->addChild(&m_bgColor);
192   bgBox->addChild(m_bgWarningIcon);
193   addChild(fgBox);
194   addChild(bgBox);
195 
196   m_fgColor.setId("fg_color");
197   m_bgColor.setId("bg_color");
198   m_fgColor.setExpansive(true);
199   m_bgColor.setExpansive(true);
200 
201   m_remapButton.Click.connect(base::Bind<void>(&ColorBar::onRemapButtonClick, this));
202   m_fgColor.Change.connect(&ColorBar::onFgColorButtonChange, this);
203   m_fgColor.BeforeChange.connect(&ColorBar::onFgColorButtonBeforeChange, this);
204   m_bgColor.Change.connect(&ColorBar::onBgColorButtonChange, this);
205   m_fgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_fgColor, m_fgWarningIcon));
206   m_bgWarningIcon->Click.connect(base::Bind<void>(&ColorBar::onFixWarningClick, this, &m_bgColor, m_bgWarningIcon));
207   m_redrawTimer.Tick.connect(base::Bind<void>(&ColorBar::onTimerTick, this));
208   m_buttons.ItemChange.connect(base::Bind<void>(&ColorBar::onPaletteButtonClick, this));
209 
210   m_tooltips.addTooltipFor(&m_fgColor, "Foreground color", LEFT);
211   m_tooltips.addTooltipFor(&m_bgColor, "Background color", LEFT);
212   m_tooltips.addTooltipFor(m_fgWarningIcon, "Add foreground color to the palette", LEFT);
213   m_tooltips.addTooltipFor(m_bgWarningIcon, "Add background color to the palette", LEFT);
214 
215   InitTheme.connect(
216     [this, fgBox, bgBox]{
217       SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
218 
219       setBorder(gfx::Border(2*guiscale(), 0, 0, 0));
220       setChildSpacing(2*guiscale());
221 
222       m_fgColor.resetSizeHint();
223       m_bgColor.resetSizeHint();
224       m_fgColor.setSizeHint(0, m_fgColor.sizeHint().h);
225       m_bgColor.setSizeHint(0, m_bgColor.sizeHint().h);
226       m_buttons.setMaxSize(gfx::Size(std::numeric_limits<int>::max(),
227                                      std::numeric_limits<int>::max())); // TODO add resetMaxSize
228       m_buttons.setMaxSize(gfx::Size(m_buttons.sizeHint().w,
229                                      16*ui::guiscale()));
230 
231       // TODO hardcoded scroll bar width should be get from skin.xml file
232       int scrollBarWidth = 6*guiscale();
233       m_scrollableView.horizontalBar()->setBarWidth(scrollBarWidth);
234       m_scrollableView.verticalBar()->setBarWidth(scrollBarWidth);
235 
236       // Change color-bar background color (not ColorBar::setBgColor)
237       this->Widget::setBgColor(theme->colors.tabActiveFace());
238       m_paletteView.setBgColor(theme->colors.tabActiveFace());
239       m_paletteView.setBoxSize(
240         Preferences::instance().colorBar.boxSize());
241       m_paletteView.initTheme();
242 
243       // Styles
244       m_splitter.setStyle(theme->styles.workspaceSplitter());
245 
246       fgBox->noBorderNoChildSpacing();
247       bgBox->noBorderNoChildSpacing();
248 
249       if (m_palettePopup)
250         m_palettePopup->initTheme();
251     });
252   initTheme();
253 
254   // Set background color reading its value from the configuration.
255   setBgColor(Preferences::instance().colorBar.bgColor());
256 
257   // Clear the selection of the BG color in the palette.
258   m_paletteView.deselect();
259 
260   // Set foreground color reading its value from the configuration.
261   setFgColor(Preferences::instance().colorBar.fgColor());
262 
263   // Tooltips
264   TooltipManager* tooltipManager = new TooltipManager();
265   addChild(tooltipManager);
266   setupTooltips(tooltipManager);
267 
268   onColorButtonChange(getFgColor());
269 
270   UIContext::instance()->add_observer(this);
271   m_beforeCmdConn = UIContext::instance()->BeforeCommandExecution.connect(&ColorBar::onBeforeExecuteCommand, this);
272   m_afterCmdConn = UIContext::instance()->AfterCommandExecution.connect(&ColorBar::onAfterExecuteCommand, this);
273   m_fgConn = Preferences::instance().colorBar.fgColor.AfterChange.connect(base::Bind<void>(&ColorBar::onFgColorChangeFromPreferences, this));
274   m_bgConn = Preferences::instance().colorBar.bgColor.AfterChange.connect(base::Bind<void>(&ColorBar::onBgColorChangeFromPreferences, this));
275   m_paletteView.FocusOrClick.connect(&ColorBar::onFocusPaletteView, this);
276   m_appPalChangeConn = App::instance()->PaletteChange.connect(&ColorBar::onAppPaletteChange, this);
277   KeyboardShortcuts::instance()->UserChange.connect(
278     base::Bind<void>(&ColorBar::setupTooltips, this, tooltipManager));
279 
280   setEditMode(false);
281   registerCommands();
282 }
283 
~ColorBar()284 ColorBar::~ColorBar()
285 {
286   UIContext::instance()->remove_observer(this);
287 }
288 
setPixelFormat(PixelFormat pixelFormat)289 void ColorBar::setPixelFormat(PixelFormat pixelFormat)
290 {
291   m_fgColor.setPixelFormat(pixelFormat);
292   m_bgColor.setPixelFormat(pixelFormat);
293 }
294 
getFgColor() const295 app::Color ColorBar::getFgColor() const
296 {
297   return m_fgColor.getColor();
298 }
299 
getBgColor() const300 app::Color ColorBar::getBgColor() const
301 {
302   return m_bgColor.getColor();
303 }
304 
setFgColor(const app::Color & color)305 void ColorBar::setFgColor(const app::Color& color)
306 {
307   if (m_fromFgButton)
308     return;
309 
310   m_fgColor.setColor(color);
311   if (!m_fromPalView)
312     onColorButtonChange(color);
313 }
314 
setBgColor(const app::Color & color)315 void ColorBar::setBgColor(const app::Color& color)
316 {
317   if (m_fromBgButton)
318     return;
319 
320   m_bgColor.setColor(color);
321   if (!m_fromPalView)
322     onColorButtonChange(color);
323 }
324 
getPaletteView()325 PaletteView* ColorBar::getPaletteView()
326 {
327   return &m_paletteView;
328 }
329 
getColorSelector() const330 ColorBar::ColorSelector ColorBar::getColorSelector() const
331 {
332   return m_selector;
333 }
334 
setColorSelector(ColorSelector selector)335 void ColorBar::setColorSelector(ColorSelector selector)
336 {
337   if (m_selector == selector)
338     return;
339 
340   if (m_tintShadeTone) m_tintShadeTone->setVisible(false);
341   if (m_spectrum) m_spectrum->setVisible(false);
342   if (m_wheel) m_wheel->setVisible(false);
343 
344   m_selector = selector;
345   Preferences::instance().colorBar.selector(m_selector);
346 
347   switch (m_selector) {
348 
349     case ColorSelector::TINT_SHADE_TONE:
350       if (!m_tintShadeTone) {
351         m_tintShadeTone = new ColorTintShadeTone;
352         m_tintShadeTone->setExpansive(true);
353         m_tintShadeTone->selectColor(m_fgColor.getColor());
354         m_tintShadeTone->ColorChange.connect(&ColorBar::onPickSpectrum, this);
355         m_selectorPlaceholder.addChild(m_tintShadeTone);
356       }
357       m_tintShadeTone->setVisible(true);
358       break;
359 
360     case ColorSelector::SPECTRUM:
361       if (!m_spectrum) {
362         m_spectrum = new ColorSpectrum;
363         m_spectrum->setExpansive(true);
364         m_spectrum->selectColor(m_fgColor.getColor());
365         m_spectrum->ColorChange.connect(&ColorBar::onPickSpectrum, this);
366         m_selectorPlaceholder.addChild(m_spectrum);
367       }
368       m_spectrum->setVisible(true);
369       break;
370 
371     case ColorSelector::RGB_WHEEL:
372     case ColorSelector::RYB_WHEEL:
373     case ColorSelector::NORMAL_MAP_WHEEL:
374       if (!m_wheel) {
375         m_wheel = new ColorWheel;
376         m_wheel->setExpansive(true);
377         m_wheel->selectColor(m_fgColor.getColor());
378         m_wheel->ColorChange.connect(&ColorBar::onPickSpectrum, this);
379         m_selectorPlaceholder.addChild(m_wheel);
380       }
381       if (m_selector == ColorSelector::RGB_WHEEL) {
382         m_wheel->setColorModel(ColorWheel::ColorModel::RGB);
383       }
384       else if (m_selector == ColorSelector::RYB_WHEEL) {
385         m_wheel->setColorModel(ColorWheel::ColorModel::RYB);
386       }
387       else if (m_selector == ColorSelector::NORMAL_MAP_WHEEL) {
388         m_wheel->setColorModel(ColorWheel::ColorModel::NORMAL_MAP);
389       }
390       m_wheel->setVisible(true);
391       break;
392 
393   }
394 
395   m_selectorPlaceholder.layout();
396 }
397 
inEditMode() const398 bool ColorBar::inEditMode() const
399 {
400   return
401     (m_editMode &&
402      m_lastDocument &&
403      m_lastDocument->sprite() &&
404      m_lastDocument->sprite()->pixelFormat() != IMAGE_GRAYSCALE);
405 }
406 
setEditMode(bool state)407 void ColorBar::setEditMode(bool state)
408 {
409   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
410   ButtonSet::Item* item = m_buttons.getItem((int)PalButton::EDIT);
411 
412   m_editMode = state;
413   item->setIcon(state ? theme->parts.timelineOpenPadlockActive():
414                         theme->parts.timelineClosedPadlockNormal());
415   item->setHotColor(state ? theme->colors.editPalFace():
416                             gfx::ColorNone);
417 
418   // Deselect color entries when we cancel editing
419   if (!state)
420     m_paletteView.deselect();
421 }
422 
onActiveSiteChange(const Site & site)423 void ColorBar::onActiveSiteChange(const Site& site)
424 {
425   if (m_lastDocument != site.document()) {
426     if (m_lastDocument)
427       m_lastDocument->remove_observer(this);
428 
429     m_lastDocument = const_cast<Doc*>(site.document());
430 
431     if (m_lastDocument)
432       m_lastDocument->add_observer(this);
433 
434     hideRemap();
435   }
436 }
437 
onGeneralUpdate(DocEvent & ev)438 void ColorBar::onGeneralUpdate(DocEvent& ev)
439 {
440   // TODO Observe palette changes only
441   invalidate();
442 }
443 
onAppPaletteChange()444 void ColorBar::onAppPaletteChange()
445 {
446   COLOR_BAR_TRACE("ColorBar::onAppPaletteChange()\n");
447 
448   fixColorIndex(m_fgColor);
449   fixColorIndex(m_bgColor);
450 
451   updateWarningIcon(m_fgColor.getColor(), m_fgWarningIcon);
452   updateWarningIcon(m_bgColor.getColor(), m_bgWarningIcon);
453 }
454 
onFocusPaletteView(ui::Message * msg)455 void ColorBar::onFocusPaletteView(ui::Message* msg)
456 {
457   App::instance()->inputChain().prioritize(this, msg);
458 }
459 
onBeforeExecuteCommand(CommandExecutionEvent & ev)460 void ColorBar::onBeforeExecuteCommand(CommandExecutionEvent& ev)
461 {
462   if (ev.command()->id() == CommandId::SetPalette() ||
463       ev.command()->id() == CommandId::LoadPalette() ||
464       ev.command()->id() == CommandId::ColorQuantization())
465     showRemap();
466 }
467 
onAfterExecuteCommand(CommandExecutionEvent & ev)468 void ColorBar::onAfterExecuteCommand(CommandExecutionEvent& ev)
469 {
470   if (ev.command()->id() == CommandId::Undo() ||
471       ev.command()->id() == CommandId::Redo())
472     invalidate();
473 
474   // If the sprite isn't Indexed anymore (e.g. because we've just
475   // undone a "RGB -> Indexed" conversion), we hide the "Remap"
476   // button.
477   Site site = UIContext::instance()->activeSite();
478   if (site.sprite() &&
479       site.sprite()->pixelFormat() != IMAGE_INDEXED) {
480     hideRemap();
481   }
482 }
483 
484 // Switches the palette-editor
onPaletteButtonClick()485 void ColorBar::onPaletteButtonClick()
486 {
487   int item = m_buttons.selectedItem();
488   m_buttons.deselectItems();
489 
490   switch (static_cast<PalButton>(item)) {
491 
492     case PalButton::EDIT:
493       setEditMode(!inEditMode());
494       break;
495 
496     case PalButton::SORT:
497       showPaletteSortOptions();
498       break;
499 
500     case PalButton::PRESETS:
501       showPalettePresets();
502       break;
503 
504     case PalButton::OPTIONS:
505       showPaletteOptions();
506       break;
507 
508   }
509 }
510 
onRemapButtonClick()511 void ColorBar::onRemapButtonClick()
512 {
513   ASSERT(m_oldPalette);
514 
515   // Create remap from m_oldPalette to the current palette
516   Remap remap(1);
517   try {
518     ContextWriter writer(UIContext::instance(), 500);
519     Sprite* sprite = writer.sprite();
520     ASSERT(sprite);
521     if (!sprite)
522       return;
523 
524     remap = create_remap_to_change_palette(
525       m_oldPalette, get_current_palette(),
526       sprite->transparentColor(), true);
527   }
528   catch (base::Exception& e) {
529     Console::showException(e);
530   }
531 
532   // Check the remap
533   if (!remap.isFor8bit() &&
534       Alert::show(Strings::alerts_auto_remap()) != 1) {
535     return;
536   }
537 
538   try {
539     ContextWriter writer(UIContext::instance(), 500);
540     Sprite* sprite = writer.sprite();
541     if (sprite) {
542       ASSERT(sprite->pixelFormat() == IMAGE_INDEXED);
543 
544       Transaction transaction(writer.context(), "Remap Colors", ModifyDocument);
545       bool remapPixels = true;
546 
547       if (remap.isFor8bit()) {
548         PalettePicks usedEntries(256);
549 
550         for (const Cel* cel : sprite->uniqueCels()) {
551           for (const auto& i : LockImageBits<IndexedTraits>(cel->image()))
552             usedEntries[i] = true;
553         }
554 
555         if (remap.isInvertible(usedEntries)) {
556           transaction.execute(new cmd::RemapColors(sprite, remap));
557           remapPixels = false;
558         }
559       }
560 
561       // Special remap saving original images in undo history
562       if (remapPixels) {
563         for (Cel* cel : sprite->uniqueCels()) {
564           ImageRef celImage = cel->imageRef();
565           ImageRef newImage(Image::createCopy(celImage.get()));
566           doc::remap_image(newImage.get(), remap);
567 
568           transaction.execute(new cmd::ReplaceImage(
569                                 sprite, celImage, newImage));
570         }
571       }
572 
573       color_t oldTransparent = sprite->transparentColor();
574       color_t newTransparent = remap[oldTransparent];
575       if (oldTransparent != newTransparent)
576         transaction.execute(new cmd::SetTransparentColor(sprite, newTransparent));
577 
578       transaction.commit();
579     }
580     update_screen_for_document(writer.document());
581     hideRemap();
582   }
583   catch (base::Exception& e) {
584     Console::showException(e);
585   }
586 }
587 
onPaletteViewIndexChange(int index,ui::MouseButtons buttons)588 void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButtons buttons)
589 {
590   COLOR_BAR_TRACE("ColorBar::onPaletteViewIndexChange(%d)\n", index);
591 
592   base::ScopedValue<bool> lock(m_fromPalView, true, m_fromPalView);
593 
594   app::Color color = app::Color::fromIndex(index);
595 
596   if ((buttons & kButtonRight) == kButtonRight)
597     setBgColor(color);
598   else if ((buttons & kButtonLeft) == kButtonLeft)
599     setFgColor(color);
600   else if ((buttons & kButtonMiddle) == kButtonMiddle)
601     setTransparentIndex(index);
602 
603   ChangeSelection();
604 }
605 
onPaletteViewModification(const Palette * newPalette,PaletteViewModification mod)606 void ColorBar::onPaletteViewModification(const Palette* newPalette,
607                                          PaletteViewModification mod)
608 {
609   const char* text = "Palette Change";
610   switch (mod) {
611     case PaletteViewModification::CLEAR: text = "Clear Colors"; break;
612     case PaletteViewModification::DRAGANDDROP: text = "Drag-and-Drop Colors"; break;
613     case PaletteViewModification::RESIZE: text = "Resize Palette"; break;
614   }
615   setPalette(newPalette, text);
616 }
617 
setPalette(const doc::Palette * newPalette,const std::string & actionText)618 void ColorBar::setPalette(const doc::Palette* newPalette, const std::string& actionText)
619 {
620   showRemap();
621 
622   try {
623     ContextWriter writer(UIContext::instance(), 500);
624     Sprite* sprite = writer.sprite();
625     frame_t frame = writer.frame();
626     if (sprite &&
627         newPalette->countDiff(sprite->palette(frame), nullptr, nullptr)) {
628       Transaction transaction(writer.context(), actionText, ModifyDocument);
629       transaction.execute(new cmd::SetPalette(sprite, frame, newPalette));
630       transaction.commit();
631     }
632   }
633   catch (base::Exception& e) {
634     Console::showException(e);
635   }
636 
637   set_current_palette(newPalette, false);
638   manager()->invalidate();
639 }
640 
setTransparentIndex(int index)641 void ColorBar::setTransparentIndex(int index)
642 {
643   try {
644     ContextWriter writer(UIContext::instance(), 500);
645     Sprite* sprite = writer.sprite();
646     if (sprite &&
647         sprite->pixelFormat() == IMAGE_INDEXED &&
648         int(sprite->transparentColor()) != index) {
649       // TODO merge this code with SpritePropertiesCommand
650       Transaction transaction(writer.context(), "Set Transparent Color");
651       DocApi api = writer.document()->getApi(transaction);
652       api.setSpriteTransparentColor(sprite, index);
653       transaction.commit();
654 
655       update_screen_for_document(writer.document());
656     }
657   }
658   catch (base::Exception& e) {
659     Console::showException(e);
660   }
661 }
662 
onPaletteViewChangeSize(int boxsize)663 void ColorBar::onPaletteViewChangeSize(int boxsize)
664 {
665   Preferences::instance().colorBar.boxSize(boxsize);
666 }
667 
onPaletteViewPasteColors(const Palette * fromPal,const doc::PalettePicks & from,const doc::PalettePicks & _to)668 void ColorBar::onPaletteViewPasteColors(
669   const Palette* fromPal, const doc::PalettePicks& from, const doc::PalettePicks& _to)
670 {
671   if (!from.picks() || !_to.picks()) // Nothing to do
672     return;
673 
674   doc::PalettePicks to = _to;
675   int to_first = to.firstPick();
676   int to_last = to.lastPick();
677 
678   // Add extra picks in to range if it's needed to paste more colors.
679   int from_picks = from.picks();
680   int to_picks = to.picks();
681   if (to_picks < from_picks) {
682     for (int j=to_last+1; j<to.size() && to_picks<from_picks; ++j) {
683       to[j] = true;
684       ++to_picks;
685     }
686   }
687 
688   Palette newPalette(*get_current_palette());
689 
690   int i = 0;
691   int j = to_first;
692 
693   for (auto state : from) {
694     if (state) {
695       if (j < newPalette.size())
696         newPalette.setEntry(j, fromPal->getEntry(i));
697       else
698         newPalette.addEntry(fromPal->getEntry(i));
699 
700       for (++j; j<to.size(); ++j)
701         if (to[j])
702           break;
703     }
704     ++i;
705   }
706 
707   setPalette(&newPalette, "Paste Colors");
708 }
709 
onPaletteViewGetForegroundIndex()710 app::Color ColorBar::onPaletteViewGetForegroundIndex()
711 {
712   return getFgColor();
713 }
714 
onPaletteViewGetBackgroundIndex()715 app::Color ColorBar::onPaletteViewGetBackgroundIndex()
716 {
717   return getBgColor();
718 }
719 
onFgColorChangeFromPreferences()720 void ColorBar::onFgColorChangeFromPreferences()
721 {
722   COLOR_BAR_TRACE("ColorBar::onFgColorChangeFromPreferences() -> %s\n",
723                   Preferences::instance().colorBar.fgColor().toString().c_str());
724 
725   if (m_fromPref)
726     return;
727 
728   base::ScopedValue<bool> sync(m_fromPref, true, false);
729   setFgColor(Preferences::instance().colorBar.fgColor());
730 }
731 
onBgColorChangeFromPreferences()732 void ColorBar::onBgColorChangeFromPreferences()
733 {
734   COLOR_BAR_TRACE("ColorBar::onBgColorChangeFromPreferences() -> %s\n",
735                   Preferences::instance().colorBar.bgColor().toString().c_str());
736 
737   if (m_fromPref)
738     return;
739 
740   if (inEditMode()) {
741     // In edit mode, clicking with right-click will copy the color
742     // selected with eyedropper to the active color entry.
743     setFgColor(Preferences::instance().colorBar.bgColor());
744   }
745   else {
746     base::ScopedValue<bool> sync(m_fromPref, true, false);
747     setBgColor(Preferences::instance().colorBar.bgColor());
748   }
749 }
750 
onFgColorButtonBeforeChange(app::Color & color)751 void ColorBar::onFgColorButtonBeforeChange(app::Color& color)
752 {
753   COLOR_BAR_TRACE("ColorBar::onFgColorButtonBeforeChange(%s)\n", color.toString().c_str());
754 
755   if (m_fromPalView)
756     return;
757 
758   if (!inEditMode()) {
759     m_paletteView.deselect();
760     return;
761   }
762 
763   // Here we change the selected colors in the
764   // palette. "m_fromPref" must be false to edit the color. (It
765   // means, if the eyedropper was used with the left-click, we don't
766   // edit the color, we just select the color to as the normal
767   // non-edit mode.)
768   if (!m_fromPref) {
769     int i = setPaletteEntry(color);
770     if (i >= 0) {
771       updateCurrentSpritePalette("Color Change");
772       color = app::Color::fromIndex(i);
773     }
774   }
775 }
776 
onFgColorButtonChange(const app::Color & color)777 void ColorBar::onFgColorButtonChange(const app::Color& color)
778 {
779   COLOR_BAR_TRACE("ColorBar::onFgColorButtonChange(%s)\n", color.toString().c_str());
780 
781   if (m_fromFgButton)
782     return;
783 
784   base::ScopedValue<bool> lock(m_fromFgButton, true, false);
785 
786   if (!m_fromPref) {
787     base::ScopedValue<bool> sync(m_fromPref, true, false);
788     Preferences::instance().colorBar.fgColor(color);
789   }
790 
791   updateWarningIcon(color, m_fgWarningIcon);
792   onColorButtonChange(color);
793 }
794 
onBgColorButtonChange(const app::Color & color)795 void ColorBar::onBgColorButtonChange(const app::Color& color)
796 {
797   COLOR_BAR_TRACE("ColorBar::onBgColorButtonChange(%s)\n", color.toString().c_str());
798 
799   if (m_fromBgButton)
800     return;
801 
802   base::ScopedValue<bool> lock(m_fromBgButton, true, false);
803 
804   if (!m_fromPalView && !inEditMode())
805     m_paletteView.deselect();
806 
807   if (!m_fromPref) {
808     base::ScopedValue<bool> sync(m_fromPref, true, false);
809     Preferences::instance().colorBar.bgColor(color);
810   }
811 
812   updateWarningIcon(color, m_bgWarningIcon);
813   onColorButtonChange(color);
814 }
815 
onColorButtonChange(const app::Color & color)816 void ColorBar::onColorButtonChange(const app::Color& color)
817 {
818   COLOR_BAR_TRACE("ColorBar::onColorButtonChange(%s)\n", color.toString().c_str());
819 
820   if (!inEditMode() ||
821       m_fromPref) {
822     if (color.getType() == app::Color::IndexType)
823       m_paletteView.selectColor(color.getIndex());
824     else {
825       m_paletteView.selectExactMatchColor(color);
826 
827       // As foreground or background color changed, we've to redraw the
828       // palette view fg/bg indicators.
829       m_paletteView.invalidate();
830     }
831   }
832 
833   if (m_tintShadeTone && m_tintShadeTone->isVisible())
834     m_tintShadeTone->selectColor(color);
835 
836   if (m_spectrum && m_spectrum->isVisible())
837     m_spectrum->selectColor(color);
838 
839   if (m_wheel && m_wheel->isVisible())
840     m_wheel->selectColor(color);
841 }
842 
onPickSpectrum(const app::Color & color,ui::MouseButtons buttons)843 void ColorBar::onPickSpectrum(const app::Color& color, ui::MouseButtons buttons)
844 {
845   if (buttons == kButtonNone)
846     buttons = m_lastButtons;
847 
848   if ((buttons & kButtonRight) == kButtonRight)
849     setBgColor(color);
850   else if ((buttons & kButtonLeft) == kButtonLeft)
851     setFgColor(color);
852 
853   m_lastButtons = buttons;
854 }
855 
onReverseColors()856 void ColorBar::onReverseColors()
857 {
858   doc::PalettePicks entries;
859   m_paletteView.getSelectedEntries(entries);
860 
861   entries.pickAllIfNeeded();
862   int n = entries.picks();
863 
864   std::vector<int> mapToOriginal(n); // Maps index from selectedPalette -> palette
865   int i = 0, j = 0;
866   for (bool state : entries) {
867     if (state)
868       mapToOriginal[j++] = i;
869     ++i;
870   }
871 
872   Remap remap(get_current_palette()->size());
873   i = 0;
874   j = n;
875   for (bool state : entries) {
876     if (state)
877       remap.map(i, mapToOriginal[--j]);
878     else
879       remap.map(i, i);
880     ++i;
881   }
882 
883   Palette newPalette(*get_current_palette(), remap);
884   setPalette(&newPalette, "Reverse Colors");
885 }
886 
onSortBy(SortPaletteBy channel)887 void ColorBar::onSortBy(SortPaletteBy channel)
888 {
889   PalettePicks entries;
890   m_paletteView.getSelectedEntries(entries);
891 
892   entries.pickAllIfNeeded();
893   int n = entries.picks();
894 
895   // Create a "subpalette" with selected entries only.
896   Palette palette(*get_current_palette());
897   Palette selectedPalette(0, n);
898   std::vector<int> mapToOriginal(n); // Maps index from selectedPalette -> palette
899   int i = 0, j = 0;
900   for (bool state : entries) {
901     if (state) {
902       selectedPalette.setEntry(j, palette.getEntry(i));
903       mapToOriginal[j] = i;
904       ++j;
905     }
906     ++i;
907   }
908 
909   // Create a remap to sort the selected entries with the given color
910   // component/channel.
911   Remap remap = doc::sort_palette(&selectedPalette, channel, m_ascending);
912 
913   // Create a bigger new remap for the original palette (with all
914   // entries, selected and deselected).
915   Remap remapOrig(palette.size());
916   i = j = 0;
917   for (bool state : entries) {
918     if (state)
919       remapOrig.map(i, mapToOriginal[remap[j++]]);
920     else
921       remapOrig.map(i, i);
922     ++i;
923   }
924 
925   // Create a new palette and apply the remap. This is the final new
926   // palette for the sprite.
927   Palette newPalette(palette, remapOrig);
928   setPalette(&newPalette, "Sort Colors");
929 }
930 
onGradient()931 void ColorBar::onGradient()
932 {
933   int index1, index2;
934   if (!m_paletteView.getSelectedRange(index1, index2))
935     return;
936 
937   Palette newPalette(*get_current_palette());
938   newPalette.makeGradient(index1, index2);
939 
940   setPalette(&newPalette, "Gradient");
941 }
942 
setAscending(bool ascending)943 void ColorBar::setAscending(bool ascending)
944 {
945   m_ascending = ascending;
946 }
947 
showRemap()948 void ColorBar::showRemap()
949 {
950   Site site = UIContext::instance()->activeSite();
951   if (site.sprite() &&
952       site.sprite()->pixelFormat() == IMAGE_INDEXED) {
953     if (!m_oldPalette) {
954       m_oldPalette.reset(new Palette(*get_current_palette()));
955       m_remapButton.setVisible(true);
956       layout();
957     }
958   }
959 }
960 
hideRemap()961 void ColorBar::hideRemap()
962 {
963   if (!m_oldPalette)
964     return;
965 
966   m_oldPalette.reset();
967   m_remapButton.setVisible(false);
968   layout();
969 }
970 
onNewInputPriority(InputChainElement * element,const ui::Message * msg)971 void ColorBar::onNewInputPriority(InputChainElement* element,
972                                   const ui::Message* msg)
973 {
974   if (dynamic_cast<Timeline*>(element) &&
975       msg && (msg->ctrlPressed() || msg->shiftPressed()))
976     return;
977 
978   if (element != this)
979     m_paletteView.deselect();
980 }
981 
onCanCut(Context * ctx)982 bool ColorBar::onCanCut(Context* ctx)
983 {
984   return (m_paletteView.getSelectedEntriesCount() > 0);
985 }
986 
onCanCopy(Context * ctx)987 bool ColorBar::onCanCopy(Context* ctx)
988 {
989   return (m_paletteView.getSelectedEntriesCount() > 0);
990 }
991 
onCanPaste(Context * ctx)992 bool ColorBar::onCanPaste(Context* ctx)
993 {
994   return (clipboard::get_current_format() == clipboard::ClipboardPaletteEntries);
995 }
996 
onCanClear(Context * ctx)997 bool ColorBar::onCanClear(Context* ctx)
998 {
999   return (m_paletteView.getSelectedEntriesCount() > 0);
1000 }
1001 
onCut(Context * ctx)1002 bool ColorBar::onCut(Context* ctx)
1003 {
1004   m_paletteView.cutToClipboard();
1005   return true;
1006 }
1007 
onCopy(Context * ctx)1008 bool ColorBar::onCopy(Context* ctx)
1009 {
1010   m_paletteView.copyToClipboard();
1011   return true;
1012 }
1013 
onPaste(Context * ctx)1014 bool ColorBar::onPaste(Context* ctx)
1015 {
1016   m_paletteView.pasteFromClipboard();
1017   return true;
1018 }
1019 
onClear(Context * ctx)1020 bool ColorBar::onClear(Context* ctx)
1021 {
1022   m_paletteView.clearSelection();
1023   return true;
1024 }
1025 
onCancel(Context * ctx)1026 void ColorBar::onCancel(Context* ctx)
1027 {
1028   m_paletteView.deselect();
1029   m_paletteView.discardClipboardSelection();
1030   invalidate();
1031 }
1032 
onFixWarningClick(ColorButton * colorButton,ui::Button * warningIcon)1033 void ColorBar::onFixWarningClick(ColorButton* colorButton, ui::Button* warningIcon)
1034 {
1035   COLOR_BAR_TRACE("ColorBar::onFixWarningClick(%s)\n", colorButton->getColor().toString().c_str());
1036 
1037   Palette* palette = get_current_palette();
1038   const int oldEntries = palette->size();
1039 
1040   Command* command = Commands::instance()->byId(CommandId::AddColor());
1041   Params params;
1042   params.set("source", "color");
1043   params.set("color", colorButton->getColor().toString().c_str());
1044   UIContext::instance()->executeCommand(command, params);
1045 
1046   // Select the new FG/BG color as an indexed color
1047   if (inEditMode()) {
1048     const int newEntries = palette->size();
1049     if (oldEntries != newEntries) {
1050       base::ScopedValue<bool> sync(m_fromPref, true, m_fromPref);
1051       app::Color newIndex = app::Color::fromIndex(newEntries-1);
1052       if (colorButton == &m_bgColor)
1053         setBgColor(newIndex);
1054       setFgColor(newIndex);
1055     }
1056   }
1057 }
1058 
onTimerTick()1059 void ColorBar::onTimerTick()
1060 {
1061   // Redraw all editors
1062   if (m_redrawAll) {
1063     m_redrawAll = false;
1064     m_implantChange = false;
1065     m_redrawTimer.stop();
1066 
1067     // Call all observers of PaletteChange event.
1068     m_selfPalChange = true;
1069     App::instance()->PaletteChange();
1070     m_selfPalChange = false;
1071 
1072     // Redraw all editors
1073     try {
1074       ContextWriter writer(UIContext::instance(), 500);
1075       Doc* document(writer.document());
1076       if (document != NULL)
1077         document->notifyGeneralUpdate();
1078     }
1079     catch (...) {
1080       // Do nothing
1081     }
1082   }
1083   // Redraw just the current editor
1084   else {
1085     m_redrawAll = true;
1086     if (current_editor != NULL)
1087       current_editor->updateEditor();
1088   }
1089 }
1090 
updateWarningIcon(const app::Color & color,ui::Button * warningIcon)1091 void ColorBar::updateWarningIcon(const app::Color& color, ui::Button* warningIcon)
1092 {
1093   int index = -1;
1094 
1095   if (color.getType() == app::Color::MaskType) {
1096     if (current_editor &&
1097         current_editor->sprite()) {
1098       index = current_editor->sprite()->transparentColor();
1099     }
1100     else
1101       index = 0;
1102   }
1103   else {
1104     index = get_current_palette()->findExactMatch(
1105       color.getRed(),
1106       color.getGreen(),
1107       color.getBlue(),
1108       color.getAlpha(), -1);
1109   }
1110 
1111   warningIcon->setVisible(index < 0);
1112   warningIcon->parent()->layout();
1113 }
1114 
1115 // Changes the selected color palettes with the given
1116 // app::Color. Returns the first modified index in the palette.
setPaletteEntry(const app::Color & color)1117 int ColorBar::setPaletteEntry(const app::Color& color)
1118 {
1119   int selIdx = m_paletteView.getSelectedEntry();
1120   if (selIdx < 0) {
1121     if (getFgColor().getType() == app::Color::IndexType) {
1122       selIdx = getFgColor().getIndex();
1123     }
1124   }
1125 
1126   PalettePicks entries;
1127   m_paletteView.getSelectedEntries(entries);
1128   if (entries.picks() == 0) {
1129     if (selIdx >= 0 && selIdx < entries.size()) {
1130       entries[selIdx] = true;
1131     }
1132   }
1133 
1134   doc::color_t c =
1135     doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
1136 
1137   Palette* palette = get_current_palette();
1138   for (int i=0; i<palette->size(); ++i) {
1139     if (entries[i])
1140       palette->setEntry(i, c);
1141   }
1142 
1143   if (selIdx < 0 ||
1144       selIdx >= entries.size() ||
1145       !entries[selIdx])
1146     selIdx = entries.firstPick();
1147 
1148   return selIdx;
1149 }
1150 
updateCurrentSpritePalette(const char * operationName)1151 void ColorBar::updateCurrentSpritePalette(const char* operationName)
1152 {
1153   if (UIContext::instance()->activeDocument() &&
1154       UIContext::instance()->activeDocument()->sprite()) {
1155     try {
1156       ContextWriter writer(UIContext::instance(), 500);
1157       Doc* document(writer.document());
1158       Sprite* sprite(writer.sprite());
1159       Palette* newPalette = get_current_palette(); // System current pal
1160       frame_t frame = writer.frame();
1161       Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal
1162       int from, to;
1163 
1164       // Check differences between current sprite palette and current system palette
1165       from = to = -1;
1166       currentSpritePalette->countDiff(newPalette, &from, &to);
1167 
1168       if (from >= 0 && to >= from) {
1169         DocUndo* undo = document->undoHistory();
1170         Cmd* cmd = new cmd::SetPalette(sprite, frame, newPalette);
1171 
1172         // Add undo information to save the range of pal entries that will be modified.
1173         if (m_implantChange &&
1174             undo->lastExecutedCmd() &&
1175             undo->lastExecutedCmd()->label() == operationName) {
1176           // Implant the cmd in the last CmdSequence if it's
1177           // related about color palette modifications
1178           ASSERT(dynamic_cast<CmdSequence*>(undo->lastExecutedCmd()));
1179           static_cast<CmdSequence*>(undo->lastExecutedCmd())->add(cmd);
1180           cmd->execute(UIContext::instance());
1181         }
1182         else {
1183           Transaction transaction(writer.context(), operationName, ModifyDocument);
1184           transaction.execute(cmd);
1185           transaction.commit();
1186         }
1187       }
1188     }
1189     catch (base::Exception& e) {
1190       Console::showException(e);
1191     }
1192   }
1193 
1194   m_paletteView.invalidate();
1195 
1196   if (!m_redrawTimer.isRunning())
1197     m_redrawTimer.start();
1198 
1199   m_redrawAll = false;
1200   m_implantChange = true;
1201 }
1202 
setupTooltips(TooltipManager * tooltipManager)1203 void ColorBar::setupTooltips(TooltipManager* tooltipManager)
1204 {
1205   tooltipManager->addTooltipFor(
1206     m_buttons.getItem((int)PalButton::EDIT),
1207     key_tooltip("Edit Color", CommandId::PaletteEditor()), BOTTOM);
1208 
1209   tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::SORT), "Sort & Gradients", BOTTOM);
1210   tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::PRESETS), "Presets", BOTTOM);
1211   tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::OPTIONS), "Options", BOTTOM);
1212   tooltipManager->addTooltipFor(&m_remapButton, "Matches old indexes with new indexes", BOTTOM);
1213 }
1214 
1215 // static
fixColorIndex(ColorButton & colorButton)1216 void ColorBar::fixColorIndex(ColorButton& colorButton)
1217 {
1218   app::Color color = colorButton.getColor();
1219 
1220   if (color.getType() == Color::IndexType) {
1221     int oldIndex = color.getIndex();
1222     int newIndex = MID(0, oldIndex, get_current_palette()->size()-1);
1223     if (oldIndex != newIndex) {
1224       color = Color::fromIndex(newIndex);
1225       colorButton.setColor(color);
1226     }
1227   }
1228 }
1229 
registerCommands()1230 void ColorBar::registerCommands()
1231 {
1232   Commands::instance()
1233     ->add(
1234       new QuickCommand(
1235         CommandId::ShowPaletteSortOptions(),
1236         [this]{ this->showPaletteSortOptions(); }))
1237     ->add(
1238       new QuickCommand(
1239         CommandId::ShowPalettePresets(),
1240         [this]{ this->showPalettePresets(); }))
1241     ->add(
1242       new QuickCommand(
1243         CommandId::ShowPaletteOptions(),
1244         [this]{ this->showPaletteOptions(); }));
1245 }
1246 
showPaletteSortOptions()1247 void ColorBar::showPaletteSortOptions()
1248 {
1249   gfx::Rect bounds = m_buttons.getItem(
1250     static_cast<int>(PalButton::SORT))->bounds();
1251 
1252   Menu menu;
1253   MenuItem
1254     rev("Reverse Colors"),
1255     grd("Gradient"),
1256     hue("Sort by Hue"),
1257     sat("Sort by Saturation"),
1258     bri("Sort by Brightness"),
1259     lum("Sort by Luminance"),
1260     red("Sort by Red"),
1261     grn("Sort by Green"),
1262     blu("Sort by Blue"),
1263     alp("Sort by Alpha"),
1264     asc("Ascending"),
1265     des("Descending");
1266   menu.addChild(&rev);
1267   menu.addChild(&grd);
1268   menu.addChild(new ui::MenuSeparator);
1269   menu.addChild(&hue);
1270   menu.addChild(&sat);
1271   menu.addChild(&bri);
1272   menu.addChild(&lum);
1273   menu.addChild(new ui::MenuSeparator);
1274   menu.addChild(&red);
1275   menu.addChild(&grn);
1276   menu.addChild(&blu);
1277   menu.addChild(&alp);
1278   menu.addChild(new ui::MenuSeparator);
1279   menu.addChild(&asc);
1280   menu.addChild(&des);
1281 
1282   if (m_ascending) asc.setSelected(true);
1283   else des.setSelected(true);
1284 
1285   rev.Click.connect(base::Bind<void>(&ColorBar::onReverseColors, this));
1286   grd.Click.connect(base::Bind<void>(&ColorBar::onGradient, this));
1287   hue.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::HUE));
1288   sat.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::SATURATION));
1289   bri.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::VALUE));
1290   lum.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::LUMA));
1291   red.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::RED));
1292   grn.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::GREEN));
1293   blu.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::BLUE));
1294   alp.Click.connect(base::Bind<void>(&ColorBar::onSortBy, this, SortPaletteBy::ALPHA));
1295   asc.Click.connect(base::Bind<void>(&ColorBar::setAscending, this, true));
1296   des.Click.connect(base::Bind<void>(&ColorBar::setAscending, this, false));
1297 
1298   menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
1299 }
1300 
showPalettePresets()1301 void ColorBar::showPalettePresets()
1302 {
1303   if (!m_palettePopup) {
1304     try {
1305       m_palettePopup.reset(new PalettePopup());
1306     }
1307     catch (const std::exception& ex) {
1308       Console::showException(ex);
1309       return;
1310     }
1311   }
1312 
1313   if (!m_palettePopup->isVisible()) {
1314     gfx::Rect bounds = m_buttons.getItem(
1315       static_cast<int>(PalButton::PRESETS))->bounds();
1316 
1317     m_palettePopup->showPopup(
1318       gfx::Rect(bounds.x, bounds.y+bounds.h,
1319                 ui::display_w()/2, ui::display_h()*3/4));
1320   }
1321   else {
1322     m_palettePopup->closeWindow(NULL);
1323   }
1324 }
1325 
showPaletteOptions()1326 void ColorBar::showPaletteOptions()
1327 {
1328   Menu* menu = AppMenus::instance()->getPalettePopupMenu();
1329   if (menu) {
1330     gfx::Rect bounds = m_buttons.getItem(
1331       static_cast<int>(PalButton::OPTIONS))->bounds();
1332 
1333     menu->showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
1334   }
1335 }
1336 
1337 } // namespace app
1338