|
@@ -1,72 +1,84 @@
|
|
|
#include "MainWindow.hpp"
|
|
|
#include "core.hpp"
|
|
|
-#include <YoutubeApi/youtubeapi.hpp>
|
|
|
#include <unistd.h>
|
|
|
+#include <gtkmm/signallistitemfactory.h>
|
|
|
+#include <glibmm/ustring.h>
|
|
|
+#include <Database/entities.hpp>
|
|
|
+#include <vector>
|
|
|
+#include <string>
|
|
|
+#include <YoutubeApi/youtubeapi.hpp>
|
|
|
+#include <gdkmm/texture.h>
|
|
|
+#include <gtkmm/picture.h>
|
|
|
+#include <gtkmm/label.h>
|
|
|
+#include <gdkmm/pixbuf.h>
|
|
|
+#include <glibmm/fileutils.h>
|
|
|
|
|
|
namespace components {
|
|
|
|
|
|
MainWindow::MainWindow()
|
|
|
- : m_layout(Gtk::Orientation::VERTICAL)
|
|
|
- , m_search_box(Gtk::Orientation::HORIZONTAL)
|
|
|
- , m_search_field()
|
|
|
- , m_search_btn()
|
|
|
- , m_body(Gtk::Orientation::VERTICAL)
|
|
|
+ : Gtk::ApplicationWindow()
|
|
|
+ , m_layout(Gtk::Orientation::VERTICAL)
|
|
|
+ , m_stack()
|
|
|
+ , m_stackswitcher()
|
|
|
+
|
|
|
+ , m_search_layout(Gtk::Orientation::VERTICAL)
|
|
|
+ , m_search_head(Gtk::Orientation::HORIZONTAL)
|
|
|
+ , m_search_body(Gtk::Orientation::HORIZONTAL)
|
|
|
+ , m_search_entry()
|
|
|
+ , m_search_button()
|
|
|
+ , m_search_scroller()
|
|
|
+ , m_search_video_list()
|
|
|
{
|
|
|
- set_title("GTKYT");
|
|
|
+ set_title("YTMPV");
|
|
|
set_default_size(800, 600);
|
|
|
set_icon_name("youtube");
|
|
|
|
|
|
- // Searchbar
|
|
|
- m_search_btn.set_child(*(this->getButtonContents("Поиск", "find")));
|
|
|
- m_search_btn.signal_clicked().connect(sigc::mem_fun(
|
|
|
- *this,
|
|
|
- &MainWindow::onSearchButtonClicked
|
|
|
- ));
|
|
|
- m_search_field.set_hexpand(true);
|
|
|
- m_search_field.set_placeholder_text("Поиск YouTube");
|
|
|
- m_search_box.append(m_search_field);
|
|
|
- m_search_box.append(m_search_btn);
|
|
|
- m_search_box.set_margin(8);
|
|
|
-
|
|
|
- // --Body-
|
|
|
- // Модель видео
|
|
|
- m_video_storage = Gio::ListStore<VideoModel>::create();
|
|
|
-
|
|
|
- // Модель выбора
|
|
|
+ // Создание модели выбора видео в поиске
|
|
|
+ m_video_storage = Gio::ListStore<components::VideoModel>::create();
|
|
|
selection_model = Gtk::SingleSelection::create(m_video_storage);
|
|
|
selection_model->set_autoselect(false);
|
|
|
selection_model->set_can_unselect(true);
|
|
|
selection_model->property_selected().signal_changed().connect(
|
|
|
sigc::mem_fun(*this, &MainWindow::onVideoChanged));
|
|
|
- m_video_list.set_model(selection_model);
|
|
|
+ m_search_video_list.set_model(selection_model);
|
|
|
|
|
|
- // Создание фабрики
|
|
|
+ // Создание фабрики производства элементов списка видео
|
|
|
auto factory = Gtk::SignalListItemFactory::create();
|
|
|
factory->signal_setup().connect(
|
|
|
sigc::mem_fun(*this, &MainWindow::onVideoSetup));
|
|
|
factory->signal_bind().connect(
|
|
|
sigc::mem_fun(*this, &MainWindow::onVideoBind));
|
|
|
- m_video_list.set_factory(factory);
|
|
|
- m_video_list.signal_activate().connect(
|
|
|
- sigc::mem_fun(*this, &MainWindow::onVideoSelected));
|
|
|
+ m_search_video_list.set_factory(factory);
|
|
|
|
|
|
- // Прокручиваемое окно
|
|
|
- m_video_scroll.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
|
|
|
- m_video_scroll.set_expand(true);
|
|
|
- m_video_scroll.set_child(m_video_list);
|
|
|
+ // SearchView head
|
|
|
+ m_search_button.set_child(*(core::getButtonContents("Поиск", "find")));
|
|
|
+ m_search_button.signal_clicked().connect(
|
|
|
+ sigc::mem_fun(*this, &MainWindow::onSearchButtonClicked));
|
|
|
+ m_search_entry.set_hexpand(true);
|
|
|
+ m_search_entry.set_placeholder_text("Введите запрос");
|
|
|
+ m_search_head.append(m_search_entry);
|
|
|
+ m_search_head.append(m_search_button);
|
|
|
+ m_search_head.set_margin(8);
|
|
|
+ m_search_layout.append(m_search_head);
|
|
|
|
|
|
- m_body.append(m_video_scroll);
|
|
|
+ // SearchView body
|
|
|
+ m_search_scroller.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
|
|
|
+ m_search_scroller.set_expand(true);
|
|
|
+ m_search_scroller.set_child(m_search_video_list);
|
|
|
+ m_search_body.set_end_child(m_search_scroller);
|
|
|
+ // TODO брать из настроек
|
|
|
+ m_search_body.set_position(256);
|
|
|
+ m_search_layout.append(m_search_body);
|
|
|
|
|
|
- // Layout
|
|
|
- m_layout.set_homogeneous(false);
|
|
|
- m_layout.append(m_search_box);
|
|
|
- m_layout.append(m_body);
|
|
|
-
|
|
|
- set_child(m_layout);
|
|
|
- }
|
|
|
+ // WelcomeView
|
|
|
+ // TODO
|
|
|
|
|
|
- MainWindow::~MainWindow()
|
|
|
- {
|
|
|
+ m_stack.add(m_search_layout, "search", "Поиск видео");
|
|
|
+ m_stackswitcher.set_stack(m_stack);
|
|
|
+
|
|
|
+ m_layout.append(m_stackswitcher);
|
|
|
+ m_layout.append(m_stack);
|
|
|
+ set_child(m_layout);
|
|
|
}
|
|
|
|
|
|
void MainWindow::onSearchButtonClicked()
|
|
@@ -75,15 +87,17 @@ namespace components {
|
|
|
m_video_storage->remove_all();
|
|
|
|
|
|
// Получить результаты поиска
|
|
|
- Glib::ustring query = m_search_field.get_buffer()->get_text();
|
|
|
+ Glib::ustring query = m_search_entry.get_buffer()->get_text();
|
|
|
db::video found;
|
|
|
std::vector<std::string> video_ids = ytapi::getVideoIDsByQuery(query.release());
|
|
|
|
|
|
// Добавить в список видео
|
|
|
for (auto id : video_ids) {
|
|
|
found = core::getVideoByYTID(id);
|
|
|
- m_video_storage->append(VideoModel::create(found.id, found.yt_id, found.title));
|
|
|
+ m_video_storage->append(components::VideoModel::create(found));
|
|
|
}
|
|
|
+
|
|
|
+ searchSidebarRegularMode();
|
|
|
}
|
|
|
|
|
|
void MainWindow::onVideoSetup(const Glib::RefPtr<Gtk::ListItem>& list_item)
|
|
@@ -96,16 +110,71 @@ namespace components {
|
|
|
list_item->set_child(*box_main);
|
|
|
}
|
|
|
|
|
|
+ void MainWindow::onVideoBind(const Glib::RefPtr<Gtk::ListItem>& list_item)
|
|
|
+ {
|
|
|
+ auto pos = list_item->get_position();
|
|
|
+ if (pos == GTK_INVALID_LIST_POSITION) return;
|
|
|
+ auto item = m_video_storage->get_item(pos);
|
|
|
+ auto obj = item->m_obj;
|
|
|
+
|
|
|
+ auto box_main = dynamic_cast<Gtk::Box*>(list_item->get_child());
|
|
|
+ if (!box_main) return;
|
|
|
+
|
|
|
+ auto thumbnail = dynamic_cast<Gtk::Picture*>(box_main->get_first_child());
|
|
|
+ if (!thumbnail) return;
|
|
|
+
|
|
|
+ auto lbl_title = dynamic_cast<Gtk::Label*>(thumbnail->get_next_sibling());
|
|
|
+ if (!lbl_title) return;
|
|
|
+
|
|
|
+ // Загрузка изображения превью видео с диска
|
|
|
+ std::string thumbnail_path = "thumbnails/default/" + obj.yt_id + ".jpg";
|
|
|
+ Glib::RefPtr<Gdk::Pixbuf> thumbnail_pixbuf;
|
|
|
+ try {
|
|
|
+ thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path, 120, 90);
|
|
|
+ } catch (Glib::FileError& e) {
|
|
|
+ switch (e.code()) {
|
|
|
+ case Glib::FileError::Code::NO_SUCH_ENTITY:
|
|
|
+ // Превью на диске не найдено. Скачиваем...
|
|
|
+ ytapi::downloadMediumThumnail(obj.yt_id, thumbnail_path);
|
|
|
+ // ...и присваиваем
|
|
|
+ thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path, 120, 90);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ std::cout << "Unhandled error when creating thumbnail. Code is: " << e.code();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ auto thumbnail_texture = Gdk::Texture::create_for_pixbuf(thumbnail_pixbuf);
|
|
|
+ thumbnail->set_paintable(thumbnail_texture);
|
|
|
+ thumbnail->set_can_shrink(false);
|
|
|
+ thumbnail->set_content_fit(Gtk::ContentFit::COVER);
|
|
|
+
|
|
|
+ lbl_title->set_text(obj.title);
|
|
|
+ }
|
|
|
+
|
|
|
void MainWindow::onVideoChanged()
|
|
|
{
|
|
|
auto position = selection_model->get_selected();
|
|
|
auto item = m_video_storage->get_item(position);
|
|
|
if (!item) return;
|
|
|
+ auto obj = item->m_obj;
|
|
|
|
|
|
+ searchSidebarVideoDetailsMode(
|
|
|
+ obj.title,
|
|
|
+ obj.author_obj.name,
|
|
|
+ obj.description,
|
|
|
+ obj.published_at,
|
|
|
+ obj.yt_id
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ void MainWindow::playSingleVideo(std::string yt_id)
|
|
|
+ {
|
|
|
// Запуск mpv в заднем фоне
|
|
|
int pid = fork();
|
|
|
if (pid == 0) {
|
|
|
- std::string yt_url = "https://youtu.be/"+item->m_yt_id;
|
|
|
+ std::string yt_url = "https://youtu.be/"+yt_id;
|
|
|
execl(
|
|
|
"/usr/bin/mpv", // Путь к mpv
|
|
|
"mpv", // Название файла
|
|
@@ -116,20 +185,62 @@ namespace components {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- void MainWindow::onVideoBind(const Glib::RefPtr<Gtk::ListItem>& list_item)
|
|
|
+ void MainWindow::searchSidebarRegularMode()
|
|
|
{
|
|
|
- auto pos = list_item->get_position();
|
|
|
- if (pos == GTK_INVALID_LIST_POSITION) return;
|
|
|
- auto item = m_video_storage->get_item(pos);
|
|
|
+ // Получить иконку поиска и создать надпись "Поиск"
|
|
|
+ auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
|
|
|
+ box->set_valign(Gtk::Align::CENTER);
|
|
|
+
|
|
|
+ auto icon = Gio::Icon::create("find");
|
|
|
+ auto img_icon = Gtk::make_managed<Gtk::Image>(icon);
|
|
|
+ auto label = Gtk::make_managed<Gtk::Label>("Поиск видео");
|
|
|
+
|
|
|
+ box->append(*img_icon);
|
|
|
+ box->append(*label);
|
|
|
+
|
|
|
+ m_search_body.set_start_child(*box);
|
|
|
+ }
|
|
|
+
|
|
|
+ void MainWindow::searchSidebarVideoDetailsMode(
|
|
|
+ Glib::ustring title,
|
|
|
+ Glib::ustring channel_title,
|
|
|
+ Glib::ustring description,
|
|
|
+ std::string published_at,
|
|
|
+ std::string yt_id)
|
|
|
+ {
|
|
|
+ // Главный объект
|
|
|
+ auto layout = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
|
|
|
+ // Прокручиваемое окно
|
|
|
+ auto scroller = Gtk::make_managed<Gtk::ScrolledWindow>();
|
|
|
+ // Область деталей
|
|
|
+ auto box_details = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
|
|
|
+ // Область деталей (текст)
|
|
|
+ auto box_textdetails = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
|
|
|
+ // Область кнопок
|
|
|
+ auto box_buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
|
|
|
|
|
|
- auto box_main = dynamic_cast<Gtk::Box*>(list_item->get_child());
|
|
|
- if (!box_main) return;
|
|
|
+ auto thumbnail = Gtk::make_managed<Gtk::Picture>();
|
|
|
+ auto lbl_title = Gtk::make_managed<Gtk::Label>();
|
|
|
+ auto lbl_channel_title = Gtk::make_managed<Gtk::Label>(channel_title);
|
|
|
+ auto lbl_description = Gtk::make_managed<Gtk::Label>(description);
|
|
|
+ auto lbl_published_at = Gtk::make_managed<Gtk::Label>(published_at);
|
|
|
|
|
|
- auto thumbnail = dynamic_cast<Gtk::Picture*>(box_main->get_first_child());
|
|
|
- if (!thumbnail) return;
|
|
|
+ // Установка названия видео (выделение полужирным)
|
|
|
+ lbl_title->set_markup("<b>"+title+"</b>");
|
|
|
|
|
|
- // Загрузка изображения превью видео с диска
|
|
|
- std::string thumbnail_path = "thumbnails/default/" + item->m_yt_id + ".jpg";
|
|
|
+ // Добавление кнопки "Воспроизвести"
|
|
|
+ auto btn_play = Gtk::make_managed<Gtk::Button>();
|
|
|
+ btn_play->set_child(*(core::getButtonContents("Воспроизвести", "player_play")));
|
|
|
+ btn_play->set_hexpand();
|
|
|
+ btn_play->signal_clicked().connect(sigc::bind(
|
|
|
+ sigc::mem_fun(*this, &MainWindow::playSingleVideo),
|
|
|
+ yt_id
|
|
|
+ ));
|
|
|
+ box_buttons->append(*btn_play);
|
|
|
+
|
|
|
+ // Скачивание большого превью
|
|
|
+ std::string thumbnail_path = "thumbnails/large/" + yt_id + ".jpg";
|
|
|
+ std::cout << thumbnail_path << std::endl;
|
|
|
Glib::RefPtr<Gdk::Pixbuf> thumbnail_pixbuf;
|
|
|
try {
|
|
|
thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path);
|
|
@@ -137,7 +248,7 @@ namespace components {
|
|
|
switch (e.code()) {
|
|
|
case Glib::FileError::Code::NO_SUCH_ENTITY:
|
|
|
// Превью на диске не найдено. Скачиваем...
|
|
|
- ytapi::downloadMediumThumnail(item->m_yt_id, thumbnail_path);
|
|
|
+ ytapi::downloadLargeThumnail(yt_id, thumbnail_path);
|
|
|
// ...и присваиваем
|
|
|
thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path);
|
|
|
break;
|
|
@@ -149,44 +260,28 @@ namespace components {
|
|
|
}
|
|
|
auto thumbnail_texture = Gdk::Texture::create_for_pixbuf(thumbnail_pixbuf);
|
|
|
thumbnail->set_paintable(thumbnail_texture);
|
|
|
- thumbnail->set_can_shrink(false);
|
|
|
thumbnail->set_content_fit(Gtk::ContentFit::COVER);
|
|
|
+ thumbnail->set_hexpand();
|
|
|
+ thumbnail->set_size_request(-1, 360);
|
|
|
|
|
|
- auto lbl_title = dynamic_cast<Gtk::Label*>(thumbnail->get_next_sibling());
|
|
|
- if (!lbl_title) return;
|
|
|
-
|
|
|
- lbl_title->set_text(item->m_title);
|
|
|
- }
|
|
|
+ lbl_description->set_halign(Gtk::Align::START);
|
|
|
+ lbl_description->set_selectable();
|
|
|
+ lbl_description->set_wrap();
|
|
|
|
|
|
- // DO NOT USE!!
|
|
|
- void MainWindow::onVideoSelected(unsigned int position)
|
|
|
- {
|
|
|
- std::cout << position;
|
|
|
- auto item = m_video_storage->get_item(position);
|
|
|
- if (!item) return;
|
|
|
+ box_textdetails->append(*lbl_title);
|
|
|
+ box_textdetails->append(*lbl_channel_title);
|
|
|
+ box_textdetails->append(*lbl_description);
|
|
|
+ box_textdetails->append(*lbl_published_at);
|
|
|
+ box_textdetails->set_margin(8);
|
|
|
|
|
|
- std::string command = "mpv https://youtu.be/"+item->m_yt_id;
|
|
|
+ box_details->append(*thumbnail);
|
|
|
+ box_details->append(*box_textdetails);
|
|
|
+ box_details->set_vexpand();
|
|
|
+ scroller->set_child(*box_details);
|
|
|
|
|
|
- std::cout << command;
|
|
|
- system(command.c_str());
|
|
|
- }
|
|
|
-
|
|
|
- Gtk::Box* MainWindow::getButtonContents(Glib::ustring text, std::string icon_name)
|
|
|
- {
|
|
|
- // Создание иконки и текста
|
|
|
- auto icon = Gio::Icon::create(icon_name);
|
|
|
- auto img_icon = Gtk::make_managed<Gtk::Image>(icon);
|
|
|
- auto lbl_main = Gtk::make_managed<Gtk::Label>(text);
|
|
|
- lbl_main->set_hexpand(false);
|
|
|
-
|
|
|
- // Добавление в контейнер
|
|
|
- auto box_main = Gtk::make_managed<Gtk::Box>(
|
|
|
- Gtk::Orientation::HORIZONTAL,
|
|
|
- 5
|
|
|
- );
|
|
|
- box_main->append(*img_icon);
|
|
|
- box_main->append(*lbl_main);
|
|
|
+ layout->append(*scroller);
|
|
|
+ layout->append(*box_buttons);
|
|
|
|
|
|
- return box_main;
|
|
|
+ m_search_body.set_start_child(*layout);
|
|
|
}
|
|
|
}
|