]> git.sur5r.net Git - minitube/blob - src/searchview.cpp
bf48028953b1a01f0b66e2f1fb97adb7df614159
[minitube] / src / searchview.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
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.
10
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.
15
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/>.
18
19 $END_LICENSE */
20
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"
29 #else
30 #include "searchlineedit.h"
31 #endif
32 #ifdef APP_EXTRA
33 #include "extra.h"
34 #endif
35 #ifdef APP_ACTIVATION
36 #include "activation.h"
37 #include "activationview.h"
38 #endif
39 #include "mainwindow.h"
40 #include "painterutils.h"
41 #include "iconutils.h"
42 #include "clickablelabel.h"
43
44 namespace {
45 static const QString recentKeywordsKey = "recentKeywords";
46 static const QString recentChannelsKey = "recentChannels";
47 }
48
49 SearchView::SearchView(QWidget *parent) : View(parent) {
50     const int padding = 30;
51
52     // speedup painting since we'll paint the whole background
53     // by ourselves anyway in paintEvent()
54     setAttribute(Qt::WA_OpaquePaintEvent);
55
56     QBoxLayout *vLayout = new QVBoxLayout(this);
57     vLayout->setMargin(padding);
58     vLayout->setSpacing(0);
59
60     // hidden message widget
61     message = new QLabel(this);
62     message->hide();
63     vLayout->addWidget(message);
64
65     vLayout->addStretch();
66
67     QBoxLayout *hLayout = new QHBoxLayout();
68     hLayout->setAlignment(Qt::AlignCenter);
69
70     vLayout->addLayout(hLayout);
71
72     hLayout->addStretch();
73
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);
79
80     QVBoxLayout *layout = new QVBoxLayout();
81     layout->setAlignment(Qt::AlignCenter);
82     hLayout->addLayout(layout);
83
84     QColor titleColor = palette().color(QPalette::WindowText);
85     titleColor.setAlphaF(.75);
86     int r,g,b,a;
87     titleColor.getRgb(&r,&g,&b,&a);
88     QString cssColor = QString::asprintf("rgba(%d,%d,%d,%d)", r, g, b, a);
89
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)
95                        + "</h1>");
96     welcomeLabel->setOpenExternalLinks(true);
97     welcomeLabel->setProperty("heading", true);
98     welcomeLabel->setFont(FontUtils::light(welcomeLabel->font().pointSize() * 1.25));
99     layout->addWidget(welcomeLabel);
100
101     layout->addSpacing(padding / 2);
102
103 #ifndef APP_MAC
104     const QFont &biggerFont = FontUtils::big();
105 #endif
106
107     //: "Enter", as in "type". The whole phrase says: "Enter a keyword to start watching videos"
108     // QLabel *tipLabel = new QLabel(tr("Enter"), this);
109
110     QString tip;
111     if (qApp->layoutDirection() == Qt::RightToLeft) {
112         tip = tr("to start watching videos.") + " " + tr("a keyword") + " " + tr("Enter");
113     } else {
114         tip = tr("Enter") + " " + tr("a keyword") + " " + tr("to start watching videos.");
115     }
116     QLabel *tipLabel = new QLabel(tip);
117
118 #ifndef APP_MAC
119     tipLabel->setFont(biggerFont);
120 #endif
121     layout->addWidget(tipLabel);
122
123     /*
124     typeCombo = new QComboBox(this);
125     typeCombo->addItem(tr("a keyword"));
126     typeCombo->addItem(tr("a channel"));
127 #ifndef APP_MAC
128     typeCombo->setFont(biggerFont);
129 #endif
130     connect(typeCombo, SIGNAL(currentIndexChanged(int)), SLOT(searchTypeChanged(int)));
131     tipLayout->addWidget(typeCombo);
132
133     tipLabel = new QLabel(tr("to start watching videos."), this);
134 #ifndef APP_MAC
135     tipLabel->setFont(biggerFont);
136 #endif
137     tipLayout->addWidget(tipLabel);
138     */
139
140     layout->addSpacing(padding / 2);
141
142     QHBoxLayout *searchLayout = new QHBoxLayout();
143     searchLayout->setAlignment(Qt::AlignVCenter);
144
145 #ifdef APP_MAC_SEARCHFIELD
146     SearchLineEditMac *slem = new SearchLineEditMac(this);
147     queryEdit = slem;
148     setFocusProxy(slem);
149 #else
150     SearchLineEdit *sle = new SearchLineEdit(this);
151     sle->setFont(biggerFont);
152     queryEdit = sle;
153 #endif
154
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*)));
159
160     youtubeSuggest = new YTSuggester(this);
161     channelSuggest = new ChannelSuggest(this);
162     connect(channelSuggest, SIGNAL(ready(QVector<Suggestion*>)), SLOT(onChannelSuggestions(QVector<Suggestion*>)));
163     searchTypeChanged(0);
164
165     searchLayout->addWidget(queryEdit->toWidget(), 0, Qt::AlignBaseline);
166     searchLayout->addSpacing(padding);
167
168     watchButton = new QPushButton(tr("Watch"));
169 #ifndef APP_MAC
170     watchButton->setFont(biggerFont);
171 #endif
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);
177
178     layout->addItem(searchLayout);
179
180     layout->addSpacing(padding);
181
182     QHBoxLayout *recentLayout = new QHBoxLayout();
183     recentLayout->setMargin(0);
184     recentLayout->setSpacing(10);
185
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);
196
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);
207
208     layout->addLayout(recentLayout);
209
210     hLayout->addStretch();
211
212     vLayout->addStretch();
213
214 #ifdef APP_ACTIVATION
215     if (!Activation::instance().isActivated())
216         vLayout->addWidget(ActivationView::buyButton(tr("Get the full version")), 0, Qt::AlignRight);
217 #endif
218 }
219
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);
225
226     updateRecentKeywords();
227     updateRecentChannels();
228
229     queryEdit->selectAll();
230     queryEdit->enableSuggest();
231     if (!queryEdit->toWidget()->hasFocus()) queryEdit->toWidget()->setFocus();
232
233     connect(window()->windowHandle(), SIGNAL(screenChanged(QScreen*)), SLOT(screenChanged()), Qt::UniqueConnection);
234
235     qApp->processEvents();
236     update();
237
238 #ifdef APP_MAC
239     // Workaround cursor bug on macOS
240     window()->unsetCursor();
241 #endif
242 }
243
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);
249 }
250
251 void SearchView::updateRecentKeywords() {
252     // load
253     QSettings settings;
254     const QStringList keywords = settings.value(recentKeywordsKey).toStringList();
255     if (keywords == recentKeywords) return;
256     recentKeywords = keywords;
257
258     // cleanup
259     QLayoutItem *item;
260     while ((item = recentKeywordsLayout->takeAt(1)) != 0) {
261         item->widget()->close();
262         delete item;
263     }
264
265     recentKeywordsLabel->setVisible(!keywords.isEmpty());
266     MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty());
267
268     const int maxDisplayLength = 25;
269
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);
278             }
279         }
280         bool needStatusTip = false;
281         if (display.length() > maxDisplayLength) {
282             display.truncate(maxDisplayLength);
283             display.append(QStringLiteral("\u2026"));
284             needStatusTip = true;
285         }
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);
292         if (needStatusTip)
293             itemButton->setStatusTip(link);
294         connect(itemButton, &QPushButton::clicked, [this,link]() {
295             watchKeywords(link);
296         });
297
298         recentKeywordsLayout->addWidget(itemButton);
299     }
300
301 }
302
303 void SearchView::updateRecentChannels() {
304     // load
305     QSettings settings;
306     const QStringList keywords = settings.value(recentChannelsKey).toStringList();
307     if (keywords == recentChannels) return;
308     recentChannels = keywords;
309
310     // cleanup
311     QLayoutItem *item;
312     while ((item = recentChannelsLayout->takeAt(1)) != 0) {
313         item->widget()->close();
314         delete item;
315     }
316
317     recentChannelsLabel->setVisible(!keywords.isEmpty());
318     // TODO MainWindow::instance()->getAction("clearRecentKeywords")->setEnabled(!keywords.isEmpty());
319
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);
327         }
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]() {
335             watchChannel(link);
336         });
337         recentChannelsLayout->addWidget(itemButton);
338     }
339 }
340
341 void SearchView::watch() {
342     QString query = queryEdit->text();
343     watch(query);
344 }
345
346 void SearchView::textChanged(const QString &text) {
347     watchButton->setEnabled(!text.simplified().isEmpty());
348     lastChannelSuggestions.clear();
349 }
350
351 void SearchView::watch(const QString &query) {
352     QString q = query.simplified();
353
354     // check for empty query
355     if (q.isEmpty()) {
356         queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
357         return;
358     }
359
360     /*
361     if (typeCombo->currentIndex() == 1) {
362         // Channel search
363         MainWindow::instance()->channelSearch(q);
364         return;
365     }
366     */
367
368     SearchParams *searchParams = new SearchParams();
369     searchParams->setKeywords(q);
370
371     // go!
372     emit search(searchParams);
373 }
374
375 void SearchView::watchChannel(const QString &channelId) {
376     if (channelId.isEmpty()) {
377         queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
378         return;
379     }
380
381     QString id = channelId;
382
383     // Fix old settings
384     const QLatin1String uc("UC");
385     if (!id.startsWith(uc)) id = uc + id;
386
387     SearchParams *searchParams = new SearchParams();
388     searchParams->setChannelId(id);
389     searchParams->setSortBy(SearchParams::SortByNewest);
390
391     // go!
392     emit search(searchParams);
393 }
394
395 void SearchView::watchKeywords(const QString &query) {
396     QString q = query.simplified();
397
398     // check for empty query
399     if (q.isEmpty()) {
400         queryEdit->toWidget()->setFocus(Qt::OtherFocusReason);
401         return;
402     }
403
404     // if (typeCombo->currentIndex() == 0) {
405         queryEdit->setText(q);
406         watchButton->setEnabled(true);
407     // }
408
409     SearchParams *searchParams = new SearchParams();
410     searchParams->setKeywords(q);
411
412     // go!
413     emit search(searchParams);
414 }
415
416 void SearchView::paintEvent(QPaintEvent *event) {
417     QWidget::paintEvent(event);
418     QBrush brush;
419     if (window()->isActiveWindow()) {
420         brush = palette().base();
421     } else {
422         brush = palette().window();
423     }
424     QPainter painter(this);
425     painter.fillRect(0, 0, width(), height(), brush);
426 }
427
428 void SearchView::searchTypeChanged(int index) {
429     if (index == 0) {
430         queryEdit->setSuggester(youtubeSuggest);
431     } else {
432         queryEdit->setSuggester(channelSuggest);
433     }
434     queryEdit->selectAll();
435     queryEdit->toWidget()->setFocus();
436 }
437
438 void SearchView::suggestionAccepted(Suggestion *suggestion) {
439     if (suggestion->type == QLatin1String("channel")) {
440         watchChannel(suggestion->userData);
441     } else watch(suggestion->value);
442 }
443
444 void SearchView::screenChanged() {
445     logo->setPixmap(IconUtils::pixmap(":/images/app.png"));
446 }
447
448 void SearchView::onChannelSuggestions(const QVector<Suggestion *> &suggestions) {
449     lastChannelSuggestions = suggestions;
450 }