MoveIntervalState.hpp
1 #pragma once
2 
3 #include <Scenario/Commands/Scenario/Displacement/MoveEventMeta.hpp>
4 #include <Scenario/Commands/Scenario/Merge/MergeEvents.hpp>
5 #include <Scenario/Document/Event/EventPresenter.hpp>
6 #include <Scenario/Document/Event/EventView.hpp>
7 #include <Scenario/Document/Interval/Temporal/TemporalIntervalPresenter.hpp>
8 #include <Scenario/Document/Interval/Temporal/TemporalIntervalView.hpp>
9 #include <Scenario/Document/State/StatePresenter.hpp>
10 #include <Scenario/Document/State/StateView.hpp>
11 #include <Scenario/Document/TimeSync/TimeSyncPresenter.hpp>
12 #include <Scenario/Document/TimeSync/TimeSyncView.hpp>
13 #include <Scenario/Document/TimeSync/TriggerView.hpp>
14 #include <Scenario/Palette/ScenarioPaletteBaseStates.hpp>
15 #include <Scenario/Palette/Tools/ScenarioRollbackStrategy.hpp>
16 #include <Scenario/Palette/Transitions/AnythingTransitions.hpp>
17 #include <Scenario/Palette/Transitions/EventTransitions.hpp>
18 #include <Scenario/Palette/Transitions/IntervalTransitions.hpp>
19 #include <Scenario/Palette/Transitions/NothingTransitions.hpp>
20 #include <Scenario/Palette/Transitions/TimeSyncTransitions.hpp>
21 #include <Scenario/Process/Algorithms/Accessors.hpp>
22 
23 #include <score/command/Dispatchers/MultiOngoingCommandDispatcher.hpp>
24 #include <score/locking/ObjectLocker.hpp>
25 
26 #include <QApplication>
27 #include <QFinalState>
28 
29 namespace Scenario
30 {
31 template <typename T>
32 class MoveIntervalState final : public StateBase<Scenario::ProcessModel>
33 {
34 public:
36  const T& stateMachine, const Scenario::ProcessModel& scenario,
37  const score::CommandStackFacade& stack, score::ObjectLocker& locker,
38  QState* parent)
39  : StateBase<Scenario::ProcessModel>{scenario, parent}
40  , m_sm{stateMachine}
41  , m_movingDispatcher{stack}
42  {
43  this->setObjectName("MoveIntervalState");
44  using namespace Scenario::Command;
45  auto finalState = new QFinalState{this};
46 
47  auto mainState = new QState{this};
48  {
49  auto pressed = new QState{mainState};
50  auto released = new QState{mainState};
51  auto moving = new QState{mainState};
52 
53  // General setup
54  mainState->setInitialState(pressed);
55  released->addTransition(finalState);
56 
57  auto t_pressed
58  = score::make_transition<MoveOnAnything_Transition<Scenario::ProcessModel>>(
59  pressed, moving, *this);
60  QObject::connect(t_pressed, &QAbstractTransition::triggered, [&]() {
61  auto& scenar = stateMachine.model();
62  m_initialClick = this->currentPoint;
63  if(!this->clickedInterval)
64  return;
65 
66  auto& cst = scenario.interval(*this->clickedInterval);
67  auto& sev = Scenario::startEvent(cst, scenario);
68  auto& eev = Scenario::endEvent(cst, scenario);
69 
70  m_intervalInitialPoint = {cst.date(), cst.heightPercentage()};
71  m_lastDate = m_intervalInitialPoint.date;
72 
73  auto prev_csts = previousNonGraphIntervals(sev, scenar);
74  if(!prev_csts.empty())
75  {
76  // We find the one that starts the latest.
77  TimeVal t = TimeVal::zero();
78  for(const auto& cst_id : prev_csts)
79  {
80  const auto& other_date = scenar.interval(cst_id).date();
81  if(other_date > t)
82  t = other_date;
83  }
84 
85  // These 10 milliseconds are here to prevent "squashing"
86  // processes to zero, which leads to problem (they can't scale back!)
87  this->m_pressedPrevious = t + TimeVal::fromMsecs(10);
88  }
89  else
90  {
91  this->m_pressedPrevious = std::nullopt;
92  }
93 
94  this->m_startEventCanBeMerged = previousIntervals(sev, scenar).empty();
95  this->m_endEventCanBeMerged = nextIntervals(eev, scenar).empty();
96  });
97 
98  score::make_transition<ReleaseOnAnything_Transition>(pressed, finalState);
99  score::make_transition<MoveOnAnything_Transition<Scenario::ProcessModel>>(
100  moving, moving, *this);
101  score::make_transition<ReleaseOnAnything_Transition>(moving, released);
102 
103  QObject::connect(moving, &QState::entered, [&] {
104  auto& scenario = stateMachine.model();
105  if(!this->clickedInterval)
106  return;
107  auto& cst = scenario.interval(*this->clickedInterval);
108  if(cst.graphal())
109  return;
110  auto& sst = Scenario::startState(cst, scenario);
111  auto& sev = Scenario::parentEvent(sst, scenario);
112  auto& sts = Scenario::parentTimeSync(sev, scenario);
113 
114  if(qApp->keyboardModifiers() & Qt::ShiftModifier)
115  {
116  if(&sts == &scenario.startTimeSync())
117  return;
118 
119  m_lastDate = m_intervalInitialPoint.date
120  + (this->currentPoint.date - m_initialClick.date);
121  if(this->m_pressedPrevious)
122  m_lastDate = std::max(m_lastDate, *this->m_pressedPrevious);
123 
124  m_lastDate = stateMachine.magnetic().getPosition(&sts, m_lastDate);
125  m_lastDate = std::max(m_lastDate, TimeVal{});
126  }
127  else
128  {
129  m_lastDate = m_intervalInitialPoint.date;
130  }
131 
132  // If the start event does not have previous intervals
133  // we will try to merge the start sync with other syncs...
134  // Idea : if it's an empty interval we can maybe merge...
135  // and if we are dragging an interval we can maybe create an empty
136  // interval between...
138 
139  this->m_movingDispatcher.template submit<Command::MoveEventMeta>(
140  this->m_scenario, sev.id(), m_lastDate,
141  m_intervalInitialPoint.y + (this->currentPoint.y - m_initialClick.y),
142  stateMachine.editionSettings().expandMode(),
143  stateMachine.editionSettings().lockMode(), cst.startState());
144  });
145 
146  QObject::connect(released, &QState::entered, [&]() {
147  if(!this->clickedInterval)
148  return;
149  auto& cst = scenario.interval(*this->clickedInterval);
150  if(cst.graphal())
151  return;
152  auto& sst = Scenario::startState(cst, scenario);
153  auto& sev = Scenario::parentEvent(sst, scenario);
154  auto& sts = Scenario::parentTimeSync(sev, scenario);
155 
156  if(qApp->keyboardModifiers() & Qt::ShiftModifier)
157  {
158  if(&sts == &scenario.startTimeSync())
159  return;
160 
161  if(this->m_startEventCanBeMerged)
162  {
163  merge(cst, Scenario::startState(cst, m_scenario), m_lastDate);
164  }
165  /*
166  if(this->m_endEventCanBeMerged)
167  {
168  merge(cst, Scenario::endState(cst, m_scenario), m_lastDate +
169  cst.duration.defaultDuration());
170  }
171  */
172  }
173 
174  m_movingDispatcher.template commit<Command::MoveIntervalMacro>();
175  m_pressedPrevious = {};
176  });
177  }
178 
179  auto rollbackState = new QState{this};
180  score::make_transition<score::Cancel_Transition>(mainState, rollbackState);
181  rollbackState->addTransition(finalState);
182  QObject::connect(rollbackState, &QState::entered, [&]() {
183  this->rollback();
184  m_pressedPrevious = {};
185  });
186 
187  this->setInitialState(mainState);
188  }
189 
190 private:
191  void rollback() { m_movingDispatcher.template rollback<DefaultRollbackStrategy>(); }
192 
193  void merge(const IntervalModel& cst, const StateModel& st, TimeVal date)
194  {
195  auto& ev = Scenario::parentEvent(st, m_scenario);
196  auto& ts = Scenario::parentTimeSync(ev, m_scenario);
197 
198  auto& sst_pres = m_sm.presenter().state(st.id());
199  auto& sev_pres = m_sm.presenter().event(ev.id());
200  auto& sts_pres = m_sm.presenter().timeSync(ts.id());
201  auto& itv_pres = m_sm.presenter().interval(cst.id());
202 
203  std::vector<QGraphicsItem*> toIgnore;
204  toIgnore.push_back(sst_pres.view());
205  toIgnore.push_back(sev_pres.view());
206  toIgnore.push_back(sts_pres.view());
207  toIgnore.push_back(&sts_pres.trigger());
208  toIgnore.push_back(itv_pres.view());
209  QGraphicsItem* item = m_sm.itemAt({date, cst.heightPercentage()}, toIgnore);
210 
211  if(auto stateToMerge = qgraphicsitem_cast<Scenario::StateView*>(item))
212  {
213  // this->rollback();
214  this->m_movingDispatcher.template submit<Command::MergeEvents>(
215  this->m_scenario, ev.id(),
216  Scenario::parentEvent(stateToMerge->presenter().model().id(), this->m_scenario)
217  .id());
218  }
219  else if(auto eventToMerge = qgraphicsitem_cast<Scenario::EventView*>(item))
220  {
221  // this->rollback();
222  this->m_movingDispatcher.template submit<Command::MergeEvents>(
223  this->m_scenario, ev.id(), eventToMerge->presenter().model().id());
224  }
225  else if(auto syncToMerge = qgraphicsitem_cast<Scenario::TimeSyncView*>(item))
226  {
227  // this->rollback();
228  this->m_movingDispatcher.template submit<Command::MergeTimeSyncs>(
229  this->m_scenario, ts.id(), syncToMerge->presenter().model().id());
230  }
231  }
232 
233  const T& m_sm;
234  MultiOngoingCommandDispatcher m_movingDispatcher;
235 
236  Scenario::Point m_initialClick{};
237  Scenario::Point m_intervalInitialPoint{};
238  std::optional<TimeVal> m_pressedPrevious;
239  TimeVal m_lastDate{};
240  bool m_startEventCanBeMerged{};
241  bool m_endEventCanBeMerged{};
242 };
243 
244 }
The MultiOngoingCommandDispatcher class.
Definition: MultiOngoingCommandDispatcher.hpp:33
Definition: IntervalModel.hpp:50
Definition: MoveIntervalState.hpp:33
MoveIntervalState(const T &stateMachine, const Scenario::ProcessModel &scenario, const score::CommandStackFacade &stack, score::ObjectLocker &locker, QState *parent)
Definition: MoveIntervalState.hpp:35
The core hierarchical and temporal process of score.
Definition: ScenarioModel.hpp:37
Definition: ScenarioPaletteBaseStates.hpp:20
A small abstraction layer over the score::CommandStack.
Definition: CommandStackFacade.hpp:20
The ObjectLocker class.
Definition: ObjectLocker.hpp:21
Main plug-in of score.
Definition: score-plugin-dataflow/Dataflow/PortItem.hpp:14
Definition: ScenarioPoint.hpp:13
Definition: TimeValue.hpp:21