1 /*
2     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
3     SPDX-FileCopyrightText: 2010-2019 Mladen Milinkovic <max@smoothware.net>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "useraction.h"
9 #include "application.h"
10 #include "core/subtitle.h"
11 #include "videoplayer/videoplayer.h"
12 #include "gui/treeview/lineswidget.h"
13 #include "gui/currentlinewidget.h"
14 
15 #include <QApplication>
16 
17 using namespace SubtitleComposer;
18 
UserAction(QAction * action,int enableFlags)19 UserAction::UserAction(QAction *action, int enableFlags) :
20 	m_action(action),
21 	m_enableFlags(enableFlags),
22 	m_actionEnabled(action->isEnabled()),
23 	m_contextEnabled(false),
24 	m_ignoreActionEnabledSignal(false)
25 {
26 	updateEnabledState();
27 
28 	connect(action, &QAction::changed, this, &UserAction::onActionChanged);
29 }
30 
31 void
onActionChanged()32 UserAction::onActionChanged()
33 {
34 	if(!m_ignoreActionEnabledSignal) {
35 		if(m_action->isEnabled() != isEnabled()) {
36 			// qDebug() << "enabled state externally changed for" << m_action->objectName();
37 
38 			m_actionEnabled = m_action->isEnabled();
39 			updateEnabledState();
40 		}
41 	}
42 }
43 
44 int
enableFlags()45 UserAction::enableFlags()
46 {
47 	return m_enableFlags;
48 }
49 
50 QAction *
action()51 UserAction::action()
52 {
53 	return m_action;
54 }
55 
56 bool
isEnabled()57 UserAction::isEnabled()
58 {
59 	return m_actionEnabled && m_contextEnabled;
60 }
61 
62 void
setActionEnabled(bool enabled)63 UserAction::setActionEnabled(bool enabled)
64 {
65 	if(m_actionEnabled != enabled) {
66 		m_actionEnabled = enabled;
67 		updateEnabledState();
68 	}
69 }
70 
71 void
setContextFlags(int contextFlags)72 UserAction::setContextFlags(int contextFlags)
73 {
74 	bool contextEnabled = (contextFlags & m_enableFlags) == m_enableFlags;
75 	if(m_contextEnabled != contextEnabled) {
76 		m_contextEnabled = contextEnabled;
77 		updateEnabledState();
78 	}
79 }
80 
81 void
updateEnabledState()82 UserAction::updateEnabledState()
83 {
84 	bool enabled = m_actionEnabled && m_contextEnabled;
85 
86 	if(m_action->isEnabled() != enabled) {
87 		m_ignoreActionEnabledSignal = true;
88 
89 		m_action->setEnabled(enabled);
90 
91 //		QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
92 
93 //		qDebug() << "setting action" << m_action->text() << (m_action->isEnabled() ? "ENABLED" : "DISABLED");
94 
95 		m_ignoreActionEnabledSignal = false;
96 	}
97 }
98 
99 /// USER ACTION MANAGER
100 
UserActionManager()101 UserActionManager::UserActionManager()
102 	: m_actionSpecs(),
103 	  m_subtitle(nullptr),
104 	  m_linesWidget(nullptr),
105 	  m_translationMode(false),
106 	  m_contextFlags(UserAction::SubClosed | UserAction::SubTrClosed | UserAction::VideoClosed | UserAction::FullScreenOff | UserAction::AnchorsNone)
107 {
108 	VideoPlayer *videoPlayer = VideoPlayer::instance();
109 	connect(videoPlayer, &VideoPlayer::fileOpened, this, &UserActionManager::onPlayerStateChanged);
110 	connect(videoPlayer, &VideoPlayer::fileClosed, this, &UserActionManager::onPlayerStateChanged);
111 	connect(videoPlayer, &VideoPlayer::playing, this, &UserActionManager::onPlayerStateChanged);
112 	connect(videoPlayer, &VideoPlayer::paused, this, &UserActionManager::onPlayerStateChanged);
113 	connect(videoPlayer, &VideoPlayer::stopped, this, &UserActionManager::onPlayerStateChanged);
114 	onPlayerStateChanged();
115 }
116 
117 UserActionManager *
instance()118 UserActionManager::instance()
119 {
120 	static UserActionManager *actionManager = nullptr;
121 	if(!actionManager) {
122 		actionManager = new UserActionManager();
123 		actionManager->setParent(QApplication::instance());
124 	}
125 	return actionManager;
126 }
127 
128 void
addAction(QAction * action,int enableFlags)129 UserActionManager::addAction(QAction *action, int enableFlags)
130 {
131 	UserAction *a = new UserAction(action, enableFlags);
132 	addAction(a);
133 	a->setParent(this); // UserActionManager will delete *a
134 }
135 
136 void
addAction(UserAction * actionSpec)137 UserActionManager::addAction(UserAction *actionSpec)
138 {
139 	m_actionSpecs.append(actionSpec);
140 
141 	actionSpec->setContextFlags(m_contextFlags);
142 }
143 
144 void
setSubtitle(const Subtitle * subtitle)145 UserActionManager::setSubtitle(const Subtitle *subtitle)
146 {
147 	if(m_subtitle) {
148 		disconnect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &UserActionManager::onSubtitleLinesChanged);
149 		disconnect(m_subtitle.constData(), &Subtitle::linesInserted, this, &UserActionManager::onSubtitleLinesChanged);
150 
151 		disconnect(m_subtitle.constData(), &Subtitle::primaryDirtyStateChanged, this, &UserActionManager::onPrimaryDirtyStateChanged);
152 		disconnect(m_subtitle.constData(), &Subtitle::secondaryDirtyStateChanged, this, &UserActionManager::onSecondaryDirtyStateChanged);
153 
154 		disconnect(m_subtitle.constData(), &Subtitle::lineAnchorChanged, this, &UserActionManager::onSubtitleAnchorsChanged);
155 	}
156 
157 	m_subtitle = subtitle;
158 
159 	int newContextFlags = m_contextFlags & ~UserAction::SubtitleMask;
160 
161 	if(m_subtitle) {
162 		connect(m_subtitle.constData(), &Subtitle::linesRemoved, this, &UserActionManager::onSubtitleLinesChanged);
163 		connect(m_subtitle.constData(), &Subtitle::linesInserted, this, &UserActionManager::onSubtitleLinesChanged);
164 
165 		connect(m_subtitle.constData(), &Subtitle::primaryDirtyStateChanged, this, &UserActionManager::onPrimaryDirtyStateChanged);
166 		connect(m_subtitle.constData(), &Subtitle::secondaryDirtyStateChanged, this, &UserActionManager::onSecondaryDirtyStateChanged);
167 
168 		connect(m_subtitle.constData(), &Subtitle::lineAnchorChanged, this, &UserActionManager::onSubtitleAnchorsChanged);
169 
170 		newContextFlags |= UserAction::SubOpened;
171 
172 		if(m_subtitle->isPrimaryDirty())
173 			newContextFlags |= UserAction::SubPDirty;
174 		else
175 			newContextFlags |= UserAction::SubPClean;
176 
177 		if(m_translationMode) {
178 			newContextFlags |= UserAction::SubTrOpened;
179 
180 			if(m_subtitle->isSecondaryDirty())
181 				newContextFlags |= UserAction::SubSDirty;
182 			else
183 				newContextFlags |= UserAction::SubSClean;
184 		} else {
185 			newContextFlags |= (UserAction::SubTrClosed | UserAction::SubSClean);
186 		}
187 
188 		if(m_subtitle->linesCount() > 0)
189 			newContextFlags |= UserAction::SubHasLine;
190 		if(m_subtitle->linesCount() > 1)
191 			newContextFlags |= UserAction::SubHasLines;
192 
193 		if(!m_subtitle->hasAnchors()) {
194 			newContextFlags |= UserAction::AnchorsNone;
195 			newContextFlags |= UserAction::EditableShowTime;
196 		} else {
197 			newContextFlags |= UserAction::AnchorsSome;
198 			const SubtitleLine *selected = m_subtitle->line(m_linesWidget->firstSelectedIndex());
199 			if(m_subtitle->isLineAnchored(selected))
200 				newContextFlags |= UserAction::EditableShowTime;
201 		}
202 	} else {
203 		newContextFlags |= (UserAction::SubClosed | UserAction::SubPClean | UserAction::SubTrClosed | UserAction::SubSClean);
204 	}
205 
206 	updateActionsContext(newContextFlags);
207 }
208 
209 void
onSubtitleLinesChanged()210 UserActionManager::onSubtitleLinesChanged()
211 {
212 	int newContextFlags = m_contextFlags & ~(UserAction::SubHasLine | UserAction::SubHasLines);
213 
214 	if(m_subtitle->linesCount() > 0)
215 		newContextFlags |= UserAction::SubHasLine;
216 	if(m_subtitle->linesCount() > 1)
217 		newContextFlags |= UserAction::SubHasLines;
218 
219 	updateActionsContext(newContextFlags);
220 }
221 
222 void
onPrimaryDirtyStateChanged(bool dirty)223 UserActionManager::onPrimaryDirtyStateChanged(bool dirty)
224 {
225 	int newContextFlags = m_contextFlags & ~(UserAction::SubPDirty | UserAction::SubPClean);
226 
227 	if(dirty)
228 		newContextFlags |= UserAction::SubPDirty;
229 	else
230 		newContextFlags |= UserAction::SubPClean;
231 
232 	updateActionsContext(newContextFlags);
233 }
234 
235 void
onSecondaryDirtyStateChanged(bool dirty)236 UserActionManager::onSecondaryDirtyStateChanged(bool dirty)
237 {
238 	int newContextFlags = m_contextFlags & ~(UserAction::SubSDirty | UserAction::SubSClean);
239 
240 	if(m_translationMode) {
241 		if(dirty)
242 			newContextFlags |= UserAction::SubSDirty;
243 		else
244 			newContextFlags |= UserAction::SubSClean;
245 	} else {
246 		newContextFlags |= UserAction::SubSClean;
247 	}
248 
249 	updateActionsContext(newContextFlags);
250 }
251 
252 void
setLinesWidget(LinesWidget * linesWidget)253 UserActionManager::setLinesWidget(LinesWidget *linesWidget)
254 {
255 	if(m_linesWidget)
256 		disconnect(m_linesWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &UserActionManager::onLinesWidgetSelectionChanged);
257 
258 	m_linesWidget = linesWidget;
259 
260 	if(m_linesWidget) {
261 		connect(m_linesWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &UserActionManager::onLinesWidgetSelectionChanged);
262 		onLinesWidgetSelectionChanged();
263 	}
264 }
265 
266 void
onLinesWidgetSelectionChanged()267 UserActionManager::onLinesWidgetSelectionChanged()
268 {
269 	int newContextFlags = m_contextFlags & ~(UserAction::SelectionMask | UserAction::EditableShowTime);
270 
271 	int selectedIndex = m_linesWidget->firstSelectedIndex();
272 
273 	if(selectedIndex >= 0)
274 		newContextFlags |= UserAction::HasSelection;
275 
276 	if(m_subtitle) {
277 		if(!m_subtitle->hasAnchors()) {
278 			newContextFlags |= UserAction::EditableShowTime;
279 		} else {
280 			const SubtitleLine *line = m_subtitle->line(selectedIndex);
281 			if(m_subtitle->isLineAnchored(line))
282 				newContextFlags |= UserAction::EditableShowTime;
283 		}
284 	}
285 
286 	updateActionsContext(newContextFlags);
287 }
288 
289 void
onPlayerStateChanged()290 UserActionManager::onPlayerStateChanged()
291 {
292 	int newContextFlags = m_contextFlags & ~UserAction::VideoMask;
293 
294 	const int state = VideoPlayer::instance()->state();
295 	if(state > VideoPlayer::Opening) {
296 		newContextFlags |= UserAction::VideoOpened;
297 		if(state < VideoPlayer::Playing)
298 			newContextFlags |= UserAction::VideoStopped;
299 		else
300 			newContextFlags |= UserAction::VideoPlaying;
301 	} else {
302 		newContextFlags |= UserAction::VideoClosed;
303 	}
304 
305 	updateActionsContext(newContextFlags);
306 }
307 
308 void
onSubtitleAnchorsChanged()309 UserActionManager::onSubtitleAnchorsChanged()
310 {
311 	int newContextFlags = m_contextFlags & ~UserAction::AnchorsMask;
312 
313 	if(!m_subtitle->hasAnchors()) {
314 		newContextFlags |= UserAction::AnchorsNone;
315 		newContextFlags |= UserAction::EditableShowTime;
316 	} else {
317 		newContextFlags |= UserAction::AnchorsSome;
318 		const SubtitleLine *selected = m_subtitle->line(m_linesWidget->firstSelectedIndex());
319 		if(m_subtitle->isLineAnchored(selected))
320 			newContextFlags |= UserAction::EditableShowTime;
321 	}
322 
323 	updateActionsContext(newContextFlags);
324 }
325 
326 void
setTranslationMode(bool translationMode)327 UserActionManager::setTranslationMode(bool translationMode)
328 {
329 	m_translationMode = translationMode;
330 
331 	int newContextFlags = m_contextFlags & ~(UserAction::SubTrClosed | UserAction::SubTrOpened | UserAction::SubSDirty | UserAction::SubSClean);
332 
333 	if(m_subtitle && m_translationMode) {
334 		newContextFlags |= UserAction::SubTrOpened;
335 
336 		if(m_subtitle->isSecondaryDirty())
337 			newContextFlags |= UserAction::SubSDirty;
338 		else
339 			newContextFlags |= UserAction::SubSClean;
340 	} else {
341 		newContextFlags |= (UserAction::SubTrClosed | UserAction::SubSClean);
342 	}
343 
344 	updateActionsContext(newContextFlags);
345 }
346 
347 void
setFullScreenMode(bool fullScreenMode)348 UserActionManager::setFullScreenMode(bool fullScreenMode)
349 {
350 	int newContextFlags = m_contextFlags & ~UserAction::FullScreenMask;
351 
352 	if(fullScreenMode)
353 		newContextFlags |= UserAction::FullScreenOn;
354 	else
355 		newContextFlags |= UserAction::FullScreenOff;
356 
357 	updateActionsContext(newContextFlags);
358 }
359 
360 void
updateActionsContext(int contextFlags)361 UserActionManager::updateActionsContext(int contextFlags)
362 {
363 //  if ( (m_contextFlags & UserAction::SubHasLine) != (contextFlags & UserAction::SubHasLine)  )
364 //      qDebug() << "has line:" << ((contextFlags & UserAction::SubHasLine) != 0);
365 //  if ( (m_contextFlags & UserAction::SubHasLines) != (contextFlags & UserAction::SubHasLines)  )
366 //      qDebug() << "has lines:" << ((contextFlags & UserAction::SubHasLines) != 0);
367 //  if ( (m_contextFlags & UserAction::HasSelection) != (contextFlags & UserAction::HasSelection)  )
368 //      qDebug() << "has selection:" << ((contextFlags & UserAction::HasSelection) != 0);
369 
370 	if(m_contextFlags != contextFlags) {
371 		m_contextFlags = contextFlags;
372 		for(QList<UserAction *>::ConstIterator it = m_actionSpecs.constBegin(), end = m_actionSpecs.constEnd(); it != end; ++it)
373 			(*it)->setContextFlags(m_contextFlags);
374 	}
375 }
376 
377 
378