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 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/timeline/timeline.h"
12 
13 #include "app/app.h"
14 #include "app/app_menus.h"
15 #include "app/color_utils.h"
16 #include "app/commands/command.h"
17 #include "app/commands/commands.h"
18 #include "app/commands/params.h"
19 #include "app/console.h"
20 #include "app/context_access.h"
21 #include "app/doc.h"
22 #include "app/doc_api.h"
23 #include "app/doc_event.h"
24 #include "app/doc_range_ops.h"
25 #include "app/doc_undo.h"
26 #include "app/loop_tag.h"
27 #include "app/modules/editors.h"
28 #include "app/modules/gfx.h"
29 #include "app/modules/gui.h"
30 #include "app/thumbnails.h"
31 #include "app/transaction.h"
32 #include "app/ui/app_menuitem.h"
33 #include "app/ui/configure_timeline_popup.h"
34 #include "app/ui/doc_view.h"
35 #include "app/ui/editor/editor.h"
36 #include "app/ui/input_chain.h"
37 #include "app/ui/skin/skin_theme.h"
38 #include "app/ui/status_bar.h"
39 #include "app/ui/workspace.h"
40 #include "app/ui_context.h"
41 #include "app/util/clipboard.h"
42 #include "app/util/layer_boundaries.h"
43 #include "app/util/readable_time.h"
44 #include "base/bind.h"
45 #include "base/convert_to.h"
46 #include "base/memory.h"
47 #include "base/scoped_value.h"
48 #include "base/unique_ptr.h"
49 #include "doc/doc.h"
50 #include "doc/frame_tag.h"
51 #include "gfx/point.h"
52 #include "gfx/rect.h"
53 #include "she/font.h"
54 #include "she/surface.h"
55 #include "she/system.h"
56 #include "ui/scroll_helper.h"
57 #include "ui/ui.h"
58 
59 #include <cstdio>
60 #include <vector>
61 
62 namespace app {
63 
64 using namespace app::skin;
65 using namespace gfx;
66 using namespace doc;
67 using namespace ui;
68 
69 enum {
70   PART_NOTHING = 0,
71   PART_TOP,
72   PART_SEPARATOR,
73   PART_HEADER_EYE,
74   PART_HEADER_PADLOCK,
75   PART_HEADER_CONTINUOUS,
76   PART_HEADER_GEAR,
77   PART_HEADER_ONIONSKIN,
78   PART_HEADER_ONIONSKIN_RANGE_LEFT,
79   PART_HEADER_ONIONSKIN_RANGE_RIGHT,
80   PART_HEADER_LAYER,
81   PART_HEADER_FRAME,
82   PART_ROW,
83   PART_ROW_EYE_ICON,
84   PART_ROW_PADLOCK_ICON,
85   PART_ROW_CONTINUOUS_ICON,
86   PART_ROW_TEXT,
87   PART_CEL,
88   PART_RANGE_OUTLINE,
89   PART_FRAME_TAG,
90   PART_FRAME_TAGS,
91   PART_FRAME_TAG_BAND,
92   PART_FRAME_TAG_SWITCH_BUTTONS,
93   PART_FRAME_TAG_SWITCH_BAND_BUTTON,
94 };
95 
96 struct Timeline::DrawCelData {
97   CelIterator begin;
98   CelIterator end;
99   CelIterator it;
100   CelIterator prevIt;           // Previous Cel to "it"
101   CelIterator nextIt;           // Next Cel to "it"
102   CelIterator activeIt;         // Active Cel iterator
103   CelIterator firstLink;        // First link to the active cel
104   CelIterator lastLink;         // Last link to the active cel
105 };
106 
107 namespace {
108 
109   template<typename Pred>
for_each_expanded_layer(LayerGroup * group,Pred && pred,int level=0,LayerFlags flags=LayerFlags (int (LayerFlags::Visible)|int (LayerFlags::Editable)))110   void for_each_expanded_layer(LayerGroup* group,
111                                Pred&& pred,
112                                int level = 0,
113                                LayerFlags flags =
114                                  LayerFlags(int(LayerFlags::Visible) |
115                                             int(LayerFlags::Editable))) {
116     if (!group->isVisible())
117       flags = static_cast<LayerFlags>(int(flags) & ~int(LayerFlags::Visible));
118 
119     if (!group->isEditable())
120       flags = static_cast<LayerFlags>(int(flags) & ~int(LayerFlags::Editable));
121 
122     for (Layer* child : group->layers()) {
123       if (child->isGroup() && !child->isCollapsed())
124         for_each_expanded_layer<Pred>(
125           static_cast<LayerGroup*>(child),
126           std::forward<Pred>(pred),
127           level+1,
128           flags);
129 
130       pred(child, level, flags);
131     }
132   }
133 
is_copy_key_pressed(ui::Message * msg)134   bool is_copy_key_pressed(ui::Message* msg) {
135     return
136       msg->ctrlPressed() ||  // Ctrl is common on Windows
137       msg->altPressed();    // Alt is common on Mac OS X
138   }
139 
is_select_layer_in_canvas_key_pressed(ui::Message * msg)140   bool is_select_layer_in_canvas_key_pressed(ui::Message* msg) {
141 #ifdef __APPLE__
142     return msg->cmdPressed();
143 #else
144     return msg->ctrlPressed();
145 #endif
146   }
147 
get_select_layer_in_canvas_op(ui::Message * msg)148   SelectLayerBoundariesOp get_select_layer_in_canvas_op(ui::Message* msg) {
149     if (msg->altPressed() && msg->shiftPressed())
150       return SelectLayerBoundariesOp::INTERSECT;
151     else if (msg->shiftPressed())
152       return SelectLayerBoundariesOp::ADD;
153     else if (msg->altPressed())
154       return SelectLayerBoundariesOp::SUBTRACT;
155     else
156       return SelectLayerBoundariesOp::REPLACE;
157   }
158 
159 } // anonymous namespace
160 
Hit(int part,layer_t layer,frame_t frame,ObjectId frameTag,int band)161 Timeline::Hit::Hit(int part,
162                    layer_t layer,
163                    frame_t frame,
164                    ObjectId frameTag,
165                    int band)
166   : part(part),
167     layer(layer),
168     frame(frame),
169     frameTag(frameTag),
170     veryBottom(false),
171     band(band)
172 {
173 }
174 
operator !=(const Hit & other) const175 bool Timeline::Hit::operator!=(const Hit& other) const
176 {
177   return
178     part != other.part ||
179     layer != other.layer ||
180     frame != other.frame ||
181     frameTag != other.frameTag ||
182     band != other.band;
183 }
184 
getFrameTag() const185 FrameTag* Timeline::Hit::getFrameTag() const
186 {
187   return get<FrameTag>(frameTag);
188 }
189 
DropTarget()190 Timeline::DropTarget::DropTarget()
191 {
192   hhit = HNone;
193   vhit = VNone;
194   outside = false;
195 }
196 
DropTarget(const DropTarget & o)197 Timeline::DropTarget::DropTarget(const DropTarget& o)
198   : hhit(o.hhit)
199   , vhit(o.vhit)
200   , outside(o.outside)
201 {
202 }
203 
Row()204 Timeline::Row::Row()
205   : m_layer(nullptr),
206     m_level(0),
207     m_inheritedFlags(LayerFlags::None)
208 {
209 }
210 
Row(Layer * layer,const int level,const LayerFlags inheritedFlags)211 Timeline::Row::Row(Layer* layer,
212                    const int level,
213                    const LayerFlags inheritedFlags)
214   : m_layer(layer),
215     m_level(level),
216     m_inheritedFlags(inheritedFlags)
217 {
218 }
219 
parentVisible() const220 bool Timeline::Row::parentVisible() const
221 {
222   return ((int(m_inheritedFlags) & int(LayerFlags::Visible)) != 0);
223 }
224 
parentEditable() const225 bool Timeline::Row::parentEditable() const
226 {
227   return ((int(m_inheritedFlags) & int(LayerFlags::Editable)) != 0);
228 }
229 
Timeline()230 Timeline::Timeline()
231   : Widget(kGenericWidget)
232   , m_hbar(HORIZONTAL, this)
233   , m_vbar(VERTICAL, this)
234   , m_zoom(1.0)
235   , m_context(UIContext::instance())
236   , m_editor(NULL)
237   , m_document(NULL)
238   , m_sprite(NULL)
239   , m_rangeLocks(0)
240   , m_state(STATE_STANDBY)
241   , m_tagBands(0)
242   , m_tagFocusBand(-1)
243   , m_separator_x(100 * guiscale())
244   , m_separator_w(1)
245   , m_confPopup(NULL)
246   , m_clipboard_timer(100, this)
247   , m_offset_count(0)
248   , m_redrawMarchingAntsOnly(false)
249   , m_scroll(false)
250   , m_fromTimeline(false)
251 {
252   enableFlags(CTRL_RIGHT_CLICK);
253 
254   m_ctxConn = m_context->AfterCommandExecution.connect(
255     &Timeline::onAfterCommandExecution, this);
256   m_context->documents().add_observer(this);
257   m_context->add_observer(this);
258 
259   setDoubleBuffered(true);
260   addChild(&m_aniControls);
261   addChild(&m_hbar);
262   addChild(&m_vbar);
263 
264   m_hbar.setTransparent(true);
265   m_vbar.setTransparent(true);
266   initTheme();
267 }
268 
~Timeline()269 Timeline::~Timeline()
270 {
271   m_clipboard_timer.stop();
272 
273   detachDocument();
274   m_context->documents().remove_observer(this);
275   m_context->remove_observer(this);
276   delete m_confPopup;
277 }
278 
setZoom(const double zoom)279 void Timeline::setZoom(const double zoom)
280 {
281   m_zoom = MID(1.0, zoom, 10.0);
282   m_thumbnailsOverlayDirection = gfx::Point(int(frameBoxWidth()*1.0), int(frameBoxWidth()*0.5));
283   m_thumbnailsOverlayVisible = false;
284 }
285 
setZoomAndUpdate(const double zoom,const bool updatePref)286 void Timeline::setZoomAndUpdate(const double zoom,
287                                 const bool updatePref)
288 {
289   if (zoom != m_zoom) {
290     setZoom(zoom);
291     regenerateTagBands();
292     updateScrollBars();
293     setViewScroll(viewScroll());
294     invalidate();
295   }
296   if (updatePref && zoom != docPref().thumbnails.zoom()) {
297     docPref().thumbnails.zoom(zoom);
298     docPref().thumbnails.enabled(zoom > 1);
299   }
300 }
301 
onThumbnailsPrefChange()302 void Timeline::onThumbnailsPrefChange()
303 {
304   setZoomAndUpdate(
305     docPref().thumbnails.enabled() ?
306     docPref().thumbnails.zoom(): 1.0,
307     false);
308 }
309 
updateUsingEditor(Editor * editor)310 void Timeline::updateUsingEditor(Editor* editor)
311 {
312   // TODO if editor == m_editor, avoid doing a lot of extra work here
313 
314   m_aniControls.updateUsingEditor(editor);
315 
316   if (editor != m_editor) {
317     // Save active m_tagFocusBand into the old focused editor
318     if (m_editor)
319       m_editor->setTagFocusBand(m_tagFocusBand);
320     m_tagFocusBand = -1;
321   }
322 
323   detachDocument();
324 
325   if (m_range.enabled() &&
326       m_rangeLocks == 0) {
327     m_range.clearRange();
328   }
329 
330   // We always update the editor. In this way the timeline keeps in
331   // sync with the active editor.
332   m_editor = editor;
333   if (!m_editor)
334     return;                // No editor specified.
335 
336   m_editor->add_observer(this);
337   m_tagFocusBand = m_editor->tagFocusBand();
338 
339   Site site;
340   DocView* view = m_editor->getDocView();
341   view->getSite(&site);
342 
343   site.document()->add_observer(this);
344 
345   Doc* app_document = site.document();
346   DocumentPreferences& docPref = Preferences::instance().document(app_document);
347 
348   m_thumbnailsPrefConn = docPref.thumbnails.AfterChange.connect(
349     base::Bind<void>(&Timeline::onThumbnailsPrefChange, this));
350 
351   setZoom(
352     docPref.thumbnails.enabled() ?
353     docPref.thumbnails.zoom(): 1.0);
354 
355   // If we are already in the same position as the "editor", we don't
356   // need to update the at all timeline.
357   if (m_document == site.document() &&
358       m_sprite == site.sprite() &&
359       m_layer == site.layer() &&
360       m_frame == site.frame())
361     return;
362 
363   m_document = site.document();
364   m_sprite = site.sprite();
365   m_layer = site.layer();
366   m_frame = site.frame();
367   m_state = STATE_STANDBY;
368   m_hot.part = PART_NOTHING;
369   m_clk.part = PART_NOTHING;
370 
371   m_firstFrameConn = Preferences::instance().document(m_document)
372     .timeline.firstFrame.AfterChange.connect(base::Bind<void>(&Timeline::invalidate, this));
373 
374   setFocusStop(true);
375   regenerateRows();
376   setViewScroll(viewScroll());
377   showCurrentCel();
378 }
379 
detachDocument()380 void Timeline::detachDocument()
381 {
382   m_firstFrameConn.disconnect();
383 
384   if (m_document) {
385     m_thumbnailsPrefConn.disconnect();
386     m_document->remove_observer(this);
387     m_document = nullptr;
388     m_sprite = nullptr;
389     m_layer = nullptr;
390   }
391 
392   if (m_editor) {
393     m_editor->remove_observer(this);
394     m_editor = nullptr;
395   }
396 
397   invalidate();
398 }
399 
isMovingCel() const400 bool Timeline::isMovingCel() const
401 {
402   return (m_state == STATE_MOVING_RANGE &&
403           m_range.type() == Range::kCels);
404 }
405 
selectedLayersBounds(const SelectedLayers & layers,layer_t * first,layer_t * last) const406 bool Timeline::selectedLayersBounds(const SelectedLayers& layers,
407                                     layer_t* first, layer_t* last) const
408 {
409   if (layers.empty())
410     return false;
411 
412   *first = *last = getLayerIndex(*layers.begin());
413 
414   for (auto layer : layers) {
415     layer_t i = getLayerIndex(layer);
416     if (*first > i) *first = i;
417     if (*last < i) *last = i;
418   }
419 
420   return true;
421 }
422 
setLayer(Layer * layer)423 void Timeline::setLayer(Layer* layer)
424 {
425   ASSERT(m_editor != NULL);
426 
427   invalidateLayer(m_layer);
428   invalidateLayer(layer);
429 
430   m_layer = layer;
431 
432   // Expand all parents
433   if (m_layer) {
434     LayerGroup* group = m_layer->parent();
435     while (group != m_layer->sprite()->root()) {
436       // Expand this group
437       group->setCollapsed(false);
438       group = group->parent();
439     }
440     regenerateRows();
441     invalidate();
442   }
443 
444   if (m_editor->layer() != layer)
445     m_editor->setLayer(m_layer);
446 }
447 
setFrame(frame_t frame,bool byUser)448 void Timeline::setFrame(frame_t frame, bool byUser)
449 {
450   ASSERT(m_editor != NULL);
451   // ASSERT(frame >= 0 && frame < m_sprite->totalFrames());
452 
453   if (frame < 0)
454     frame = firstFrame();
455   else if (frame >= m_sprite->totalFrames())
456     frame = frame_t(m_sprite->totalFrames()-1);
457 
458   if (m_layer) {
459     Cel* oldCel = m_layer->cel(m_frame);
460     Cel* newCel = m_layer->cel(frame);
461     std::size_t oldLinks = (oldCel ? oldCel->links(): 0);
462     std::size_t newLinks = (newCel ? newCel->links(): 0);
463     if ((oldLinks && !newCel) ||
464         (newLinks && !oldCel) ||
465         ((oldLinks || newLinks) && (oldCel->data() != newCel->data())))
466       invalidateLayer(m_layer);
467   }
468 
469   invalidateFrame(m_frame);
470   invalidateFrame(frame);
471 
472   gfx::Rect onionRc = getOnionskinFramesBounds();
473 
474   m_frame = frame;
475 
476   // Invalidate the onionskin handles area
477   onionRc |= getOnionskinFramesBounds();
478   if (!onionRc.isEmpty())
479     invalidateRect(onionRc.offset(origin()));
480 
481   if (m_editor->frame() != frame) {
482     const bool isPlaying = m_editor->isPlaying();
483 
484     if (isPlaying)
485       m_editor->stop();
486 
487     m_editor->setFrame(m_frame);
488 
489     if (isPlaying)
490       m_editor->play(false,
491                      Preferences::instance().editor.playAll());
492   }
493 }
494 
prepareToMoveRange()495 void Timeline::prepareToMoveRange()
496 {
497   ASSERT(m_range.enabled());
498 
499   layer_t i = 0;
500   for (auto layer : m_range.selectedLayers().toLayerList()) {
501     if (layer == m_layer)
502       break;
503     ++i;
504   }
505 
506   frame_t j = 0;
507   for (auto frame : m_range.selectedFrames()) {
508     if (frame == m_frame)
509       break;
510     ++j;
511   }
512 
513   m_moveRangeData.activeRelativeLayer = i;
514   m_moveRangeData.activeRelativeFrame = j;
515 }
516 
moveRange(const Range & range)517 void Timeline::moveRange(const Range& range)
518 {
519   regenerateRows();
520 
521   // We have to change the range before we generate an
522   // onActiveSiteChange() event so observers (like cel properties
523   // dialog) know the new selected range.
524   m_range = range;
525 
526   layer_t i = 0;
527   for (auto layer : range.selectedLayers().toLayerList()) {
528     if (i == m_moveRangeData.activeRelativeLayer) {
529       setLayer(layer);
530       break;
531     }
532     ++i;
533   }
534 
535   frame_t j = 0;
536   for (auto frame : range.selectedFrames()) {
537     if (j == m_moveRangeData.activeRelativeFrame) {
538       setFrame(frame, true);
539       break;
540     }
541     ++j;
542   }
543 
544   // Select the range again (it might be lost between all the
545   // setLayer()/setFrame() calls).
546   m_range = range;
547 }
548 
setRange(const Range & range)549 void Timeline::setRange(const Range& range)
550 {
551   m_range = range;
552   invalidate();
553 }
554 
activateClipboardRange()555 void Timeline::activateClipboardRange()
556 {
557   m_clipboard_timer.start();
558   invalidate();
559 }
560 
getFrameTagByFrame(const frame_t frame,const bool getLoopTagIfNone)561 FrameTag* Timeline::getFrameTagByFrame(const frame_t frame,
562                                        const bool getLoopTagIfNone)
563 {
564   if (!m_sprite)
565     return nullptr;
566 
567   if (m_tagFocusBand < 0) {
568     FrameTag* tag = get_animation_tag(m_sprite, frame);
569     if (!tag && getLoopTagIfNone)
570       tag = get_loop_tag(m_sprite);
571     return tag;
572   }
573 
574   for (FrameTag* frameTag : m_sprite->frameTags()) {
575     if (frame >= frameTag->fromFrame() &&
576         frame <= frameTag->toFrame() &&
577         m_tagBand[frameTag] == m_tagFocusBand) {
578       return frameTag;
579     }
580   }
581 
582   return nullptr;
583 }
584 
onProcessMessage(Message * msg)585 bool Timeline::onProcessMessage(Message* msg)
586 {
587   switch (msg->type()) {
588 
589     case kFocusEnterMessage:
590       App::instance()->inputChain().prioritize(this, msg);
591       break;
592 
593     case kTimerMessage:
594       if (static_cast<TimerMessage*>(msg)->timer() == &m_clipboard_timer) {
595         Doc* clipboard_document;
596         DocRange clipboard_range;
597         clipboard::get_document_range_info(
598           &clipboard_document,
599           &clipboard_range);
600 
601         if (isVisible() &&
602             m_document &&
603             m_document == clipboard_document) {
604           // Set offset to make selection-movement effect
605           if (m_offset_count < 7)
606             m_offset_count++;
607           else
608             m_offset_count = 0;
609 
610           bool redrawOnlyMarchingAnts = getUpdateRegion().isEmpty();
611           invalidateRect(gfx::Rect(getRangeBounds(clipboard_range)).offset(origin()));
612           if (redrawOnlyMarchingAnts)
613             m_redrawMarchingAntsOnly = true;
614         }
615         else if (m_clipboard_timer.isRunning()) {
616           m_clipboard_timer.stop();
617         }
618       }
619       break;
620 
621     case kMouseDownMessage: {
622       MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
623 
624       if (!m_document)
625         break;
626 
627       if (mouseMsg->middle() ||
628           she::instance()->isKeyPressed(kKeySpace)) {
629         captureMouse();
630         m_state = STATE_SCROLLING;
631         m_oldPos = static_cast<MouseMessage*>(msg)->position();
632         return true;
633       }
634 
635       // As we can ctrl+click color bar + timeline, now we have to
636       // re-prioritize timeline on each click.
637       App::instance()->inputChain().prioritize(this, msg);
638 
639       // Update hot part (as the user might have left clicked with
640       // Ctrl on OS X, which it's converted to a right-click and it's
641       // interpreted as other action by the Timeline::hitTest())
642       setHot(hitTest(msg, mouseMsg->position() - bounds().origin()));
643 
644       // Clicked-part = hot-part.
645       m_clk = m_hot;
646 
647       // With Ctrl+click (Win/Linux) or Shift+click (OS X) we can
648       // select non-adjacents layer/frame ranges
649       bool clearRange =
650 #if !defined(__APPLE__)
651         !msg->ctrlPressed() &&
652 #endif
653         !msg->shiftPressed();
654 
655       captureMouse();
656 
657       switch (m_hot.part) {
658 
659         case PART_SEPARATOR:
660           m_state = STATE_MOVING_SEPARATOR;
661           break;
662 
663         case PART_HEADER_EYE: {
664           ASSERT(m_sprite);
665           if (!m_sprite)
666             break;
667 
668           bool regenRows = false;
669           bool newVisibleState = !allLayersVisible();
670           for (Layer* topLayer : m_sprite->root()->layers()) {
671             if (topLayer->isVisible() != newVisibleState) {
672               topLayer->setVisible(newVisibleState);
673               if (topLayer->isGroup())
674                 regenRows = true;
675             }
676           }
677 
678           if (regenRows) {
679             regenerateRows();
680             invalidate();
681           }
682 
683           // Redraw all views.
684           m_document->notifyGeneralUpdate();
685           break;
686         }
687 
688         case PART_HEADER_PADLOCK: {
689           ASSERT(m_sprite);
690           if (!m_sprite)
691             break;
692 
693           bool regenRows = false;
694           bool newEditableState = !allLayersUnlocked();
695           for (Layer* topLayer : m_sprite->root()->layers()) {
696             if (topLayer->isEditable() != newEditableState) {
697               topLayer->setEditable(newEditableState);
698               if (topLayer->isGroup()) {
699                 regenRows = true;
700               }
701             }
702           }
703 
704           if (regenRows) {
705             regenerateRows();
706             invalidate();
707           }
708           break;
709         }
710 
711         case PART_HEADER_CONTINUOUS: {
712           bool newContinuousState = !allLayersContinuous();
713           for (size_t i=0; i<m_rows.size(); i++)
714             m_rows[i].layer()->setContinuous(newContinuousState);
715           invalidate();
716           break;
717         }
718 
719         case PART_HEADER_ONIONSKIN: {
720           docPref().onionskin.active(!docPref().onionskin.active());
721           invalidate();
722           break;
723         }
724         case PART_HEADER_ONIONSKIN_RANGE_LEFT: {
725           m_state = STATE_MOVING_ONIONSKIN_RANGE_LEFT;
726           m_origFrames = docPref().onionskin.prevFrames();
727           break;
728         }
729         case PART_HEADER_ONIONSKIN_RANGE_RIGHT: {
730           m_state = STATE_MOVING_ONIONSKIN_RANGE_RIGHT;
731           m_origFrames = docPref().onionskin.nextFrames();
732           break;
733         }
734         case PART_HEADER_FRAME: {
735           bool selectFrame = (mouseMsg->left() || !isFrameActive(m_clk.frame));
736 
737           if (selectFrame) {
738             m_state = STATE_SELECTING_FRAMES;
739             if (clearRange)
740               clearAndInvalidateRange();
741             m_range.startRange(m_layer, m_clk.frame, Range::kFrames);
742             m_startRange = m_range;
743 
744             setFrame(m_clk.frame, true);
745           }
746           break;
747         }
748         case PART_ROW_TEXT: {
749           base::ScopedValue<bool> lock(m_fromTimeline, true, false);
750           const layer_t old_layer = getLayerIndex(m_layer);
751           const bool selectLayer = (mouseMsg->left() || !isLayerActive(m_clk.layer));
752           const bool selectLayerInCanvas =
753             (m_clk.layer != -1 &&
754              mouseMsg->left() &&
755              is_select_layer_in_canvas_key_pressed(mouseMsg));
756 
757           if (selectLayerInCanvas) {
758             select_layer_boundaries(m_rows[m_clk.layer].layer(), m_frame,
759                                     get_select_layer_in_canvas_op(mouseMsg));
760           }
761           else if (selectLayer) {
762             m_state = STATE_SELECTING_LAYERS;
763             if (clearRange)
764               clearAndInvalidateRange();
765             m_range.startRange(m_rows[m_clk.layer].layer(),
766                                m_frame, Range::kLayers);
767             m_startRange = m_range;
768 
769             // Did the user select another layer?
770             if (old_layer != m_clk.layer) {
771               setLayer(m_rows[m_clk.layer].layer());
772               invalidate();
773             }
774           }
775 
776           // Change the scroll to show the new selected layer/cel.
777           showCel(m_clk.layer, m_frame);
778           break;
779         }
780 
781         case PART_ROW_EYE_ICON:
782           if (validLayer(m_clk.layer)) {
783             Row& row = m_rows[m_clk.layer];
784             Layer* layer = row.layer();
785             ASSERT(layer)
786 
787             // Hide everything or restore alternative state
788             bool oneWithInternalState = false;
789             if (msg->altPressed()) {
790               for (const Row& row : m_rows) {
791                 const Layer* l = row.layer();
792                 if (l->hasFlags(LayerFlags::Internal_WasVisible)) {
793                   oneWithInternalState = true;
794                   break;
795                 }
796               }
797 
798               // If there is one layer with the internal state, restore the previous visible state
799               if (oneWithInternalState) {
800                 for (Row& row : m_rows) {
801                   Layer* l = row.layer();
802                   if (l->hasFlags(LayerFlags::Internal_WasVisible)) {
803                     l->setVisible(true);
804                     l->switchFlags(LayerFlags::Internal_WasVisible, false);
805                   }
806                   else {
807                     l->setVisible(false);
808                   }
809                 }
810               }
811               // In other case, hide everything
812               else {
813                 for (Row& row : m_rows) {
814                   Layer* l = row.layer();
815                   l->switchFlags(LayerFlags::Internal_WasVisible, l->isVisible());
816                   l->setVisible(false);
817                 }
818               }
819 
820               regenerateRows();
821               invalidate();
822 
823               m_document->notifyGeneralUpdate();
824             }
825 
826             if (layer->isVisible() && !oneWithInternalState)
827               m_state = STATE_HIDING_LAYERS;
828             else
829               m_state = STATE_SHOWING_LAYERS;
830 
831             setLayerVisibleFlag(m_clk.layer, m_state == STATE_SHOWING_LAYERS);
832           }
833           break;
834 
835         case PART_ROW_PADLOCK_ICON:
836           if (validLayer(m_hot.layer)) {
837             Row& row = m_rows[m_clk.layer];
838             Layer* layer = row.layer();
839             ASSERT(layer);
840             if (layer->isEditable())
841               m_state = STATE_LOCKING_LAYERS;
842             else
843               m_state = STATE_UNLOCKING_LAYERS;
844 
845             setLayerEditableFlag(m_clk.layer, m_state == STATE_UNLOCKING_LAYERS);
846           }
847           break;
848 
849         case PART_ROW_CONTINUOUS_ICON:
850           if (validLayer(m_hot.layer)) {
851             Row& row = m_rows[m_clk.layer];
852             Layer* layer = row.layer();
853             ASSERT(layer);
854 
855             if (layer->isImage()) {
856               if (layer->isContinuous())
857                 m_state = STATE_DISABLING_CONTINUOUS_LAYERS;
858               else
859                 m_state = STATE_ENABLING_CONTINUOUS_LAYERS;
860 
861               setLayerContinuousFlag(m_clk.layer, m_state == STATE_ENABLING_CONTINUOUS_LAYERS);
862             }
863             else if (layer->isGroup()) {
864               if (layer->isCollapsed())
865                 m_state = STATE_EXPANDING_LAYERS;
866               else
867                 m_state = STATE_COLLAPSING_LAYERS;
868 
869               setLayerCollapsedFlag(m_clk.layer, m_state == STATE_COLLAPSING_LAYERS);
870               updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
871             }
872           }
873           break;
874 
875         case PART_CEL: {
876           base::ScopedValue<bool> lock(m_fromTimeline, true, false);
877           const layer_t old_layer = getLayerIndex(m_layer);
878           const bool selectCel = (mouseMsg->left()
879             || !isLayerActive(m_clk.layer)
880             || !isFrameActive(m_clk.frame));
881           const bool selectCelInCanvas =
882             (m_clk.layer != -1 &&
883              mouseMsg->left() &&
884              is_select_layer_in_canvas_key_pressed(mouseMsg));
885           const frame_t old_frame = m_frame;
886 
887           if (selectCelInCanvas) {
888             select_layer_boundaries(m_rows[m_clk.layer].layer(),
889                                     m_clk.frame,
890                                     get_select_layer_in_canvas_op(mouseMsg));
891           }
892           else {
893             if (selectCel) {
894               m_state = STATE_SELECTING_CELS;
895               if (clearRange)
896                 clearAndInvalidateRange();
897               m_range.startRange(m_rows[m_clk.layer].layer(),
898                                  m_clk.frame, Range::kCels);
899               m_startRange = m_range;
900             }
901 
902             // Select the new clicked-part.
903             if (old_layer != m_clk.layer
904                 || old_frame != m_clk.frame) {
905               setLayer(m_rows[m_clk.layer].layer());
906               setFrame(m_clk.frame, true);
907               invalidate();
908             }
909           }
910 
911           // Change the scroll to show the new selected cel.
912           showCel(m_clk.layer, m_frame);
913           invalidate();
914           break;
915         }
916         case PART_RANGE_OUTLINE:
917           m_state = STATE_MOVING_RANGE;
918 
919           // If we select the outline of a cels range, we have to
920           // recalculate the dragged cel (m_clk) using a special
921           // hitTestCel() and limiting the clicked cel inside the
922           // range bounds.
923           if (m_range.type() == Range::kCels) {
924             m_clk = hitTestCel(mouseMsg->position() - bounds().origin());
925 
926             if (m_range.layers() > 0) {
927               layer_t layerFirst, layerLast;
928               if (selectedLayersBounds(selectedLayers(),
929                                        &layerFirst, &layerLast)) {
930                 layer_t layerIdx = m_clk.layer;
931                 layerIdx = MID(layerFirst, layerIdx, layerLast);
932                 m_clk.layer = layerIdx;
933               }
934             }
935 
936             if (m_clk.frame < m_range.firstFrame())
937               m_clk.frame = m_range.firstFrame();
938             else if (m_clk.frame > m_range.lastFrame())
939               m_clk.frame = m_range.lastFrame();
940           }
941           break;
942       }
943 
944       // Redraw the new clicked part (header, layer or cel).
945       invalidateHit(m_clk);
946       break;
947     }
948 
949     case kMouseLeaveMessage: {
950       if (m_hot.part != PART_NOTHING) {
951         invalidateHit(m_hot);
952         m_hot = Hit();
953       }
954       break;
955     }
956 
957     case kMouseMoveMessage: {
958       if (!m_document)
959         break;
960 
961       gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position()
962         - bounds().origin();
963 
964       Hit hit;
965       setHot(hit = hitTest(msg, mousePos));
966 
967       if (hasCapture()) {
968         switch (m_state) {
969 
970           case STATE_SCROLLING: {
971             gfx::Point absMousePos = static_cast<MouseMessage*>(msg)->position();
972             setViewScroll(
973               viewScroll() - gfx::Point(
974                 (absMousePos.x - m_oldPos.x),
975                 (absMousePos.y - m_oldPos.y)));
976 
977             m_oldPos = absMousePos;
978             return true;
979           }
980 
981           case STATE_MOVING_ONIONSKIN_RANGE_LEFT: {
982             gfx::Rect onionRc = getOnionskinFramesBounds();
983 
984             int newValue = m_origFrames + (m_clk.frame - hit.frame);
985             docPref().onionskin.prevFrames(MAX(0, newValue));
986 
987             onionRc |= getOnionskinFramesBounds();
988             invalidateRect(onionRc.offset(origin()));
989             return true;
990           }
991 
992           case STATE_MOVING_ONIONSKIN_RANGE_RIGHT: {
993             gfx::Rect onionRc = getOnionskinFramesBounds();
994 
995             int newValue = m_origFrames - (m_clk.frame - hit.frame);
996             docPref().onionskin.nextFrames(MAX(0, newValue));
997 
998             onionRc |= getOnionskinFramesBounds();
999             invalidateRect(onionRc.offset(origin()));
1000             return true;
1001           }
1002 
1003           case STATE_SHOWING_LAYERS:
1004           case STATE_HIDING_LAYERS:
1005             m_clk = hit;
1006             if (hit.part == PART_ROW_EYE_ICON) {
1007               setLayerVisibleFlag(hit.layer, m_state == STATE_SHOWING_LAYERS);
1008             }
1009             break;
1010 
1011           case STATE_LOCKING_LAYERS:
1012           case STATE_UNLOCKING_LAYERS:
1013             m_clk = hit;
1014             if (hit.part == PART_ROW_PADLOCK_ICON) {
1015               setLayerEditableFlag(hit.layer, m_state == STATE_UNLOCKING_LAYERS);
1016             }
1017             break;
1018 
1019           case STATE_ENABLING_CONTINUOUS_LAYERS:
1020           case STATE_DISABLING_CONTINUOUS_LAYERS:
1021             m_clk = hit;
1022             if (hit.part == PART_ROW_CONTINUOUS_ICON) {
1023               setLayerContinuousFlag(hit.layer, m_state == STATE_ENABLING_CONTINUOUS_LAYERS);
1024             }
1025             break;
1026 
1027           case STATE_EXPANDING_LAYERS:
1028           case STATE_COLLAPSING_LAYERS:
1029             m_clk = hit;
1030             if (hit.part == PART_ROW_CONTINUOUS_ICON) {
1031               setLayerCollapsedFlag(hit.layer, m_state == STATE_COLLAPSING_LAYERS);
1032               updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1033             }
1034             break;
1035 
1036         }
1037 
1038         // If the mouse pressed the mouse's button in the separator,
1039         // we shouldn't change the hot (so the separator can be
1040         // tracked to the mouse's released).
1041         if (m_clk.part == PART_SEPARATOR) {
1042           m_separator_x = MAX(0, mousePos.x);
1043           layout();
1044           return true;
1045         }
1046       }
1047 
1048       updateDropRange(mousePos);
1049 
1050       if (hasCapture()) {
1051         switch (m_state) {
1052 
1053           case STATE_SELECTING_LAYERS: {
1054             Layer* hitLayer = m_rows[hit.layer].layer();
1055             if (m_layer != hitLayer) {
1056               m_clk.layer = hit.layer;
1057 
1058               // We have to change the range before we generate an
1059               // onActiveSiteChange() event so observers (like cel
1060               // properties dialog) know the new selected range.
1061               m_range = m_startRange;
1062               m_range.endRange(hitLayer, m_frame);
1063 
1064               setLayer(hitLayer);
1065             }
1066             break;
1067           }
1068 
1069           case STATE_SELECTING_FRAMES: {
1070             invalidateRange();
1071 
1072             m_range = m_startRange;
1073             m_range.endRange(m_layer, hit.frame);
1074 
1075             setFrame(m_clk.frame = hit.frame, true);
1076 
1077             invalidateRange();
1078             break;
1079           }
1080 
1081           case STATE_SELECTING_CELS:
1082             Layer* hitLayer = m_rows[hit.layer].layer();
1083             if ((m_layer != hitLayer) || (m_frame != hit.frame)) {
1084               m_clk.layer = hit.layer;
1085 
1086               m_range = m_startRange;
1087               m_range.endRange(hitLayer, hit.frame);
1088 
1089               setLayer(hitLayer);
1090               setFrame(m_clk.frame = hit.frame, true);
1091             }
1092             break;
1093         }
1094       }
1095 
1096       updateStatusBar(msg);
1097       updateCelOverlayBounds(hit);
1098       return true;
1099     }
1100 
1101     case kMouseUpMessage:
1102       if (hasCapture()) {
1103         ASSERT(m_document != NULL);
1104 
1105         MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1106 
1107         if (m_state == STATE_SCROLLING) {
1108           m_state = STATE_STANDBY;
1109           releaseMouse();
1110           return true;
1111         }
1112 
1113         bool regenRows = false;
1114         bool relayout = false;
1115         setHot(hitTest(msg, mouseMsg->position() - bounds().origin()));
1116 
1117         switch (m_hot.part) {
1118 
1119           case PART_HEADER_GEAR: {
1120             gfx::Rect gearBounds =
1121               getPartBounds(Hit(PART_HEADER_GEAR)).offset(bounds().origin());
1122 
1123             if (!m_confPopup) {
1124               ConfigureTimelinePopup* popup =
1125                 new ConfigureTimelinePopup();
1126 
1127               popup->remapWindow();
1128               m_confPopup = popup;
1129             }
1130 
1131             if (!m_confPopup->isVisible()) {
1132               gfx::Rect bounds = m_confPopup->bounds();
1133               ui::fit_bounds(BOTTOM, gearBounds, bounds);
1134 
1135               m_confPopup->moveWindow(bounds);
1136               m_confPopup->openWindow();
1137             }
1138             else
1139               m_confPopup->closeWindow(NULL);
1140             break;
1141           }
1142 
1143           case PART_HEADER_FRAME:
1144             // Show the frame pop-up menu.
1145             if (mouseMsg->right()) {
1146               if (m_clk.frame == m_hot.frame) {
1147                 Menu* popupMenu = AppMenus::instance()->getFramePopupMenu();
1148                 if (popupMenu) {
1149                   popupMenu->showPopup(mouseMsg->position());
1150 
1151                   m_state = STATE_STANDBY;
1152                   invalidate();
1153                 }
1154               }
1155             }
1156             break;
1157 
1158           case PART_ROW_TEXT:
1159             // Show the layer pop-up menu.
1160             if (mouseMsg->right()) {
1161               if (m_clk.layer == m_hot.layer) {
1162                 Menu* popupMenu = AppMenus::instance()->getLayerPopupMenu();
1163                 if (popupMenu) {
1164                   popupMenu->showPopup(mouseMsg->position());
1165 
1166                   m_state = STATE_STANDBY;
1167                   invalidate();
1168                 }
1169               }
1170             }
1171             break;
1172 
1173           case PART_CEL: {
1174             // Show the cel pop-up menu.
1175             if (mouseMsg->right()) {
1176               Menu* popupMenu =
1177                 (m_state == STATE_MOVING_RANGE &&
1178                  m_range.type() == Range::kCels &&
1179                  (m_hot.layer != m_clk.layer ||
1180                   m_hot.frame != m_clk.frame)) ?
1181                   AppMenus::instance()->getCelMovementPopupMenu():
1182                   AppMenus::instance()->getCelPopupMenu();
1183               if (popupMenu) {
1184                 popupMenu->showPopup(mouseMsg->position());
1185 
1186                 // Do not drop in this function, the drop is done from
1187                 // the menu in case we've used the
1188                 // CelMovementPopupMenu
1189                 m_state = STATE_STANDBY;
1190                 invalidate();
1191               }
1192             }
1193             break;
1194           }
1195 
1196           case PART_FRAME_TAG: {
1197             FrameTag* frameTag = m_clk.getFrameTag();
1198             if (frameTag) {
1199               Params params;
1200               params.set("id", base::convert_to<std::string>(frameTag->id()).c_str());
1201 
1202               // As the m_clk.frameTag can be deleted with
1203               // RemoveFrameTag command, we've to clean all references
1204               // to it from Hit() structures.
1205               cleanClk();
1206               m_hot = m_clk;
1207 
1208               if (mouseMsg->right()) {
1209                 Menu* popupMenu = AppMenus::instance()->getFrameTagPopupMenu();
1210                 if (popupMenu) {
1211                   AppMenuItem::setContextParams(params);
1212                   popupMenu->showPopup(mouseMsg->position());
1213                   AppMenuItem::setContextParams(Params());
1214 
1215                   m_state = STATE_STANDBY;
1216                   invalidate();
1217                 }
1218               }
1219               else if (mouseMsg->left()) {
1220                 Command* command = Commands::instance()
1221                   ->byId(CommandId::FrameTagProperties());
1222                 UIContext::instance()->executeCommand(command, params);
1223               }
1224             }
1225             break;
1226           }
1227 
1228           case PART_FRAME_TAG_SWITCH_BAND_BUTTON:
1229             if (m_clk.band >= 0) {
1230               focusTagBand(m_clk.band);
1231               regenRows = true;
1232               relayout = true;
1233             }
1234             break;
1235 
1236         }
1237 
1238         if (regenRows) {
1239           regenerateRows();
1240           invalidate();
1241         }
1242         if (relayout)
1243           layout();
1244 
1245         if (m_state == STATE_MOVING_RANGE &&
1246             m_dropRange.type() != Range::kNone) {
1247           dropRange(is_copy_key_pressed(mouseMsg) ?
1248             Timeline::kCopy:
1249             Timeline::kMove);
1250         }
1251 
1252         // Clean the clicked-part & redraw the hot-part.
1253         cleanClk();
1254 
1255         if (hasCapture())
1256           invalidate();
1257         else
1258           invalidateHit(m_hot);
1259 
1260         // Restore the cursor.
1261         m_state = STATE_STANDBY;
1262         setCursor(msg, hitTest(msg, mouseMsg->position() - bounds().origin()));
1263 
1264         releaseMouse();
1265         updateStatusBar(msg);
1266         return true;
1267       }
1268       break;
1269 
1270     case kDoubleClickMessage:
1271       switch (m_hot.part) {
1272 
1273         case PART_ROW_TEXT: {
1274           Command* command = Commands::instance()
1275             ->byId(CommandId::LayerProperties());
1276 
1277           UIContext::instance()->executeCommand(command);
1278           return true;
1279         }
1280 
1281         case PART_HEADER_FRAME: {
1282           Command* command = Commands::instance()
1283             ->byId(CommandId::FrameProperties());
1284           Params params;
1285           params.set("frame", "current");
1286 
1287           UIContext::instance()->executeCommand(command, params);
1288           return true;
1289         }
1290 
1291         case PART_CEL: {
1292           Command* command = Commands::instance()
1293             ->byId(CommandId::CelProperties());
1294 
1295           UIContext::instance()->executeCommand(command);
1296           return true;
1297         }
1298 
1299         case PART_FRAME_TAG_BAND:
1300           if (m_hot.band >= 0) {
1301             focusTagBand(m_hot.band);
1302             regenerateRows();
1303             invalidate();
1304             layout();
1305             return true;
1306           }
1307           break;
1308 
1309       }
1310       break;
1311 
1312     case kKeyDownMessage: {
1313       bool used = false;
1314 
1315       switch (static_cast<KeyMessage*>(msg)->scancode()) {
1316 
1317         case kKeyEsc:
1318           if (m_state == STATE_STANDBY) {
1319             clearAndInvalidateRange();
1320           }
1321           else {
1322             m_state = STATE_STANDBY;
1323           }
1324 
1325           // Don't use this key, so it's caught by CancelCommand.
1326           // TODO The deselection of the current range should be
1327           // handled in CancelCommand itself.
1328           //used = true;
1329           break;
1330 
1331         case kKeySpace: {
1332           // If we receive a key down event when the Space bar is
1333           // pressed (because the Timeline has the keyboard focus) but
1334           // we don't have the mouse inside, we don't consume this
1335           // event so the Space bar can be used by the Editor to
1336           // activate the hand/pan/scroll tool.
1337           if (!hasMouse())
1338             break;
1339 
1340           m_scroll = true;
1341           used = true;
1342           break;
1343         }
1344       }
1345 
1346       updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1347       if (used)
1348         return true;
1349 
1350       break;
1351     }
1352 
1353     case kKeyUpMessage: {
1354       bool used = false;
1355 
1356       switch (static_cast<KeyMessage*>(msg)->scancode()) {
1357 
1358         case kKeySpace: {
1359           m_scroll = false;
1360 
1361           // We have to clear all the kKeySpace keys in buffer.
1362           she::instance()->clearKeyboardBuffer();
1363           used = true;
1364           break;
1365         }
1366       }
1367 
1368       updateByMousePos(msg, ui::get_mouse_position() - bounds().origin());
1369       if (used)
1370         return true;
1371 
1372       break;
1373     }
1374 
1375     case kMouseWheelMessage:
1376       if (m_document) {
1377         gfx::Point delta = static_cast<MouseMessage*>(msg)->wheelDelta();
1378         const bool precise = static_cast<MouseMessage*>(msg)->preciseWheel();
1379 
1380         // Zoom timeline
1381         if (msg->ctrlPressed() || // TODO configurable
1382             msg->cmdPressed()) {
1383           double dz = delta.x + delta.y;
1384 
1385           if (precise) {
1386             dz /= 1.5;
1387             if (dz < -1.0) dz = -1.0;
1388             else if (dz > 1.0) dz = 1.0;
1389           }
1390 
1391           setZoomAndUpdate(m_zoom - dz, true);
1392         }
1393         else {
1394           if (!precise) {
1395             delta.x *= frameBoxWidth();
1396             delta.y *= layerBoxHeight();
1397 
1398             if (delta.x == 0 && // On macOS shift already changes the wheel axis
1399                 msg->shiftPressed()) {
1400               if (std::fabs(delta.y) > delta.x)
1401                 std::swap(delta.x, delta.y);
1402             }
1403 
1404             if (msg->altPressed()) {
1405               delta.x *= 3;
1406               delta.y *= 3;
1407             }
1408           }
1409           setViewScroll(viewScroll() + delta);
1410         }
1411       }
1412       break;
1413 
1414     case kSetCursorMessage:
1415       if (m_document) {
1416         setCursor(msg, m_hot);
1417         return true;
1418       }
1419       break;
1420 
1421     case kTouchMagnifyMessage:
1422       setZoomAndUpdate(
1423         m_zoom + m_zoom * static_cast<ui::TouchMessage*>(msg)->magnification(),
1424         true);
1425       break;
1426   }
1427 
1428   return Widget::onProcessMessage(msg);
1429 }
1430 
onInitTheme(ui::InitThemeEvent & ev)1431 void Timeline::onInitTheme(ui::InitThemeEvent& ev)
1432 {
1433   Widget::onInitTheme(ev);
1434 
1435   SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
1436   int barsize = theme->dimensions.miniScrollbarSize();
1437   m_hbar.setBarWidth(barsize);
1438   m_vbar.setBarWidth(barsize);
1439   m_hbar.setStyle(theme->styles.transparentScrollbar());
1440   m_vbar.setStyle(theme->styles.transparentScrollbar());
1441   m_hbar.setThumbStyle(theme->styles.transparentScrollbarThumb());
1442   m_vbar.setThumbStyle(theme->styles.transparentScrollbarThumb());
1443 }
1444 
onInvalidateRegion(const gfx::Region & region)1445 void Timeline::onInvalidateRegion(const gfx::Region& region)
1446 {
1447   Widget::onInvalidateRegion(region);
1448   m_redrawMarchingAntsOnly = false;
1449 }
1450 
onSizeHint(SizeHintEvent & ev)1451 void Timeline::onSizeHint(SizeHintEvent& ev)
1452 {
1453   // This doesn't matter, the AniEditor'll use the entire screen anyway.
1454   ev.setSizeHint(Size(32, 32));
1455 }
1456 
onResize(ui::ResizeEvent & ev)1457 void Timeline::onResize(ui::ResizeEvent& ev)
1458 {
1459   gfx::Rect rc = ev.bounds();
1460   setBoundsQuietly(rc);
1461 
1462   gfx::Size sz = m_aniControls.sizeHint();
1463   m_aniControls.setBounds(
1464     gfx::Rect(
1465       rc.x,
1466       rc.y+(visibleTagBands()-1)*oneTagHeight(),
1467       MIN(sz.w, m_separator_x),
1468       oneTagHeight()));
1469 
1470   updateScrollBars();
1471 }
1472 
onPaint(ui::PaintEvent & ev)1473 void Timeline::onPaint(ui::PaintEvent& ev)
1474 {
1475   Graphics* g = ev.graphics();
1476   bool noDoc = (m_document == NULL);
1477   if (noDoc)
1478     goto paintNoDoc;
1479 
1480   try {
1481     // Lock the sprite to read/render it. We wait 1/4 secs in case
1482     // the background thread is making a backup.
1483     const DocReader docReader(m_document, 250);
1484 
1485     if (m_redrawMarchingAntsOnly) {
1486       drawClipboardRange(g);
1487       m_redrawMarchingAntsOnly = false;
1488       return;
1489     }
1490 
1491     layer_t layer, firstLayer, lastLayer;
1492     frame_t frame, firstFrame, lastFrame;
1493 
1494     getDrawableLayers(g, &firstLayer, &lastLayer);
1495     getDrawableFrames(g, &firstFrame, &lastFrame);
1496 
1497     drawTop(g);
1498 
1499     // Draw the header for layers.
1500     drawHeader(g);
1501 
1502     // Draw the header for each visible frame.
1503     {
1504       IntersectClip clip(g, getFrameHeadersBounds());
1505       if (clip) {
1506         for (frame=firstFrame; frame<=lastFrame; ++frame)
1507           drawHeaderFrame(g, frame);
1508 
1509         // Draw onionskin indicators.
1510         gfx::Rect bounds = getOnionskinFramesBounds();
1511         if (!bounds.isEmpty()) {
1512           drawPart(
1513             g, bounds, nullptr,
1514             skinTheme()->styles.timelineOnionskinRange(),
1515             false, false, false);
1516         }
1517       }
1518     }
1519 
1520     // Draw each visible layer.
1521     DrawCelData data;
1522     for (layer=lastLayer; layer>=firstLayer; --layer) {
1523       {
1524         IntersectClip clip(g, getLayerHeadersBounds());
1525         if (clip)
1526           drawLayer(g, layer);
1527       }
1528 
1529       IntersectClip clip(g, getCelsBounds());
1530       if (!clip)
1531         continue;
1532 
1533       Layer* layerPtr = m_rows[layer].layer();
1534       if (!layerPtr->isImage()) {
1535         // Draw empty cels
1536         for (frame=firstFrame; frame<=lastFrame; ++frame) {
1537           drawCel(g, layer, frame, nullptr, nullptr);
1538         }
1539         continue;
1540       }
1541 
1542       // Get the first CelIterator to be drawn (it is the first cel with cel->frame >= first_frame)
1543       LayerImage* layerImagePtr = static_cast<LayerImage*>(layerPtr);
1544       data.begin = layerImagePtr->getCelBegin();
1545       data.end = layerImagePtr->getCelEnd();
1546       data.it = layerImagePtr->findFirstCelIteratorAfter(firstFrame-1);
1547       data.prevIt = data.end;
1548       data.nextIt = (data.it != data.end ? data.it+1: data.end);
1549 
1550       // Calculate link range for the active cel
1551       data.firstLink = data.end;
1552       data.lastLink = data.end;
1553 
1554       if (layerPtr == m_layer) {
1555         data.activeIt = layerImagePtr->findCelIterator(m_frame);
1556         if (data.activeIt != data.end) {
1557           data.firstLink = data.activeIt;
1558           data.lastLink = data.activeIt;
1559 
1560           ObjectId imageId = (*data.activeIt)->image()->id();
1561 
1562           auto it2 = data.activeIt;
1563           if (it2 != data.begin) {
1564             do {
1565               --it2;
1566               if ((*it2)->image()->id() == imageId) {
1567                 data.firstLink = it2;
1568                 if ((*data.firstLink)->frame() < firstFrame)
1569                   break;
1570               }
1571             } while (it2 != data.begin);
1572           }
1573 
1574           it2 = data.activeIt;
1575           while (it2 != data.end) {
1576             if ((*it2)->image()->id() == imageId) {
1577               data.lastLink = it2;
1578               if ((*data.lastLink)->frame() > lastFrame)
1579                 break;
1580             }
1581             ++it2;
1582           }
1583         }
1584       }
1585       else
1586         data.activeIt = data.end;
1587 
1588       // Draw every visible cel for each layer.
1589       for (frame=firstFrame; frame<=lastFrame; ++frame) {
1590         Cel* cel =
1591           (data.it != data.end &&
1592            (*data.it)->frame() == frame ? *data.it: nullptr);
1593 
1594         drawCel(g, layer, frame, cel, &data);
1595 
1596         if (cel) {
1597           data.prevIt = data.it;
1598           data.it = data.nextIt; // Point to next cel
1599           if (data.nextIt != data.end)
1600             ++data.nextIt;
1601         }
1602       }
1603     }
1604 
1605     drawPaddings(g);
1606     drawFrameTags(g);
1607     drawRangeOutline(g);
1608     drawClipboardRange(g);
1609     drawCelOverlay(g);
1610 
1611 #if 0 // Use this code to debug the calculated m_dropRange by updateDropRange()
1612     {
1613       g->drawRect(gfx::rgba(255, 255, 0), getRangeBounds(m_range));
1614       g->drawRect(gfx::rgba(255, 0, 0), getRangeBounds(m_dropRange));
1615     }
1616 #endif
1617   }
1618   catch (const LockedDocException&) {
1619     noDoc = true;
1620     defer_invalid_rect(g->getClipBounds().offset(bounds().origin()));
1621   }
1622 
1623 paintNoDoc:;
1624   if (noDoc)
1625     drawPart(
1626       g, clientBounds(), nullptr,
1627       skinTheme()->styles.timelinePadding());
1628 }
1629 
onAfterCommandExecution(CommandExecutionEvent & ev)1630 void Timeline::onAfterCommandExecution(CommandExecutionEvent& ev)
1631 {
1632   if (!m_document)
1633     return;
1634 
1635   // TODO improve this: no need to regenerate everything after each command
1636   regenerateRows();
1637   showCurrentCel();
1638   invalidate();
1639 }
1640 
onActiveSiteChange(const Site & site)1641 void Timeline::onActiveSiteChange(const Site& site)
1642 {
1643   if (hasMouse()) {
1644     updateStatusBarForFrame(site.frame(), nullptr, site.cel());
1645   }
1646 }
1647 
onRemoveDocument(Doc * document)1648 void Timeline::onRemoveDocument(Doc* document)
1649 {
1650   if (document == m_document) {
1651     detachDocument();
1652   }
1653 }
1654 
onGeneralUpdate(DocEvent & ev)1655 void Timeline::onGeneralUpdate(DocEvent& ev)
1656 {
1657   invalidate();
1658 }
1659 
onAddLayer(DocEvent & ev)1660 void Timeline::onAddLayer(DocEvent& ev)
1661 {
1662   ASSERT(ev.layer() != NULL);
1663 
1664   setLayer(ev.layer());
1665 
1666   regenerateRows();
1667   showCurrentCel();
1668   clearClipboardRange();
1669   invalidate();
1670 }
1671 
onAfterRemoveLayer(DocEvent & ev)1672 void Timeline::onAfterRemoveLayer(DocEvent& ev)
1673 {
1674   Sprite* sprite = ev.sprite();
1675   Layer* layer = ev.layer();
1676 
1677   // If the layer that was removed is the selected one
1678   if (layer == getLayer()) {
1679     LayerGroup* parent = layer->parent();
1680     Layer* layer_select = NULL;
1681 
1682     // Select previous layer, or next layer, or the parent (if it is
1683     // not the main layer of sprite set).
1684     if (layer->getPrevious())
1685       layer_select = layer->getPrevious();
1686     else if (layer->getNext())
1687       layer_select = layer->getNext();
1688     else if (parent != sprite->root())
1689       layer_select = parent;
1690 
1691     setLayer(layer_select);
1692   }
1693 
1694   regenerateRows();
1695   showCurrentCel();
1696   clearClipboardRange();
1697   invalidate();
1698 }
1699 
onAddFrame(DocEvent & ev)1700 void Timeline::onAddFrame(DocEvent& ev)
1701 {
1702   setFrame(ev.frame(), false);
1703 
1704   showCurrentCel();
1705   clearClipboardRange();
1706   invalidate();
1707 }
1708 
onRemoveFrame(DocEvent & ev)1709 void Timeline::onRemoveFrame(DocEvent& ev)
1710 {
1711   // Adjust current frame of all editors that are in a frame more
1712   // advanced that the removed one.
1713   if (getFrame() > ev.frame()) {
1714     setFrame(getFrame()-1, false);
1715   }
1716   // If the editor was in the previous "last frame" (current value of
1717   // totalFrames()), we've to adjust it to the new last frame
1718   // (lastFrame())
1719   else if (getFrame() >= sprite()->totalFrames()) {
1720     setFrame(sprite()->lastFrame(), false);
1721   }
1722 
1723   // Disable the selected range when we remove frames
1724   if (m_range.enabled())
1725     clearAndInvalidateRange();
1726 
1727   showCurrentCel();
1728   clearClipboardRange();
1729   invalidate();
1730 }
1731 
onSelectionChanged(DocEvent & ev)1732 void Timeline::onSelectionChanged(DocEvent& ev)
1733 {
1734   if (m_rangeLocks == 0)
1735     clearAndInvalidateRange();
1736 }
1737 
onLayerNameChange(DocEvent & ev)1738 void Timeline::onLayerNameChange(DocEvent& ev)
1739 {
1740   invalidate();
1741 }
1742 
onAddFrameTag(DocEvent & ev)1743 void Timeline::onAddFrameTag(DocEvent& ev)
1744 {
1745   if (m_tagFocusBand >= 0) {
1746     m_tagFocusBand = -1;
1747     regenerateRows();
1748     layout();
1749   }
1750 }
1751 
onRemoveFrameTag(DocEvent & ev)1752 void Timeline::onRemoveFrameTag(DocEvent& ev)
1753 {
1754   onAddFrameTag(ev);
1755 }
1756 
onStateChanged(Editor * editor)1757 void Timeline::onStateChanged(Editor* editor)
1758 {
1759   m_aniControls.updateUsingEditor(editor);
1760 }
1761 
onAfterFrameChanged(Editor * editor)1762 void Timeline::onAfterFrameChanged(Editor* editor)
1763 {
1764   if (m_fromTimeline)
1765     return;
1766 
1767   setFrame(editor->frame(), false);
1768 
1769   if (!hasCapture())
1770     clearAndInvalidateRange();
1771 
1772   showCurrentCel();
1773 }
1774 
onAfterLayerChanged(Editor * editor)1775 void Timeline::onAfterLayerChanged(Editor* editor)
1776 {
1777   if (m_fromTimeline)
1778     return;
1779 
1780   if (!hasCapture())
1781     m_range.clearRange();
1782 
1783   setLayer(editor->layer());
1784   showCurrentCel();
1785 }
1786 
onDestroyEditor(Editor * editor)1787 void Timeline::onDestroyEditor(Editor* editor)
1788 {
1789   ASSERT(m_editor == editor);
1790   if (m_editor == editor) {
1791     m_editor->remove_observer(this);
1792     m_editor = nullptr;
1793   }
1794 }
1795 
setCursor(ui::Message * msg,const Hit & hit)1796 void Timeline::setCursor(ui::Message* msg, const Hit& hit)
1797 {
1798   // Scrolling.
1799   if (m_state == STATE_SCROLLING || m_scroll) {
1800     ui::set_mouse_cursor(kScrollCursor);
1801   }
1802   // Moving.
1803   else if (m_state == STATE_MOVING_RANGE) {
1804     if (is_copy_key_pressed(msg))
1805       ui::set_mouse_cursor(kArrowPlusCursor);
1806     else
1807       ui::set_mouse_cursor(kMoveCursor);
1808   }
1809   // Normal state.
1810   else if (hit.part == PART_HEADER_ONIONSKIN_RANGE_LEFT
1811     || m_state == STATE_MOVING_ONIONSKIN_RANGE_LEFT) {
1812     ui::set_mouse_cursor(kSizeWCursor);
1813   }
1814   else if (hit.part == PART_HEADER_ONIONSKIN_RANGE_RIGHT
1815     || m_state == STATE_MOVING_ONIONSKIN_RANGE_RIGHT) {
1816     ui::set_mouse_cursor(kSizeECursor);
1817   }
1818   else if (hit.part == PART_RANGE_OUTLINE) {
1819     ui::set_mouse_cursor(kMoveCursor);
1820   }
1821   else if (hit.part == PART_SEPARATOR) {
1822     ui::set_mouse_cursor(kSizeWECursor);
1823   }
1824   else if (hit.part == PART_FRAME_TAG) {
1825     ui::set_mouse_cursor(kHandCursor);
1826   }
1827   else {
1828     ui::set_mouse_cursor(kArrowCursor);
1829   }
1830 }
1831 
getDrawableLayers(ui::Graphics * g,layer_t * firstLayer,layer_t * lastLayer)1832 void Timeline::getDrawableLayers(ui::Graphics* g, layer_t* firstLayer, layer_t* lastLayer)
1833 {
1834   int hpx = (clientBounds().h - headerBoxHeight() - topHeight());
1835   layer_t i = this->lastLayer() - ((viewScroll().y+hpx) / layerBoxHeight());
1836   i = MID(this->firstLayer(), i, this->lastLayer());
1837 
1838   layer_t j = i + (hpx / layerBoxHeight() + 1);
1839   if (!m_rows.empty())
1840     j = MID(this->firstLayer(), j, this->lastLayer());
1841   else
1842     j = -1;
1843 
1844   *firstLayer = i;
1845   *lastLayer = j;
1846 }
1847 
getDrawableFrames(ui::Graphics * g,frame_t * firstFrame,frame_t * lastFrame)1848 void Timeline::getDrawableFrames(ui::Graphics* g, frame_t* firstFrame, frame_t* lastFrame)
1849 {
1850   int availW = (clientBounds().w - m_separator_x);
1851 
1852   *firstFrame = frame_t(viewScroll().x / frameBoxWidth());
1853   *lastFrame = *firstFrame
1854     + frame_t(availW / frameBoxWidth())
1855     + ((availW % frameBoxWidth()) > 0 ? 1: 0);
1856 }
1857 
drawPart(ui::Graphics * g,const gfx::Rect & bounds,const std::string * text,ui::Style * style,const bool is_active,const bool is_hover,const bool is_clicked,const bool is_disabled)1858 void Timeline::drawPart(ui::Graphics* g, const gfx::Rect& bounds,
1859                         const std::string* text, ui::Style* style,
1860                         const bool is_active,
1861                         const bool is_hover,
1862                         const bool is_clicked,
1863                         const bool is_disabled)
1864 {
1865   IntersectClip clip(g, bounds);
1866   if (!clip)
1867     return;
1868 
1869   PaintWidgetPartInfo info;
1870   info.text = text;
1871   info.styleFlags =
1872     (is_active ? ui::Style::Layer::kFocus: 0) |
1873     (is_hover ? ui::Style::Layer::kMouse: 0) |
1874     (is_clicked ? ui::Style::Layer::kSelected: 0) |
1875     (is_disabled ? ui::Style::Layer::kDisabled: 0);
1876 
1877   theme()->paintWidgetPart(g, style, bounds, info);
1878 }
1879 
drawClipboardRange(ui::Graphics * g)1880 void Timeline::drawClipboardRange(ui::Graphics* g)
1881 {
1882   Doc* clipboard_document;
1883   DocRange clipboard_range;
1884   clipboard::get_document_range_info(
1885     &clipboard_document,
1886     &clipboard_range);
1887 
1888   if (!m_document || clipboard_document != m_document)
1889     return;
1890 
1891   if (!m_clipboard_timer.isRunning())
1892     m_clipboard_timer.start();
1893 
1894   IntersectClip clip(g, getRangeClipBounds(clipboard_range));
1895   if (clip) {
1896     CheckedDrawMode checked(g, m_offset_count,
1897                             gfx::rgba(0, 0, 0, 255),
1898                             gfx::rgba(255, 255, 255, 255));
1899     g->drawRect(gfx::rgba(0, 0, 0),
1900                 getRangeBounds(clipboard_range));
1901   }
1902 }
1903 
drawTop(ui::Graphics * g)1904 void Timeline::drawTop(ui::Graphics* g)
1905 {
1906   g->fillRect(skinTheme()->colors.workspace(),
1907     getPartBounds(Hit(PART_TOP)));
1908 }
1909 
drawHeader(ui::Graphics * g)1910 void Timeline::drawHeader(ui::Graphics* g)
1911 {
1912   auto& styles = skinTheme()->styles;
1913   bool allInvisible = allLayersInvisible();
1914   bool allLocked = allLayersLocked();
1915   bool allContinuous = allLayersContinuous();
1916 
1917   drawPart(g, getPartBounds(Hit(PART_HEADER_EYE)),
1918     nullptr,
1919     allInvisible ? styles.timelineClosedEye(): styles.timelineOpenEye(),
1920     m_clk.part == PART_HEADER_EYE,
1921     m_hot.part == PART_HEADER_EYE,
1922     m_clk.part == PART_HEADER_EYE);
1923 
1924   drawPart(g, getPartBounds(Hit(PART_HEADER_PADLOCK)),
1925     nullptr,
1926     allLocked ? styles.timelineClosedPadlock(): styles.timelineOpenPadlock(),
1927     m_clk.part == PART_HEADER_PADLOCK,
1928     m_hot.part == PART_HEADER_PADLOCK,
1929     m_clk.part == PART_HEADER_PADLOCK);
1930 
1931   drawPart(g, getPartBounds(Hit(PART_HEADER_CONTINUOUS)),
1932     nullptr,
1933     allContinuous ? styles.timelineContinuous(): styles.timelineDiscontinuous(),
1934     m_clk.part == PART_HEADER_CONTINUOUS,
1935     m_hot.part == PART_HEADER_CONTINUOUS,
1936     m_clk.part == PART_HEADER_CONTINUOUS);
1937 
1938   drawPart(g, getPartBounds(Hit(PART_HEADER_GEAR)),
1939     nullptr,
1940     styles.timelineGear(),
1941     m_clk.part == PART_HEADER_GEAR,
1942     m_hot.part == PART_HEADER_GEAR,
1943     m_clk.part == PART_HEADER_GEAR);
1944 
1945   drawPart(g, getPartBounds(Hit(PART_HEADER_ONIONSKIN)),
1946     NULL, styles.timelineOnionskin(),
1947     docPref().onionskin.active() || (m_clk.part == PART_HEADER_ONIONSKIN),
1948     m_hot.part == PART_HEADER_ONIONSKIN,
1949     m_clk.part == PART_HEADER_ONIONSKIN);
1950 
1951   // Empty header space.
1952   drawPart(g, getPartBounds(Hit(PART_HEADER_LAYER)),
1953     NULL, styles.timelineBox(), false, false, false);
1954 }
1955 
drawHeaderFrame(ui::Graphics * g,frame_t frame)1956 void Timeline::drawHeaderFrame(ui::Graphics* g, frame_t frame)
1957 {
1958   bool is_active = isFrameActive(frame);
1959   bool is_hover = (m_hot.part == PART_HEADER_FRAME && m_hot.frame == frame);
1960   bool is_clicked = (m_clk.part == PART_HEADER_FRAME && m_clk.frame == frame);
1961   gfx::Rect bounds = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frame));
1962   IntersectClip clip(g, bounds);
1963   if (!clip)
1964     return;
1965 
1966   // Draw the header for the layers.
1967   const int n = (docPref().timeline.firstFrame()+frame);
1968   std::string text = base::convert_to<std::string, int>(n % 100);
1969   if (n >= 100 && (n % 100) < 10)
1970     text.insert(0, 1, '0');
1971 
1972   drawPart(g, bounds, &text,
1973            skinTheme()->styles.timelineHeaderFrame(),
1974            is_active, is_hover, is_clicked);
1975 }
1976 
drawLayer(ui::Graphics * g,int layerIdx)1977 void Timeline::drawLayer(ui::Graphics* g, int layerIdx)
1978 {
1979   auto& styles = skinTheme()->styles;
1980   Layer* layer = m_rows[layerIdx].layer();
1981   bool is_active = isLayerActive(layerIdx);
1982   bool hotlayer = (m_hot.layer == layerIdx);
1983   bool clklayer = (m_clk.layer == layerIdx);
1984   gfx::Rect bounds = getPartBounds(Hit(PART_ROW, layerIdx, firstFrame()));
1985   IntersectClip clip(g, bounds);
1986   if (!clip)
1987     return;
1988 
1989   // Draw the eye (visible flag).
1990   bounds = getPartBounds(Hit(PART_ROW_EYE_ICON, layerIdx));
1991   drawPart(
1992     g, bounds, nullptr,
1993     (layer->isVisible() ? styles.timelineOpenEye():
1994                           styles.timelineClosedEye()),
1995     is_active || (clklayer && m_clk.part == PART_ROW_EYE_ICON),
1996     (hotlayer && m_hot.part == PART_ROW_EYE_ICON),
1997     (clklayer && m_clk.part == PART_ROW_EYE_ICON),
1998     !m_rows[layerIdx].parentVisible());
1999 
2000   // Draw the padlock (editable flag).
2001   bounds = getPartBounds(Hit(PART_ROW_PADLOCK_ICON, layerIdx));
2002   drawPart(
2003     g, bounds, nullptr,
2004     (layer->isEditable() ? styles.timelineOpenPadlock():
2005                            styles.timelineClosedPadlock()),
2006     is_active || (clklayer && m_clk.part == PART_ROW_PADLOCK_ICON),
2007     (hotlayer && m_hot.part == PART_ROW_PADLOCK_ICON),
2008     (clklayer && m_clk.part == PART_ROW_PADLOCK_ICON),
2009     !m_rows[layerIdx].parentEditable());
2010 
2011   // Draw the continuous flag/group icon.
2012   bounds = getPartBounds(Hit(PART_ROW_CONTINUOUS_ICON, layerIdx));
2013   if (layer->isImage()) {
2014     drawPart(g, bounds, nullptr,
2015              layer->isContinuous() ? styles.timelineContinuous():
2016                                      styles.timelineDiscontinuous(),
2017              is_active || (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON),
2018              (hotlayer && m_hot.part == PART_ROW_CONTINUOUS_ICON),
2019              (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON));
2020   }
2021   else if (layer->isGroup()) {
2022     drawPart(g, bounds, nullptr,
2023              layer->isCollapsed() ? styles.timelineClosedGroup():
2024                                     styles.timelineOpenGroup(),
2025              is_active || (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON),
2026              (hotlayer && m_hot.part == PART_ROW_CONTINUOUS_ICON),
2027              (clklayer && m_clk.part == PART_ROW_CONTINUOUS_ICON));
2028   }
2029 
2030   // Get the layer's name bounds.
2031   bounds = getPartBounds(Hit(PART_ROW_TEXT, layerIdx));
2032 
2033   // Draw layer name.
2034   doc::color_t layerColor = layer->userData().color();
2035   gfx::Rect textBounds = bounds;
2036   if (m_rows[layerIdx].level() > 0) {
2037     int w = m_rows[layerIdx].level()*frameBoxWidth();
2038     textBounds.x += w;
2039     textBounds.w -= w;
2040   }
2041 
2042   drawPart(g, bounds, nullptr, styles.timelineLayer(),
2043            is_active || (clklayer && m_clk.part == PART_ROW_TEXT),
2044            (hotlayer && m_hot.part == PART_ROW_TEXT),
2045            (clklayer && m_clk.part == PART_ROW_TEXT));
2046 
2047   if (doc::rgba_geta(layerColor) > 0) {
2048     // Fill with an user-defined custom color.
2049     auto b2 = bounds;
2050     b2.shrink(1*guiscale()).inflate(1*guiscale());
2051     g->fillRect(gfx::rgba(doc::rgba_getr(layerColor),
2052                           doc::rgba_getg(layerColor),
2053                           doc::rgba_getb(layerColor),
2054                           doc::rgba_geta(layerColor)),
2055                 b2);
2056 
2057     drawPart(g, textBounds,
2058              &layer->name(),
2059              styles.timelineLayerTextOnly(),
2060              is_active,
2061              (hotlayer && m_hot.part == PART_ROW_TEXT),
2062              (clklayer && m_clk.part == PART_ROW_TEXT));
2063   }
2064   else {
2065     drawPart(g, textBounds,
2066              &layer->name(),
2067              styles.timelineLayer(),
2068              is_active,
2069              (hotlayer && m_hot.part == PART_ROW_TEXT),
2070              (clklayer && m_clk.part == PART_ROW_TEXT));
2071   }
2072 
2073   if (layer->isBackground()) {
2074     int s = ui::guiscale();
2075     g->fillRect(
2076       is_active ?
2077       skinTheme()->colors.timelineClickedText():
2078       skinTheme()->colors.timelineNormalText(),
2079       gfx::Rect(bounds.x+4*s,
2080         bounds.y+bounds.h-2*s,
2081         font()->textLength(layer->name().c_str()), s));
2082   }
2083   else if (layer->isReference()) {
2084     int s = ui::guiscale();
2085     g->fillRect(
2086       is_active ?
2087       skinTheme()->colors.timelineClickedText():
2088       skinTheme()->colors.timelineNormalText(),
2089       gfx::Rect(bounds.x+4*s,
2090         bounds.y+bounds.h/2,
2091         font()->textLength(layer->name().c_str()), s));
2092   }
2093 
2094   // If this layer wasn't clicked but there are another layer clicked,
2095   // we have to draw some indicators to show that the user can move
2096   // layers.
2097   if (hotlayer && !is_active && m_clk.part == PART_ROW_TEXT) {
2098     // TODO this should be skinneable
2099     g->fillRect(
2100       skinTheme()->colors.timelineActive(),
2101       gfx::Rect(bounds.x, bounds.y, bounds.w, 2));
2102   }
2103 }
2104 
drawCel(ui::Graphics * g,layer_t layerIndex,frame_t frame,Cel * cel,DrawCelData * data)2105 void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel* cel, DrawCelData* data)
2106 {
2107   auto& styles = skinTheme()->styles;
2108   Layer* layer = m_rows[layerIndex].layer();
2109   Image* image = (cel ? cel->image(): nullptr);
2110   bool is_hover = (m_hot.part == PART_CEL &&
2111     m_hot.layer == layerIndex &&
2112     m_hot.frame == frame);
2113   const bool is_active = isCelActive(layerIndex, frame);
2114   const bool is_loosely_active = isCelLooselyActive(layerIndex, frame);
2115   const bool is_empty = (image == nullptr);
2116   gfx::Rect bounds = getPartBounds(Hit(PART_CEL, layerIndex, frame));
2117   gfx::Rect full_bounds = bounds;
2118   IntersectClip clip(g, bounds);
2119   if (!clip)
2120     return;
2121 
2122   // Draw background
2123   if (layer == m_layer && frame == m_frame)
2124     drawPart(g, bounds, nullptr,
2125              m_range.enabled() ? styles.timelineFocusedCel():
2126                                  styles.timelineSelectedCel(), false, is_hover, true);
2127   else if (m_range.enabled() && is_active)
2128     drawPart(g, bounds, nullptr, styles.timelineSelectedCel(), false, is_hover, true);
2129   else
2130     drawPart(g, bounds, nullptr, styles.timelineBox(), is_loosely_active, is_hover);
2131 
2132   // Fill with an user-defined custom color.
2133   if (cel && cel->data()) {
2134     doc::color_t celColor = cel->data()->userData().color();
2135     if (doc::rgba_geta(celColor) > 0) {
2136       auto b2 = bounds;
2137       b2.shrink(1 * guiscale()).inflate(1 * guiscale());
2138       g->fillRect(gfx::rgba(doc::rgba_getr(celColor),
2139                             doc::rgba_getg(celColor),
2140                             doc::rgba_getb(celColor),
2141                             doc::rgba_geta(celColor)),
2142                   b2);
2143     }
2144   }
2145 
2146   // Draw keyframe shape
2147 
2148   ui::Style* style = nullptr;
2149   bool fromLeft = false;
2150   bool fromRight = false;
2151   if (is_empty || !data) {
2152     style = styles.timelineEmptyFrame();
2153   }
2154   else {
2155     // Calculate which cel is next to this one (in previous and next
2156     // frame).
2157     Cel* left = (data->prevIt != data->end ? *data->prevIt: nullptr);
2158     Cel* right = (data->nextIt != data->end ? *data->nextIt: nullptr);
2159     if (left && left->frame() != frame-1) left = nullptr;
2160     if (right && right->frame() != frame+1) right = nullptr;
2161 
2162     ObjectId leftImg = (left ? left->image()->id(): 0);
2163     ObjectId rightImg = (right ? right->image()->id(): 0);
2164     fromLeft = (leftImg == cel->image()->id());
2165     fromRight = (rightImg == cel->image()->id());
2166 
2167     if (fromLeft && fromRight)
2168       style = styles.timelineFromBoth();
2169     else if (fromLeft)
2170       style = styles.timelineFromLeft();
2171     else if (fromRight)
2172       style = styles.timelineFromRight();
2173     else
2174       style = styles.timelineKeyframe();
2175   }
2176 
2177   drawPart(g, bounds, nullptr, style, is_loosely_active, is_hover);
2178 
2179   // Draw thumbnail
2180   if ((docPref().thumbnails.enabled() && m_zoom > 1) && image) {
2181     gfx::Rect thumb_bounds =
2182       gfx::Rect(bounds).shrink(
2183         skinTheme()->calcBorder(this, style));
2184 
2185     if (!thumb_bounds.isEmpty()) {
2186       she::Surface* thumb_surf = thumb::get_cel_thumbnail(cel, thumb_bounds.size());
2187       if (thumb_surf) {
2188         g->drawRgbaSurface(thumb_surf, thumb_bounds.x, thumb_bounds.y);
2189         thumb_surf->dispose();
2190       }
2191     }
2192   }
2193 
2194   // Draw decorators to link the activeCel with its links.
2195   if (data && data->activeIt != data->end)
2196     drawCelLinkDecorators(g, full_bounds, cel, frame, is_loosely_active, is_hover, data);
2197 }
2198 
updateCelOverlayBounds(const Hit & hit)2199 void Timeline::updateCelOverlayBounds(const Hit& hit)
2200 {
2201   gfx::Rect inner, outer;
2202 
2203   if (docPref().thumbnails.overlayEnabled() && hit.part == PART_CEL) {
2204     m_thumbnailsOverlayHit = hit;
2205 
2206     int max_size = headerBoxWidth() * docPref().thumbnails.overlaySize();
2207     int width, height;
2208     if (m_sprite->width() > m_sprite->height()) {
2209       width  = max_size;
2210       height = max_size * m_sprite->height() / m_sprite->width();
2211     }
2212     else {
2213       width  = max_size * m_sprite->width() / m_sprite->height();
2214       height = max_size;
2215     }
2216 
2217     gfx::Rect client_bounds = clientBounds();
2218     gfx::Point center = client_bounds.center();
2219 
2220     gfx::Rect bounds_cel = getPartBounds(m_thumbnailsOverlayHit);
2221     inner = gfx::Rect(
2222       bounds_cel.x + m_thumbnailsOverlayDirection.x,
2223       bounds_cel.y + m_thumbnailsOverlayDirection.y,
2224       width,
2225       height
2226     );
2227 
2228     if (!client_bounds.contains(inner)) {
2229       m_thumbnailsOverlayDirection = gfx::Point(
2230         bounds_cel.x < center.x ? (int)(frameBoxWidth()*1.0) : -width,
2231         bounds_cel.y < center.y ? (int)(frameBoxWidth()*0.5) : -height+(int)(frameBoxWidth()*0.5)
2232       );
2233       inner.setOrigin(gfx::Point(
2234         bounds_cel.x + m_thumbnailsOverlayDirection.x,
2235         bounds_cel.y + m_thumbnailsOverlayDirection.y
2236       ));
2237     }
2238 
2239     outer = gfx::Rect(inner).enlarge(1);
2240   }
2241   else {
2242     outer = gfx::Rect(0, 0, 0, 0);
2243   }
2244 
2245   if (outer != m_thumbnailsOverlayOuter) {
2246     if (!m_thumbnailsOverlayOuter.isEmpty()) {
2247       invalidateRect(gfx::Rect(m_thumbnailsOverlayOuter).offset(origin()));
2248     }
2249     if (!outer.isEmpty()) {
2250       invalidateRect(gfx::Rect(outer).offset(origin()));
2251     }
2252     m_thumbnailsOverlayVisible = !outer.isEmpty();
2253     m_thumbnailsOverlayOuter = outer;
2254     m_thumbnailsOverlayInner = inner;
2255   }
2256 }
2257 
drawCelOverlay(ui::Graphics * g)2258 void Timeline::drawCelOverlay(ui::Graphics* g)
2259 {
2260   if (!m_thumbnailsOverlayVisible) {
2261     return;
2262   }
2263 
2264   Layer* layer = m_rows[m_thumbnailsOverlayHit.layer].layer();
2265   Cel* cel = layer->cel(m_thumbnailsOverlayHit.frame);
2266   if (!cel) {
2267     return;
2268   }
2269   Image* image = cel->image();
2270   if (!image) {
2271     return;
2272   }
2273 
2274   IntersectClip clip(g, m_thumbnailsOverlayOuter);
2275   if (!clip)
2276     return;
2277 
2278   double scale = (
2279     m_sprite->width() > m_sprite->height() ?
2280     m_thumbnailsOverlayInner.w / (double)m_sprite->width() :
2281     m_thumbnailsOverlayInner.h / (double)m_sprite->height()
2282   );
2283 
2284   gfx::Size overlay_size(
2285     m_thumbnailsOverlayInner.w,
2286     m_thumbnailsOverlayInner.h
2287   );
2288 
2289   gfx::Rect cel_image_on_overlay(
2290     (int)(cel->x() * scale),
2291     (int)(cel->y() * scale),
2292     (int)(image->width() * scale),
2293     (int)(image->height() * scale)
2294   );
2295 
2296   she::Surface* overlay_surf = thumb::get_cel_thumbnail(cel, overlay_size, cel_image_on_overlay);
2297 
2298   g->drawRgbaSurface(overlay_surf,
2299     m_thumbnailsOverlayInner.x, m_thumbnailsOverlayInner.y);
2300   g->drawRect(gfx::rgba(0,0,0,255), m_thumbnailsOverlayOuter);
2301 
2302   overlay_surf->dispose();
2303 }
2304 
drawCelLinkDecorators(ui::Graphics * g,const gfx::Rect & bounds,Cel * cel,frame_t frame,bool is_active,bool is_hover,DrawCelData * data)2305 void Timeline::drawCelLinkDecorators(ui::Graphics* g, const gfx::Rect& bounds,
2306                                      Cel* cel, frame_t frame, bool is_active, bool is_hover,
2307                                      DrawCelData* data)
2308 {
2309   auto& styles = skinTheme()->styles;
2310   ObjectId imageId = (*data->activeIt)->image()->id();
2311 
2312   ui::Style* style1 = nullptr;
2313   ui::Style* style2 = nullptr;
2314 
2315   // Links at the left or right side
2316   bool left = (data->firstLink != data->end ? frame > (*data->firstLink)->frame(): false);
2317   bool right = (data->lastLink != data->end ? frame < (*data->lastLink)->frame(): false);
2318 
2319   if (cel && cel->image()->id() == imageId) {
2320     if (left) {
2321       Cel* prevCel = m_layer->cel(cel->frame()-1);
2322       if (!prevCel || prevCel->image()->id() != imageId)
2323         style1 = styles.timelineLeftLink();
2324     }
2325     if (right) {
2326       Cel* nextCel = m_layer->cel(cel->frame()+1);
2327       if (!nextCel || nextCel->image()->id() != imageId)
2328         style2 = styles.timelineRightLink();
2329     }
2330   }
2331   else {
2332     if (left && right)
2333       style1 = styles.timelineBothLinks();
2334   }
2335 
2336   if (style1) drawPart(g, bounds, nullptr, style1, is_active, is_hover);
2337   if (style2) drawPart(g, bounds, nullptr, style2, is_active, is_hover);
2338 }
2339 
drawFrameTags(ui::Graphics * g)2340 void Timeline::drawFrameTags(ui::Graphics* g)
2341 {
2342   IntersectClip clip(g, getPartBounds(Hit(PART_FRAME_TAGS)));
2343   if (!clip)
2344     return;
2345 
2346   SkinTheme* theme = skinTheme();
2347   auto& styles = theme->styles;
2348 
2349   g->fillRect(theme->colors.workspace(),
2350     gfx::Rect(
2351       0, font()->height(),
2352       clientBounds().w,
2353       theme->dimensions.timelineTagsAreaHeight()));
2354 
2355   // Draw active frame tag band
2356   if (m_hot.band >= 0 &&
2357       m_tagBands > 1 &&
2358       m_tagFocusBand < 0) {
2359     gfx::Rect bandBounds =
2360       getPartBounds(Hit(PART_FRAME_TAG_BAND, -1, 0,
2361                         doc::NullId, m_hot.band));
2362     g->fillRect(theme->colors.timelineBandHighlight(), bandBounds);
2363   }
2364 
2365   int passes = (m_tagFocusBand >= 0 ? 2: 1);
2366   for (int pass=0; pass<passes; ++pass) {
2367     for (FrameTag* frameTag : m_sprite->frameTags()) {
2368       int band = -1;
2369       if (m_tagFocusBand >= 0) {
2370         auto it = m_tagBand.find(frameTag);
2371         if (it != m_tagBand.end()) {
2372           band = it->second;
2373           if ((pass == 0 && band == m_tagFocusBand) ||
2374               (pass == 1 && band != m_tagFocusBand))
2375             continue;
2376         }
2377       }
2378 
2379       gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->fromFrame()));
2380       gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->toFrame()));
2381       gfx::Rect bounds = bounds1.createUnion(bounds2);
2382       gfx::Rect frameTagBounds = getPartBounds(Hit(PART_FRAME_TAG, 0, 0, frameTag->id()));
2383       bounds.h = bounds.y2() - frameTagBounds.y2();
2384       bounds.y = frameTagBounds.y2();
2385 
2386       int dx = 0, dw = 0;
2387       if (m_dropTarget.outside &&
2388           m_dropTarget.hhit != DropTarget::HNone &&
2389           m_dropRange.type() == DocRange::kFrames) {
2390         switch (m_dropTarget.hhit) {
2391           case DropTarget::Before:
2392             if (m_dropRange.firstFrame() == frameTag->fromFrame()) {
2393               dx = +frameBoxWidth()/4;
2394               dw = -frameBoxWidth()/4;
2395             }
2396             else if (m_dropRange.firstFrame()-1 == frameTag->toFrame()) {
2397               dw = -frameBoxWidth()/4;
2398             }
2399             break;
2400           case DropTarget::After:
2401             if (m_dropRange.lastFrame() == frameTag->toFrame()) {
2402               dw = -frameBoxWidth()/4;
2403             }
2404             else if (m_dropRange.lastFrame()+1 == frameTag->fromFrame()) {
2405               dx = +frameBoxWidth()/4;
2406               dw = -frameBoxWidth()/4;
2407             }
2408             break;
2409         }
2410       }
2411       bounds.x += dx;
2412       bounds.w += dw;
2413       frameTagBounds.x += dx;
2414 
2415       gfx::Color bg =
2416         (m_tagFocusBand < 0 || pass == 1) ?
2417         frameTag->color(): theme->colors.timelineBandBg();
2418       {
2419         IntersectClip clip(g, bounds);
2420         if (clip) {
2421           for (auto& layer : styles.timelineLoopRange()->layers()) {
2422             if (layer.type() == Style::Layer::Type::kBackground ||
2423                 layer.type() == Style::Layer::Type::kBackgroundBorder ||
2424                 layer.type() == Style::Layer::Type::kBorder) {
2425               const_cast<Style::Layer*>(&layer)->setColor(bg);
2426             }
2427           }
2428           drawPart(g, bounds, nullptr, styles.timelineLoopRange());
2429         }
2430       }
2431 
2432       if (m_tagFocusBand < 0 || pass == 1) {
2433         bounds = frameTagBounds;
2434 
2435         if (m_clk.part == PART_FRAME_TAG && m_clk.frameTag == frameTag->id()) {
2436           bg = color_utils::blackandwhite_neg(bg);
2437         }
2438         else if (m_hot.part == PART_FRAME_TAG && m_hot.frameTag == frameTag->id()) {
2439           int r, g, b;
2440           r = gfx::getr(bg)+32;
2441           g = gfx::getg(bg)+32;
2442           b = gfx::getb(bg)+32;
2443           r = MID(0, r, 255);
2444           g = MID(0, g, 255);
2445           b = MID(0, b, 255);
2446           bg = gfx::rgba(r, g, b, gfx::geta(bg));
2447         }
2448         g->fillRect(bg, bounds);
2449 
2450         bounds.y += 2*ui::guiscale();
2451         bounds.x += 2*ui::guiscale();
2452         g->drawText(
2453           frameTag->name(),
2454           color_utils::blackandwhite_neg(bg),
2455           gfx::ColorNone,
2456           bounds.origin());
2457       }
2458     }
2459   }
2460 
2461   // Draw button to expand/collapse the active band
2462   if (m_hot.band >= 0 && m_tagBands > 1) {
2463     gfx::Rect butBounds =
2464       getPartBounds(Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, -1, 0,
2465                         doc::NullId, m_hot.band));
2466     PaintWidgetPartInfo info;
2467     if (m_hot.part == PART_FRAME_TAG_SWITCH_BAND_BUTTON) {
2468       info.styleFlags |= ui::Style::Layer::kMouse;
2469       if (hasCapture())
2470         info.styleFlags |= ui::Style::Layer::kSelected;
2471     }
2472     theme->paintWidgetPart(g, styles.timelineSwitchBandButton(),
2473                            butBounds, info);
2474   }
2475 }
2476 
drawRangeOutline(ui::Graphics * g)2477 void Timeline::drawRangeOutline(ui::Graphics* g)
2478 {
2479   auto& styles = skinTheme()->styles;
2480 
2481   IntersectClip clip(g, getRangeClipBounds(m_range).enlarge(outlineWidth()));
2482   if (!clip)
2483     return;
2484 
2485   PaintWidgetPartInfo info;
2486   info.styleFlags =
2487     (m_range.enabled() ? ui::Style::Layer::kFocus: 0) |
2488     (m_hot.part == PART_RANGE_OUTLINE ? ui::Style::Layer::kMouse: 0);
2489 
2490   gfx::Rect bounds = getPartBounds(Hit(PART_RANGE_OUTLINE));
2491   theme()->paintWidgetPart(
2492     g, styles.timelineRangeOutline(), bounds, info);
2493 
2494   Range drop = m_dropRange;
2495   gfx::Rect dropBounds = getRangeBounds(drop);
2496 
2497   switch (drop.type()) {
2498 
2499     case Range::kCels: {
2500       dropBounds = dropBounds.enlarge(outlineWidth());
2501       info.styleFlags = ui::Style::Layer::kFocus;
2502       theme()->paintWidgetPart(
2503         g, styles.timelineRangeOutline(), dropBounds, info);
2504       break;
2505     }
2506 
2507     case Range::kFrames: {
2508       int w = 5 * guiscale(); // TODO get width from the skin info
2509 
2510       if (m_dropTarget.hhit == DropTarget::Before)
2511         dropBounds.x -= w/2;
2512       else if (drop == m_range)
2513         dropBounds.x = dropBounds.x + getRangeBounds(m_range).w - w/2;
2514       else
2515         dropBounds.x = dropBounds.x + dropBounds.w - w/2;
2516 
2517       dropBounds.w = w;
2518 
2519       info.styleFlags = 0;
2520       theme()->paintWidgetPart(
2521         g, styles.timelineDropFrameDeco(), dropBounds, info);
2522       break;
2523     }
2524 
2525     case Range::kLayers: {
2526       int h = 5 * guiscale(); // TODO get height from the skin info
2527 
2528       if (m_dropTarget.vhit == DropTarget::Top)
2529         dropBounds.y -= h/2;
2530       else if (drop == m_range)
2531         dropBounds.y = dropBounds.y + getRangeBounds(m_range).h - h/2;
2532       else
2533         dropBounds.y = dropBounds.y + dropBounds.h - h/2;
2534 
2535       dropBounds.h = h;
2536 
2537       theme()->paintWidgetPart(
2538         g, styles.timelineDropLayerDeco(), dropBounds, info);
2539       break;
2540     }
2541   }
2542 }
2543 
drawPaddings(ui::Graphics * g)2544 void Timeline::drawPaddings(ui::Graphics* g)
2545 {
2546   auto& styles = skinTheme()->styles;
2547 
2548   gfx::Rect client = clientBounds();
2549   gfx::Rect bottomLayer;
2550   gfx::Rect lastFrame;
2551   int top = topHeight();
2552 
2553   if (!m_rows.empty()) {
2554     bottomLayer = getPartBounds(Hit(PART_ROW, firstLayer()));
2555     lastFrame = getPartBounds(Hit(PART_CEL, firstLayer(), this->lastFrame()));
2556   }
2557   else {
2558     bottomLayer = getPartBounds(Hit(PART_HEADER_LAYER));
2559     lastFrame = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), this->lastFrame()));
2560   }
2561 
2562   drawPart(g,
2563     gfx::Rect(lastFrame.x+lastFrame.w, client.y + top,
2564       client.w - (lastFrame.x+lastFrame.w),
2565       bottomLayer.y+bottomLayer.h),
2566     NULL, styles.timelinePaddingTr());
2567 
2568   drawPart(g,
2569     gfx::Rect(client.x, bottomLayer.y+bottomLayer.h,
2570       lastFrame.x+lastFrame.w - client.x, client.h - (bottomLayer.y+bottomLayer.h)),
2571     NULL, styles.timelinePaddingBl());
2572 
2573   drawPart(g,
2574     gfx::Rect(lastFrame.x+lastFrame.w, bottomLayer.y+bottomLayer.h,
2575       client.w - (lastFrame.x+lastFrame.w),
2576       client.h - (bottomLayer.y+bottomLayer.h)),
2577     NULL, styles.timelinePaddingBr());
2578 }
2579 
getLayerHeadersBounds() const2580 gfx::Rect Timeline::getLayerHeadersBounds() const
2581 {
2582   gfx::Rect rc = clientBounds();
2583   rc.w = m_separator_x;
2584   int h = topHeight() + headerBoxHeight();
2585   rc.y += h;
2586   rc.h -= h;
2587   return rc;
2588 }
2589 
getFrameHeadersBounds() const2590 gfx::Rect Timeline::getFrameHeadersBounds() const
2591 {
2592   gfx::Rect rc = clientBounds();
2593   rc.x += m_separator_x;
2594   rc.y += topHeight();
2595   rc.w -= m_separator_x;
2596   rc.h = headerBoxHeight();
2597   return rc;
2598 }
2599 
getOnionskinFramesBounds() const2600 gfx::Rect Timeline::getOnionskinFramesBounds() const
2601 {
2602   DocumentPreferences& docPref = this->docPref();
2603   if (!docPref.onionskin.active())
2604     return gfx::Rect();
2605 
2606   frame_t firstFrame = m_frame - docPref.onionskin.prevFrames();
2607   frame_t lastFrame = m_frame + docPref.onionskin.nextFrames();
2608 
2609   if (firstFrame < this->firstFrame())
2610     firstFrame = this->firstFrame();
2611 
2612   if (lastFrame > this->lastFrame())
2613     lastFrame = this->lastFrame();
2614 
2615   return getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), firstFrame))
2616     .createUnion(getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), lastFrame)));
2617 }
2618 
getCelsBounds() const2619 gfx::Rect Timeline::getCelsBounds() const
2620 {
2621   gfx::Rect rc = clientBounds();
2622   rc.x += m_separator_x;
2623   rc.w -= m_separator_x;
2624   rc.y += headerBoxHeight() + topHeight();
2625   rc.h -= headerBoxHeight() + topHeight();
2626   return rc;
2627 }
2628 
getPartBounds(const Hit & hit) const2629 gfx::Rect Timeline::getPartBounds(const Hit& hit) const
2630 {
2631   gfx::Rect bounds = clientBounds();
2632   int y = topHeight();
2633 
2634   switch (hit.part) {
2635 
2636     case PART_NOTHING:
2637       break;
2638 
2639     case PART_TOP:
2640       return gfx::Rect(bounds.x, bounds.y, bounds.w, y);
2641 
2642     case PART_SEPARATOR:
2643       return gfx::Rect(bounds.x + m_separator_x, bounds.y + y,
2644         m_separator_x + m_separator_w, bounds.h - y);
2645 
2646     case PART_HEADER_EYE:
2647       return gfx::Rect(bounds.x + headerBoxWidth()*0, bounds.y + y,
2648                        headerBoxWidth(), headerBoxHeight());
2649 
2650     case PART_HEADER_PADLOCK:
2651       return gfx::Rect(bounds.x + headerBoxWidth()*1, bounds.y + y,
2652                        headerBoxWidth(), headerBoxHeight());
2653 
2654     case PART_HEADER_CONTINUOUS:
2655       return gfx::Rect(bounds.x + headerBoxWidth()*2, bounds.y + y,
2656                        headerBoxWidth(), headerBoxHeight());
2657 
2658     case PART_HEADER_GEAR:
2659       return gfx::Rect(bounds.x + headerBoxWidth()*3, bounds.y + y,
2660                        headerBoxWidth(), headerBoxHeight());
2661 
2662     case PART_HEADER_ONIONSKIN:
2663       return gfx::Rect(bounds.x + headerBoxWidth()*4, bounds.y + y,
2664                        headerBoxWidth(), headerBoxHeight());
2665 
2666     case PART_HEADER_LAYER:
2667       return gfx::Rect(bounds.x + headerBoxWidth()*5, bounds.y + y,
2668                        m_separator_x - headerBoxWidth()*5, headerBoxHeight());
2669 
2670     case PART_HEADER_FRAME:
2671       return gfx::Rect(
2672         bounds.x + m_separator_x + m_separator_w - 1
2673         + frameBoxWidth()*MAX(firstFrame(), hit.frame) - viewScroll().x,
2674         bounds.y + y, frameBoxWidth(), headerBoxHeight());
2675 
2676     case PART_ROW:
2677       if (validLayer(hit.layer)) {
2678         return gfx::Rect(bounds.x,
2679           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2680           m_separator_x, layerBoxHeight());
2681       }
2682       break;
2683 
2684     case PART_ROW_EYE_ICON:
2685       if (validLayer(hit.layer)) {
2686         return gfx::Rect(bounds.x,
2687           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2688           headerBoxWidth(), layerBoxHeight());
2689       }
2690       break;
2691 
2692     case PART_ROW_PADLOCK_ICON:
2693       if (validLayer(hit.layer)) {
2694         return gfx::Rect(bounds.x + headerBoxWidth(),
2695           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2696           headerBoxWidth(), layerBoxHeight());
2697       }
2698       break;
2699 
2700     case PART_ROW_CONTINUOUS_ICON:
2701       if (validLayer(hit.layer)) {
2702         return gfx::Rect(bounds.x + 2* headerBoxWidth(),
2703           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2704           headerBoxWidth(), layerBoxHeight());
2705       }
2706       break;
2707 
2708     case PART_ROW_TEXT:
2709       if (validLayer(hit.layer)) {
2710         int x = headerBoxWidth()*3;
2711         return gfx::Rect(bounds.x + x,
2712           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2713           m_separator_x - x, layerBoxHeight());
2714       }
2715       break;
2716 
2717     case PART_CEL:
2718       if (validLayer(hit.layer) && hit.frame >= frame_t(0)) {
2719         return gfx::Rect(
2720           bounds.x + m_separator_x + m_separator_w - 1 + frameBoxWidth()*hit.frame - viewScroll().x,
2721           bounds.y + y + headerBoxHeight() + layerBoxHeight()*(lastLayer()-hit.layer) - viewScroll().y,
2722           frameBoxWidth(), layerBoxHeight());
2723       }
2724       break;
2725 
2726     case PART_RANGE_OUTLINE: {
2727       gfx::Rect rc = getRangeBounds(m_range);
2728       int s = outlineWidth();
2729       rc.enlarge(s);
2730       if (rc.x < bounds.x) rc.offset(s, 0).inflate(-s, 0);
2731       if (rc.y < bounds.y) rc.offset(0, s).inflate(0, -s);
2732       return rc;
2733     }
2734 
2735     case PART_FRAME_TAG: {
2736       FrameTag* frameTag = hit.getFrameTag();
2737       if (frameTag) {
2738         gfx::Rect bounds1 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->fromFrame()));
2739         gfx::Rect bounds2 = getPartBounds(Hit(PART_HEADER_FRAME, firstLayer(), frameTag->toFrame()));
2740         gfx::Rect bounds = bounds1.createUnion(bounds2);
2741         bounds.y -= skinTheme()->dimensions.timelineTagsAreaHeight();
2742 
2743         int textHeight = font()->height();
2744         bounds.y -= textHeight + 2*ui::guiscale();
2745         bounds.x += 3*ui::guiscale();
2746         bounds.w = font()->textLength(frameTag->name().c_str()) + 4*ui::guiscale();
2747         bounds.h = font()->height() + 2*ui::guiscale();
2748 
2749         if (m_tagFocusBand < 0) {
2750           auto it = m_tagBand.find(frameTag);
2751           if (it != m_tagBand.end()) {
2752             int dy = (m_tagBands-it->second-1)*oneTagHeight();
2753             bounds.y -= dy;
2754           }
2755         }
2756 
2757         return bounds;
2758       }
2759       break;
2760     }
2761 
2762     case PART_FRAME_TAGS:
2763       return gfx::Rect(
2764         bounds.x + m_separator_x + m_separator_w - 1,
2765         bounds.y,
2766         bounds.w - m_separator_x - m_separator_w + 1, y);
2767 
2768     case PART_FRAME_TAG_BAND:
2769       return gfx::Rect(
2770         bounds.x + m_separator_x + m_separator_w - 1,
2771         bounds.y
2772         + (m_tagFocusBand < 0 ? oneTagHeight() * MAX(0, hit.band): 0),
2773         bounds.w - m_separator_x - m_separator_w + 1,
2774         oneTagHeight());
2775 
2776     case PART_FRAME_TAG_SWITCH_BUTTONS: {
2777       gfx::Size sz = theme()->calcSizeHint(
2778         this, skinTheme()->styles.timelineSwitchBandButton());
2779 
2780       return gfx::Rect(
2781         bounds.x + bounds.w - sz.w,
2782         bounds.y,
2783         sz.w, y);
2784     }
2785 
2786     case PART_FRAME_TAG_SWITCH_BAND_BUTTON: {
2787       gfx::Size sz = theme()->calcSizeHint(
2788         this, skinTheme()->styles.timelineSwitchBandButton());
2789 
2790       return gfx::Rect(
2791         bounds.x + bounds.w - sz.w - 2*ui::guiscale(),
2792         bounds.y
2793         + (m_tagFocusBand < 0 ? oneTagHeight() * MAX(0, hit.band): 0)
2794         + oneTagHeight()/2 - sz.h/2,
2795         sz.w, sz.h);
2796     }
2797 
2798   }
2799 
2800   return gfx::Rect();
2801 }
2802 
getRangeBounds(const Range & range) const2803 gfx::Rect Timeline::getRangeBounds(const Range& range) const
2804 {
2805   gfx::Rect rc;
2806   switch (range.type()) {
2807     case Range::kNone:
2808       // Return empty rectangle
2809       break;
2810     case Range::kCels:
2811       for (auto layer : range.selectedLayers()) {
2812         layer_t layerIdx = getLayerIndex(layer);
2813         for (auto frame : range.selectedFrames())
2814           rc |= getPartBounds(Hit(PART_CEL, layerIdx, frame));
2815       }
2816       break;
2817     case Range::kFrames: {
2818       for (auto frame : range.selectedFrames())
2819         rc |= getPartBounds(Hit(PART_HEADER_FRAME, 0, frame));
2820       break;
2821     }
2822     case Range::kLayers:
2823       for (auto layer : range.selectedLayers()) {
2824         layer_t layerIdx = getLayerIndex(layer);
2825         rc |= getPartBounds(Hit(PART_ROW, layerIdx));
2826       }
2827       break;
2828   }
2829   return rc;
2830 }
2831 
getRangeClipBounds(const Range & range) const2832 gfx::Rect Timeline::getRangeClipBounds(const Range& range) const
2833 {
2834   gfx::Rect clipBounds;
2835   switch (range.type()) {
2836     case Range::kCels: clipBounds = getCelsBounds(); break;
2837     case Range::kFrames: clipBounds = getFrameHeadersBounds(); break;
2838     case Range::kLayers: clipBounds = getLayerHeadersBounds(); break;
2839   }
2840   return clipBounds;
2841 }
2842 
invalidateHit(const Hit & hit)2843 void Timeline::invalidateHit(const Hit& hit)
2844 {
2845   if (hit.band >= 0) {
2846     Hit hit2 = hit;
2847     hit2.part = PART_FRAME_TAG_BAND;
2848     invalidateRect(getPartBounds(hit2).offset(origin()));
2849   }
2850 
2851   invalidateRect(getPartBounds(hit).offset(origin()));
2852 }
2853 
invalidateLayer(const Layer * layer)2854 void Timeline::invalidateLayer(const Layer* layer)
2855 {
2856   if (layer == nullptr)
2857     return;
2858 
2859   layer_t layerIdx = getLayerIndex(layer);
2860   if (layerIdx < firstLayer())
2861     return;
2862 
2863   gfx::Rect rc = getPartBounds(Hit(PART_ROW, layerIdx));
2864   gfx::Rect rcCels;
2865   rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, firstFrame()));
2866   rcCels |= getPartBounds(Hit(PART_CEL, layerIdx, lastFrame()));
2867   rcCels &= getCelsBounds();
2868   rc |= rcCels;
2869   rc.offset(origin());
2870   invalidateRect(rc);
2871 }
2872 
invalidateFrame(const frame_t frame)2873 void Timeline::invalidateFrame(const frame_t frame)
2874 {
2875   if (!validFrame(frame))
2876     return;
2877 
2878   gfx::Rect rc = getPartBounds(Hit(PART_HEADER_FRAME, -1, frame));
2879   gfx::Rect rcCels;
2880   rcCels |= getPartBounds(Hit(PART_CEL, firstLayer(), frame));
2881   rcCels |= getPartBounds(Hit(PART_CEL, lastLayer(), frame));
2882   rcCels &= getCelsBounds();
2883   rc |= rcCels;
2884   rc.offset(origin());
2885   invalidateRect(rc);
2886 }
2887 
regenerateRows()2888 void Timeline::regenerateRows()
2889 {
2890   ASSERT(m_document);
2891   ASSERT(m_sprite);
2892 
2893   size_t nlayers = 0;
2894   for_each_expanded_layer(
2895     m_sprite->root(),
2896     [&nlayers](Layer* layer, int level, LayerFlags flags) {
2897       ++nlayers;
2898     });
2899 
2900   if (m_rows.size() != nlayers) {
2901     if (nlayers > 0)
2902       m_rows.resize(nlayers);
2903     else
2904       m_rows.clear();
2905   }
2906 
2907   size_t i = 0;
2908   for_each_expanded_layer(
2909     m_sprite->root(),
2910     [&i, this](Layer* layer, int level, LayerFlags flags) {
2911       m_rows[i++] = Row(layer, level, flags);
2912     });
2913 
2914   regenerateTagBands();
2915   updateScrollBars();
2916 }
2917 
regenerateTagBands()2918 void Timeline::regenerateTagBands()
2919 {
2920   // TODO improve this implementation
2921   std::vector<unsigned char> tagsPerFrame(m_sprite->totalFrames(), 0);
2922   std::vector<FrameTag*> bands(4, nullptr);
2923   m_tagBand.clear();
2924   for (FrameTag* frameTag : m_sprite->frameTags()) {
2925     frame_t f = frameTag->fromFrame();
2926 
2927     int b=0;
2928     for (; b<int(bands.size()); ++b) {
2929       if (!bands[b] ||
2930           frameTag->fromFrame() > calcTagVisibleToFrame(bands[b])) {
2931         bands[b] = frameTag;
2932         m_tagBand[frameTag] = b;
2933         break;
2934       }
2935     }
2936     if (b == int(bands.size()))
2937       m_tagBand[frameTag] = tagsPerFrame[f];
2938 
2939     frame_t toFrame = calcTagVisibleToFrame(frameTag);
2940     if (toFrame >= frame_t(tagsPerFrame.size()))
2941       tagsPerFrame.resize(toFrame+1, 0);
2942     for (; f<=toFrame; ++f) {
2943       ASSERT(f < frame_t(tagsPerFrame.size()));
2944       if (tagsPerFrame[f] < 255)
2945         ++tagsPerFrame[f];
2946     }
2947   }
2948 
2949   const int oldVisibleBands = visibleTagBands();
2950   m_tagBands = 0;
2951   for (int i : tagsPerFrame)
2952     m_tagBands = MAX(m_tagBands, i);
2953 
2954   if (m_tagFocusBand >= m_tagBands)
2955     m_tagFocusBand = -1;
2956 
2957   if (oldVisibleBands != visibleTagBands())
2958     layout();
2959 }
2960 
visibleTagBands() const2961 int Timeline::visibleTagBands() const
2962 {
2963   if (m_tagBands > 1 && m_tagFocusBand == -1)
2964     return m_tagBands;
2965   else
2966     return 1;
2967 }
2968 
updateScrollBars()2969 void Timeline::updateScrollBars()
2970 {
2971   gfx::Rect rc = bounds();
2972   m_viewportArea = getCelsBounds().offset(rc.origin());
2973   ui::setup_scrollbars(getScrollableSize(),
2974                        m_viewportArea, *this,
2975                        m_hbar,
2976                        m_vbar);
2977 }
2978 
updateByMousePos(ui::Message * msg,const gfx::Point & mousePos)2979 void Timeline::updateByMousePos(ui::Message* msg, const gfx::Point& mousePos)
2980 {
2981   Hit hit = hitTest(msg, mousePos);
2982   if (hasMouseOver())
2983     setCursor(msg, hit);
2984   setHot(hit);
2985 }
2986 
hitTest(ui::Message * msg,const gfx::Point & mousePos)2987 Timeline::Hit Timeline::hitTest(ui::Message* msg, const gfx::Point& mousePos)
2988 {
2989   Hit hit(PART_NOTHING, -1, -1);
2990   if (!m_document)
2991     return hit;
2992 
2993   if (m_clk.part == PART_SEPARATOR) {
2994     hit.part = PART_SEPARATOR;
2995   }
2996   else {
2997     gfx::Point scroll = viewScroll();
2998     int top = topHeight();
2999 
3000     hit.layer = lastLayer() -
3001       ((mousePos.y
3002         - top
3003         - headerBoxHeight()
3004         + scroll.y) / layerBoxHeight());
3005 
3006     hit.frame = frame_t((mousePos.x
3007         - m_separator_x
3008         - m_separator_w
3009         + scroll.x) / frameBoxWidth());
3010 
3011     // Flag which indicates that we are in the are below the Background layer/last layer area
3012     if (hit.layer < 0)
3013       hit.veryBottom = true;
3014 
3015     if (hasCapture()) {
3016       hit.layer = MID(firstLayer(), hit.layer, lastLayer());
3017       if (isMovingCel())
3018         hit.frame = MAX(firstFrame(), hit.frame);
3019       else
3020         hit.frame = MID(firstFrame(), hit.frame, lastFrame());
3021     }
3022     else {
3023       if (hit.layer > lastLayer()) hit.layer = -1;
3024       if (hit.frame > lastFrame()) hit.frame = -1;
3025     }
3026 
3027     // Is the mouse over onionskin handles?
3028     gfx::Rect bounds = getOnionskinFramesBounds();
3029     if (!bounds.isEmpty() && gfx::Rect(bounds.x, bounds.y, 3, bounds.h).contains(mousePos)) {
3030       hit.part = PART_HEADER_ONIONSKIN_RANGE_LEFT;
3031     }
3032     else if (!bounds.isEmpty() && gfx::Rect(bounds.x+bounds.w-3, bounds.y, 3, bounds.h).contains(mousePos)) {
3033       hit.part = PART_HEADER_ONIONSKIN_RANGE_RIGHT;
3034     }
3035     // Is the mouse on the separator.
3036     else if (mousePos.x > m_separator_x-4
3037           && mousePos.x <= m_separator_x)  {
3038       hit.part = PART_SEPARATOR;
3039     }
3040     // Is the mouse on the frame tags area?
3041     else if (getPartBounds(Hit(PART_FRAME_TAGS)).contains(mousePos)) {
3042       // Mouse in switch band button
3043       if (hit.part == PART_NOTHING) {
3044         if (m_tagFocusBand < 0) {
3045           for (int band=0; band<m_tagBands; ++band) {
3046             gfx::Rect bounds = getPartBounds(
3047               Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, 0, 0,
3048                   doc::NullId, band));
3049             if (bounds.contains(mousePos)) {
3050               hit.part = PART_FRAME_TAG_SWITCH_BAND_BUTTON;
3051               hit.band = band;
3052               break;
3053             }
3054           }
3055         }
3056         else {
3057           gfx::Rect bounds = getPartBounds(
3058             Hit(PART_FRAME_TAG_SWITCH_BAND_BUTTON, 0, 0,
3059                 doc::NullId, m_tagFocusBand));
3060           if (bounds.contains(mousePos)) {
3061             hit.part = PART_FRAME_TAG_SWITCH_BAND_BUTTON;
3062             hit.band = m_tagFocusBand;
3063           }
3064         }
3065       }
3066 
3067       // Mouse in frame tags
3068       if (hit.part == PART_NOTHING) {
3069         for (FrameTag* frameTag : m_sprite->frameTags()) {
3070           gfx::Rect bounds = getPartBounds(Hit(PART_FRAME_TAG, 0, 0, frameTag->id()));
3071           if (bounds.contains(mousePos)) {
3072             const int band = m_tagBand[frameTag];
3073             if (m_tagFocusBand >= 0 &&
3074                 m_tagFocusBand != band)
3075               continue;
3076 
3077             hit.part = PART_FRAME_TAG;
3078             hit.frameTag = frameTag->id();
3079             hit.band = band;
3080             break;
3081           }
3082         }
3083       }
3084 
3085       // Mouse in bands
3086       if (hit.part == PART_NOTHING) {
3087         if (m_tagFocusBand < 0) {
3088           for (int band=0; band<m_tagBands; ++band) {
3089             gfx::Rect bounds = getPartBounds(
3090               Hit(PART_FRAME_TAG_BAND, 0, 0,
3091                   doc::NullId, band));
3092             if (bounds.contains(mousePos)) {
3093               hit.part = PART_FRAME_TAG_BAND;
3094               hit.band = band;
3095               break;
3096             }
3097           }
3098         }
3099         else {
3100           gfx::Rect bounds = getPartBounds(
3101             Hit(PART_FRAME_TAG_BAND, 0, 0,
3102                 doc::NullId, m_tagFocusBand));
3103           if (bounds.contains(mousePos)) {
3104             hit.part = PART_FRAME_TAG_BAND;
3105             hit.band = m_tagFocusBand;
3106           }
3107         }
3108       }
3109     }
3110     // Is the mouse on the headers?
3111     else if (mousePos.y >= top && mousePos.y < top+headerBoxHeight()) {
3112       if (mousePos.x < m_separator_x) {
3113         if (getPartBounds(Hit(PART_HEADER_EYE)).contains(mousePos))
3114           hit.part = PART_HEADER_EYE;
3115         else if (getPartBounds(Hit(PART_HEADER_PADLOCK)).contains(mousePos))
3116           hit.part = PART_HEADER_PADLOCK;
3117         else if (getPartBounds(Hit(PART_HEADER_CONTINUOUS)).contains(mousePos))
3118           hit.part = PART_HEADER_CONTINUOUS;
3119         else if (getPartBounds(Hit(PART_HEADER_GEAR)).contains(mousePos))
3120           hit.part = PART_HEADER_GEAR;
3121         else if (getPartBounds(Hit(PART_HEADER_ONIONSKIN)).contains(mousePos))
3122           hit.part = PART_HEADER_ONIONSKIN;
3123         else if (getPartBounds(Hit(PART_HEADER_LAYER)).contains(mousePos))
3124           hit.part = PART_HEADER_LAYER;
3125       }
3126       else {
3127         hit.part = PART_HEADER_FRAME;
3128       }
3129     }
3130     // Activate a flag in case that the hit is in the header area (AniControls and tags).
3131     else if (mousePos.y < top+headerBoxHeight())
3132       hit.part = PART_TOP;
3133     // Is the mouse on a layer's label?
3134     else if (mousePos.x < m_separator_x) {
3135       if (getPartBounds(Hit(PART_ROW_EYE_ICON, hit.layer)).contains(mousePos))
3136         hit.part = PART_ROW_EYE_ICON;
3137       else if (getPartBounds(Hit(PART_ROW_PADLOCK_ICON, hit.layer)).contains(mousePos))
3138         hit.part = PART_ROW_PADLOCK_ICON;
3139       else if (getPartBounds(Hit(PART_ROW_CONTINUOUS_ICON, hit.layer)).contains(mousePos))
3140         hit.part = PART_ROW_CONTINUOUS_ICON;
3141       else if (getPartBounds(Hit(PART_ROW_TEXT, hit.layer)).contains(mousePos))
3142         hit.part = PART_ROW_TEXT;
3143       else
3144         hit.part = PART_ROW;
3145     }
3146     else if (validLayer(hit.layer) && validFrame(hit.frame)) {
3147       hit.part = PART_CEL;
3148     }
3149     else
3150       hit.part = PART_NOTHING;
3151 
3152     if (!hasCapture()) {
3153       gfx::Rect outline = getPartBounds(Hit(PART_RANGE_OUTLINE));
3154       if (outline.contains(mousePos)) {
3155         auto mouseMsg = dynamic_cast<MouseMessage*>(msg);
3156 
3157         if (// With Ctrl and Alt key we can drag the range from any place (not necessary from the outline.
3158             is_copy_key_pressed(msg) ||
3159             // Drag with right-click
3160             (m_state == STATE_STANDBY &&
3161              mouseMsg &&
3162              mouseMsg->right()) ||
3163             // Drag with left-click only if we are inside the range edges
3164             !gfx::Rect(outline).shrink(2*outlineWidth()).contains(mousePos)) {
3165           hit.part = PART_RANGE_OUTLINE;
3166         }
3167       }
3168     }
3169   }
3170 
3171   return hit;
3172 }
3173 
hitTestCel(const gfx::Point & mousePos)3174 Timeline::Hit Timeline::hitTestCel(const gfx::Point& mousePos)
3175 {
3176   Hit hit(PART_NOTHING, -1, -1);
3177   if (!m_document)
3178     return hit;
3179 
3180   gfx::Point scroll = viewScroll();
3181   int top = topHeight();
3182 
3183   hit.layer = lastLayer() - (
3184     (mousePos.y
3185      - top
3186      - headerBoxHeight()
3187      + scroll.y) / layerBoxHeight());
3188 
3189   hit.frame = frame_t((mousePos.x
3190                        - m_separator_x
3191                        - m_separator_w
3192                        + scroll.x) / frameBoxWidth());
3193 
3194   hit.layer = MID(firstLayer(), hit.layer, lastLayer());
3195   hit.frame = MAX(firstFrame(), hit.frame);
3196 
3197   return hit;
3198 }
3199 
setHot(const Hit & hit)3200 void Timeline::setHot(const Hit& hit)
3201 {
3202   // If the part, layer or frame change.
3203   if (m_hot != hit) {
3204     // Invalidate the whole control.
3205     if (isMovingCel()) {
3206       invalidate();
3207     }
3208     // Invalidate the old and new 'hot' thing.
3209     else {
3210       invalidateHit(m_hot);
3211       invalidateHit(hit);
3212     }
3213 
3214     // Change the new 'hot' thing.
3215     m_hot = hit;
3216   }
3217 }
3218 
updateStatusBar(ui::Message * msg)3219 void Timeline::updateStatusBar(ui::Message* msg)
3220 {
3221   if (!hasMouse())
3222     return;
3223 
3224   StatusBar* sb = StatusBar::instance();
3225 
3226   if (m_state == STATE_MOVING_RANGE && msg) {
3227     const char* verb = is_copy_key_pressed(msg) ? "Copy": "Move";
3228 
3229     switch (m_range.type()) {
3230 
3231       case Range::kCels:
3232         sb->setStatusText(0, "%s cels", verb);
3233         break;
3234 
3235       case Range::kFrames:
3236         if (validFrame(m_hot.frame)) {
3237           if (m_dropTarget.hhit == DropTarget::Before) {
3238             sb->setStatusText(0, "%s before frame %d", verb, int(m_dropRange.firstFrame()+1));
3239             return;
3240           }
3241           else if (m_dropTarget.hhit == DropTarget::After) {
3242             sb->setStatusText(0, "%s after frame %d", verb, int(m_dropRange.lastFrame()+1));
3243             return;
3244           }
3245         }
3246         break;
3247 
3248       case Range::kLayers: {
3249         layer_t firstLayer;
3250         layer_t lastLayer;
3251         if (!selectedLayersBounds(m_dropRange.selectedLayers(),
3252                                   &firstLayer, &lastLayer))
3253           break;
3254 
3255         if (m_dropTarget.vhit == DropTarget::VeryBottom) {
3256           sb->setStatusText(0, "%s at the very bottom", verb);
3257           return;
3258         }
3259 
3260         layer_t layerIdx = -1;
3261         if (m_dropTarget.vhit == DropTarget::Bottom ||
3262             m_dropTarget.vhit == DropTarget::FirstChild)
3263           layerIdx = firstLayer;
3264         else if (m_dropTarget.vhit == DropTarget::Top)
3265           layerIdx = lastLayer;
3266 
3267         Layer* layer = (validLayer(layerIdx) ? m_rows[layerIdx].layer(): nullptr);
3268         if (layer) {
3269           switch (m_dropTarget.vhit) {
3270             case DropTarget::Bottom:
3271               sb->setStatusText(0, "%s at bottom of layer %s", verb, layer->name().c_str());
3272               return;
3273             case DropTarget::Top:
3274               sb->setStatusText(0, "%s at top of layer %s", verb, layer->name().c_str());
3275               return;
3276             case DropTarget::FirstChild:
3277               sb->setStatusText(0, "%s as first child of layer %s", verb, layer->name().c_str());
3278               return;
3279           }
3280         }
3281         break;
3282       }
3283 
3284     }
3285   }
3286   else {
3287     Layer* layer = (validLayer(m_hot.layer) ? m_rows[m_hot.layer].layer():
3288                                               nullptr);
3289 
3290     switch (m_hot.part) {
3291 
3292       case PART_HEADER_ONIONSKIN: {
3293         sb->setStatusText(0, "Onionskin is %s",
3294           docPref().onionskin.active() ? "enabled": "disabled");
3295         return;
3296       }
3297 
3298       case PART_ROW_TEXT:
3299         if (layer != NULL) {
3300           sb->setStatusText(
3301             0, "%s '%s' [%s%s]",
3302             layer->isReference() ? "Reference layer": "Layer",
3303             layer->name().c_str(),
3304             layer->isVisible() ? "visible": "hidden",
3305             layer->isEditable() ? "": " locked");
3306           return;
3307         }
3308         break;
3309 
3310       case PART_ROW_EYE_ICON:
3311         if (layer != NULL) {
3312           sb->setStatusText(0, "Layer '%s' is %s",
3313             layer->name().c_str(),
3314             layer->isVisible() ? "visible": "hidden");
3315           return;
3316         }
3317         break;
3318 
3319       case PART_ROW_PADLOCK_ICON:
3320         if (layer != NULL) {
3321           sb->setStatusText(0, "Layer '%s' is %s",
3322             layer->name().c_str(),
3323             layer->isEditable() ? "unlocked (editable)": "locked (read-only)");
3324           return;
3325         }
3326         break;
3327 
3328       case PART_ROW_CONTINUOUS_ICON:
3329         if (layer) {
3330           if (layer->isImage())
3331             sb->setStatusText(0, "Layer '%s' is %s (%s)",
3332                               layer->name().c_str(),
3333                               layer->isContinuous() ? "continuous": "discontinuous",
3334                               layer->isContinuous() ? "prefer linked cels/frames": "prefer individual cels/frames");
3335           else if (layer->isGroup())
3336             sb->setStatusText(0, "Group '%s'", layer->name().c_str());
3337           return;
3338         }
3339         break;
3340 
3341       case PART_HEADER_FRAME:
3342       case PART_CEL:
3343       case PART_FRAME_TAG: {
3344         frame_t frame = m_frame;
3345         if (validFrame(m_hot.frame))
3346           frame = m_hot.frame;
3347 
3348         updateStatusBarForFrame(
3349           frame,
3350           m_hot.getFrameTag(),
3351           (layer ? layer->cel(frame) : nullptr));
3352         return;
3353       }
3354     }
3355   }
3356 
3357   sb->clearText();
3358 }
3359 
updateStatusBarForFrame(const frame_t frame,const FrameTag * frameTag,const Cel * cel)3360 void Timeline::updateStatusBarForFrame(const frame_t frame,
3361                                        const FrameTag* frameTag,
3362                                        const Cel* cel)
3363 {
3364   if (!m_sprite)
3365     return;
3366 
3367   char buf[256] = { 0 };
3368   frame_t base = docPref().timeline.firstFrame();
3369   frame_t firstFrame = frame;
3370   frame_t lastFrame = frame;
3371 
3372   if (frameTag) {
3373     firstFrame = frameTag->fromFrame();
3374     lastFrame = frameTag->toFrame();
3375   }
3376   else if (m_range.enabled() &&
3377            m_range.frames() > 1) {
3378     firstFrame = m_range.firstFrame();
3379     lastFrame = m_range.lastFrame();
3380   }
3381 
3382   std::sprintf(
3383     buf+std::strlen(buf), ":frame: %d",
3384     base+frame);
3385   if (firstFrame != lastFrame) {
3386     std::sprintf(
3387       buf+std::strlen(buf), " [%d...%d]",
3388       int(base+firstFrame),
3389       int(base+lastFrame));
3390   }
3391 
3392   std::sprintf(
3393     buf+std::strlen(buf), " :clock: %s",
3394     human_readable_time(m_sprite->frameDuration(frame)).c_str());
3395   if (firstFrame != lastFrame) {
3396     std::sprintf(
3397       buf+std::strlen(buf), " [%s]",
3398       frameTag ?
3399       human_readable_time(tagFramesDuration(frameTag)).c_str():
3400       human_readable_time(selectedFramesDuration()).c_str());
3401   }
3402   if (m_sprite->totalFrames() > 1)
3403     std::sprintf(
3404       buf+std::strlen(buf), "/%s",
3405       human_readable_time(m_sprite->totalAnimationDuration()).c_str());
3406 
3407   if (cel) {
3408     std::sprintf(
3409       buf+std::strlen(buf), " Cel :pos: %d %d :size: %d %d",
3410       cel->bounds().x, cel->bounds().y,
3411       cel->bounds().w, cel->bounds().h);
3412 
3413     if (cel->links() > 0) {
3414       std::sprintf(
3415         buf+std::strlen(buf), " Links %d",
3416         int(cel->links()));
3417     }
3418   }
3419 
3420   StatusBar::instance()
3421     ->setStatusText(0, buf);
3422 }
3423 
showCel(layer_t layer,frame_t frame)3424 void Timeline::showCel(layer_t layer, frame_t frame)
3425 {
3426   gfx::Point scroll = viewScroll();
3427 
3428   gfx::Rect viewport = m_viewportArea;
3429 
3430   // Add the horizontal bar space to the viewport area if the viewport
3431   // is not big enough to show one cel.
3432   if (m_hbar.isVisible() && viewport.h < layerBoxHeight())
3433     viewport.h += m_vbar.getBarWidth();
3434 
3435   gfx::Rect celBounds(
3436     viewport.x + frameBoxWidth()*frame - scroll.x,
3437     viewport.y + layerBoxHeight()*(lastLayer() - layer) - scroll.y,
3438     frameBoxWidth(), layerBoxHeight());
3439 
3440   const bool isPlaying = m_editor->isPlaying();
3441 
3442   // Here we use <= instead of < to avoid jumping between this
3443   // condition and the "else if" one when we are playing the
3444   // animation.
3445   if (celBounds.x <= viewport.x) {
3446     scroll.x -= viewport.x - celBounds.x;
3447     if (isPlaying)
3448       scroll.x -= viewport.w/2 - celBounds.w/2;
3449   }
3450   else if (celBounds.x2() > viewport.x2()) {
3451     scroll.x += celBounds.x2() - viewport.x2();
3452     if (isPlaying)
3453       scroll.x += viewport.w/2 - celBounds.w/2;
3454   }
3455 
3456   if (celBounds.y <= viewport.y) {
3457     scroll.y -= viewport.y - celBounds.y;
3458   }
3459   else if (celBounds.y2() > viewport.y2()) {
3460     scroll.y += celBounds.y2() - viewport.y2();
3461   }
3462 
3463   setViewScroll(scroll);
3464 }
3465 
showCurrentCel()3466 void Timeline::showCurrentCel()
3467 {
3468   layer_t layer = getLayerIndex(m_layer);
3469   if (layer >= firstLayer())
3470     showCel(layer, m_frame);
3471 }
3472 
focusTagBand(int band)3473 void Timeline::focusTagBand(int band)
3474 {
3475   if (m_tagFocusBand < 0) {
3476     m_tagFocusBand = band;
3477   }
3478   else {
3479     m_tagFocusBand = -1;
3480   }
3481 }
3482 
cleanClk()3483 void Timeline::cleanClk()
3484 {
3485   invalidateHit(m_clk);
3486   m_clk = Hit(PART_NOTHING);
3487 }
3488 
getScrollableSize() const3489 gfx::Size Timeline::getScrollableSize() const
3490 {
3491   if (m_sprite) {
3492     return gfx::Size(
3493       m_sprite->totalFrames() * frameBoxWidth() + getCelsBounds().w/2,
3494       (m_rows.size()+1) * layerBoxHeight());
3495   }
3496   else
3497     return gfx::Size(0, 0);
3498 }
3499 
getMaxScrollablePos() const3500 gfx::Point Timeline::getMaxScrollablePos() const
3501 {
3502   if (m_sprite) {
3503     gfx::Size size = getScrollableSize();
3504     int max_scroll_x = size.w - getCelsBounds().w + 1*guiscale();
3505     int max_scroll_y = size.h - getCelsBounds().h + 1*guiscale();
3506     max_scroll_x = MAX(0, max_scroll_x);
3507     max_scroll_y = MAX(0, max_scroll_y);
3508     return gfx::Point(max_scroll_x, max_scroll_y);
3509   }
3510   else
3511     return gfx::Point(0, 0);
3512 }
3513 
allLayersVisible()3514 bool Timeline::allLayersVisible()
3515 {
3516   for (Layer* topLayer : m_sprite->root()->layers())
3517     if (!topLayer->isVisible())
3518       return false;
3519 
3520   return true;
3521 }
3522 
allLayersInvisible()3523 bool Timeline::allLayersInvisible()
3524 {
3525   for (Layer* topLayer : m_sprite->root()->layers())
3526     if (topLayer->isVisible())
3527       return false;
3528 
3529   return true;
3530 }
3531 
allLayersLocked()3532 bool Timeline::allLayersLocked()
3533 {
3534   for (Layer* topLayer : m_sprite->root()->layers())
3535     if (topLayer->isEditable())
3536       return false;
3537 
3538   return true;
3539 }
3540 
allLayersUnlocked()3541 bool Timeline::allLayersUnlocked()
3542 {
3543   for (Layer* topLayer : m_sprite->root()->layers())
3544     if (!topLayer->isEditable())
3545       return false;
3546 
3547   return true;
3548 }
3549 
allLayersContinuous()3550 bool Timeline::allLayersContinuous()
3551 {
3552   for (size_t i=0; i<m_rows.size(); i++)
3553     if (!m_rows[i].layer()->isContinuous())
3554       return false;
3555 
3556   return true;
3557 }
3558 
allLayersDiscontinuous()3559 bool Timeline::allLayersDiscontinuous()
3560 {
3561   for (size_t i=0; i<m_rows.size(); i++)
3562     if (m_rows[i].layer()->isContinuous())
3563       return false;
3564 
3565   return true;
3566 }
3567 
getLayerIndex(const Layer * layer) const3568 layer_t Timeline::getLayerIndex(const Layer* layer) const
3569 {
3570   for (int i=0; i<(int)m_rows.size(); i++)
3571     if (m_rows[i].layer() == layer)
3572       return i;
3573 
3574   return -1;
3575 }
3576 
isLayerActive(const layer_t layerIndex) const3577 bool Timeline::isLayerActive(const layer_t layerIndex) const
3578 {
3579   if (layerIndex == getLayerIndex(m_layer))
3580     return true;
3581   else
3582     return m_range.contains(m_rows[layerIndex].layer());
3583 }
3584 
isFrameActive(const frame_t frame) const3585 bool Timeline::isFrameActive(const frame_t frame) const
3586 {
3587   if (frame == m_frame)
3588     return true;
3589   else
3590     return m_range.contains(frame);
3591 }
3592 
isCelActive(const layer_t layerIdx,const frame_t frame) const3593 bool Timeline::isCelActive(const layer_t layerIdx, const frame_t frame) const
3594 {
3595   if (m_range.enabled())
3596     return m_range.contains(m_rows[layerIdx].layer(), frame);
3597   else
3598     return (layerIdx == getLayerIndex(m_layer) &&
3599             frame == m_frame);
3600 }
3601 
isCelLooselyActive(const layer_t layerIdx,const frame_t frame) const3602 bool Timeline::isCelLooselyActive(const layer_t layerIdx, const frame_t frame) const
3603 {
3604   if (m_range.enabled())
3605     return (m_range.contains(m_rows[layerIdx].layer()) ||
3606             m_range.contains(frame));
3607   else
3608     return (layerIdx == getLayerIndex(m_layer) ||
3609             frame == m_frame);
3610 }
3611 
dropRange(DropOp op)3612 void Timeline::dropRange(DropOp op)
3613 {
3614   bool copy = (op == Timeline::kCopy);
3615   Range newFromRange;
3616   DocRangePlace place = kDocRangeAfter;
3617   Range dropRange = m_dropRange;
3618   bool outside = m_dropTarget.outside;
3619 
3620   switch (m_range.type()) {
3621 
3622     case Range::kFrames:
3623       if (m_dropTarget.hhit == DropTarget::Before)
3624         place = kDocRangeBefore;
3625       break;
3626 
3627     case Range::kLayers:
3628       switch (m_dropTarget.vhit) {
3629         case DropTarget::Bottom:
3630           place = kDocRangeBefore;
3631           break;
3632         case DropTarget::FirstChild:
3633           place = kDocRangeFirstChild;
3634           break;
3635         case DropTarget::VeryBottom:
3636           place = kDocRangeBefore;
3637           {
3638             Layer* layer = m_sprite->root()->firstLayer();
3639             dropRange.clearRange();
3640             dropRange.startRange(layer, -1, Range::kLayers);
3641             dropRange.endRange(layer, -1);
3642           }
3643           break;
3644       }
3645       break;
3646   }
3647 
3648   prepareToMoveRange();
3649 
3650   try {
3651     TagsHandling tagsHandling = (outside ? kFitOutsideTags:
3652                                            kFitInsideTags);
3653 
3654     invalidateRange();
3655     if (copy)
3656       newFromRange = copy_range(m_document, m_range, dropRange,
3657                                 place, tagsHandling);
3658     else
3659       newFromRange = move_range(m_document, m_range, dropRange,
3660                                 place, tagsHandling);
3661 
3662     // If we drop a cel in the same frame (but in another layer),
3663     // document views are not updated, so we are forcing the updating of
3664     // all views.
3665     m_document->notifyGeneralUpdate();
3666 
3667     moveRange(newFromRange);
3668 
3669     invalidateRange();
3670 
3671     if (m_range.type() == Range::kFrames &&
3672         m_sprite &&
3673         !m_sprite->frameTags().empty()) {
3674       invalidateRect(getFrameHeadersBounds().offset(origin()));
3675     }
3676   }
3677   catch (const std::exception& ex) {
3678     Console::showException(ex);
3679   }
3680 }
3681 
visibleSize() const3682 gfx::Size Timeline::visibleSize() const
3683 {
3684   return getCelsBounds().size();
3685 }
3686 
viewScroll() const3687 gfx::Point Timeline::viewScroll() const
3688 {
3689   return gfx::Point(m_hbar.getPos(), m_vbar.getPos());
3690 }
3691 
setViewScroll(const gfx::Point & pt)3692 void Timeline::setViewScroll(const gfx::Point& pt)
3693 {
3694   const gfx::Point oldScroll = viewScroll();
3695   const gfx::Point maxPos = getMaxScrollablePos();
3696   gfx::Point newScroll = pt;
3697   newScroll.x = MID(0, newScroll.x, maxPos.x);
3698   newScroll.y = MID(0, newScroll.y, maxPos.y);
3699 
3700   if (newScroll.y != oldScroll.y) {
3701     gfx::Rect rc;
3702     rc |= getPartBounds(Hit(PART_ROW, 0));
3703     rc |= getPartBounds(Hit(PART_ROW, lastLayer()));
3704     rc.offset(origin());
3705     invalidateRect(rc);
3706   }
3707 
3708   if (newScroll != oldScroll) {
3709     gfx::Rect rc;
3710     if (m_tagBands > 0)
3711       rc |= getPartBounds(Hit(PART_FRAME_TAG_BAND));
3712     rc |= getFrameHeadersBounds();
3713     rc |= getCelsBounds();
3714     rc.offset(origin());
3715     invalidateRect(rc);
3716   }
3717 
3718   m_hbar.setPos(newScroll.x);
3719   m_vbar.setPos(newScroll.y);
3720 }
3721 
3722 
lockRange()3723 void Timeline::lockRange()
3724 {
3725   ++m_rangeLocks;
3726 }
3727 
unlockRange()3728 void Timeline::unlockRange()
3729 {
3730   --m_rangeLocks;
3731 }
3732 
updateDropRange(const gfx::Point & pt)3733 void Timeline::updateDropRange(const gfx::Point& pt)
3734 {
3735   const DropTarget oldDropTarget = m_dropTarget;
3736   m_dropTarget.hhit = DropTarget::HNone;
3737   m_dropTarget.vhit = DropTarget::VNone;
3738   m_dropTarget.outside = false;
3739 
3740   if (m_state != STATE_MOVING_RANGE) {
3741     m_dropRange.clearRange();
3742     return;
3743   }
3744 
3745   switch (m_range.type()) {
3746 
3747     case Range::kCels:
3748       m_dropRange = m_range;
3749       m_dropRange.displace(m_hot.layer - m_clk.layer,
3750                            m_hot.frame - m_clk.frame);
3751       break;
3752 
3753     case Range::kFrames:
3754     case Range::kLayers:
3755       m_dropRange.clearRange();
3756       m_dropRange.startRange(m_rows[m_hot.layer].layer(), m_hot.frame, m_range.type());
3757       m_dropRange.endRange(m_rows[m_hot.layer].layer(), m_hot.frame);
3758       break;
3759   }
3760 
3761   gfx::Rect bounds = getRangeBounds(m_dropRange);
3762 
3763   if (pt.x < bounds.x + bounds.w/2) {
3764     m_dropTarget.hhit = DropTarget::Before;
3765     m_dropTarget.outside = (pt.x < bounds.x+2);
3766   }
3767   else {
3768     m_dropTarget.hhit = DropTarget::After;
3769     m_dropTarget.outside = (pt.x >= bounds.x2()-2);
3770   }
3771 
3772   if (m_hot.veryBottom)
3773     m_dropTarget.vhit = DropTarget::VeryBottom;
3774   else if (pt.y < bounds.y + bounds.h/2)
3775     m_dropTarget.vhit = DropTarget::Top;
3776   // Special drop target for expanded groups
3777   else if (m_range.type() == Range::kLayers &&
3778            m_hot.layer >= 0 &&
3779            m_hot.layer < int(m_rows.size()) &&
3780            m_rows[m_hot.layer].layer()->isGroup() &&
3781            static_cast<LayerGroup*>(m_rows[m_hot.layer].layer())->isExpanded()) {
3782     m_dropTarget.vhit = DropTarget::FirstChild;
3783   }
3784   else {
3785     m_dropTarget.vhit = DropTarget::Bottom;
3786   }
3787 
3788   if (oldDropTarget != m_dropTarget)
3789     invalidate();
3790 }
3791 
clearClipboardRange()3792 void Timeline::clearClipboardRange()
3793 {
3794   Doc* clipboard_document;
3795   DocRange clipboard_range;
3796   clipboard::get_document_range_info(
3797     &clipboard_document,
3798     &clipboard_range);
3799 
3800   if (!m_document || clipboard_document != m_document)
3801     return;
3802 
3803   clipboard::clear_content();
3804   m_clipboard_timer.stop();
3805 }
3806 
invalidateRange()3807 void Timeline::invalidateRange()
3808 {
3809   if (m_range.enabled()) {
3810     for (const Layer* layer : m_range.selectedLayers())
3811       invalidateLayer(layer);
3812     for (const frame_t frame : m_range.selectedFrames())
3813       invalidateFrame(frame);
3814 
3815     invalidateHit(Hit(PART_RANGE_OUTLINE));
3816   }
3817 }
3818 
clearAndInvalidateRange()3819 void Timeline::clearAndInvalidateRange()
3820 {
3821   if (m_range.enabled()) {
3822     invalidateRange();
3823     m_range.clearRange();
3824   }
3825 }
3826 
docPref() const3827 DocumentPreferences& Timeline::docPref() const
3828 {
3829   return Preferences::instance().document(m_document);
3830 }
3831 
skinTheme() const3832 skin::SkinTheme* Timeline::skinTheme() const
3833 {
3834   return static_cast<SkinTheme*>(theme());
3835 }
3836 
celBoxSize() const3837 gfx::Size Timeline::celBoxSize() const
3838 {
3839   return gfx::Size(frameBoxWidth(), layerBoxHeight());
3840 }
3841 
headerBoxWidth() const3842 int Timeline::headerBoxWidth() const
3843 {
3844   return skinTheme()->dimensions.timelineBaseSize();
3845 }
3846 
headerBoxHeight() const3847 int Timeline::headerBoxHeight() const
3848 {
3849   return skinTheme()->dimensions.timelineBaseSize();
3850 }
3851 
layerBoxHeight() const3852 int Timeline::layerBoxHeight() const
3853 {
3854   return int(zoom()*skinTheme()->dimensions.timelineBaseSize());
3855 }
3856 
frameBoxWidth() const3857 int Timeline::frameBoxWidth() const
3858 {
3859   return int(zoom()*skinTheme()->dimensions.timelineBaseSize());
3860 }
3861 
outlineWidth() const3862 int Timeline::outlineWidth() const
3863 {
3864   return skinTheme()->dimensions.timelineOutlineWidth();
3865 }
3866 
oneTagHeight() const3867 int Timeline::oneTagHeight() const
3868 {
3869   return
3870     font()->height() +
3871     2*ui::guiscale() +
3872     skinTheme()->dimensions.timelineTagsAreaHeight();
3873 }
3874 
zoom() const3875 double Timeline::zoom() const
3876 {
3877   if (docPref().thumbnails.enabled())
3878     return m_zoom;
3879   else
3880     return 1.0;
3881 }
3882 
3883 // Returns the last frame where the frame tag (or frame tag label)
3884 // is visible in the timeline.
calcTagVisibleToFrame(FrameTag * frameTag) const3885 int Timeline::calcTagVisibleToFrame(FrameTag* frameTag) const
3886 {
3887   return
3888     MAX(frameTag->toFrame(),
3889         frameTag->fromFrame() +
3890         font()->textLength(frameTag->name())/frameBoxWidth());
3891 }
3892 
topHeight() const3893 int Timeline::topHeight() const
3894 {
3895   int h = 0;
3896   if (m_document && m_sprite) {
3897     h += skinTheme()->dimensions.timelineTopBorder();
3898     h += oneTagHeight() * visibleTagBands();
3899   }
3900   return h;
3901 }
3902 
onNewInputPriority(InputChainElement * element,const ui::Message * msg)3903 void Timeline::onNewInputPriority(InputChainElement* element,
3904                                   const ui::Message* msg)
3905 {
3906   // It looks like the user wants to execute commands targetting the
3907   // ColorBar instead of the Timeline. Here we disable the selected
3908   // range, so commands like Clear, Copy, Cut, etc. don't target the
3909   // Timeline and they are sent to the active sprite editor.
3910   //
3911   // If the Workspace is selected (an sprite Editor), maybe the user
3912   // want to move the X/Y position of all cels in the Timeline range.
3913   // That is why we don't disable the range in this case.
3914   Workspace* workspace = dynamic_cast<Workspace*>(element);
3915   if (!workspace) {
3916     // With Ctrl or Shift we can combine ColorBar selection + Timeline
3917     // selection.
3918     if (msg && (msg->ctrlPressed() || msg->shiftPressed()))
3919       return;
3920 
3921     if (element != this && m_rangeLocks == 0) {
3922       m_range.clearRange();
3923       invalidate();
3924     }
3925   }
3926 }
3927 
onCanCut(Context * ctx)3928 bool Timeline::onCanCut(Context* ctx)
3929 {
3930   return false;                 // TODO
3931 }
3932 
onCanCopy(Context * ctx)3933 bool Timeline::onCanCopy(Context* ctx)
3934 {
3935   return
3936     m_range.enabled() &&
3937     ctx->checkFlags(ContextFlags::HasActiveDocument);
3938 }
3939 
onCanPaste(Context * ctx)3940 bool Timeline::onCanPaste(Context* ctx)
3941 {
3942   return
3943     (clipboard::get_current_format() == clipboard::ClipboardDocRange &&
3944      ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable));
3945 }
3946 
onCanClear(Context * ctx)3947 bool Timeline::onCanClear(Context* ctx)
3948 {
3949   return (m_document && m_sprite && m_range.enabled());
3950 }
3951 
onCut(Context * ctx)3952 bool Timeline::onCut(Context* ctx)
3953 {
3954   return false;                 // TODO
3955 }
3956 
onCopy(Context * ctx)3957 bool Timeline::onCopy(Context* ctx)
3958 {
3959   if (m_range.enabled()) {
3960     const ContextReader reader(ctx);
3961     if (reader.document()) {
3962       clipboard::copy_range(reader, m_range);
3963       return true;
3964     }
3965   }
3966   return false;
3967 }
3968 
onPaste(Context * ctx)3969 bool Timeline::onPaste(Context* ctx)
3970 {
3971   if (clipboard::get_current_format() == clipboard::ClipboardDocRange) {
3972     clipboard::paste();
3973     return true;
3974   }
3975   else
3976     return false;
3977 }
3978 
onClear(Context * ctx)3979 bool Timeline::onClear(Context* ctx)
3980 {
3981   if (!m_document || !m_sprite || !m_range.enabled())
3982     return false;
3983 
3984   Command* cmd = nullptr;
3985 
3986   switch (m_range.type()) {
3987     case DocRange::kCels:
3988       cmd = Commands::instance()->byId(CommandId::ClearCel());
3989       break;
3990     case DocRange::kFrames:
3991       cmd = Commands::instance()->byId(CommandId::RemoveFrame());
3992       break;
3993     case DocRange::kLayers:
3994       cmd = Commands::instance()->byId(CommandId::RemoveLayer());
3995       break;
3996   }
3997 
3998   if (cmd) {
3999     ctx->executeCommand(cmd);
4000     return true;
4001   }
4002   else
4003     return false;
4004 }
4005 
onCancel(Context * ctx)4006 void Timeline::onCancel(Context* ctx)
4007 {
4008   if (m_rangeLocks == 0)
4009     clearAndInvalidateRange();
4010 
4011   clearClipboardRange();
4012   invalidate();
4013 }
4014 
tagFramesDuration(const FrameTag * frameTag) const4015 int Timeline::tagFramesDuration(const FrameTag* frameTag) const
4016 {
4017   ASSERT(m_sprite);
4018   ASSERT(frameTag);
4019 
4020   int duration = 0;
4021   for (frame_t f=frameTag->fromFrame();
4022        f<frameTag->toFrame(); ++f) {
4023     duration += m_sprite->frameDuration(f);
4024   }
4025   return duration;
4026 }
4027 
selectedFramesDuration() const4028 int Timeline::selectedFramesDuration() const
4029 {
4030   ASSERT(m_sprite);
4031 
4032   int duration = 0;
4033   for (frame_t f=0; f<m_sprite->totalFrames(); ++f) {
4034     if (isFrameActive(f))
4035       duration += m_sprite->frameDuration(f);
4036   }
4037   return duration; // TODO cache this value
4038 }
4039 
setLayerVisibleFlag(const layer_t l,const bool state)4040 void Timeline::setLayerVisibleFlag(const layer_t l, const bool state)
4041 {
4042   if (!validLayer(l))
4043     return;
4044 
4045   Row& row = m_rows[l];
4046   Layer* layer = row.layer();
4047   ASSERT(layer);
4048   if (!layer)
4049     return;
4050 
4051   bool redrawEditors = false;
4052   bool regenRows = false;
4053 
4054   if (layer->isVisible() != state) {
4055     layer->setVisible(state);
4056     redrawEditors = true;
4057 
4058     // Regenerate rows because might change the flag of the children
4059     // (the flag is propagated to the children in m_inheritedFlags).
4060     if (layer->isGroup() && layer->isExpanded()) {
4061       regenRows = true;
4062     }
4063 
4064     // Show parents too
4065     if (!row.parentVisible() && state) {
4066       layer = layer->parent();
4067       while (layer) {
4068         if (!layer->isVisible()) {
4069           layer->setVisible(true);
4070           regenRows = true;
4071           redrawEditors = true;
4072         }
4073         layer = layer->parent();
4074       }
4075     }
4076   }
4077 
4078   if (regenRows) {
4079     regenerateRows();
4080     invalidate();
4081   }
4082 
4083   if (redrawEditors)
4084     m_document->notifyGeneralUpdate();
4085 }
4086 
setLayerEditableFlag(const layer_t l,const bool state)4087 void Timeline::setLayerEditableFlag(const layer_t l, const bool state)
4088 {
4089   Row& row = m_rows[l];
4090   Layer* layer = row.layer();
4091   ASSERT(layer);
4092   if (!layer)
4093     return;
4094 
4095   bool regenRows = false;
4096 
4097   if (layer->isEditable() != state) {
4098     layer->setEditable(state);
4099 
4100     if (layer->isGroup() && layer->isExpanded())
4101       regenRows = true;
4102 
4103     // Make parents editable too
4104     if (!row.parentEditable() && state) {
4105       layer = layer->parent();
4106       while (layer) {
4107         if (!layer->isEditable()) {
4108           layer->setEditable(true);
4109           regenRows = true;
4110         }
4111         layer = layer->parent();
4112       }
4113     }
4114   }
4115 
4116   if (regenRows) {
4117     regenerateRows();
4118     invalidate();
4119   }
4120 }
4121 
setLayerContinuousFlag(const layer_t l,const bool state)4122 void Timeline::setLayerContinuousFlag(const layer_t l, const bool state)
4123 {
4124   Layer* layer = m_rows[l].layer();
4125   ASSERT(layer);
4126   if (!layer)
4127     return;
4128 
4129   if (layer->isImage() && layer->isContinuous() != state) {
4130     layer->setContinuous(state);
4131     invalidate();
4132   }
4133 }
4134 
setLayerCollapsedFlag(const layer_t l,const bool state)4135 void Timeline::setLayerCollapsedFlag(const layer_t l, const bool state)
4136 {
4137   Layer* layer = m_rows[l].layer();
4138   ASSERT(layer);
4139   if (!layer)
4140     return;
4141 
4142   if (layer->isGroup() && layer->isCollapsed() != state) {
4143     layer->setCollapsed(state);
4144 
4145     regenerateRows();
4146     invalidate();
4147   }
4148 }
4149 
4150 } // namespace app
4151