Browse Source

Добавлено окно настроек

Вадим Королёв 1 year ago
parent
commit
39166b33cf

+ 48 - 17
src/YTMPV/MainWindow.cpp

@@ -14,12 +14,19 @@
 #include <glibmm/fileutils.h>
 
 namespace components {
+
+    MainWindow* MainWindow::create()
+    {
+        return Gtk::make_managed<MainWindow>();
+    }
     
     MainWindow::MainWindow()
         : Gtk::ApplicationWindow()
         , m_layout(Gtk::Orientation::VERTICAL)
-        , m_stack()
+        , m_head(Gtk::Orientation::VERTICAL)
+        , m_body()
         , m_stackswitcher()
+        //~ , m_gears()
 
         , m_search_layout(Gtk::Orientation::VERTICAL)
         , m_search_head(Gtk::Orientation::HORIZONTAL)
@@ -30,10 +37,15 @@ namespace components {
         , m_search_video_list()
     {
         set_title("YTMPV");
-        set_default_size(800, 600);
+        set_default_size(1024, 640);
         set_icon_name("youtube");
+        set_show_menubar();
 
-        // Создание модели выбора видео в поиске
+        // -- Переключатель вкладок --
+        m_stackswitcher.set_stack(m_body);
+        m_head.append(m_stackswitcher);
+
+        // -- Создание модели выбора видео в поиске --
         m_video_storage = Gio::ListStore<components::VideoModel>::create();
         selection_model = Gtk::SingleSelection::create(m_video_storage);
         selection_model->set_autoselect(false);
@@ -51,11 +63,11 @@ namespace components {
         m_search_video_list.set_factory(factory);
 
         // SearchView head
-        m_search_button.set_child(*(core::getButtonContents("Поиск", "find")));
+        m_search_button.set_child(*(core::getButtonContents("Find", "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_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);
@@ -73,11 +85,10 @@ namespace components {
         // WelcomeView
         // TODO
 
-        m_stack.add(m_search_layout, "search", "Поиск видео");
-        m_stackswitcher.set_stack(m_stack);
+        m_body.add(m_search_layout, "search", "Searching video");
 
-        m_layout.append(m_stackswitcher);
-        m_layout.append(m_stack);
+        m_layout.append(m_head);
+        m_layout.append(m_body);
         set_child(m_layout);
     }
 
@@ -193,7 +204,7 @@ namespace components {
 
         auto icon = Gio::Icon::create("find");
         auto img_icon = Gtk::make_managed<Gtk::Image>(icon);
-        auto label = Gtk::make_managed<Gtk::Label>("Поиск видео");
+        auto label = Gtk::make_managed<Gtk::Label>("Search results");
 
         box->append(*img_icon);
         box->append(*label);
@@ -202,11 +213,12 @@ namespace components {
     }
 
     void MainWindow::searchSidebarVideoDetailsMode(
-            Glib::ustring title,
-            Glib::ustring channel_title,
-            Glib::ustring description,
-            std::string published_at,
-            std::string yt_id)
+        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);
@@ -217,7 +229,7 @@ namespace components {
         // Область деталей (текст)
         auto box_textdetails = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
         // Область кнопок
-        auto box_buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
+        auto box_buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
         
         auto thumbnail = Gtk::make_managed<Gtk::Picture>();
         auto lbl_title = Gtk::make_managed<Gtk::Label>();
@@ -230,7 +242,7 @@ namespace components {
 
         // Добавление кнопки "Воспроизвести"
         auto btn_play = Gtk::make_managed<Gtk::Button>();
-        btn_play->set_child(*(core::getButtonContents("Воспроизвести", "player_play")));
+        btn_play->set_child(*(core::getButtonContents("Play video", "player_play")));
         btn_play->set_hexpand();
         btn_play->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::playSingleVideo),
@@ -238,6 +250,15 @@ namespace components {
         ));
         box_buttons->append(*btn_play);
 
+        // Добавление кнопки "Открыть в веб-браузере"
+        auto btn_webbrowser = Gtk::make_managed<Gtk::Button>();
+        btn_webbrowser->set_child(*(core::getButtonContents("Open in web browser", "web-browser")));
+        btn_webbrowser->set_hexpand();
+        btn_webbrowser->signal_clicked().connect(sigc::bind(
+            sigc::mem_fun(*this, &MainWindow::openVideoInWebBrowser),
+            yt_id
+        ));
+
         // Скачивание большого превью
         std::string thumbnail_path = "thumbnails/large/" + yt_id + ".jpg";
         std::cout << thumbnail_path << std::endl;
@@ -272,6 +293,7 @@ namespace components {
         box_textdetails->append(*lbl_channel_title);
         box_textdetails->append(*lbl_description);
         box_textdetails->append(*lbl_published_at);
+        box_textdetails->append(*btn_webbrowser);
         box_textdetails->set_margin(8);
 
         box_details->append(*thumbnail);
@@ -284,4 +306,13 @@ namespace components {
 
         m_search_body.set_start_child(*layout);
     }
+
+    void MainWindow::openVideoInWebBrowser(std::string yt_id)
+    {
+        // TODO:
+        // - Windows
+        // - MacOs
+        std::string command = "xdg-open https://youtu.be/"+yt_id;
+        system(command.c_str());
+    }
 }

+ 6 - 1
src/YTMPV/MainWindow.hpp

@@ -13,6 +13,7 @@
 #include <giomm/liststore.h>
 #include <gtkmm/listitem.h>
 #include <gtkmm/paned.h>
+#include <gtkmm/menubutton.h>
 
 namespace components {
 
@@ -21,10 +22,12 @@ namespace components {
     {
     public:
         MainWindow();
+        static MainWindow* create();
 
     protected:
         Gtk::Box m_layout;
-        Gtk::Stack m_stack;
+        Gtk::Box m_head;
+        Gtk::Stack m_body;
         Gtk::StackSwitcher m_stackswitcher;
 
         /// ПОИСК ///
@@ -70,5 +73,7 @@ namespace components {
             std::string yt_id);
         // Проигрывает конкретное видео
         void playSingleVideo(std::string yt_id);
+        // Открывает страницу видео в YouTube
+        void openVideoInWebBrowser(std::string yt_id);
     };
 }

+ 116 - 0
src/YTMPV/PrefWindow.cpp

@@ -0,0 +1,116 @@
+#include "PrefWindow.hpp"
+#include <array>
+#include <fstream>
+#include <iostream>
+#include <gtkmm/stringlist.h>
+#include <assert.h>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
+
+namespace
+{
+    struct QualityTypeStruct
+    {
+        // Значение ключа в Gio::Settings (240,360,480...)
+        int value;
+        // Текст для надписи
+        Glib::ustring text;
+    };
+
+    const std::array<QualityTypeStruct, 4> quality_types =
+    {
+        QualityTypeStruct{240, "240p"},
+        QualityTypeStruct{360, "360p"},
+        QualityTypeStruct{480, "480p"},
+        QualityTypeStruct{720, "720p"}
+    };
+} // anonymous namespace
+
+namespace components {
+    PrefWindow::PrefWindow(
+        BaseObjectType* cobject,
+        const Glib::RefPtr<Gtk::Builder>& refBuilder
+    )
+        : Gtk::Window(cobject)
+        , m_builder(refBuilder)
+    {
+        // Заполнение выпадающего списка качества видео
+        std::vector<Glib::ustring> quality_strings =
+        {
+            "240p",
+            "360p",
+            "480p",
+            "720p"
+        };
+        
+        m_quality_dropdown = m_builder->get_widget<Gtk::DropDown>("quality_entry");
+        m_cancel_btn = m_builder->get_widget<Gtk::Button>("cancel_button");
+        m_save_btn = m_builder->get_widget<Gtk::Button>("saveclose_button");
+        m_apikey_entry = m_builder->get_widget<Gtk::Entry>("apikey_entry");
+
+        assert(m_quality_dropdown != nullptr);
+        assert(m_cancel_btn != nullptr);
+        assert(m_save_btn != nullptr);
+        assert(m_apikey_entry != nullptr);
+
+        auto string_list = Gtk::StringList::create(quality_strings);
+        m_quality_dropdown->set_model(string_list);
+
+        m_save_btn->signal_clicked().connect(sigc::mem_fun(
+            *this, &PrefWindow::on_save_clicked));
+        m_cancel_btn->signal_clicked().connect(sigc::mem_fun(
+            *this, &PrefWindow::on_cancel_clicked));
+    }
+
+    void PrefWindow::init(
+        std::string config_path,
+        Glib::ustring api_key,
+        int quality
+    )
+    {
+        m_config_path = config_path;
+        m_apikey_entry->set_text(api_key);
+    }
+
+    PrefWindow* PrefWindow::create(
+        Gtk::Window& parent,
+        std::string config_path,
+        std::string api_key
+    )
+    {
+        auto builder = Gtk::Builder::create_from_resource("/src/YTMPV/res/PrefsWindow.ui");
+        auto win = Gtk::Builder::get_widget_derived<PrefWindow>(builder, "prefs_dialog");
+        assert(win != nullptr);
+
+        win->set_transient_for(parent);
+        win->init(config_path, api_key, 720);
+        return win;
+    }
+
+    void PrefWindow::on_cancel_clicked()
+    {
+        this->close();
+    }
+
+    void PrefWindow::on_save_clicked()
+    {
+        this->save_config();
+        this->close();
+    }
+
+    void PrefWindow::save_config()
+    {
+        // Получение данных
+        Glib::ustring api_key = m_apikey_entry->get_text();
+        
+        // Сериализация
+        json j_object = { {"apikey", api_key}, {"quality", 480} };
+        std::string dump = j_object.dump(1, '\t');
+
+        // Запись в файл
+        std::ofstream config(m_config_path);
+        config << dump;
+        config.close();
+    }
+}

+ 43 - 0
src/YTMPV/PrefWindow.hpp

@@ -0,0 +1,43 @@
+#pragma once
+#include <gtkmm/window.h>
+#include <glibmm.h>
+#include <gtkmm/builder.h>
+#include <gtkmm/dropdown.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/button.h>
+
+namespace components {
+    class PrefWindow : public Gtk::Window
+    {
+    public:
+        PrefWindow(
+            BaseObjectType* cobject,
+            const Glib::RefPtr<Gtk::Builder>& refBuilder);
+        static PrefWindow* create(
+            Gtk::Window& parent,
+            std::string config_path,
+            std::string api_key);
+
+        // Инициализирует окно
+        void init(
+            std::string config_path,
+            Glib::ustring api_key,
+            int quality);
+
+    protected:
+        Glib::RefPtr<Gtk::Builder> m_builder;
+        Gtk::DropDown* m_quality_dropdown;
+        Gtk::Entry* m_apikey_entry;
+        Gtk::Button* m_cancel_btn;
+        Gtk::Button* m_save_btn;
+
+        std::string m_config_path;
+
+        // -- Обработчики событий --
+        void on_cancel_clicked();
+        void on_save_clicked();
+
+        // Сохраняет настройки
+        void save_config();
+    };
+}

+ 60 - 5
src/YTMPV/app.cpp

@@ -1,14 +1,39 @@
 #include "app.hpp"
+#include <iostream>
+#include <giomm/menu.h>
+#include <gtkmm/builder.h>
 
 namespace components {
-    App::App()
+    App::App(std::string config_path, std::string api_key)
         : Gtk::Application("com.github.aquadim.YTMPV")
+        , m_config_path(config_path)
+        , m_api_key(api_key)
     {
     }
 
-    Glib::RefPtr<App> App::create()
+    Glib::RefPtr<App> App::create(std::string config_path, std::string api_key)
     {
-        return Glib::make_refptr_for_instance<App>(new App());
+        return Glib::make_refptr_for_instance<App>(new App(config_path, api_key));
+    }
+
+    void App::on_startup()
+    {
+        Gtk::Application::on_startup();
+
+        add_action("preferences", sigc::mem_fun(*this, &App::on_action_prefs));
+        set_accel_for_action("app.preferences", "<Ctrl>P");
+
+        add_action("quit", sigc::mem_fun(*this, &App::on_action_quit));
+        set_accel_for_action("app.quit", "<Ctrl>Q");
+
+        // Установка менюбара
+        auto menu_builder = Gtk::Builder::create_from_resource("/src/YTMPV/res/MainMenu.ui");
+        auto menu = menu_builder->get_object<Gio::Menu>("menu");
+        if (!menu)
+        {
+            std::cout << "menu not found :(" << std::endl;
+        }
+        set_menubar(menu);
     }
 
     void App::on_activate()
@@ -20,9 +45,39 @@ namespace components {
     MainWindow* App::create_appwindow()
     {
         // Создание окна и привязка обработчика к его сигналу скрытия
-        auto appwindow = new MainWindow();
+        auto appwindow = MainWindow::create();
         add_window(*appwindow);
-        appwindow->signal_hide().connect([appwindow](){ delete appwindow; });
+        appwindow->signal_hide().connect(
+            [appwindow](){ delete appwindow; });
         return appwindow;
     }
+
+    PrefWindow* App::create_prefwindow()
+    {
+        auto prefwindow = PrefWindow::create(
+            *get_active_window(),
+            m_config_path,
+            m_api_key
+        );
+        add_window(*prefwindow);
+        prefwindow->signal_hide().connect(
+            [prefwindow](){ delete prefwindow; });
+        return prefwindow;
+    }
+
+    void App::on_action_quit()
+    {
+        auto windows = get_windows();
+        for (auto window : windows)
+        {
+            window->set_visible(false);
+        }
+        quit();
+    }
+    
+    void App::on_action_prefs()
+    {
+        PrefWindow* win = create_prefwindow();
+        win->present();
+    }
 }

+ 14 - 2
src/YTMPV/app.hpp

@@ -2,6 +2,7 @@
 #include <gtkmm/application.h>
 #include <glibmm/ustring.h>
 #include "MainWindow.hpp"
+#include "PrefWindow.hpp"
 
 namespace components {
 
@@ -9,13 +10,24 @@ namespace components {
     class App: public Gtk::Application
     {
     public:
-        static Glib::RefPtr<App> create();
+        static Glib::RefPtr<App> create(
+            std::string config_path,
+            std::string api_key);
 
     protected:
-        App();
+        App(
+            std::string config_path,
+            std::string api_key);
+        void on_startup() override;
         void on_activate() override;
+        void on_action_quit();
+        void on_action_prefs();
+
+        std::string m_config_path;
+        std::string m_api_key;
 
     private:
         MainWindow* create_appwindow();
+        PrefWindow* create_prefwindow();
     };
 }

+ 11 - 1
src/YTMPV/meson.build

@@ -1,8 +1,18 @@
+gnome = import('gnome')
+
+# Компилирование ресурсов
+resources = gnome.compile_resources(
+    'resources',
+    'resources.gresource.xml',
+    source_dir: './res'
+)
+
+# Сборка
 ytmpv = executable(
   'ytmpv',
   [
     'ytmpv.cpp', 'core.cpp', 'MainWindow.cpp', 'VideoModel.cpp',
-    'app.cpp'
+    'app.cpp', 'PrefWindow.cpp', resources
   ],
   link_with: [ytapi_lib, database_lib],
   include_directories: [dir_includes],

+ 36 - 0
src/YTMPV/res/MainMenu.ui

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--Menubar for MainWindow-->
+<interface>
+    <menu id='menu'>
+
+    <!--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>
+    </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>
+    </submenu>
+  </menu>
+</interface>

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

@@ -0,0 +1,77 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<interface>
+  <object class="GtkWindow" id="prefs_dialog">
+    <property name="hide-on-close">True</property>
+    <property name="modal">True</property>
+    <property name="resizable">False</property>
+    <property name="title" translatable="yes">Preferences</property>
+    <child>
+      <object class="GtkGrid" id="grid">
+        <property name="column-spacing">16</property>
+        <property name="margin-bottom">16</property>
+        <property name="margin-end">16</property>
+        <property name="margin-start">16</property>
+        <property name="margin-top">16</property>
+        <property name="row-spacing">16</property>
+        <child>
+          <object class="GtkLabel" id="apikey_label">
+            <property name="label" translatable="yes">_API key:</property>
+            <property name="mnemonic-widget">apikey_entry</property>
+            <property name="use-underline">True</property>
+            <property name="xalign">1</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkEntry" id="apikey_entry">
+            <layout>
+              <property name="column">1</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="quality_label">
+            <property name="label" translatable="yes">_Preferred quality:</property>
+            <property name="mnemonic-widget">quality_entry</property>
+            <property name="use-underline">True</property>
+            <property name="xalign">1</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">1</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkDropDown" id="quality_entry">
+            <layout>
+              <property name="column">1</property>
+              <property name="row">1</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel_button">
+            <property name="label" translatable="yes">Cancel</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">2</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="saveclose_button">
+            <property name="label" translatable="yes">Save and close</property>
+            <layout>
+              <property name="column">1</property>
+              <property name="row">2</property>
+            </layout>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

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

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+    <gresource prefix="/src/YTMPV/res">
+        <file preprocess="xml-stripblanks">MainMenu.ui</file>
+        <file preprocess="xml-stripblanks">PrefsWindow.ui</file>
+    </gresource>
+</gresources>

+ 20 - 54
src/YTMPV/ytmpv.cpp

@@ -1,71 +1,37 @@
 #include "app.hpp"
-#include <glibmm/optioncontext.h>
-#include <glibmm/optiongroup.h>
-#include <glibmm/optionentry.h>
+#include <glibmm.h>
 #include <curl/curl.h>
 #include <Database/database.hpp>
 #include <YoutubeApi/youtubeapi.hpp>
+#include <iostream>
+#include <fstream>
+#include <nlohmann/json.hpp>
+
+using json = nlohmann::json;
 
 int main(int argc, char** argv) {
-    // ==Инициализация==
-    // --Язык--
+    // -- Локаль --
     std::locale::global(std::locale(""));
 
-    // --Аргументы приложения--
-    Glib::ustring config_path;
-    Glib::OptionEntry opt_config_path = Glib::OptionEntry();
-    opt_config_path.set_long_name("config");
-    opt_config_path.set_short_name('c');
-    opt_config_path.set_flags(Glib::OptionEntry::Flags::NONE);
-    opt_config_path.set_description("Path to JSON configuration file");
-    opt_config_path.set_arg_description("<path to config>");
-
-    Glib::OptionContext option_context = Glib::OptionContext(
-        "- launcher for mpv to watch youtube videos"
-    );
-    Glib::OptionGroup main_group = Glib::OptionGroup(
-        "Main option group",
-        "main option description",
-        "no help."
-    );
-    main_group.add_entry(opt_config_path, config_path);
-    option_context.set_main_group(main_group);
-
-    try {
-        option_context.parse(argc, argv);
-    } catch (Glib::OptionError* e) {
-        std::cout << e->what();
-        return 1;
-    }
-
-    // --Получение данных из файла настроек--
+    // -- Получение данных из файла настроек --
+    std::string config_path = Glib::build_filename(
+        Glib::get_user_config_dir(),
+        "ytmpv",
+        "config.json");
+    std::ifstream config_file(config_path);
+    json j_config = json::parse(config_file);
+    config_file.close();
+    
     // TODO
     std::string db_path = "/home/vad/Code/YTMPV/db.sqlite3";
-    std::string api_key = "AIzaSyAymnCxcWln0h379LA_q5TT_tJuTANhTyg";
     
-    // --Общая инициализация--
+    // -- Общая инициализация --
     curl_global_init(CURL_GLOBAL_ALL);
     db::databaseInit(db_path);
     db::createTables();
-    ytapi::youtubeInit(api_key);
+    ytapi::youtubeInit(j_config["apikey"]);
 
-    // ==Запуск приложения==
-    auto app = components::App::create();
+    // -- Запуск приложения --
+    auto app = components::App::create(config_path, j_config["apikey"]);
     return app->run(argc, argv);
-
-    //~ db::video found;
-    //~ std::vector<std::string> video_ids = ytapi::getVideoIDsByQuery(argv[3]);
-    //~ for (auto id : video_ids) {
-        //~ found = getVideoByYTID(id);
-
-        //~ std::cout << found.title << ":" << found.author_obj.name << std::endl;
-    //~ }
-
-    //~ std::ifstream ifs("example.json");
-    //~ std::string content(
-        //~ (std::istreambuf_iterator<char>(ifs)),
-        //~ (std::istreambuf_iterator<char>())
-    //~ );
-
-    //~ ytapi::getVideosFromJSON(content);
 }