Эх сурвалжийг харах

Добавлен атрибут самого большого превью

Вадим Королёв 1 жил өмнө
parent
commit
621dcb3cd5

+ 1 - 0
debug.sh

@@ -0,0 +1 @@
+gdb ./bin/src/YTMPV/ytmpv

+ 9 - 4
lib/Database/database.cpp

@@ -43,6 +43,7 @@ namespace db {
                 \"views_count\"	INTEGER NOT NULL DEFAULT 0,\
                 \"likes_count\"	INTEGER NOT NULL DEFAULT 0,\
                 \"rate_status\"	INTEGER NOT NULL DEFAULT 0,\
+                \"big_thumbnail\"	TEXT NOT NULL,\
                 PRIMARY KEY(\"id\" AUTOINCREMENT)\
             )",
 
@@ -67,7 +68,7 @@ namespace db {
         sqlite3_stmt* stmt;
         std::string query =
         "SELECT v.id, v.yt_id, v.title, v.description, a.id, a.yt_id, a.name,"
-        " v.published_at, v.views_count, v.rate_status"
+        " v.published_at, v.views_count, v.rate_status, v.big_thumbnail"
         " FROM video v LEFT JOIN author a ON v.author_id=a.id"
         " WHERE v.yt_id=?";
         databaseOpen();
@@ -113,6 +114,9 @@ namespace db {
         ));
         output->views_count = sqlite3_column_int(stmt, 8);
         output->rate_status = sqlite3_column_int(stmt, 9);
+        output->big_thumbnail = std::string(reinterpret_cast<const char*>(
+            sqlite3_column_text(stmt, 10)
+        ));
         output->author_obj = video_author;
 
         // Завершение
@@ -177,8 +181,8 @@ namespace db {
         sqlite3_stmt* stmt;
         std::string query =
         "INSERT INTO video (yt_id, title, description, author_id, published_at,"
-        " views_count, rate_status)"
-        " VALUES(?, ?, ?, ?, ?, ?, ?)";
+        " views_count, rate_status, big_thumbnail)"
+        " VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
         databaseOpen();
 
         sqlite3_prepare_v2(DB, query.c_str(), query.length(), &stmt, nullptr);
@@ -188,7 +192,8 @@ namespace db {
         sqlite3_bind_int(stmt, 4, v->author_obj.id);
         sqlite3_bind_text(stmt, 5, v->published_at.c_str(), v->published_at.length(), SQLITE_STATIC);
         sqlite3_bind_int(stmt, 6, v->views_count);
-        sqlite3_bind_int(stmt, 7, static_cast<int>(v->rate_status));
+        sqlite3_bind_int(stmt, 7, v->rate_status);
+        sqlite3_bind_text(stmt, 8, v->big_thumbnail.c_str(), v->big_thumbnail.length(), SQLITE_STATIC);
         sqlite3_step(stmt);
 
         v->id = sqlite3_last_insert_rowid(DB);

+ 1 - 0
lib/Database/entities.hpp

@@ -19,6 +19,7 @@ namespace db {
         std::string published_at;
         int views_count;
         int rate_status;
+        std::string big_thumbnail;
         author author_obj;
     };
 }

+ 13 - 0
lib/YoutubeApi/youtubeapi.cpp

@@ -118,8 +118,16 @@ namespace ytapi {
         output.author_name      = Glib::ustring(snippet["channelTitle"]);
         output.published_at     = std::string(snippet["publishedAt"]);
         output.views_count      = std::stoi(stats["viewCount"].template get<std::string>());
+
+        // -- Оценка видео --
+        // TODO: с помощью API узнавать оценку видео которую я поставил
         output.rating           = 0;
 
+        // -- Превью --
+        // С помощью .back() получаем самое большое изображение превью и
+        // сохраняем его в ответ видео
+        output.big_thumbnail    = snippet["thumbnails"].back()["url"];
+
         return output;
     }
 
@@ -136,4 +144,9 @@ namespace ytapi {
         std::string url = "https://img.youtube.com/vi/" + yt_id + "/maxresdefault.jpg";
         performCurlDownload(url, save_path);
     }
+
+    void downloadThumbnail(std::string url, std::string save_path)
+    {
+        performCurlDownload(url, save_path);
+    }
 }

+ 6 - 0
lib/YoutubeApi/youtubeapi.hpp

@@ -22,6 +22,7 @@ namespace ytapi {
         std::string     published_at;   // Когда было опубликовано
         int             views_count;    // Количество просмотров
         int             rating;         // Моя оценка видео
+        std::string     big_thumbnail;  // URL самого большого превью
     };
     
     // API ключ
@@ -65,4 +66,9 @@ namespace ytapi {
 
     // Скачивает большое превью видео и сохраняет в путь save_path
     void downloadLargeThumnail(std::string yt_id, std::string save_path);
+
+    // Скачивает файл превью и сохраняет его
+    // url: ссылка на превью
+    // save_path: путь сохранения на диске
+    void downloadThumbnail(std::string url, std::string save_path);
 }

+ 115 - 118
src/YTMPV/MainWindow.cpp

@@ -1,8 +1,6 @@
 #include "MainWindow.hpp"
 #include "core.hpp"
-#include <unistd.h>
 #include <gtkmm/signallistitemfactory.h>
-#include <glibmm/ustring.h>
 #include <Database/entities.hpp>
 #include <vector>
 #include <string>
@@ -11,126 +9,126 @@
 #include <gtkmm/picture.h>
 #include <gtkmm/label.h>
 #include <gdkmm/pixbuf.h>
-#include <glibmm/fileutils.h>
-#include <glibmm/markup.h>
+#include <glibmm.h>
 
 namespace components {
 
     MainWindow* MainWindow::create()
     {
-        return Gtk::make_managed<MainWindow>();
+        auto builder = Gtk::Builder::create_from_resource("/src/YTMPV/res/MainWindow.ui");
+        auto win = Gtk::Builder::get_widget_derived<MainWindow>(builder, "mainWindow");
+        assert(win != nullptr);
+        return win;
     }
     
-    MainWindow::MainWindow()
-        : Gtk::ApplicationWindow()
-        , m_layout(Gtk::Orientation::VERTICAL)
-        , m_head(Gtk::Orientation::VERTICAL)
-        , m_body()
-        , 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()
-
+    MainWindow::MainWindow(
+        BaseObjectType* cobject,
+        const Glib::RefPtr<Gtk::Builder>& refBuilder)
+        : Gtk::ApplicationWindow(cobject)
+        , m_builder(refBuilder)
         , m_rate_container(Gtk::Orientation::HORIZONTAL)
-        , m_rate_dislike_btn()
-        , m_rate_meh_btn()
-        , m_rate_like_btn()
     {
-        set_title("YTMPV");
-        set_default_size(1024, 640);
-        set_icon_name("youtube");
-        set_show_menubar();
-
-        // -- Переключатель вкладок --
-        m_stackswitcher.set_stack(m_body);
-        m_head.append(m_stackswitcher);
-
+        // Загрузка виджетов
+        auto video_list = m_builder->get_widget<Gtk::ListView>("videoList");
+        auto search_button = m_builder->get_widget<Gtk::Button>("searchButton");
+        auto start_search_button = m_builder->get_widget<Gtk::Button>("startSearchButton");
+        m_search_body = m_builder->get_widget<Gtk::Paned>("searchBody");
+        m_search_entry = m_builder->get_widget<Gtk::Entry>("searchEntry");
+        m_start_search_entry = m_builder->get_widget<Gtk::Entry>("startSearchEntry");
+        m_stackswitcher = m_builder->get_widget<Gtk::StackSwitcher>("switcher");
+        m_body = m_builder->get_widget<Gtk::Stack>("body");
+
+        assert(video_list != nullptr);
+        assert(search_button != nullptr);
+        assert(start_search_button != nullptr);
+        assert(m_search_body != nullptr);
+        assert(m_search_entry != nullptr);
+        assert(m_start_search_entry != nullptr);
+        assert(m_stackswitcher != nullptr);
+        assert(m_body != nullptr);
+        
         // -- Создание модели выбора видео в поиске --
         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(
+        m_selection_model = Gtk::SingleSelection::create(m_video_storage);
+        m_selection_model->set_autoselect(false);
+        m_selection_model->set_can_unselect(true);
+        m_selection_model->property_selected().signal_changed().connect(
             sigc::mem_fun(*this, &MainWindow::onVideoChanged));
-        m_search_video_list.set_model(selection_model);
+        video_list->set_model(m_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_search_video_list.set_factory(factory);
+        video_list->set_factory(factory);
 
-        // SearchView head
-        m_search_button.set_child(*(core::getButtonContents("Find", "find")));
-        m_search_button.signal_clicked().connect(
+        // -- Подключение обработчиков к кнопкам --
+
+        // Кнопки поиска
+        search_button->signal_clicked().connect(
             sigc::mem_fun(*this, &MainWindow::onSearchButtonClicked));
-        m_search_entry.set_hexpand(true);
-        m_search_entry.set_placeholder_text("Search something");
-        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);
-
-        // 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);
+        start_search_button->signal_clicked().connect(
+            sigc::mem_fun(*this, &MainWindow::onStartSearchButtonClicked));
+
         // TODO брать из настроек
-        m_search_body.set_position(256);
-        m_search_layout.append(m_search_body);
+        m_search_body->set_position(256);
 
         // Кнопки оценки видео
-        m_rate_dislike_btn.set_child(*(core::getButtonContents("Dislike", "face-angry-symbolic")));
-        m_rate_meh_btn.set_child(*(core::getButtonContents("No rating", "face-plain-symbolic")));
-        m_rate_like_btn.set_child(*(core::getButtonContents("Like", "face-smile-big-symbolic")));
+        m_rate_dislike_btn = Gtk::make_managed<Gtk::Button>("Dislike");
+        m_rate_meh_btn = Gtk::make_managed<Gtk::Button>("No rating");
+        m_rate_like_btn = Gtk::make_managed<Gtk::Button>("Like");
         m_rate_container.set_homogeneous();
-        m_rate_container.append(m_rate_dislike_btn);
-        m_rate_container.append(m_rate_meh_btn);
-        m_rate_container.append(m_rate_like_btn);
-        m_rate_dislike_btn.signal_clicked().connect(sigc::bind(
+        m_rate_container.append(*m_rate_dislike_btn);
+        m_rate_container.append(*m_rate_meh_btn);
+        m_rate_container.append(*m_rate_like_btn);
+        m_rate_dislike_btn->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::onRateButtonClicked),
             RateButtonParams {2}));
-        m_rate_meh_btn.signal_clicked().connect(sigc::bind(
+        m_rate_meh_btn->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::onRateButtonClicked),
             RateButtonParams {0} ));
-        m_rate_like_btn.signal_clicked().connect(sigc::bind(
+        m_rate_like_btn->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::onRateButtonClicked),
             RateButtonParams {1} ));
         
         // WelcomeView
         // TODO
-
-        m_body.add(m_search_layout, "search", "Searching video");
-
-        m_layout.append(m_head);
-        m_layout.append(m_body);
-        set_child(m_layout);
     }
 
-    void MainWindow::onSearchButtonClicked()
+    void MainWindow::searchVideoByQuery(Glib::ustring query)
     {
-        // Очистить предыдущие результаты
+        // Очистить предыдущие результаты поиска
         m_video_storage->remove_all();
-        
-        // Получить результаты поиска
-        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(components::VideoModel::create(found));
+            m_video_storage->append(VideoModel::create(found));
         }
+    }
+
+    void MainWindow::onSearchButtonClicked()
+    {
+        auto query = m_search_entry->get_text();
+        searchVideoByQuery(query);
+        searchSidebarRegularMode();
+    }
 
+    void MainWindow::onStartSearchButtonClicked()
+    {
+        auto query = m_start_search_entry->get_text();
+        m_search_entry->set_text(query);
+        m_body->set_visible_child(
+            "videos",
+            Gtk::StackTransitionType::SLIDE_LEFT
+        );
+        searchVideoByQuery(query);
         searchSidebarRegularMode();
     }
 
@@ -189,7 +187,7 @@ namespace components {
 
     void MainWindow::onVideoChanged()
     {
-        auto position = selection_model->get_selected();
+        auto position = m_selection_model->get_selected();
         auto item = m_video_storage->get_item(position);
         if (!item) return;
         auto obj = item->m_obj;
@@ -200,31 +198,30 @@ namespace components {
             obj.description,
             obj.published_at,
             obj.yt_id,
-            obj.views_count
+            obj.views_count,
+            obj.big_thumbnail
         );
     }
 
     void MainWindow::playSingleVideo(std::string yt_id)
     {
         // Запуск mpv в заднем фоне
-        int pid = fork();
-        if (pid == 0) {
-            std::string yt_url = "https://youtu.be/"+yt_id;
-            execl(
-                // Путь к mpv
+        std::string yt_url = "https://youtu.be/"+yt_id;
+
+        // TODO: Заставить работать
+        Glib::spawn_async(
+            "",
+            {
                 "/usr/bin/mpv",
-                // Название файла
-                "mpv",
-                // Терминал не нужен
                 "--no-terminal",
-                // Качество
-                "--ytdl-format="+core::getYTDLFormat(core::Settings::getQuality()),
-                // URL видео
-                yt_url.c_str(),
-                // Терминатор
-                (char*)0
-            );
-        }
+                "--ytdl-format=\""+core::getYTDLFormat(core::Settings::getQuality())+"\"",
+                yt_url
+                // TODO: --fullscreen
+                // TODO: --ontop
+                // TODO: мини-проигрыватель
+            },
+            Glib::SpawnFlags::DEFAULT
+        );
     }
 
     void MainWindow::searchSidebarRegularMode()
@@ -240,7 +237,7 @@ namespace components {
         box->append(*img_icon);
         box->append(*label);
 
-        m_search_body.set_start_child(*box);
+        m_search_body->set_start_child(*box);
     }
 
     void MainWindow::searchSidebarVideoDetailsMode(
@@ -249,7 +246,8 @@ namespace components {
         Glib::ustring description,
         std::string published_at,
         std::string yt_id,
-        int views_count)
+        int views_count,
+        std::string thumbnail_url)
     {
         // Главный объект
         auto layout = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
@@ -277,8 +275,7 @@ namespace components {
         lbl_views_count->set_text(std::to_string(views_count) + " " + "views");
 
         // Добавление кнопки "Воспроизвести"
-        auto btn_play = Gtk::make_managed<Gtk::Button>();
-        btn_play->set_child(*(core::getButtonContents("Play video", "player_play")));
+        auto btn_play = Gtk::make_managed<Gtk::Button>("Play video");
         btn_play->set_hexpand();
         btn_play->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::onPlayButtonClicked),
@@ -288,13 +285,10 @@ namespace components {
 
         // Кнопки оценки видео
         int my_rating = db::getVideoRating(yt_id);
-        setCurrentVideo(yt_id, my_rating);
-        m_rate_container.unparent();            
-        box_buttons->append(m_rate_container);
+        updateVideoDetails(yt_id, my_rating);
 
         // Добавление кнопки "Открыть в веб-браузере"
-        auto btn_webbrowser = Gtk::make_managed<Gtk::Button>();
-        btn_webbrowser->set_child(*(core::getButtonContents("Open in web browser", "web-browser")));
+        auto btn_webbrowser = Gtk::make_managed<Gtk::Button>("Open in web browser");
         btn_webbrowser->set_hexpand();
         btn_webbrowser->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::openVideoInWebBrowser),
@@ -310,7 +304,7 @@ namespace components {
             switch (e.code()) {
                 case Glib::FileError::Code::NO_SUCH_ENTITY:
                     // Превью на диске не найдено. Скачиваем...
-                    ytapi::downloadLargeThumnail(yt_id, thumbnail_path);
+                    ytapi::downloadThumbnail(thumbnail_url, thumbnail_path);
                     // ...и присваиваем
                     thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path);
                     break;
@@ -333,9 +327,11 @@ namespace components {
         box_textdetails->append(*lbl_title);
         box_textdetails->append(*lbl_channel_title);
         box_textdetails->append(*lbl_views_count);
-        box_textdetails->append(*lbl_description);
         box_textdetails->append(*lbl_published_at);
+        m_rate_container.unparent();
+        box_textdetails->append(m_rate_container);
         box_textdetails->append(*btn_webbrowser);
+        box_textdetails->append(*lbl_description);
         box_textdetails->set_margin(8);
 
         box_details->append(*thumbnail);
@@ -346,7 +342,7 @@ namespace components {
         layout->append(*scroller);
         layout->append(*box_buttons);
 
-        m_search_body.set_start_child(*layout);
+        m_search_body->set_start_child(*layout);
     }
 
     void MainWindow::openVideoInWebBrowser(std::string yt_id)
@@ -361,34 +357,35 @@ namespace components {
     void MainWindow::onPlayButtonClicked(PlayButtonParams e)
     {
         e.caller->set_sensitive(false);
-        e.caller->set_child(*(core::getButtonContents("mpv started", "window-duplicate")));
+        e.caller->set_label("MPV started");
         MainWindow::playSingleVideo(e.yt_id);
     }
 
     void MainWindow::onRateButtonClicked(RateButtonParams e)
     {
-        setCurrentVideo(m_current_yt_id, e.rating);
+        updateVideoDetails(m_current_yt_id, e.rating);
         core::rateVideo(m_current_yt_id, e.rating);
     }
 
-    void MainWindow::setCurrentVideo(std::string yt_id, int rating)
+    void MainWindow::updateVideoDetails(std::string yt_id, int rating)
     {
+        core::AppState::current_yt_id = yt_id;
         m_current_yt_id = yt_id;
-        
         m_current_rating = rating;
-        m_rate_dislike_btn.set_state_flags(Gtk::StateFlags::NORMAL, true);
-        m_rate_like_btn.set_state_flags(Gtk::StateFlags::NORMAL, true);
-        m_rate_meh_btn.set_state_flags(Gtk::StateFlags::NORMAL, true);
+        
+        m_rate_dislike_btn->set_state_flags(Gtk::StateFlags::NORMAL, true);
+        m_rate_like_btn->set_state_flags(Gtk::StateFlags::NORMAL, true);
+        m_rate_meh_btn->set_state_flags(Gtk::StateFlags::NORMAL, true);
         switch (rating)
         {
             case 0:
-                m_rate_meh_btn.set_state_flags(Gtk::StateFlags::CHECKED);
+                m_rate_meh_btn->set_state_flags(Gtk::StateFlags::CHECKED);
                 break;
             case 1:
-                m_rate_like_btn.set_state_flags(Gtk::StateFlags::CHECKED);
+                m_rate_like_btn->set_state_flags(Gtk::StateFlags::CHECKED);
                 break;
             case 2:
-                m_rate_dislike_btn.set_state_flags(Gtk::StateFlags::CHECKED);
+                m_rate_dislike_btn->set_state_flags(Gtk::StateFlags::CHECKED);
                 break;
             default:
                 break;

+ 37 - 25
src/YTMPV/MainWindow.hpp

@@ -14,6 +14,7 @@
 #include <gtkmm/listitem.h>
 #include <gtkmm/paned.h>
 #include <gtkmm/menubutton.h>
+#include <gtkmm/builder.h>
 
 namespace
 {
@@ -38,46 +39,41 @@ namespace components {
     class MainWindow : public Gtk::ApplicationWindow
     {
     public:
-        MainWindow();
+        MainWindow(
+            BaseObjectType* cobject,
+            const Glib::RefPtr<Gtk::Builder>& refBuilder);
         static MainWindow* create();
 
     protected:
+        Glib::RefPtr<Gtk::Builder> m_builder;
         Gtk::Box m_layout;
         Gtk::Box m_head;
-        Gtk::Stack m_body;
-        Gtk::StackSwitcher m_stackswitcher;
+        Gtk::Stack* m_body;
+        Gtk::StackSwitcher* m_stackswitcher;
 
         /// ПОИСК ///
-        // --Виджеты--
-        Gtk::Box m_search_layout;
-        // Панель поиска
-        Gtk::Box m_search_head;
-        // Тело
-        Gtk::Paned m_search_body;
+        // Тело поиска
+        Gtk::Paned* m_search_body;
         // Поле ввода запроса
-        Gtk::Entry m_search_entry;
-        // Кнопка поиска
-        Gtk::Button m_search_button;
-        // Окно прокрутки для видео     
-        Gtk::ScrolledWindow m_search_scroller;
-        // Список видео
-        Gtk::ListView m_search_video_list;
+        Gtk::Entry* m_search_entry;
+        // Поле ввода запроса на стартовой странице
+        Gtk::Entry* m_start_search_entry;
         // Список моделей видео
         Glib::RefPtr<Gio::ListStore<components::VideoModel>> m_video_storage;
         // Модель выбора
-        Glib::RefPtr<Gtk::SingleSelection> selection_model;
+        Glib::RefPtr<Gtk::SingleSelection> m_selection_model;
 
         /// ДЕТАЛИ ВИДЕО ///
         Gtk::Box m_rate_container;
-        Gtk::Button m_rate_dislike_btn;
-        Gtk::Button m_rate_meh_btn;
-        Gtk::Button m_rate_like_btn;
-        int m_current_rating;
-        std::string m_current_yt_id;
+        Gtk::Button* m_rate_dislike_btn;
+        Gtk::Button* m_rate_meh_btn;
+        Gtk::Button* m_rate_like_btn;
 
         // --События--
         // Клик кнопки поиска
         void onSearchButtonClicked();
+        // Клик кнопки поиска на стартовой странице
+        void onStartSearchButtonClicked();
         // При создании элемента в m_video_list
         void onVideoSetup(const Glib::RefPtr<Gtk::ListItem>& list_item);
         // При записи данных в m_video_list
@@ -90,22 +86,38 @@ namespace components {
         void onRateButtonClicked(RateButtonParams e);
 
         // --Общее--
+        // Оценка выбранного видео
+        int m_current_rating;
+        // Youtube id у выбранного видео
+        std::string m_current_yt_id;
         // Устанавливает боковую панель просмотра видео в обычный режим
         void searchSidebarRegularMode();
+
         // Устанавливает боковую панель просмотра видео в режим просмотра
         // информации о видео
+        // title - Название видео
+        // channel_title - Название канала
+        // description - Описание видео
+        // published_at - Дата публикации
+        // yt_id - id видео на YouTube
+        // views_count - количество просмотров
+        // thumbnail_url - ссылка на большое превью
         void searchSidebarVideoDetailsMode(
             Glib::ustring title,
             Glib::ustring channel_title,
             Glib::ustring description,
             std::string published_at,
             std::string yt_id,
-            int views_count);
+            int views_count,
+            std::string thumbnail_url);
+
         // Проигрывает конкретное видео
         void playSingleVideo(std::string yt_id);
         // Открывает страницу видео в YouTube
         void openVideoInWebBrowser(std::string yt_id);
-        // Устанавливает текущие переменные при просмотре информации о видео
-        void setCurrentVideo(std::string yt_id, int rating);
+        // Обновляет текущие переменные при просмотре информации о видео
+        void updateVideoDetails(std::string yt_id, int rating);
+        // Выполняет основную часть поиска видео
+        void searchVideoByQuery(Glib::ustring query);
     };
 }

+ 0 - 1
src/YTMPV/PrefWindow.hpp

@@ -13,7 +13,6 @@ namespace components {
         PrefWindow(
             BaseObjectType* cobject,
             const Glib::RefPtr<Gtk::Builder>& refBuilder);
-
         static PrefWindow* create(Gtk::Window& parent);
 
     protected:

+ 29 - 2
src/YTMPV/app.cpp

@@ -1,12 +1,16 @@
 #include "app.hpp"
+#include "core.hpp"
 #include <iostream>
 #include <giomm/menu.h>
 #include <gtkmm/builder.h>
 #include <assert.h>
+#include <sstream>
 
 namespace components {
+
     App::App()
         : Gtk::Application("com.github.aquadim.YTMPV")
+        , m_current_yt_id()
     {
     }
 
@@ -26,6 +30,12 @@ namespace components {
         add_action("quit", sigc::mem_fun(*this, &App::onActionQuit));
         set_accel_for_action("app.quit", "<Ctrl>Q");
 
+        add_action("copyytid", sigc::mem_fun(*this, &App::onActionCopyYTID));
+        set_accel_for_action("app.copyytid", "<Ctrl>I");
+
+        add_action("copyytlink", sigc::mem_fun(*this, &App::onActionCopyYTLink));
+        set_accel_for_action("app.copyytlink", "<Ctrl>L");
+
         // Установка менюбара
         auto menu_builder = Gtk::Builder::create_from_resource("/src/YTMPV/res/MainMenu.ui");
         auto menu = menu_builder->get_object<Gio::Menu>("menu");
@@ -36,8 +46,8 @@ namespace components {
 
     void App::on_activate()
     {
-        auto appwindow = createMainWindow();
-        appwindow->present();
+        m_main_window = createMainWindow();
+        m_main_window->present();
     }
 
     MainWindow* App::createMainWindow()
@@ -76,4 +86,21 @@ namespace components {
         PrefWindow* win = createPrefWindow();
         win->present();
     }
+
+    void App::onActionCopyYTID()
+    {
+        m_main_window->get_clipboard()->set_text(core::AppState::current_yt_id);
+    }
+    
+    void App::onActionCopyYTLink()
+    {
+        std::cout << core::AppState::current_yt_id << std::endl;
+        std::string url = "https://youtu.be/" + core::AppState::current_yt_id;
+        m_main_window->get_clipboard()->set_text(url);
+    }
+
+    void App::setCurrentYTID(std::string yt_id)
+    {
+        this->m_current_yt_id = yt_id;
+    }
 }

+ 25 - 2
src/YTMPV/app.hpp

@@ -1,4 +1,5 @@
 #pragma once
+
 #include <gtkmm/application.h>
 #include <glibmm/ustring.h>
 #include "MainWindow.hpp"
@@ -12,15 +13,37 @@ namespace components {
     public:
         static Glib::RefPtr<App> create();
 
+        // Устанавливает текущий id видео
+        void setCurrentYTID(std::string yt_id);
+
     protected:
         App();
+
         void on_startup() override;
+
         void on_activate() override;
+
+        // ID "выбранного" на данный момент видео
+        std::string m_current_yt_id;
+
+        // Закрывает приложение
         void onActionQuit();
+
+        // ПОказывает диалог настроек
         void onActionPrefs();
-        
-    private:
+
+        // Копирует ID текущего видео
+        void onActionCopyYTID();
+
+        // Копирует ссылку на видео
+        void onActionCopyYTLink();
+
+        MainWindow* m_main_window;
+
+        // Создаёт главное окно приложения
         MainWindow* createMainWindow();
+
+        // Создаёт окно настроек приложения
         PrefWindow* createPrefWindow();
     };
 }

+ 3 - 18
src/YTMPV/core.cpp

@@ -5,6 +5,8 @@
 #include <string>
 
 namespace core {
+
+    std::string AppState::current_yt_id = "";
     
     db::video getVideoByYTID(std::string yt_id)
     {
@@ -23,6 +25,7 @@ namespace core {
         output.published_at = found_video.published_at;
         output.views_count  = found_video.views_count;
         output.rate_status  = found_video.rating;
+        output.big_thumbnail= found_video.big_thumbnail;
 
         // Ищем автора в базе данных
         db::author video_author;
@@ -40,24 +43,6 @@ namespace core {
         return output;
     }
 
-    Gtk::Box* 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);
-
-        return box_main;
-    }
-
     std::string getYTDLFormat(int height)
     {
         return "bestvideo[height<=?"+std::to_string(height)+"]+bestaudio/best";

+ 8 - 6
src/YTMPV/core.hpp

@@ -1,20 +1,15 @@
 #pragma once
 #include <YoutubeApi/youtubeapi.hpp>
 #include <Database/database.hpp>
-#include <gtkmm/box.h>
-#include <glibmm/ustring.h>
 #include <fstream>
 #include <iostream>
 #include <string>
 
 namespace core {
-  
+    
     // Возвращает объект видео по его Youtube id
     db::video getVideoByYTID(std::string yt_id);
 
-    // Возвращает дочерний виджет для кнопок, которым нужны иконка с текстом
-    Gtk::Box* getButtonContents(Glib::ustring text, std::string icon_name);
-
     // Возвращает строку формата yt-dl
     std::string getYTDLFormat(int height);
 
@@ -22,6 +17,13 @@ namespace core {
     // rating: 0 - нет, 1 - лайк, 2 - дизлайк
     void rateVideo(std::string yt_id, int rating);
 
+    class AppState
+    {
+        public:
+        // ID "выбранного" на данный момент видео
+        static std::string current_yt_id;
+    };
+
     class Settings {
     public:
         static void setConfigPath(std::string config_path);

+ 46 - 26
src/YTMPV/res/MainMenu.ui

@@ -1,36 +1,56 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Created with Cambalache 0.90.2 -->
 <!--Menubar for MainWindow-->
 <interface>
-    <menu id='menu'>
+  <!-- interface-name MainMenu.ui -->
+  <requires lib="gio" version="2.0"/>
+  <menu id="menu">
+    <submenu>
+      <attribute name="label" translatable="yes">_File</attribute>
+      <section>
+        <item>
+          <attribute name="action">app.preferences</attribute>
+          <attribute name="icon">preferences-other-symbolic</attribute>
+          <attribute name="label" translatable="yes">_Preferences</attribute>
+        </item>
+        <item>
+          <attribute name="action">app.quit</attribute>
+          <attribute name="icon">application-exit-symbolic</attribute>
+          <attribute name="label" translatable="yes">_Quit</attribute>
+        </item>
+      </section>
+    </submenu>
 
-    <!--File-->
     <submenu>
-        <attribute name='label' translatable='yes'>_File</attribute>
-        <section>
-            <item>
-                <attribute name='label' translatable='yes'>_Preferences</attribute>
-                <attribute name='action'>app.preferences</attribute>
-            </item>
-            <item>
-                <attribute name='label' translatable='yes'>_Quit</attribute>
-                <attribute name='action'>app.quit</attribute>
-            </item>
-        </section>
+      <attribute name="label" translatable="yes">_Video</attribute>
+      <section>
+        <item>
+          <attribute name="action">app.copyytid</attribute>
+          <attribute name="icon">copy</attribute>
+          <attribute name="label" translatable="yes">_Copy id of this video</attribute>
+        </item>
+        <item>
+          <attribute name="action">app.copyytlink</attribute>
+          <attribute name="icon">copy</attribute>
+          <attribute name="label" translatable="yes">_Copy link to this video</attribute>
+        </item>
+      </section>
     </submenu>
     
-    <!--Help-->
     <submenu>
-        <attribute name='label' translatable='yes'>_Help</attribute>
-        <section>
-            <item>
-                <attribute name='label' translatable='yes'>_Help</attribute>
-                <attribute name='action'>app.help</attribute>
-            </item>
-            <item>
-                <attribute name='label' translatable='yes'>_About</attribute>
-                <attribute name='action'>app.about</attribute>
-            </item>
-        </section>
+      <attribute name="label" translatable="yes">_Help</attribute>
+      <section>
+        <item>
+          <attribute name="action">app.help</attribute>
+          <attribute name="icon">help-browser-symbolic</attribute>
+          <attribute name="label" translatable="yes">_Help</attribute>
+        </item>
+        <item>
+          <attribute name="action">app.about</attribute>
+          <attribute name="icon">help-about-symbolic</attribute>
+          <attribute name="label" translatable="yes">_About</attribute>
+        </item>
+      </section>
     </submenu>
   </menu>
 </interface>

+ 133 - 0
src/YTMPV/res/MainWindow.ui

@@ -0,0 +1,133 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Created with Cambalache 0.90.2 -->
+<interface>
+  <!-- interface-name ytmpv.ui -->
+  <requires lib="gtk" version="4.8"/>
+  <object class="GtkApplicationWindow" id="mainWindow">
+    <property name="child">layout</property>
+    <property name="default-height">600</property>
+    <property name="default-width">1024</property>
+    <property name="height-request">0</property>
+    <property name="icon-name">youtube</property>
+    <property name="show-menubar">True</property>
+    <property name="title" translatable="yes">YTMPV</property>
+    <property name="width-request">0</property>
+    <child>
+      <object class="GtkBox" id="layout">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="head">
+            <property name="margin-bottom">8</property>
+            <property name="margin-end">8</property>
+            <property name="margin-start">8</property>
+            <property name="margin-top">8</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkStackSwitcher" id="switcher">
+                <property name="stack">body</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="body">
+            <child>
+
+              <!--Стартовая страница приложения-->
+              <object class="GtkStackPage" id="startPage">
+                <property name="name">start</property>
+                <property name="title">Start page</property>
+                <property name="child">
+                  <object class="GtkBox" id="startLayout">
+                    <property name="orientation">horizontal</property>
+                    <property name="valign">center</property>
+                    <property name="halign">center</property>
+                    <property name="hexpand">False</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="justify">center</property>
+                        <property name="label">&lt;b&gt;Welcome to YTMPV!&lt;/b&gt;
+Get comfortable</property>
+                        <property name="use-markup">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="startSearchBox">
+                        <property name="margin-bottom">8</property>
+                        <property name="margin-end">8</property>
+                        <property name="margin-start">8</property>
+                        <property name="margin-top">8</property>
+                        <property name="spacing">8</property>
+                        <child>
+                          <object class="GtkEntry" id="startSearchEntry">
+                            <property name="primary-icon-name">system-search-symbolic</property>
+                            <property name="hexpand">True</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="startSearchButton">
+                            <property name="label" translatable="yes">Search</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+              </object>
+            </child>
+
+            <!--Страница просмотра видео-->
+            <child>
+              <object class="GtkStackPage" id="searchPage">
+                <property name="child">
+                  <object class="GtkBox" id="searchLayout">
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox" id="searchHead">
+                        <property name="margin-bottom">8</property>
+                        <property name="margin-end">8</property>
+                        <property name="margin-start">8</property>
+                        <property name="margin-top">8</property>
+                        <property name="spacing">8</property>
+                        <child>
+                          <object class="GtkEntry" id="searchEntry">
+                            <property name="hexpand">True</property>
+                            <property name="primary-icon-name">system-search-symbolic</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="searchButton">
+                            <property name="label" translatable="yes">Search</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkPaned" id="searchBody">
+                        <property name="end-child">
+                          <object class="GtkScrolledWindow" id="videoScroller">
+                            <property name="child">
+                              <object class="GtkListView" id="videoList"/>
+                            </property>
+                          </object>
+                        </property>
+                        <property name="start-child">
+                          <object class="GtkBox" id="videoSidebar">
+                            <property name="orientation">vertical</property>
+                          </object>
+                        </property>
+                        <property name="vexpand">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+                <property name="name">videos</property>
+                <property name="title">Search videos</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

+ 3 - 0
src/YTMPV/res/PrefsWindow.ui

@@ -1,5 +1,8 @@
 <?xml version='1.0' encoding='UTF-8'?>
+<!-- Created with Cambalache 0.90.2 -->
 <interface>
+  <!-- interface-name PrefsWindow.ui -->
+  <requires lib="gtk" version="4.8"/>
   <object class="GtkWindow" id="prefs_dialog">
     <property name="hide-on-close">True</property>
     <property name="modal">True</property>

+ 144 - 0
src/YTMPV/res/YTMPV.cmb

@@ -0,0 +1,144 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!DOCTYPE cambalache-project SYSTEM "cambalache-project.dtd">
+<cambalache-project version="0.17.3" target_tk="gtk-4.0">
+  <ui>
+	(4,None,"PrefsWindow.ui","PrefsWindow.ui",None,None,None,None,None,None,None),
+	(5,None,"MainMenu.ui","MainMenu.ui",None,None,None,None,None,"Menubar for MainWindow",None),
+	(6,None,"ytmpv.ui","MainWindow.ui",None,None,None,None,None,None,None)
+  </ui>
+  <ui_library>
+	(4,"gtk","4.8",None),
+	(5,"gio","2.0",None),
+	(6,"gtk","4.8",None)
+  </ui_library>
+  <object>
+	(4,1,"GtkWindow","prefs_dialog",None,None,None,None,None,None,None),
+	(4,2,"GtkGrid","grid",1,None,None,None,None,None,None),
+	(4,3,"GtkLabel","apikey_label",2,None,None,None,None,None,None),
+	(4,4,"GtkEntry","apikey_entry",2,None,None,None,1,None,None),
+	(4,5,"GtkLabel","quality_label",2,None,None,None,2,None,None),
+	(4,6,"GtkDropDown","quality_entry",2,None,None,None,3,None,None),
+	(4,7,"GtkButton","cancel_button",2,None,None,None,4,None,None),
+	(4,8,"GtkButton","saveclose_button",2,None,None,None,5,None,None),
+	(5,1,"(menu)","menu",None,None,None,None,None,None,None),
+	(5,2,"(submenu)",None,1,None,None,None,None,None,None),
+	(5,3,"(section)",None,2,None,None,None,None,None,None),
+	(5,4,"(item)",None,3,None,None,None,None,None,None),
+	(5,5,"(item)",None,3,None,None,None,1,None,None),
+	(5,6,"(submenu)",None,1,None,None,None,1,None,None),
+	(5,7,"(section)",None,6,None,None,None,None,None,None),
+	(5,8,"(item)",None,7,None,None,None,None,None,None),
+	(5,9,"(item)",None,7,None,None,None,1,None,None),
+	(6,1,"GtkApplicationWindow","mainWindow",None,None,None,None,None,None,None),
+	(6,2,"GtkBox","layout",1,None,None,None,None,None,None),
+	(6,3,"GtkBox","head",2,None,None,None,None,None,None),
+	(6,4,"GtkStackSwitcher","switcher",3,None,None,None,None,None,None),
+	(6,5,"GtkStack","body",2,None,None,None,1,None,None),
+	(6,6,"GtkStackPage","startPage",5,None,None,None,None,None,None),
+	(6,7,"GtkBox","startLayout",6,None,None,None,None,None,None),
+	(6,8,"GtkStackPage","searchPage",5,None,None,None,1,None,None),
+	(6,9,"GtkBox","searchLayout",8,None,None,None,None,None,None),
+	(6,10,"GtkBox","searchHead",9,None,None,None,None,None,None),
+	(6,11,"GtkEntry","searchEntry",10,None,None,None,None,None,None),
+	(6,12,"GtkButton","searchButton",10,None,None,None,1,None,None),
+	(6,13,"GtkPaned","searchBody",9,None,None,None,1,None,None),
+	(6,14,"GtkScrolledWindow","videoScroller",13,None,None,None,None,None,None),
+	(6,15,"GtkListView","videoList",14,None,None,None,None,None,None),
+	(6,16,"GtkBox","videoSidebar",13,None,None,None,None,None,None),
+	(6,17,"GtkLabel",None,7,None,None,None,None,None,None),
+	(6,19,"GtkBox",None,7,None,None,None,1,None,None),
+	(6,20,"GtkEntry",None,19,None,None,None,-1,None,None),
+	(6,21,"GtkButton",None,19,None,None,None,1,None,None)
+  </object>
+  <object_property>
+	(4,1,"GtkWindow","hide-on-close","True",None,None,None,None,None,None,None,None,None),
+	(4,1,"GtkWindow","modal","True",None,None,None,None,None,None,None,None,None),
+	(4,1,"GtkWindow","resizable","False",None,None,None,None,None,None,None,None,None),
+	(4,1,"GtkWindow","title","Preferences",1,None,None,None,None,None,None,None,None),
+	(4,2,"GtkGrid","column-spacing","16",None,None,None,None,None,None,None,None,None),
+	(4,2,"GtkGrid","row-spacing","16",None,None,None,None,None,None,None,None,None),
+	(4,2,"GtkWidget","margin-bottom","16",None,None,None,None,None,None,None,None,None),
+	(4,2,"GtkWidget","margin-end","16",None,None,None,None,None,None,None,None,None),
+	(4,2,"GtkWidget","margin-start","16",None,None,None,None,None,None,None,None,None),
+	(4,2,"GtkWidget","margin-top","16",None,None,None,None,None,None,None,None,None),
+	(4,3,"GtkLabel","label","_API key:",1,None,None,None,None,None,None,None,None),
+	(4,3,"GtkLabel","mnemonic-widget","4",None,None,None,None,None,None,None,None,None),
+	(4,3,"GtkLabel","use-underline","True",None,None,None,None,None,None,None,None,None),
+	(4,3,"GtkLabel","xalign","1",None,None,None,None,None,None,None,None,None),
+	(4,5,"GtkLabel","label","_Preferred quality:",1,None,None,None,None,None,None,None,None),
+	(4,5,"GtkLabel","mnemonic-widget","6",None,None,None,None,None,None,None,None,None),
+	(4,5,"GtkLabel","use-underline","True",None,None,None,None,None,None,None,None,None),
+	(4,5,"GtkLabel","xalign","1",None,None,None,None,None,None,None,None,None),
+	(4,7,"GtkButton","label","Cancel",1,None,None,None,None,None,None,None,None),
+	(4,8,"GtkButton","label","Save and close",1,None,None,None,None,None,None,None,None),
+	(5,2,"(submenu)","label","_File",1,None,None,None,None,None,None,None,None),
+	(5,4,"(item)","action","app.preferences",None,None,None,None,None,None,None,None,None),
+	(5,4,"(item)","icon","preferences-other-symbolic",None,None,None,None,None,None,None,None,None),
+	(5,4,"(item)","label","_Preferences",1,None,None,None,None,None,None,None,None),
+	(5,5,"(item)","action","app.quit",None,None,None,None,None,None,None,None,None),
+	(5,5,"(item)","icon","application-exit-symbolic",None,None,None,None,None,None,None,None,None),
+	(5,5,"(item)","label","_Quit",1,None,None,None,None,None,None,None,None),
+	(5,6,"(submenu)","label","_Help",1,None,None,None,None,None,None,None,None),
+	(5,8,"(item)","action","app.help",None,None,None,None,None,None,None,None,None),
+	(5,8,"(item)","icon","help-browser-symbolic",None,None,None,None,None,None,None,None,None),
+	(5,8,"(item)","label","_Help",1,None,None,None,None,None,None,None,None),
+	(5,9,"(item)","action","app.about",None,None,None,None,None,None,None,None,None),
+	(5,9,"(item)","icon","help-about-symbolic",None,None,None,None,None,None,None,None,None),
+	(5,9,"(item)","label","_About",1,None,None,None,None,None,None,None,None),
+	(6,1,"GtkApplicationWindow","show-menubar","True",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWidget","height-request","0",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWidget","width-request","0",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWindow","child","2",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWindow","default-height","800",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWindow","default-width","800",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWindow","icon-name","youtube",None,None,None,None,None,None,None,None,None),
+	(6,1,"GtkWindow","title","YTMPV",1,None,None,None,None,None,None,None,None),
+	(6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+	(6,3,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+	(6,3,"GtkWidget","margin-bottom","8",None,None,None,None,None,None,None,None,None),
+	(6,3,"GtkWidget","margin-end","8",None,None,None,None,None,None,None,None,None),
+	(6,3,"GtkWidget","margin-start","8",None,None,None,None,None,None,None,None,None),
+	(6,3,"GtkWidget","margin-top","8",None,None,None,None,None,None,None,None,None),
+	(6,4,"GtkStackSwitcher","stack","5",None,None,None,None,None,None,None,None,None),
+	(6,6,"GtkStackPage","child",None,None,None,None,None,7,None,None,None,None),
+	(6,6,"GtkStackPage","name","start",None,None,None,None,None,None,None,None,None),
+	(6,6,"GtkStackPage","title","Start page",None,None,None,None,None,None,None,None,None),
+	(6,7,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+	(6,8,"GtkStackPage","child",None,None,None,None,None,9,None,None,None,None),
+	(6,8,"GtkStackPage","name","videos",None,None,None,None,None,None,None,None,None),
+	(6,8,"GtkStackPage","title","Search videos",None,None,None,None,None,None,None,None,None),
+	(6,9,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+	(6,10,"GtkBox","spacing","8",None,None,None,None,None,None,None,None,None),
+	(6,10,"GtkWidget","margin-bottom","8",None,None,None,None,None,None,None,None,None),
+	(6,10,"GtkWidget","margin-end","8",None,None,None,None,None,None,None,None,None),
+	(6,10,"GtkWidget","margin-start","8",None,None,None,None,None,None,None,None,None),
+	(6,10,"GtkWidget","margin-top","8",None,None,None,None,None,None,None,None,None),
+	(6,11,"GtkEntry","primary-icon-name","system-search-symbolic",None,None,None,None,None,None,None,None,None),
+	(6,11,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+	(6,12,"GtkButton","label","Search",1,None,None,None,None,None,None,None,None),
+	(6,13,"GtkPaned","end-child",None,None,None,None,None,14,None,None,None,None),
+	(6,13,"GtkPaned","start-child",None,None,None,None,None,16,None,None,None,None),
+	(6,13,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+	(6,14,"GtkScrolledWindow","child",None,None,None,None,None,15,None,None,None,None),
+	(6,16,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+	(6,17,"GtkLabel","justify","center",None,None,None,None,None,None,None,None,None),
+	(6,17,"GtkLabel","label","&lt;b&gt;Welcome to YTMPV!&lt;/b&gt;\nGet comfortable",None,None,None,None,None,None,None,None,None),
+	(6,17,"GtkLabel","use-markup","True",None,None,None,None,None,None,None,None,None),
+	(6,20,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+	(6,21,"GtkButton","label","Search",1,None,None,None,None,None,None,None,None)
+  </object_property>
+  <object_layout_property>
+	(4,2,3,"GtkGridLayoutChild","column","0",None,None,None,None),
+	(4,2,3,"GtkGridLayoutChild","row","0",None,None,None,None),
+	(4,2,4,"GtkGridLayoutChild","column","1",None,None,None,None),
+	(4,2,4,"GtkGridLayoutChild","row","0",None,None,None,None),
+	(4,2,5,"GtkGridLayoutChild","column","0",None,None,None,None),
+	(4,2,5,"GtkGridLayoutChild","row","1",None,None,None,None),
+	(4,2,6,"GtkGridLayoutChild","column","1",None,None,None,None),
+	(4,2,6,"GtkGridLayoutChild","row","1",None,None,None,None),
+	(4,2,7,"GtkGridLayoutChild","column","0",None,None,None,None),
+	(4,2,7,"GtkGridLayoutChild","row","2",None,None,None,None),
+	(4,2,8,"GtkGridLayoutChild","column","1",None,None,None,None),
+	(4,2,8,"GtkGridLayoutChild","row","2",None,None,None,None)
+  </object_layout_property>
+</cambalache-project>

+ 1 - 0
src/YTMPV/resources.gresource.xml

@@ -2,6 +2,7 @@
 <gresources>
     <gresource prefix="/src/YTMPV/res">
         <file preprocess="xml-stripblanks">MainMenu.ui</file>
+        <file preprocess="xml-stripblanks">MainWindow.ui</file>
         <file preprocess="xml-stripblanks">PrefsWindow.ui</file>
     </gresource>
 </gresources>