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