3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
6 Minitube 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.
11 Minitube 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.
16 You should have received a copy of the GNU General Public License
17 along with Minitube. If not, see <http://www.gnu.org/licenses/>.
21 #include "searchview.h"
22 #include "constants.h"
23 #include "fontutils.h"
24 #include "searchparams.h"
25 #include "ytsuggester.h"
26 #include "channelsuggest.h"
27 #ifdef APP_MAC_SEARCHFIELD
28 #include "searchlineedit_mac.h"
30 #include "searchlineedit.h"
36 #include "activation.h"
37 #include "activationview.h"
39 #include "mainwindow.h"
40 #include "painterutils.h"
41 #include "iconutils.h"
42 #include "clickablelabel.h"
45 static const QString recentKeywordsKey = "recentKeywords";
46 static const QString recentChannelsKey = "recentChannels";
49 SearchView::SearchView(QWidget *parent) : View(parent) {
50 const int padding = 30;
52 // speedup painting since we'll paint the whole background
53 // by ourselves anyway in paintEvent()
54 setAttribute(Qt::WA_OpaquePaintEvent);
56 QBoxLayout *vLayout = new QVBoxLayout(this);
57 vLayout->setMargin(padding);
58 vLayout->setSpacing(0);
60 // hidden message widget
61 message = new QLabel(this);
63 vLayout->addWidget(message);
65 vLayout->addStretch();
67 QBoxLayout *hLayout = new QHBoxLayout();
68 hLayout->setAlignment(Qt::AlignCenter);
70 vLayout->addLayout(hLayout);
72 hLayout->addStretch();
74 logo = new ClickableLabel(this);
75 logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
76 connect(logo, &ClickableLabel::clicked, MainWindow::instance(), &MainWindow::visitSite);
77 hLayout->addWidget(logo, 0, Qt::AlignTop);
78 hLayout->addSpacing(padding);
80 QVBoxLayout *layout = new QVBoxLayout();
81 layout->setAlignment(Qt::AlignCenter);
82 hLayout->addLayout(layout);
84 QColor titleColor = palette().color(QPalette::WindowText);
85 titleColor.setAlphaF(.75);
87 titleColor.getRgb(&r,&g,&b,&a);
88 QString cssColor = QString::asprintf("rgba(%d,%d,%d,%d)", r, g, b, a);
90 QLabel *welcomeLabel =
91 new QLabel(QString("<h1 style='font-weight:300;color:%1'>").arg(cssColor) +
92 tr("Welcome to <a href='%1'>%2</a>,")
93 .replace("<a ", "<a style='text-decoration:none; color:palette(text)' ")
94 .arg(Constants::WEBSITE, Constants::NAME)
96 welcomeLabel->setOpenExternalLinks(true);
97 welcomeLabel->setProperty("heading", true);
98 welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize() * 1.25));
99 layout->addWidget(welcomeLabel);
101 layout->addSpacing(padding / 2);
104 const QFont &biggerFont = FontUtils::big();
107 //: "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"
108 // QLabel *tipLabel = new QLabel(tr("Enter"), this);
111 if (qApp->layoutDirection() == Qt::RightToLeft) {
112 tip = tr("to start watching videos.") + " " + tr("a keyword") + " " + tr("Enter");
114 tip = tr("Enter") + " " + tr("a keyword") + " " + tr("to start watching videos.");
116 QLabel *tipLabel = new QLabel(tip);
119 tipLabel->setFont(biggerFont);
121 layout->addWidget(tipLabel);
124 typeCombo = new QComboBox(this);
125 typeCombo->addItem(tr("a keyword"));
126 typeCombo->addItem(tr("a channel"));
128 typeCombo->setFont(biggerFont);
130 connect(typeCombo, SIGNAL(currentIndexChanged(int)), SLOT(searchTypeChanged(int)));
131 tipLayout->addWidget(typeCombo);
133 tipLabel = new QLabel(tr("to start watching videos."), this);
135 tipLabel->setFont(biggerFont);
137 tipLayout->addWidget(tipLabel);
140 layout->addSpacing(padding / 2);
142 QHBoxLayout *searchLayout = new QHBoxLayout();
143 searchLayout->setAlignment(Qt::AlignVCenter);
145 #ifdef APP_MAC_SEARCHFIELD
146 SearchLineEditMac *slem = new SearchLineEditMac(this);
150 SearchLineEdit *sle = new SearchLineEdit(this);
151 sle->setFont(biggerFont);
155 connect(queryEdit->toWidget(), SIGNAL(search(const QString&)), SLOT(watch(const QString&)));
156 connect(queryEdit->toWidget(), SIGNAL(textChanged(const QString &)), SLOT(textChanged(const QString &)));
157 connect(queryEdit->toWidget(), SIGNAL(textEdited(const QString &)), SLOT(textChanged(const QString &)));
158 connect(queryEdit->toWidget(), SIGNAL(suggestionAccepted(Suggestion*)), SLOT(suggestionAccepted(Suggestion*)));
160 youtubeSuggest = new YTSuggester(this);
161 channelSuggest = new ChannelSuggest(this);
162 connect(channelSuggest, SIGNAL(ready(QVector<Suggestion*>)), SLOT(onChannelSuggestions(QVector<Suggestion*>)));
163 searchTypeChanged(0);
165 searchLayout->addWidget(queryEdit->toWidget(), 0, Qt::AlignBaseline);
166 searchLayout->addSpacing(padding);
168 watchButton = new QPushButton(tr("Watch"));
170 watchButton->setFont(biggerFont);
172 watchButton->setDefault(true);
173 watchButton->setEnabled(false);
174 watchButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
175 connect(watchButton, SIGNAL(clicked()), this, SLOT(watch()));
176 searchLayout->addWidget(watchButton, 0, Qt::AlignBaseline);
178 layout->addItem(searchLayout);
180 layout->addSpacing(padding);
182 QHBoxLayout *recentLayout = new QHBoxLayout();
183 recentLayout->setMargin(0);
184 recentLayout->setSpacing(10);
186 recentKeywordsLayout = new QVBoxLayout();
187 recentKeywordsLayout->setMargin(0);
188 recentKeywordsLayout->setSpacing(0);
189 recentKeywordsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
190 recentKeywordsLabel = new QLabel(tr("Recent keywords"));
191 recentKeywordsLabel->setEnabled(false);
192 recentKeywordsLabel->setProperty("recentHeader", true);
193 recentKeywordsLabel->hide();
194 recentKeywordsLayout->addWidget(recentKeywordsLabel);
195 recentLayout->addLayout(recentKeywordsLayout);
197 recentChannelsLayout = new QVBoxLayout();
198 recentChannelsLayout->setMargin(0);
199 recentChannelsLayout->setSpacing(0);
200 recentChannelsLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
201 recentChannelsLabel = new QLabel(tr("Recent channels"));
202 recentChannelsLabel->setEnabled(false);
203 recentChannelsLabel->setProperty("recentHeader", true);
204 recentChannelsLabel->hide();
205 recentChannelsLayout->addWidget(recentChannelsLabel);
206 recentLayout->addLayout(recentChannelsLayout);
208 layout->addLayout(recentLayout);
210 hLayout->addStretch();
212 vLayout->addStretch();
214 #ifdef APP_ACTIVATION
215 if (!Activation::instance().isActivated())
216 vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0, Qt::AlignRight);
220 void SearchView::appear() {
221 MainWindow *w = MainWindow::instance();
222 w->showActionInStatusBar(w->getAction("manualplay"), true);
223 w->showActionInStatusBar(w->getAction("safeSearch"), true);
224 w->showActionInStatusBar(w->getAction("definition"), true);
226 updateRecentKeywords();
227 updateRecentChannels();
229 queryEdit->selectAll();
230 queryEdit->enableSuggest();
231 if (!queryEdit->toWidget()->hasFocus()) queryEdit->toWidget()->setFocus();
233 connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection);
235 qApp->processEvents();
239 // Workaround cursor bug on macOS
240 window()->unsetCursor();
244 void SearchView::disappear() {
245 MainWindow *w = MainWindow::instance();
246 w->showActionInStatusBar(w->getAction("safeSearch"), false);
247 w->showActionInStatusBar(w->getAction("definition"), false);
248 w->showActionInStatusBar(w->getAction("manualplay"), false);
251 void SearchView::updateRecentKeywords() {
254 const QStringList keywords = settings.value(recentKeywordsKey).toStringList();
255 if (keywords == recentKeywords) return;
256 recentKeywords = keywords;
260 while ((item = recentKeywordsLayout->takeAt(1)) != 0) {
261 item->widget()->close();
265 recentKeywordsLabel->setVisible(!keywords.isEmpty());
266 MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty());
268 const int maxDisplayLength = 25;
270 for (const QString &keyword : keywords) {
271 QString link = keyword;
272 QString display = keyword;
273 if (keyword.startsWith(QLatin1String("http://")) || keyword.startsWith(QLatin1String("https://"))) {
274 int separator = keyword.indexOf('|');
275 if (separator > 0 && separator + 1 < keyword.length()) {
276 link = keyword.left(separator);
277 display = keyword.mid(separator+1);
280 bool needStatusTip = false;
281 if (display.length() > maxDisplayLength) {
282 display.truncate(maxDisplayLength);
283 display.append(QStringLiteral("\u2026"));
284 needStatusTip = true;
286 QPushButton *itemButton = new QPushButton(display);
287 itemButton->setAttribute(Qt::WA_DeleteOnClose);
288 itemButton->setProperty("recentItem", true);
289 itemButton->setCursor(Qt::PointingHandCursor);
290 itemButton->setFocusPolicy(Qt::TabFocus);
291 itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
293 itemButton->setStatusTip(link);
294 connect(itemButton, &QPushButton::clicked, [this,link]() {
298 recentKeywordsLayout->addWidget(itemButton);
303 void SearchView::updateRecentChannels() {
306 const QStringList keywords = settings.value(recentChannelsKey).toStringList();
307 if (keywords == recentChannels) return;
308 recentChannels = keywords;
312 while ((item = recentChannelsLayout->takeAt(1)) != 0) {
313 item->widget()->close();
317 recentChannelsLabel->setVisible(!keywords.isEmpty());
318 // TODO MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty());
320 for (const QString &keyword : keywords) {
321 QString link = keyword;
322 QString display = keyword;
323 int separator = keyword.indexOf('|');
324 if (separator > 0 && separator + 1 < keyword.length()) {
325 link = keyword.left(separator);
326 display = keyword.mid(separator+1);
328 QPushButton *itemButton = new QPushButton(display);
329 itemButton->setAttribute(Qt::WA_DeleteOnClose);
330 itemButton->setProperty("recentItem", true);
331 itemButton->setCursor(Qt::PointingHandCursor);
332 itemButton->setFocusPolicy(Qt::TabFocus);
333 itemButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
334 connect(itemButton, &QPushButton::clicked, [this,link]() {
337 recentChannelsLayout->addWidget(itemButton);
341 void SearchView::watch() {
342 QString query = queryEdit->text();
346 void SearchView::textChanged(const QString &text) {
347 watchButton->setEnabled(!text.simplified().isEmpty());
348 lastChannelSuggestions.clear();
351 void SearchView::watch(const QString &query) {
352 QString q = query.simplified();
354 // check for empty query
356 queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
361 if (typeCombo->currentIndex() == 1) {
363 MainWindow::instance()->channelSearch(q);
368 SearchParams *searchParams = new SearchParams();
369 searchParams->setKeywords(q);
372 emit search(searchParams);
375 void SearchView::watchChannel(const QString &channelId) {
376 if (channelId.isEmpty()) {
377 queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
381 QString id = channelId;
384 const QLatin1String uc("UC");
385 if (!id.startsWith(uc)) id = uc + id;
387 SearchParams *searchParams = new SearchParams();
388 searchParams->setChannelId(id);
389 searchParams->setSortBy(SearchParams::SortByNewest);
392 emit search(searchParams);
395 void SearchView::watchKeywords(const QString &query) {
396 QString q = query.simplified();
398 // check for empty query
400 queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
404 // if (typeCombo->currentIndex() == 0) {
405 queryEdit->setText(q);
406 watchButton->setEnabled(true);
409 SearchParams *searchParams = new SearchParams();
410 searchParams->setKeywords(q);
413 emit search(searchParams);
416 void SearchView::paintEvent(QPaintEvent *event) {
417 QWidget::paintEvent(event);
419 if (window()->isActiveWindow()) {
420 brush = palette().base();
422 brush = palette().window();
424 QPainter painter(this);
425 painter.fillRect(0, 0, width(), height(), brush);
428 void SearchView::searchTypeChanged(int index) {
430 queryEdit->setSuggester(youtubeSuggest);
432 queryEdit->setSuggester(channelSuggest);
434 queryEdit->selectAll();
435 queryEdit->toWidget()->setFocus();
438 void SearchView::suggestionAccepted(Suggestion *suggestion) {
439 if (suggestion->type == QLatin1String("channel")) {
440 watchChannel(suggestion->userData);
441 } else watch(suggestion->value);
444 void SearchView::screenChanged() {
445 logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
448 void SearchView::onChannelSuggestions(const QVector<Suggestion *> &suggestions) {
449 lastChannelSuggestions = suggestions;