1 /**************************************************************************
2 * Otter Browser: Web browser controlled by the user, not vice-versa.
3 * Copyright (C) 2013 - 2018 Michal Dutkiewicz aka Emdek <michal@emdek.pl>
4 * Copyright (C) 2016 Piotr Wójcik <chocimier@tlen.pl>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **************************************************************************/
20 
21 #include "ActionWidget.h"
22 #include "../../../core/Application.h"
23 #include "../../../core/GesturesManager.h"
24 #include "../../../core/HistoryManager.h"
25 #include "../../../ui/Action.h"
26 #include "../../../ui/ContentsWidget.h"
27 #include "../../../ui/MainWindow.h"
28 #include "../../../ui/ToolBarWidget.h"
29 #include "../../../ui/Window.h"
30 
31 #include <QtGui/QMouseEvent>
32 #include <QtWidgets/QToolTip>
33 
34 namespace Otter
35 {
36 
ActionWidget(int identifier,Window * window,const ToolBarsManager::ToolBarDefinition::Entry & definition,QWidget * parent)37 ActionWidget::ActionWidget(int identifier, Window *window, const ToolBarsManager::ToolBarDefinition::Entry &definition, QWidget *parent) : ToolButtonWidget(definition, parent),
38 	m_action(new Action(identifier, definition.parameters, definition.options, ActionExecutor::Object(window, window), this))
39 {
40 	setDefaultAction(m_action);
41 	setWindow(window);
42 
43 	switch (identifier)
44 	{
45 		case ActionsManager::NewTabAction:
46 		case ActionsManager::NewTabPrivateAction:
47 		case ActionsManager::NewWindowAction:
48 		case ActionsManager::NewWindowPrivateAction:
49 			setAcceptDrops(true);
50 
51 			break;
52 		default:
53 			break;
54 	}
55 
56 	const ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parent));
57 
58 	if (toolBar && toolBar->getDefinition().isGlobal())
59 	{
60 		connect(toolBar, &ToolBarWidget::windowChanged, this, &ActionWidget::setWindow);
61 	}
62 }
63 
mouseReleaseEvent(QMouseEvent * event)64 void ActionWidget::mouseReleaseEvent(QMouseEvent *event)
65 {
66 	if (event->button() != Qt::LeftButton)
67 	{
68 		ToolButtonWidget::mouseReleaseEvent(event);
69 
70 		return;
71 	}
72 
73 	int identifier(m_action->getIdentifier());
74 	QVariantMap parameters(m_action->getParameters());
75 
76 	switch (identifier)
77 	{
78 		case ActionsManager::NewTabAction:
79 		case ActionsManager::NewTabPrivateAction:
80 		case ActionsManager::NewWindowAction:
81 		case ActionsManager::NewWindowPrivateAction:
82 			{
83 				SessionsManager::OpenHints hints(SessionsManager::calculateOpenHints(((identifier == ActionsManager::NewWindowAction || identifier == ActionsManager::NewWindowPrivateAction) ? SessionsManager::NewWindowOpen : SessionsManager::NewTabOpen), event->button(), event->modifiers()));
84 
85 				if (identifier == ActionsManager::NewTabPrivateAction || identifier == ActionsManager::NewWindowPrivateAction)
86 				{
87 					hints |= SessionsManager::PrivateOpen;
88 				}
89 
90 				parameters[QLatin1String("hints")] = QVariant(hints);
91 
92 				identifier = ActionsManager::OpenUrlAction;
93 			}
94 
95 			break;
96 		default:
97 			break;
98 	}
99 
100 	if (isCheckable())
101 	{
102 		parameters[QLatin1String("isChecked")] = !isChecked();
103 	}
104 
105 	Application::triggerAction(identifier, parameters, this);
106 
107 	setDefaultAction(nullptr);
108 
109 	ToolButtonWidget::mouseReleaseEvent(event);
110 
111 	setDefaultAction(m_action);
112 }
113 
dragEnterEvent(QDragEnterEvent * event)114 void ActionWidget::dragEnterEvent(QDragEnterEvent *event)
115 {
116 	if (event->mimeData()->hasUrls())
117 	{
118 		event->accept();
119 	}
120 	else
121 	{
122 		event->ignore();
123 	}
124 }
125 
dropEvent(QDropEvent * event)126 void ActionWidget::dropEvent(QDropEvent *event)
127 {
128 	if (event->mimeData()->hasUrls())
129 	{
130 		QVariantMap parameters(getParameters());
131 		const QVector<QUrl> urls(Utils::extractUrls(event->mimeData()));
132 		SessionsManager::OpenHints hints(SessionsManager::calculateOpenHints(((m_action->getIdentifier() == ActionsManager::NewWindowAction || m_action->getIdentifier() == ActionsManager::NewWindowPrivateAction) ? SessionsManager::NewWindowOpen : SessionsManager::NewTabOpen), Qt::LeftButton, event->keyboardModifiers()));
133 
134 		if (m_action->getIdentifier() == ActionsManager::NewTabPrivateAction || m_action->getIdentifier() == ActionsManager::NewWindowPrivateAction)
135 		{
136 			hints |= SessionsManager::PrivateOpen;
137 		}
138 
139 		parameters[QLatin1String("hints")] = QVariant(hints);
140 
141 		for (int i = 0; i < urls.count(); ++i)
142 		{
143 			QVariantMap actionParameters(parameters);
144 			actionParameters[QLatin1String("url")] = urls.at(i);
145 
146 			Application::triggerAction(ActionsManager::OpenUrlAction, actionParameters, this);
147 		}
148 
149 		event->accept();
150 	}
151 	else
152 	{
153 		event->ignore();
154 	}
155 }
156 
setWindow(Window * window)157 void ActionWidget::setWindow(Window *window)
158 {
159 	if (m_action->getDefinition().scope == ActionsManager::ActionDefinition::WindowScope)
160 	{
161 		m_action->setExecutor(ActionExecutor::Object(window, window));
162 	}
163 	else
164 	{
165 		MainWindow *mainWindow(MainWindow::findMainWindow(this));
166 
167 		m_action->setExecutor(ActionExecutor::Object(mainWindow, mainWindow));
168 	}
169 }
170 
getIdentifier() const171 int ActionWidget::getIdentifier() const
172 {
173 	return m_action->getIdentifier();
174 }
175 
event(QEvent * event)176 bool ActionWidget::event(QEvent *event)
177 {
178 	if (event->type() == QEvent::ToolTip)
179 	{
180 		QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), text() + (m_action->shortcut().isEmpty() ? QString() : QLatin1String(" (") + m_action->shortcut().toString(QKeySequence::NativeText) + QLatin1Char(')')));
181 
182 		return true;
183 	}
184 
185 	return ToolButtonWidget::event(event);
186 }
187 
NavigationActionWidget(Window * window,const ToolBarsManager::ToolBarDefinition::Entry & definition,QWidget * parent)188 NavigationActionWidget::NavigationActionWidget(Window *window, const ToolBarsManager::ToolBarDefinition::Entry &definition, QWidget *parent) : ActionWidget(((definition.action == QLatin1String("GoBackAction")) ? ActionsManager::GoBackAction : ActionsManager::GoForwardAction), window, definition, parent),
189 	m_window(window)
190 {
191 	setMenu(new QMenu(this));
192 	setPopupMode(QToolButton::DelayedPopup);
193 	setContextMenuPolicy(Qt::DefaultContextMenu);
194 
195 	menu()->installEventFilter(this);
196 
197 	const ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parent));
198 
199 	if (toolBar && toolBar->getDefinition().isGlobal())
200 	{
201 		connect(toolBar, &ToolBarWidget::windowChanged, this, &NavigationActionWidget::setWindow);
202 	}
203 
204 	connect(menu(), &QMenu::aboutToShow, this, &NavigationActionWidget::updateMenu);
205 }
206 
addMenuEntry(int index,const WindowHistoryEntry & entry)207 void NavigationActionWidget::addMenuEntry(int index, const WindowHistoryEntry &entry)
208 {
209 	Action *action(new Action(ActionsManager::GoToHistoryIndexAction, {{QLatin1String("index"), index}}, ActionExecutor::Object(m_window, m_window), this));
210 	action->setStatusTip(entry.url);
211 
212 	menu()->addAction(action);
213 }
214 
updateMenu()215 void NavigationActionWidget::updateMenu()
216 {
217 	if (!menu() || !m_window)
218 	{
219 		return;
220 	}
221 
222 	menu()->clear();
223 
224 	const WindowHistoryInformation history(m_window->getContentsWidget()->getHistory());
225 
226 	if (getIdentifier() == ActionsManager::GoBackAction)
227 	{
228 		for (int i = (history.index - 1); i >= 0; --i)
229 		{
230 			addMenuEntry(i, history.entries.at(i));
231 		}
232 	}
233 	else
234 	{
235 		for (int i = (history.index + 1); i < history.entries.count(); ++i)
236 		{
237 			addMenuEntry(i, history.entries.at(i));
238 		}
239 	}
240 }
241 
setWindow(Window * window)242 void NavigationActionWidget::setWindow(Window *window)
243 {
244 	m_window = window;
245 }
246 
event(QEvent * event)247 bool NavigationActionWidget::event(QEvent *event)
248 {
249 	switch (event->type())
250 	{
251 		case QEvent::ContextMenu:
252 			{
253 				QContextMenuEvent *contextMenuEvent(static_cast<QContextMenuEvent*>(event));
254 
255 				if (contextMenuEvent->reason() == QContextMenuEvent::Mouse)
256 				{
257 					contextMenuEvent->accept();
258 
259 					return true;
260 				}
261 
262 				event->accept();
263 
264 				ActionExecutor::Object executor(m_window, m_window);
265 				QMenu menu(this);
266 				menu.addAction(new Action(ActionsManager::ClearTabHistoryAction, {}, executor, &menu));
267 				menu.addAction(new Action(ActionsManager::ClearTabHistoryAction, {{QLatin1String("clearGlobalHistory"), true}}, {{QLatin1String("text"), QT_TRANSLATE_NOOP("actions", "Purge Tab History")}}, executor, &menu));
268 
269 				const ToolBarWidget *toolBar(qobject_cast<ToolBarWidget*>(parentWidget()));
270 
271 				if (toolBar)
272 				{
273 					menu.addSeparator();
274 					menu.addActions(ToolBarWidget::createCustomizationMenu(toolBar->getIdentifier(), {}, &menu)->actions());
275 				}
276 
277 				menu.exec(contextMenuEvent->globalPos());
278 
279 				return true;
280 			}
281 
282 			return false;
283 		case QEvent::MouseButtonDblClick:
284 		case QEvent::MouseButtonPress:
285 		case QEvent::Wheel:
286 			GesturesManager::startGesture(this, event, {GesturesManager::ToolBarContext, GesturesManager::GenericContext});
287 
288 			break;
289 		case QEvent::ToolTip:
290 			{
291 				const QKeySequence shortcut(ActionsManager::getActionShortcut(getIdentifier()));
292 				QString toolTip(text() + (shortcut.isEmpty() ? QString() : QLatin1String(" (") + shortcut.toString(QKeySequence::NativeText) + QLatin1Char(')')));
293 
294 				if (m_window)
295 				{
296 					const WindowHistoryInformation history(m_window->getContentsWidget()->getHistory());
297 
298 					if (!history.entries.isEmpty())
299 					{
300 						int index(-1);
301 
302 						if (getIdentifier() == ActionsManager::GoBackAction && history.index > 0)
303 						{
304 							index = (history.index - 1);
305 						}
306 						else if (getIdentifier() == ActionsManager::GoForwardAction && history.index < (history.entries.count() - 1))
307 						{
308 							index = (history.index + 1);
309 						}
310 
311 						if (index >= 0)
312 						{
313 							toolTip = history.entries.at(index).getTitle().replace(QLatin1Char('&'), QLatin1String("&&")) + QLatin1String(" (") + text() + (shortcut.isEmpty() ? QString() : QLatin1String(" - ") + shortcut.toString(QKeySequence::NativeText)) + QLatin1Char(')');
314 						}
315 					}
316 				}
317 
318 				QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), toolTip);
319 			}
320 
321 			return true;
322 		default:
323 			break;
324 	}
325 
326 	return ActionWidget::event(event);
327 }
328 
eventFilter(QObject * object,QEvent * event)329 bool NavigationActionWidget::eventFilter(QObject *object, QEvent *event)
330 {
331 	if (event->type() == QEvent::ContextMenu)
332 	{
333 		const Action *action(qobject_cast<Action*>(menu()->activeAction()));
334 
335 		if (action && action->getIdentifier() == ActionsManager::GoToHistoryIndexAction)
336 		{
337 			ActionExecutor::Object executor(m_window, m_window);
338 			const int index(action->getParameters().value(QLatin1String("index")).toInt());
339 			QMenu contextMenu(menu());
340 			Action *removeEntryAction(new Action(ActionsManager::RemoveHistoryIndexAction, {{QLatin1String("index"), index}}, {{QLatin1String("text"), tr("Remove Entry")}}, executor, &contextMenu));
341 			removeEntryAction->setShortcut(QKeySequence(Qt::Key_Delete));
342 
343 			Action *purgeEntryAction(new Action(ActionsManager::RemoveHistoryIndexAction, {{QLatin1String("index"), index}}, {{QLatin1String("text"), tr("Purge Entry")}}, executor, &contextMenu));
344 			purgeEntryAction->setShortcut(QKeySequence(Qt::ShiftModifier | Qt::Key_Delete));
345 
346 			contextMenu.addAction(removeEntryAction);
347 			contextMenu.addAction(purgeEntryAction);
348 
349 			const QAction *selectedAction(contextMenu.exec(static_cast<QContextMenuEvent*>(event)->globalPos()));
350 
351 			if (selectedAction == removeEntryAction || selectedAction == purgeEntryAction)
352 			{
353 				menu()->close();
354 			}
355 		}
356 	}
357 	else if (event->type() == QEvent::KeyPress)
358 	{
359 		const QKeyEvent *keyEvent(static_cast<QKeyEvent*>(event));
360 
361 		if (keyEvent->key() == Qt::Key_Delete && m_window)
362 		{
363 			const Action *action(qobject_cast<Action*>(menu()->activeAction()));
364 
365 			if (action && action->getIdentifier() == ActionsManager::GoToHistoryIndexAction)
366 			{
367 				menu()->close();
368 
369 				m_window->triggerAction(ActionsManager::RemoveHistoryIndexAction, {{QLatin1String("index"), action->getParameters().value(QLatin1String("index"), -1).toInt()}, {QLatin1String("clearGlobalHistory"), keyEvent->modifiers().testFlag(Qt::ShiftModifier)}});
370 			}
371 		}
372 	}
373 
374 	return QObject::eventFilter(object, event);
375 }
376 
377 }
378