Use `Gio::List{Model,Store}` for torrents (#4430)
* Use `Gio::List{Model,Store}` for torrents Switch from `Gtk::{TreeModel,ListStore}` in preparation for cell renderers deprecation in GTK 4.10. That will require switching to the new view classes (`Gtk::{Column,List}View`) which only work with `Gio` models. Implement an adapter to support GTK+ 3 where the old view class (`Gtk::TreeView`) only works with `Gtk` models; it is effective enough but requires a signal connection per item to notify on row changes. Refactor filtering and sorting (which now happen over the new model) to use compatible `Gtk::Filter` and `Gtk::Sorter` classes. Although these classes are only present in GTK 4, the abstraction is suitable for GTK+ 3 as well so make our subclasses work for both versions. Since items (of `Torrent` class) of the new model provide only a very limited (by design) layer of compatibility with GTK+ 3 way of doing things, refactor selection handling to do it the new way. Move selection helpers into `MainWindow` to abstract them away since new view classes handle it differently. * Improve session load performance based on profiling results
This commit is contained in:
parent
db802afc4f
commit
32531fe5ef
|
@ -49,6 +49,7 @@
|
|||
#include "Session.h"
|
||||
#include "StatsDialog.h"
|
||||
#include "SystemTrayIcon.h"
|
||||
#include "Torrent.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
@ -136,7 +137,7 @@ private:
|
|||
void toggleMainWindow();
|
||||
|
||||
bool winclose();
|
||||
void rowChangedCB(Gtk::TreePath const& path, Gtk::TreeModel::iterator const& iter);
|
||||
void rowChangedCB(std::unordered_set<tr_torrent_id_t> const& torrent_ids, Torrent::ChangeFlags changes);
|
||||
|
||||
void app_setup();
|
||||
void main_window_setup();
|
||||
|
@ -157,12 +158,11 @@ private:
|
|||
void on_prefs_changed(tr_quark key);
|
||||
|
||||
[[nodiscard]] std::vector<tr_torrent_id_t> get_selected_torrent_ids() const;
|
||||
[[nodiscard]] tr_torrent* get_first_selected_torrent() const;
|
||||
[[nodiscard]] counts_data get_selected_torrent_counts() const;
|
||||
|
||||
void start_all_torrents();
|
||||
void pause_all_torrents();
|
||||
void copy_magnet_link_to_clipboard(tr_torrent* tor) const;
|
||||
void copy_magnet_link_to_clipboard(Glib::RefPtr<Torrent> const& torrent) const;
|
||||
bool call_rpc_for_selected_torrents(std::string const& method);
|
||||
void remove_selected(bool delete_files);
|
||||
|
||||
|
@ -194,7 +194,6 @@ private:
|
|||
std::vector<std::string> error_list_;
|
||||
std::vector<std::string> duplicates_list_;
|
||||
std::map<std::string, std::unique_ptr<DetailsDialog>> details_;
|
||||
Glib::RefPtr<Gtk::TreeSelection> sel_;
|
||||
};
|
||||
|
||||
namespace
|
||||
|
@ -232,8 +231,7 @@ std::string get_details_dialog_key(std::vector<tr_torrent_id_t> const& id_list)
|
|||
std::vector<tr_torrent_id_t> Application::Impl::get_selected_torrent_ids() const
|
||||
{
|
||||
std::vector<tr_torrent_id_t> ids;
|
||||
sel_->selected_foreach([&ids](auto const& /*path*/, auto const& iter)
|
||||
{ ids.push_back(iter->get_value(torrent_cols.torrent_id)); });
|
||||
wind_->for_each_selected_torrent([&ids](auto const& torrent) { ids.push_back(torrent->get_id()); });
|
||||
return ids;
|
||||
}
|
||||
|
||||
|
@ -266,12 +264,12 @@ Application::Impl::counts_data Application::Impl::get_selected_torrent_counts()
|
|||
{
|
||||
counts_data counts;
|
||||
|
||||
sel_->selected_foreach(
|
||||
[&counts](auto const& /*path*/, auto const& iter)
|
||||
wind_->for_each_selected_torrent(
|
||||
[&counts](auto const& torrent)
|
||||
{
|
||||
++counts.total_count;
|
||||
|
||||
auto const activity = iter->get_value(torrent_cols.activity);
|
||||
auto const activity = torrent->get_activity();
|
||||
|
||||
if (activity == TR_STATUS_DOWNLOAD_WAIT || activity == TR_STATUS_SEED_WAIT)
|
||||
{
|
||||
|
@ -293,7 +291,7 @@ bool Application::Impl::refresh_actions()
|
|||
{
|
||||
size_t const total = core_->get_torrent_count();
|
||||
size_t const active = core_->get_active_torrent_count();
|
||||
auto const torrent_count = core_->get_model()->children().size();
|
||||
auto const torrent_count = core_->get_model()->get_n_items();
|
||||
|
||||
auto const sel_counts = get_selected_torrent_counts();
|
||||
bool const has_selection = sel_counts.total_count > 0;
|
||||
|
@ -318,14 +316,9 @@ bool Application::Impl::refresh_actions()
|
|||
gtr_action_set_sensitive("open-torrent-folder", sel_counts.total_count == 1);
|
||||
gtr_action_set_sensitive("copy-magnet-link-to-clipboard", sel_counts.total_count == 1);
|
||||
|
||||
bool canUpdate = false;
|
||||
sel_->selected_foreach(
|
||||
[&canUpdate](auto const& /*path*/, auto const& iter)
|
||||
{
|
||||
auto const* tor = static_cast<tr_torrent const*>(iter->get_value(torrent_cols.torrent));
|
||||
canUpdate = canUpdate || tr_torrentCanManualUpdate(tor);
|
||||
});
|
||||
gtr_action_set_sensitive("torrent-reannounce", canUpdate);
|
||||
bool const can_update = wind_->for_each_selected_torrent_until(
|
||||
[](auto const& torrent) { return tr_torrentCanManualUpdate(&torrent->get_underlying()); });
|
||||
gtr_action_set_sensitive("torrent-reannounce", can_update);
|
||||
}
|
||||
|
||||
refresh_actions_tag_.disconnect();
|
||||
|
@ -432,7 +425,7 @@ bool Application::Impl::on_rpc_changed_idle(tr_rpc_callback_type type, tr_torren
|
|||
case TR_RPC_TORRENT_ADDED:
|
||||
if (auto* tor = core_->find_torrent(torrent_id); tor != nullptr)
|
||||
{
|
||||
core_->add_torrent(tor, true);
|
||||
core_->add_torrent(Torrent::create(tor), true);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -844,9 +837,11 @@ bool Application::Impl::winclose()
|
|||
return true; /* don't propagate event further */
|
||||
}
|
||||
|
||||
void Application::Impl::rowChangedCB(Gtk::TreePath const& path, Gtk::TreeModel::iterator const& /*iter*/)
|
||||
void Application::Impl::rowChangedCB(std::unordered_set<tr_torrent_id_t> const& torrent_ids, Torrent::ChangeFlags changes)
|
||||
{
|
||||
if (sel_->is_selected(path))
|
||||
if (changes.test(Torrent::ChangeFlag::ACTIVITY) &&
|
||||
wind_->for_each_selected_torrent_until([&torrent_ids](auto const& torrent)
|
||||
{ return torrent_ids.find(torrent->get_id()) != torrent_ids.end(); }))
|
||||
{
|
||||
refresh_actions_soon();
|
||||
}
|
||||
|
@ -912,14 +907,9 @@ void Application::Impl::on_drag_data_received(
|
|||
|
||||
void Application::Impl::main_window_setup()
|
||||
{
|
||||
// g_assert(nullptr == cbdata->wind);
|
||||
// cbdata->wind = wind;
|
||||
sel_ = wind_->get_selection();
|
||||
|
||||
sel_->signal_changed().connect(sigc::mem_fun(*this, &Impl::refresh_actions_soon));
|
||||
wind_->signal_selection_changed().connect(sigc::mem_fun(*this, &Impl::refresh_actions_soon));
|
||||
refresh_actions_soon();
|
||||
auto const model = core_->get_model();
|
||||
model->signal_row_changed().connect(sigc::mem_fun(*this, &Impl::rowChangedCB));
|
||||
core_->signal_torrents_changed().connect(sigc::mem_fun(*this, &Impl::rowChangedCB));
|
||||
gtr_window_on_close(*wind_, sigc::mem_fun(*this, &Impl::winclose));
|
||||
refresh_actions();
|
||||
|
||||
|
@ -1426,12 +1416,7 @@ bool Application::Impl::call_rpc_for_selected_torrents(std::string const& method
|
|||
tr_variantDictAddStrView(&top, TR_KEY_method, method);
|
||||
auto* const args = tr_variantDictAddDict(&top, TR_KEY_arguments, 1);
|
||||
auto* const ids = tr_variantDictAddList(args, TR_KEY_ids, 0);
|
||||
sel_->selected_foreach(
|
||||
[ids](auto const& /*path*/, auto const& iter)
|
||||
{
|
||||
auto const* const tor = static_cast<tr_torrent*>(iter->get_value(torrent_cols.torrent));
|
||||
tr_variantListAddInt(ids, tr_torrentId(tor));
|
||||
});
|
||||
wind_->for_each_selected_torrent([ids](auto const& torrent) { tr_variantListAddInt(ids, torrent->get_id()); });
|
||||
|
||||
if (tr_variantListSize(ids) != 0)
|
||||
{
|
||||
|
@ -1445,12 +1430,7 @@ bool Application::Impl::call_rpc_for_selected_torrents(std::string const& method
|
|||
|
||||
void Application::Impl::remove_selected(bool delete_files)
|
||||
{
|
||||
auto l = std::vector<tr_torrent_id_t>{};
|
||||
|
||||
sel_->selected_foreach([&l](auto const& /*path*/, auto const& iter)
|
||||
{ l.push_back(iter->get_value(torrent_cols.torrent_id)); });
|
||||
|
||||
if (!l.empty())
|
||||
if (auto const l = get_selected_torrent_ids(); !l.empty())
|
||||
{
|
||||
gtr_confirm_remove(*wind_, core_, l, delete_files);
|
||||
}
|
||||
|
@ -1478,25 +1458,9 @@ void Application::Impl::pause_all_torrents()
|
|||
tr_variantClear(&request);
|
||||
}
|
||||
|
||||
tr_torrent* Application::Impl::get_first_selected_torrent() const
|
||||
void Application::Impl::copy_magnet_link_to_clipboard(Glib::RefPtr<Torrent> const& torrent) const
|
||||
{
|
||||
tr_torrent* tor = nullptr;
|
||||
Glib::RefPtr<Gtk::TreeModel> m;
|
||||
|
||||
if (auto const l = sel_->get_selected_rows(m); !l.empty())
|
||||
{
|
||||
if (auto iter = m->get_iter(l.front()); iter)
|
||||
{
|
||||
tor = static_cast<tr_torrent*>(iter->get_value(torrent_cols.torrent));
|
||||
}
|
||||
}
|
||||
|
||||
return tor;
|
||||
}
|
||||
|
||||
void Application::Impl::copy_magnet_link_to_clipboard(tr_torrent* tor) const
|
||||
{
|
||||
auto const magnet = tr_torrentGetMagnetLink(tor);
|
||||
auto const magnet = tr_torrentGetMagnetLink(&torrent->get_underlying());
|
||||
auto const display = wind_->get_display();
|
||||
|
||||
/* this is The Right Thing for copy/paste... */
|
||||
|
@ -1548,12 +1512,8 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
|
|||
}
|
||||
else if (action_name == "copy-magnet-link-to-clipboard")
|
||||
{
|
||||
tr_torrent* tor = get_first_selected_torrent();
|
||||
|
||||
if (tor != nullptr)
|
||||
{
|
||||
copy_magnet_link_to_clipboard(tor);
|
||||
}
|
||||
wind_->for_each_selected_torrent_until(
|
||||
sigc::bind_return(sigc::mem_fun(*this, &Impl::copy_magnet_link_to_clipboard), true));
|
||||
}
|
||||
else if (action_name == "relocate-torrent")
|
||||
{
|
||||
|
@ -1575,8 +1535,7 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
|
|||
}
|
||||
else if (action_name == "open-torrent-folder")
|
||||
{
|
||||
sel_->selected_foreach([this](auto const& /*path*/, auto const& iter)
|
||||
{ core_->open_folder(iter->get_value(torrent_cols.torrent_id)); });
|
||||
wind_->for_each_selected_torrent([this](auto const& torrent) { core_->open_folder(torrent->get_id()); });
|
||||
}
|
||||
else if (action_name == "show-torrent-properties")
|
||||
{
|
||||
|
@ -1602,11 +1561,11 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
|
|||
}
|
||||
else if (action_name == "select-all")
|
||||
{
|
||||
sel_->select_all();
|
||||
wind_->select_all();
|
||||
}
|
||||
else if (action_name == "deselect-all")
|
||||
{
|
||||
sel_->unselect_all();
|
||||
wind_->unselect_all();
|
||||
}
|
||||
else if (action_name == "edit-preferences")
|
||||
{
|
||||
|
|
|
@ -110,6 +110,7 @@ set(${PROJECT_NAME}_SOURCES
|
|||
FilterBar.cc
|
||||
FreeSpaceLabel.cc
|
||||
IconCache.cc
|
||||
ListModelAdapter.cc
|
||||
main.cc
|
||||
MainWindow.cc
|
||||
MakeDialog.cc
|
||||
|
@ -123,7 +124,10 @@ set(${PROJECT_NAME}_SOURCES
|
|||
Session.cc
|
||||
StatsDialog.cc
|
||||
SystemTrayIcon.cc
|
||||
Torrent.cc
|
||||
TorrentCellRenderer.cc
|
||||
TorrentFilter.cc
|
||||
TorrentSorter.cc
|
||||
Utils.cc
|
||||
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/transmission-ui-resources.c
|
||||
|
@ -137,9 +141,11 @@ set(${PROJECT_NAME}_HEADERS
|
|||
FaviconCache.h
|
||||
FileList.h
|
||||
FilterBar.h
|
||||
Flags.h
|
||||
FreeSpaceLabel.h
|
||||
HigWorkarea.h
|
||||
IconCache.h
|
||||
ListModelAdapter.h
|
||||
MainWindow.h
|
||||
MakeDialog.h
|
||||
MessageLogWindow.h
|
||||
|
@ -152,7 +158,10 @@ set(${PROJECT_NAME}_HEADERS
|
|||
Session.h
|
||||
StatsDialog.h
|
||||
SystemTrayIcon.h
|
||||
Torrent.h
|
||||
TorrentCellRenderer.h
|
||||
TorrentFilter.h
|
||||
TorrentSorter.h
|
||||
Utils.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/transmission-ui-resources.h
|
||||
|
|
410
gtk/FilterBar.cc
410
gtk/FilterBar.cc
|
@ -18,18 +18,26 @@
|
|||
#include "FaviconCache.h" // gtr_get_favicon()
|
||||
#include "FilterBar.h"
|
||||
#include "HigWorkarea.h" // GUI_PAD
|
||||
#include "ListModelAdapter.h"
|
||||
#include "Session.h" // torrent_cols
|
||||
#include "Torrent.h"
|
||||
#include "TorrentFilter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
class FilterBar::Impl
|
||||
{
|
||||
using FilterModel = IF_GTKMM4(Gtk::FilterListModel, Gtk::TreeModelFilter);
|
||||
|
||||
using TrackerType = TorrentFilter::Tracker;
|
||||
using ActivityType = TorrentFilter::Activity;
|
||||
|
||||
public:
|
||||
Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::TreeModel> const& torrent_model);
|
||||
Impl(FilterBar& widget, Glib::RefPtr<Session> const& core);
|
||||
~Impl();
|
||||
|
||||
TR_DISABLE_COPY_MOVE(Impl)
|
||||
|
||||
[[nodiscard]] Glib::RefPtr<Gtk::TreeModel> get_filter_model() const;
|
||||
[[nodiscard]] Glib::RefPtr<FilterModel> get_filter_model() const;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
|
@ -42,39 +50,34 @@ private:
|
|||
static void render_pixbuf_func(Gtk::CellRendererPixbuf& cell_renderer, Gtk::TreeModel::const_iterator const& iter);
|
||||
static void render_number_func(Gtk::CellRendererText& cell_renderer, Gtk::TreeModel::const_iterator const& iter);
|
||||
|
||||
void selection_changed_cb();
|
||||
void filter_entry_changed();
|
||||
void update_filter_activity();
|
||||
void update_filter_tracker();
|
||||
void update_filter_text();
|
||||
|
||||
Glib::RefPtr<Gtk::ListStore> activity_filter_model_new();
|
||||
void activity_model_update_idle();
|
||||
bool activity_filter_model_update();
|
||||
void status_model_update_count(Gtk::TreeModel::iterator const& iter, int n);
|
||||
bool activity_is_it_a_separator(Gtk::TreeModel::const_iterator const& iter);
|
||||
|
||||
Glib::RefPtr<Gtk::TreeStore> tracker_filter_model_new();
|
||||
void tracker_model_update_idle();
|
||||
bool tracker_filter_model_update();
|
||||
void tracker_model_update_count(Gtk::TreeModel::iterator const& iter, int n);
|
||||
bool is_it_a_separator(Gtk::TreeModel::const_iterator const& iter);
|
||||
void favicon_ready_cb(Glib::RefPtr<Gdk::Pixbuf> const& pixbuf, Gtk::TreeRowReference& reference);
|
||||
|
||||
void update_filter_models(Torrent::ChangeFlags changes);
|
||||
void update_filter_models_idle(Torrent::ChangeFlags changes);
|
||||
|
||||
void update_count_label_idle();
|
||||
bool update_count_label();
|
||||
bool is_row_visible(Gtk::TreeModel::const_iterator const& iter);
|
||||
|
||||
bool test_tracker(tr_torrent const& tor, int active_tracker_type, Glib::ustring const& host);
|
||||
bool test_torrent_activity(tr_torrent& tor, int type);
|
||||
|
||||
static Glib::ustring get_name_from_host(std::string const& host);
|
||||
|
||||
static Gtk::CellRendererText* number_renderer_new();
|
||||
|
||||
static bool testText(tr_torrent const& tor, Glib::ustring const& key);
|
||||
|
||||
private:
|
||||
FilterBar& widget_;
|
||||
tr_session* const session_;
|
||||
Glib::RefPtr<Gtk::TreeModel> const torrent_model_;
|
||||
Glib::RefPtr<Session> const core_;
|
||||
|
||||
Glib::RefPtr<Gtk::ListStore> const activity_model_;
|
||||
Glib::RefPtr<Gtk::TreeStore> const tracker_model_;
|
||||
|
@ -83,27 +86,16 @@ private:
|
|||
Gtk::ComboBox* tracker_ = nullptr;
|
||||
Gtk::Entry* entry_ = nullptr;
|
||||
Gtk::Label* show_lb_ = nullptr;
|
||||
Glib::RefPtr<Gtk::TreeModelFilter> filter_model_;
|
||||
int active_activity_type_ = 0;
|
||||
int active_tracker_type_ = 0;
|
||||
Glib::ustring active_tracker_sitename_;
|
||||
|
||||
sigc::connection activity_model_row_changed_tag_;
|
||||
sigc::connection activity_model_row_inserted_tag_;
|
||||
sigc::connection activity_model_row_deleted_cb_tag_;
|
||||
sigc::connection activity_model_update_tag_;
|
||||
|
||||
sigc::connection tracker_model_row_changed_tag_;
|
||||
sigc::connection tracker_model_row_inserted_tag_;
|
||||
sigc::connection tracker_model_row_deleted_cb_tag_;
|
||||
sigc::connection tracker_model_update_tag_;
|
||||
|
||||
sigc::connection filter_model_row_deleted_tag_;
|
||||
sigc::connection filter_model_row_inserted_tag_;
|
||||
Glib::RefPtr<TorrentFilter> filter_ = TorrentFilter::create();
|
||||
Glib::RefPtr<FilterModel> filter_model_;
|
||||
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sigc::connection filter_model_items_changed_tag_;
|
||||
#endif
|
||||
sigc::connection update_count_label_tag_;
|
||||
|
||||
Glib::ustring filter_text_;
|
||||
sigc::connection update_filter_models_tag_;
|
||||
sigc::connection update_filter_models_on_add_remove_tag_;
|
||||
sigc::connection update_filter_models_on_change_tag_;
|
||||
};
|
||||
|
||||
/***
|
||||
|
@ -115,13 +107,6 @@ private:
|
|||
namespace
|
||||
{
|
||||
|
||||
enum
|
||||
{
|
||||
TRACKER_FILTER_TYPE_ALL,
|
||||
TRACKER_FILTER_TYPE_HOST,
|
||||
TRACKER_FILTER_TYPE_SEPARATOR,
|
||||
};
|
||||
|
||||
class TrackerFilterModelColumns : public Gtk::TreeModelColumnRecord
|
||||
{
|
||||
public:
|
||||
|
@ -194,19 +179,27 @@ bool FilterBar::Impl::tracker_filter_model_update()
|
|||
}
|
||||
};
|
||||
|
||||
auto const torrents_model = core_->get_model();
|
||||
|
||||
/* Walk through all the torrents, tallying how many matches there are
|
||||
* for the various categories. Also make a sorted list of all tracker
|
||||
* hosts s.t. we can merge it with the existing list */
|
||||
auto n_torrents = int{ 0 };
|
||||
auto site_infos = std::unordered_map<std::string /*site*/, site_info>{};
|
||||
for (auto const& row : torrent_model_->children())
|
||||
for (auto i = 0U, count = torrents_model->get_n_items(); i < count; ++i)
|
||||
{
|
||||
auto const* tor = static_cast<tr_torrent const*>(row.get_value(torrent_cols.torrent));
|
||||
auto const torrent = gtr_ptr_dynamic_cast<Torrent>(torrents_model->get_object(i));
|
||||
if (torrent == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& raw_torrent = torrent->get_underlying();
|
||||
|
||||
auto torrent_sites_and_hosts = std::map<std::string, std::string>{};
|
||||
for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i)
|
||||
for (size_t j = 0, n = tr_torrentTrackerCount(&raw_torrent); j < n; ++j)
|
||||
{
|
||||
auto const view = tr_torrentTracker(tor, i);
|
||||
auto const view = tr_torrentTracker(&raw_torrent, j);
|
||||
torrent_sites_and_hosts.try_emplace(std::data(view.sitename), view.host);
|
||||
}
|
||||
|
||||
|
@ -286,10 +279,10 @@ bool FilterBar::Impl::tracker_filter_model_update()
|
|||
add->set_value(tracker_filter_cols.sitename, Glib::ustring{ site.sitename });
|
||||
add->set_value(tracker_filter_cols.displayname, get_name_from_host(site.sitename));
|
||||
add->set_value(tracker_filter_cols.count, site.count);
|
||||
add->set_value(tracker_filter_cols.type, static_cast<int>(TRACKER_FILTER_TYPE_HOST));
|
||||
add->set_value(tracker_filter_cols.type, static_cast<int>(TrackerType::HOST));
|
||||
auto path = tracker_model_->get_path(add);
|
||||
gtr_get_favicon(
|
||||
session_,
|
||||
core_->get_session(),
|
||||
site.host,
|
||||
[this, ref = Gtk::TreeRowReference(tracker_model_, path)](auto const& pixbuf) mutable
|
||||
{ favicon_ready_cb(pixbuf, ref); });
|
||||
|
@ -313,30 +306,22 @@ Glib::RefPtr<Gtk::TreeStore> FilterBar::Impl::tracker_filter_model_new()
|
|||
|
||||
auto iter = store->append();
|
||||
iter->set_value(tracker_filter_cols.displayname, Glib::ustring(_("All")));
|
||||
iter->set_value(tracker_filter_cols.type, static_cast<int>(TRACKER_FILTER_TYPE_ALL));
|
||||
iter->set_value(tracker_filter_cols.type, static_cast<int>(TrackerType::ALL));
|
||||
|
||||
iter = store->append();
|
||||
iter->set_value(tracker_filter_cols.type, static_cast<int>(TRACKER_FILTER_TYPE_SEPARATOR));
|
||||
iter->set_value(tracker_filter_cols.type, -1);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
bool FilterBar::Impl::is_it_a_separator(Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
return iter->get_value(tracker_filter_cols.type) == TRACKER_FILTER_TYPE_SEPARATOR;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::tracker_model_update_idle()
|
||||
{
|
||||
if (!tracker_model_update_tag_.connected())
|
||||
{
|
||||
tracker_model_update_tag_ = Glib::signal_idle().connect([this]() { return tracker_filter_model_update(); });
|
||||
}
|
||||
return iter->get_value(tracker_filter_cols.type) == -1;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::render_pixbuf_func(Gtk::CellRendererPixbuf& cell_renderer, Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
cell_renderer.property_width() = (iter->get_value(tracker_filter_cols.type) == TRACKER_FILTER_TYPE_HOST) ? 20 : 0;
|
||||
cell_renderer.property_width() = TrackerType{ iter->get_value(tracker_filter_cols.type) } == TrackerType::HOST ? 20 : 0;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::render_number_func(Gtk::CellRendererText& cell_renderer, Gtk::TreeModel::const_iterator const& iter)
|
||||
|
@ -381,31 +366,6 @@ void FilterBar::Impl::tracker_combo_box_init(Gtk::ComboBox& combo)
|
|||
combo.pack_end(*r, true);
|
||||
combo.set_cell_data_func(*r, [r](auto const& iter) { render_number_func(*r, iter); });
|
||||
}
|
||||
|
||||
tracker_model_row_changed_tag_ = torrent_model_->signal_row_changed().connect( //
|
||||
[this](auto const& /*path*/, auto const& /*iter*/) { tracker_model_update_idle(); });
|
||||
tracker_model_row_inserted_tag_ = torrent_model_->signal_row_inserted().connect( //
|
||||
[this](auto const& /*path*/, auto const& /*iter*/) { tracker_model_update_idle(); });
|
||||
tracker_model_row_deleted_cb_tag_ = torrent_model_->signal_row_deleted().connect( //
|
||||
[this](auto const& /*path*/) { tracker_model_update_idle(); });
|
||||
}
|
||||
|
||||
bool FilterBar::Impl::test_tracker(tr_torrent const& tor, int active_tracker_type, Glib::ustring const& host)
|
||||
{
|
||||
if (active_tracker_type != TRACKER_FILTER_TYPE_HOST)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (size_t i = 0, n = tr_torrentTrackerCount(&tor); i < n; ++i)
|
||||
{
|
||||
if (auto const tracker = tr_torrentTracker(&tor, i); std::data(tracker.sitename) == host)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -417,19 +377,6 @@ namespace
|
|||
****
|
||||
***/
|
||||
|
||||
enum
|
||||
{
|
||||
ACTIVITY_FILTER_ALL,
|
||||
ACTIVITY_FILTER_DOWNLOADING,
|
||||
ACTIVITY_FILTER_SEEDING,
|
||||
ACTIVITY_FILTER_ACTIVE,
|
||||
ACTIVITY_FILTER_PAUSED,
|
||||
ACTIVITY_FILTER_FINISHED,
|
||||
ACTIVITY_FILTER_VERIFYING,
|
||||
ACTIVITY_FILTER_ERROR,
|
||||
ACTIVITY_FILTER_SEPARATOR
|
||||
};
|
||||
|
||||
class ActivityFilterModelColumns : public Gtk::TreeModelColumnRecord
|
||||
{
|
||||
public:
|
||||
|
@ -453,40 +400,7 @@ ActivityFilterModelColumns const activity_filter_cols;
|
|||
|
||||
bool FilterBar::Impl::activity_is_it_a_separator(Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
return iter->get_value(activity_filter_cols.type) == ACTIVITY_FILTER_SEPARATOR;
|
||||
}
|
||||
|
||||
bool FilterBar::Impl::test_torrent_activity(tr_torrent& tor, int type)
|
||||
{
|
||||
auto const* st = tr_torrentStatCached(&tor);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ACTIVITY_FILTER_DOWNLOADING:
|
||||
return st->activity == TR_STATUS_DOWNLOAD || st->activity == TR_STATUS_DOWNLOAD_WAIT;
|
||||
|
||||
case ACTIVITY_FILTER_SEEDING:
|
||||
return st->activity == TR_STATUS_SEED || st->activity == TR_STATUS_SEED_WAIT;
|
||||
|
||||
case ACTIVITY_FILTER_ACTIVE:
|
||||
return st->peersSendingToUs > 0 || st->peersGettingFromUs > 0 || st->webseedsSendingToUs > 0 ||
|
||||
st->activity == TR_STATUS_CHECK;
|
||||
|
||||
case ACTIVITY_FILTER_PAUSED:
|
||||
return st->activity == TR_STATUS_STOPPED;
|
||||
|
||||
case ACTIVITY_FILTER_FINISHED:
|
||||
return st->finished;
|
||||
|
||||
case ACTIVITY_FILTER_VERIFYING:
|
||||
return st->activity == TR_STATUS_CHECK || st->activity == TR_STATUS_CHECK_WAIT;
|
||||
|
||||
case ACTIVITY_FILTER_ERROR:
|
||||
return st->error != 0;
|
||||
|
||||
default: /* ACTIVITY_FILTER_ALL */
|
||||
return true;
|
||||
}
|
||||
return iter->get_value(activity_filter_cols.type) == -1;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::status_model_update_count(Gtk::TreeModel::iterator const& iter, int n)
|
||||
|
@ -499,14 +413,22 @@ void FilterBar::Impl::status_model_update_count(Gtk::TreeModel::iterator const&
|
|||
|
||||
bool FilterBar::Impl::activity_filter_model_update()
|
||||
{
|
||||
auto const torrents_model = core_->get_model();
|
||||
|
||||
for (auto& row : activity_model_->children())
|
||||
{
|
||||
auto const type = row.get_value(activity_filter_cols.type);
|
||||
if (type == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto hits = 0;
|
||||
|
||||
for (auto const& torrent_row : torrent_model_->children())
|
||||
for (auto i = 0U, count = torrents_model->get_n_items(); i < count; ++i)
|
||||
{
|
||||
if (test_torrent_activity(*static_cast<tr_torrent*>(torrent_row.get_value(torrent_cols.torrent)), type))
|
||||
auto const torrent = gtr_ptr_dynamic_cast<Torrent>(torrents_model->get_object(i));
|
||||
if (torrent != nullptr && TorrentFilter::match_activity(*torrent.get(), static_cast<ActivityType>(type)))
|
||||
{
|
||||
++hits;
|
||||
}
|
||||
|
@ -522,22 +444,22 @@ Glib::RefPtr<Gtk::ListStore> FilterBar::Impl::activity_filter_model_new()
|
|||
{
|
||||
struct FilterTypeInfo
|
||||
{
|
||||
int type;
|
||||
ActivityType type;
|
||||
char const* context;
|
||||
char const* name;
|
||||
Glib::ustring icon_name;
|
||||
char const* icon_name;
|
||||
};
|
||||
|
||||
static auto const types = std::array<FilterTypeInfo, 9>({ {
|
||||
{ ACTIVITY_FILTER_ALL, nullptr, N_("All"), {} },
|
||||
{ ACTIVITY_FILTER_SEPARATOR, nullptr, nullptr, {} },
|
||||
{ ACTIVITY_FILTER_ACTIVE, nullptr, N_("Active"), "system-run" },
|
||||
{ ACTIVITY_FILTER_DOWNLOADING, "Verb", NC_("Verb", "Downloading"), "network-receive" },
|
||||
{ ACTIVITY_FILTER_SEEDING, "Verb", NC_("Verb", "Seeding"), "network-transmit" },
|
||||
{ ACTIVITY_FILTER_PAUSED, nullptr, N_("Paused"), "media-playback-pause" },
|
||||
{ ACTIVITY_FILTER_FINISHED, nullptr, N_("Finished"), "media-playback-stop" },
|
||||
{ ACTIVITY_FILTER_VERIFYING, "Verb", NC_("Verb", "Verifying"), "view-refresh" },
|
||||
{ ACTIVITY_FILTER_ERROR, nullptr, N_("Error"), "dialog-error" },
|
||||
static auto constexpr types = std::array<FilterTypeInfo, 9>({ {
|
||||
{ ActivityType::ALL, nullptr, N_("All"), nullptr },
|
||||
{ ActivityType{ -1 }, nullptr, nullptr, nullptr },
|
||||
{ ActivityType::ACTIVE, nullptr, N_("Active"), "system-run" },
|
||||
{ ActivityType::DOWNLOADING, "Verb", NC_("Verb", "Downloading"), "network-receive" },
|
||||
{ ActivityType::SEEDING, "Verb", NC_("Verb", "Seeding"), "network-transmit" },
|
||||
{ ActivityType::PAUSED, nullptr, N_("Paused"), "media-playback-pause" },
|
||||
{ ActivityType::FINISHED, nullptr, N_("Finished"), "media-playback-stop" },
|
||||
{ ActivityType::VERIFYING, "Verb", NC_("Verb", "Verifying"), "view-refresh" },
|
||||
{ ActivityType::ERROR, nullptr, N_("Error"), "dialog-error" },
|
||||
} });
|
||||
|
||||
auto store = Gtk::ListStore::create(activity_filter_cols);
|
||||
|
@ -549,8 +471,8 @@ Glib::RefPtr<Gtk::ListStore> FilterBar::Impl::activity_filter_model_new()
|
|||
Glib::ustring();
|
||||
auto const iter = store->append();
|
||||
iter->set_value(activity_filter_cols.name, name);
|
||||
iter->set_value(activity_filter_cols.type, type.type);
|
||||
iter->set_value(activity_filter_cols.icon_name, type.icon_name);
|
||||
iter->set_value(activity_filter_cols.type, static_cast<int>(type.type));
|
||||
iter->set_value(activity_filter_cols.icon_name, Glib::ustring(type.icon_name != nullptr ? type.icon_name : ""));
|
||||
}
|
||||
|
||||
return store;
|
||||
|
@ -560,17 +482,9 @@ void FilterBar::Impl::render_activity_pixbuf_func(
|
|||
Gtk::CellRendererPixbuf& cell_renderer,
|
||||
Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
auto const type = iter->get_value(activity_filter_cols.type);
|
||||
cell_renderer.property_width() = type == ACTIVITY_FILTER_ALL ? 0 : 20;
|
||||
cell_renderer.property_ypad() = type == ACTIVITY_FILTER_ALL ? 0 : 2;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::activity_model_update_idle()
|
||||
{
|
||||
if (!activity_model_update_tag_.connected())
|
||||
{
|
||||
activity_model_update_tag_ = Glib::signal_idle().connect([this]() { return activity_filter_model_update(); });
|
||||
}
|
||||
auto const type = ActivityType{ iter->get_value(activity_filter_cols.type) };
|
||||
cell_renderer.property_width() = type == ActivityType::ALL ? 0 : 20;
|
||||
cell_renderer.property_ypad() = type == ActivityType::ALL ? 0 : 2;
|
||||
}
|
||||
|
||||
void FilterBar::Impl::activity_combo_box_init(Gtk::ComboBox& combo)
|
||||
|
@ -597,100 +511,45 @@ void FilterBar::Impl::activity_combo_box_init(Gtk::ComboBox& combo)
|
|||
combo.pack_end(*r, true);
|
||||
combo.set_cell_data_func(*r, [r](auto const& iter) { render_number_func(*r, iter); });
|
||||
}
|
||||
|
||||
activity_model_row_changed_tag_ = torrent_model_->signal_row_changed().connect( //
|
||||
[this](auto const& /*path*/, auto const& /*iter*/) { activity_model_update_idle(); });
|
||||
activity_model_row_inserted_tag_ = torrent_model_->signal_row_inserted().connect( //
|
||||
[this](auto const& /*path*/, auto const& /*iter*/) { activity_model_update_idle(); });
|
||||
activity_model_row_deleted_cb_tag_ = torrent_model_->signal_row_deleted().connect( //
|
||||
[this](auto const& /*path*/) { activity_model_update_idle(); });
|
||||
}
|
||||
|
||||
/****
|
||||
*****
|
||||
***** ENTRY FIELD
|
||||
*****
|
||||
****/
|
||||
|
||||
bool FilterBar::Impl::testText(tr_torrent const& tor, Glib::ustring const& key)
|
||||
void FilterBar::Impl::update_filter_text()
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (key.empty())
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* test the torrent name... */
|
||||
ret = Glib::ustring(tr_torrentName(&tor)).casefold().find(key) != Glib::ustring::npos;
|
||||
|
||||
/* test the files... */
|
||||
for (tr_file_index_t i = 0, n = tr_torrentFileCount(&tor); i < n && !ret; ++i)
|
||||
{
|
||||
ret = Glib::ustring(tr_torrentFile(&tor, i).name).casefold().find(key) != Glib::ustring::npos;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
filter_->set_text(entry_->get_text());
|
||||
}
|
||||
|
||||
void FilterBar::Impl::filter_entry_changed()
|
||||
{
|
||||
filter_text_ = gtr_str_strip(entry_->get_text().casefold());
|
||||
filter_model_->refilter();
|
||||
}
|
||||
|
||||
/*****
|
||||
******
|
||||
******
|
||||
******
|
||||
*****/
|
||||
|
||||
bool FilterBar::Impl::is_row_visible(Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
auto* const tor = static_cast<tr_torrent*>(iter->get_value(torrent_cols.torrent));
|
||||
|
||||
return tor != nullptr && test_tracker(*tor, active_tracker_type_, active_tracker_sitename_) &&
|
||||
test_torrent_activity(*tor, active_activity_type_) && testText(*tor, filter_text_);
|
||||
}
|
||||
|
||||
void FilterBar::Impl::selection_changed_cb()
|
||||
void FilterBar::Impl::update_filter_activity()
|
||||
{
|
||||
/* set active_activity_type_ from the activity combobox */
|
||||
if (auto const iter = activity_->get_active(); iter)
|
||||
{
|
||||
active_activity_type_ = iter->get_value(activity_filter_cols.type);
|
||||
filter_->set_activity(ActivityType{ iter->get_value(activity_filter_cols.type) });
|
||||
}
|
||||
else
|
||||
{
|
||||
active_activity_type_ = ACTIVITY_FILTER_ALL;
|
||||
filter_->set_activity(ActivityType::ALL);
|
||||
}
|
||||
}
|
||||
|
||||
void FilterBar::Impl::update_filter_tracker()
|
||||
{
|
||||
/* set the active tracker type & host from the tracker combobox */
|
||||
if (auto const iter = tracker_->get_active(); iter)
|
||||
{
|
||||
active_tracker_type_ = iter->get_value(tracker_filter_cols.type);
|
||||
active_tracker_sitename_ = iter->get_value(tracker_filter_cols.sitename);
|
||||
filter_->set_tracker(
|
||||
static_cast<TrackerType>(iter->get_value(tracker_filter_cols.type)),
|
||||
iter->get_value(tracker_filter_cols.sitename));
|
||||
}
|
||||
else
|
||||
{
|
||||
active_tracker_type_ = TRACKER_FILTER_TYPE_ALL;
|
||||
active_tracker_sitename_.clear();
|
||||
filter_->set_tracker(TrackerType::ALL, {});
|
||||
}
|
||||
|
||||
/* refilter */
|
||||
filter_model_->refilter();
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
bool FilterBar::Impl::update_count_label()
|
||||
{
|
||||
/* get the visible count */
|
||||
auto const visibleCount = static_cast<int>(filter_model_->children().size());
|
||||
auto const visibleCount = static_cast<int>(filter_model_->IF_GTKMM4(get_n_items(), children().size()));
|
||||
|
||||
/* get the tracker count */
|
||||
int trackerCount = 0;
|
||||
|
@ -723,6 +582,39 @@ void FilterBar::Impl::update_count_label_idle()
|
|||
}
|
||||
}
|
||||
|
||||
void FilterBar::Impl::update_filter_models(Torrent::ChangeFlags changes)
|
||||
{
|
||||
static auto constexpr activity_flags = Torrent::ChangeFlag::ACTIVE_PEERS_DOWN | Torrent::ChangeFlag::ACTIVE_PEERS_UP |
|
||||
Torrent::ChangeFlag::ACTIVE | Torrent::ChangeFlag::ACTIVITY | Torrent::ChangeFlag::ERROR_CODE |
|
||||
Torrent::ChangeFlag::FINISHED;
|
||||
static auto constexpr tracker_flags = Torrent::ChangeFlag::TRACKERS;
|
||||
|
||||
if (changes.test(activity_flags))
|
||||
{
|
||||
activity_filter_model_update();
|
||||
}
|
||||
|
||||
if (changes.test(tracker_flags))
|
||||
{
|
||||
tracker_filter_model_update();
|
||||
}
|
||||
|
||||
filter_->update(changes);
|
||||
}
|
||||
|
||||
void FilterBar::Impl::update_filter_models_idle(Torrent::ChangeFlags changes)
|
||||
{
|
||||
if (!update_filter_models_tag_.connected())
|
||||
{
|
||||
update_filter_models_tag_ = Glib::signal_idle().connect(
|
||||
[this, changes]()
|
||||
{
|
||||
update_filter_models(changes);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -761,20 +653,18 @@ FilterBar::FilterBar()
|
|||
FilterBar::FilterBar(
|
||||
BaseObjectType* cast_item,
|
||||
Glib::RefPtr<Gtk::Builder> const& /*builder*/,
|
||||
tr_session* session,
|
||||
Glib::RefPtr<Gtk::TreeModel> const& torrent_model)
|
||||
Glib::RefPtr<Session> const& core)
|
||||
: Glib::ObjectBase(typeid(FilterBar))
|
||||
, Gtk::Box(cast_item)
|
||||
, impl_(std::make_unique<Impl>(*this, session, torrent_model))
|
||||
, impl_(std::make_unique<Impl>(*this, core))
|
||||
{
|
||||
}
|
||||
|
||||
FilterBar::~FilterBar() = default;
|
||||
|
||||
FilterBar::Impl::Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::TreeModel> const& torrent_model)
|
||||
FilterBar::Impl::Impl(FilterBar& widget, Glib::RefPtr<Session> const& core)
|
||||
: widget_(widget)
|
||||
, session_(session)
|
||||
, torrent_model_(torrent_model)
|
||||
, core_(core)
|
||||
, activity_model_(activity_filter_model_new())
|
||||
, tracker_model_(tracker_filter_model_new())
|
||||
, activity_(get_template_child<Gtk::ComboBox>("activity_combo"))
|
||||
|
@ -782,58 +672,62 @@ FilterBar::Impl::Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::
|
|||
, entry_(get_template_child<Gtk::Entry>("text_entry"))
|
||||
, show_lb_(get_template_child<Gtk::Label>("show_label"))
|
||||
{
|
||||
update_filter_models_on_add_remove_tag_ = core_->get_model()->signal_items_changed().connect(
|
||||
[this](guint /*position*/, guint /*removed*/, guint /*added*/) { update_filter_models_idle(~Torrent::ChangeFlags()); });
|
||||
update_filter_models_on_change_tag_ = core_->signal_torrents_changed().connect(
|
||||
sigc::hide<0>(sigc::mem_fun(*this, &Impl::update_filter_models_idle)));
|
||||
|
||||
activity_filter_model_update();
|
||||
tracker_filter_model_update();
|
||||
|
||||
activity_combo_box_init(*activity_);
|
||||
tracker_combo_box_init(*tracker_);
|
||||
|
||||
filter_model_ = Gtk::TreeModelFilter::create(torrent_model);
|
||||
filter_model_row_deleted_tag_ = filter_model_->signal_row_deleted().connect([this](auto const& /*path*/)
|
||||
{ update_count_label_idle(); });
|
||||
filter_model_row_inserted_tag_ = filter_model_->signal_row_inserted().connect(
|
||||
[this](auto const& /*path*/, auto const& /*iter*/) { update_count_label_idle(); });
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
filter_model_ = Gtk::FilterListModel::create(core_->get_sorted_model(), filter_);
|
||||
filter_model_items_changed_tag_ = filter_model_->signal_items_changed().connect(
|
||||
[this](guint /*position*/, guint /*removed*/, guint /*added*/) { update_count_label_idle(); });
|
||||
#else
|
||||
static auto const& self_col = Torrent::get_columns().self;
|
||||
|
||||
filter_model_->set_visible_func(sigc::mem_fun(*this, &Impl::is_row_visible));
|
||||
auto const filter_func = [this](FilterModel::const_iterator const& iter)
|
||||
{
|
||||
return filter_->match(*iter->get_value(self_col));
|
||||
};
|
||||
|
||||
tracker_->signal_changed().connect(sigc::mem_fun(*this, &Impl::selection_changed_cb));
|
||||
activity_->signal_changed().connect(sigc::mem_fun(*this, &Impl::selection_changed_cb));
|
||||
filter_model_ = Gtk::TreeModelFilter::create(core_->get_sorted_model());
|
||||
filter_model_->set_visible_func(filter_func);
|
||||
filter_->signal_changed().connect([this]() { filter_model_->refilter(); });
|
||||
#endif
|
||||
|
||||
tracker_->signal_changed().connect(sigc::mem_fun(*this, &Impl::update_filter_tracker));
|
||||
activity_->signal_changed().connect(sigc::mem_fun(*this, &Impl::update_filter_activity));
|
||||
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
entry_->signal_icon_release().connect([this](auto /*icon_position*/) { entry_->set_text({}); });
|
||||
#else
|
||||
entry_->signal_icon_release().connect([this](auto /*icon_position*/, auto const* /*event*/) { entry_->set_text({}); });
|
||||
#endif
|
||||
entry_->signal_changed().connect(sigc::mem_fun(*this, &Impl::filter_entry_changed));
|
||||
|
||||
selection_changed_cb();
|
||||
update_count_label();
|
||||
entry_->signal_changed().connect(sigc::mem_fun(*this, &Impl::update_filter_text));
|
||||
}
|
||||
|
||||
FilterBar::Impl::~Impl()
|
||||
{
|
||||
update_filter_models_on_change_tag_.disconnect();
|
||||
update_filter_models_on_add_remove_tag_.disconnect();
|
||||
update_filter_models_tag_.disconnect();
|
||||
update_count_label_tag_.disconnect();
|
||||
|
||||
filter_model_row_deleted_tag_.disconnect();
|
||||
filter_model_row_inserted_tag_.disconnect();
|
||||
|
||||
activity_model_update_tag_.disconnect();
|
||||
tracker_model_row_deleted_cb_tag_.disconnect();
|
||||
tracker_model_row_inserted_tag_.disconnect();
|
||||
tracker_model_row_changed_tag_.disconnect();
|
||||
|
||||
activity_model_update_tag_.disconnect();
|
||||
activity_model_row_deleted_cb_tag_.disconnect();
|
||||
activity_model_row_inserted_tag_.disconnect();
|
||||
activity_model_row_changed_tag_.disconnect();
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
filter_model_items_changed_tag_.disconnect();
|
||||
#endif
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModel> FilterBar::get_filter_model() const
|
||||
Glib::RefPtr<FilterBar::Model> FilterBar::get_filter_model() const
|
||||
{
|
||||
return impl_->get_filter_model();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModel> FilterBar::Impl::get_filter_model() const
|
||||
Glib::RefPtr<FilterBar::Impl::FilterModel> FilterBar::Impl::get_filter_model() const
|
||||
{
|
||||
return filter_model_;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
|
||||
#include <libtransmission/tr-macros.h>
|
||||
|
||||
typedef struct tr_session tr_session;
|
||||
#include "Utils.h"
|
||||
|
||||
class Session;
|
||||
|
||||
class FilterBarExtraInit : public Glib::ExtraClassInit
|
||||
{
|
||||
|
@ -28,18 +30,17 @@ class FilterBar
|
|||
: public FilterBarExtraInit
|
||||
, public Gtk::Box
|
||||
{
|
||||
public:
|
||||
using Model = IF_GTKMM4(Gio::ListModel, Gtk::TreeModel);
|
||||
|
||||
public:
|
||||
FilterBar();
|
||||
FilterBar(
|
||||
BaseObjectType* cast_item,
|
||||
Glib::RefPtr<Gtk::Builder> const& builder,
|
||||
tr_session* session,
|
||||
Glib::RefPtr<Gtk::TreeModel> const& torrent_model);
|
||||
FilterBar(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> const& builder, Glib::RefPtr<Session> const& core);
|
||||
~FilterBar() override;
|
||||
|
||||
TR_DISABLE_COPY_MOVE(FilterBar)
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModel> get_filter_model() const;
|
||||
Glib::RefPtr<Model> get_filter_model() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#define DEFINE_FLAGS_OPERATORS(FlagType) \
|
||||
constexpr inline Flags<FlagType> operator|(FlagType lhs, FlagType rhs) noexcept \
|
||||
{ \
|
||||
return { lhs, rhs }; \
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class Flags
|
||||
{
|
||||
public:
|
||||
using FlagType = T;
|
||||
using ValueType = std::underlying_type_t<FlagType>;
|
||||
|
||||
static_assert(std::is_enum_v<FlagType> && !std::is_convertible_v<FlagType, ValueType>);
|
||||
|
||||
public:
|
||||
constexpr Flags() noexcept = default;
|
||||
|
||||
constexpr Flags(FlagType flag) noexcept
|
||||
{
|
||||
set(flag);
|
||||
}
|
||||
|
||||
constexpr Flags(std::initializer_list<FlagType> flags) noexcept
|
||||
{
|
||||
for (auto const flag : flags)
|
||||
{
|
||||
set(flag);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool none() const noexcept
|
||||
{
|
||||
return value_ == 0;
|
||||
}
|
||||
|
||||
constexpr bool any() const noexcept
|
||||
{
|
||||
return !none();
|
||||
}
|
||||
|
||||
constexpr bool test(FlagType flag) const noexcept
|
||||
{
|
||||
return (value_ & get_mask(flag)) != 0;
|
||||
}
|
||||
|
||||
constexpr bool test(Flags rhs) const noexcept
|
||||
{
|
||||
return (value_ & rhs.value_) != 0;
|
||||
}
|
||||
|
||||
constexpr void set(FlagType flag) noexcept
|
||||
{
|
||||
value_ |= get_mask(flag);
|
||||
}
|
||||
|
||||
constexpr Flags operator|(Flags rhs) noexcept
|
||||
{
|
||||
return Flags(value_ | rhs.value_);
|
||||
}
|
||||
|
||||
constexpr Flags& operator|=(Flags rhs) noexcept
|
||||
{
|
||||
value_ |= rhs.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Flags operator~() const noexcept
|
||||
{
|
||||
return Flags(~value_);
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr explicit Flags(ValueType value) noexcept
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr ValueType get_mask(FlagType flag) noexcept
|
||||
{
|
||||
return ValueType{ 1 } << static_cast<ValueType>(flag);
|
||||
}
|
||||
|
||||
private:
|
||||
ValueType value_ = {};
|
||||
};
|
|
@ -0,0 +1,281 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include "ListModelAdapter.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
int iter_get_stamp(T const& iter)
|
||||
{
|
||||
return iter.gobj()->stamp;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void iter_set_stamp(T& iter, int value)
|
||||
{
|
||||
iter.gobj()->stamp = value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int iter_get_item_id(T const& iter)
|
||||
{
|
||||
return GPOINTER_TO_INT(iter.gobj()->user_data);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void iter_set_item_id(T& iter, int value)
|
||||
{
|
||||
iter.gobj()->user_data = GINT_TO_POINTER(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void iter_clear(T& iter)
|
||||
{
|
||||
iter_set_stamp(iter, 0);
|
||||
iter_set_item_id(iter, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ListModelAdapter::ListModelAdapter(
|
||||
Glib::RefPtr<Gio::ListModel> const& adaptee,
|
||||
Gtk::TreeModelColumnRecord const& columns,
|
||||
IdGetter id_getter,
|
||||
ValueGetter value_getter)
|
||||
: Glib::ObjectBase(typeid(ListModelAdapter))
|
||||
, adaptee_(adaptee)
|
||||
, columns_(columns)
|
||||
, id_getter_(std::move(id_getter))
|
||||
, value_getter_(std::move(value_getter))
|
||||
{
|
||||
adaptee_->signal_items_changed().connect(sigc::mem_fun(*this, &ListModelAdapter::on_adaptee_items_changed));
|
||||
|
||||
on_adaptee_items_changed(0, 0, adaptee_->get_n_items());
|
||||
}
|
||||
|
||||
ListModelAdapter::TrTreeModelFlags ListModelAdapter::get_flags_vfunc() const
|
||||
{
|
||||
return TR_GTK_TREE_MODEL_FLAGS(ITERS_PERSIST) | TR_GTK_TREE_MODEL_FLAGS(LIST_ONLY);
|
||||
}
|
||||
|
||||
int ListModelAdapter::get_n_columns_vfunc() const
|
||||
{
|
||||
return columns_.size();
|
||||
}
|
||||
|
||||
GType ListModelAdapter::get_column_type_vfunc(int index) const
|
||||
{
|
||||
g_return_val_if_fail(index >= 0, G_TYPE_INVALID);
|
||||
g_return_val_if_fail(index < get_n_columns_vfunc(), G_TYPE_INVALID);
|
||||
|
||||
return columns_.types()[index];
|
||||
}
|
||||
|
||||
bool ListModelAdapter::iter_next_vfunc(iterator const& iter, iterator& iter_next) const
|
||||
{
|
||||
iter_clear(iter_next);
|
||||
|
||||
if (iter)
|
||||
{
|
||||
g_return_val_if_fail(iter_get_stamp(iter) == stamp_, false);
|
||||
|
||||
if (auto const position = find_item_position_by_id(iter_get_item_id(iter)); position.has_value())
|
||||
{
|
||||
if (auto const next_position = position.value() + 1; next_position < items_.size())
|
||||
{
|
||||
iter_set_stamp(iter_next, stamp_);
|
||||
iter_set_item_id(iter_next, items_.at(next_position).id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ListModelAdapter::get_iter_vfunc(Path const& path, iterator& iter) const
|
||||
{
|
||||
iter_clear(iter);
|
||||
|
||||
g_return_val_if_fail(path.size() == 1, false);
|
||||
|
||||
return iter_nth_root_child_vfunc(path.front(), iter);
|
||||
}
|
||||
|
||||
bool ListModelAdapter::iter_children_vfunc(iterator const& parent, iterator& iter) const
|
||||
{
|
||||
iter_clear(iter);
|
||||
|
||||
if (parent || items_.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_set_stamp(iter, stamp_);
|
||||
iter_set_item_id(iter, items_.front().id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ListModelAdapter::iter_parent_vfunc(iterator const& /*child*/, iterator& iter) const
|
||||
{
|
||||
iter_clear(iter);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ListModelAdapter::iter_nth_root_child_vfunc(int position, iterator& iter) const
|
||||
{
|
||||
iter_clear(iter);
|
||||
|
||||
g_return_val_if_fail(position >= 0, false);
|
||||
|
||||
if (position >= iter_n_root_children_vfunc())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
iter_set_stamp(iter, stamp_);
|
||||
iter_set_item_id(iter, items_.at(position).id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ListModelAdapter::iter_has_child_vfunc(const_iterator const& /*iter*/) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int ListModelAdapter::iter_n_root_children_vfunc() const
|
||||
{
|
||||
return items_.size();
|
||||
}
|
||||
|
||||
Gtk::TreeModel::Path ListModelAdapter::get_path_vfunc(const_iterator const& iter) const
|
||||
{
|
||||
auto path = Path();
|
||||
|
||||
if (iter)
|
||||
{
|
||||
g_return_val_if_fail(iter_get_stamp(iter) == stamp_, path);
|
||||
|
||||
if (auto const position = find_item_position_by_id(iter_get_item_id(iter)); position.has_value())
|
||||
{
|
||||
path.push_back(position.value());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void ListModelAdapter::get_value_vfunc(const_iterator const& iter, int column, Glib::ValueBase& value) const
|
||||
{
|
||||
g_return_if_fail(column >= 0);
|
||||
g_return_if_fail(column < get_n_columns_vfunc());
|
||||
|
||||
value.init(get_column_type_vfunc(column));
|
||||
|
||||
if (!iter)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const position = find_item_position_by_id(iter_get_item_id(iter));
|
||||
if (!position.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const item = adaptee_->get_object(position.value());
|
||||
if (item == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
value_getter_(item, column, value);
|
||||
}
|
||||
|
||||
std::optional<guint> ListModelAdapter::find_item_position_by_id(int item_id) const
|
||||
{
|
||||
auto const item_position_it = item_positions_.find(item_id);
|
||||
return item_position_it != item_positions_.end() ? std::make_optional(item_position_it->second) : std::nullopt;
|
||||
}
|
||||
|
||||
void ListModelAdapter::adjust_item_positions(guint min_position, int delta)
|
||||
{
|
||||
for (auto item_it = std::next(items_.begin(), min_position); item_it != items_.end(); ++item_it)
|
||||
{
|
||||
if (auto const item_position_it = item_positions_.find(item_it->id); item_position_it != item_positions_.end())
|
||||
{
|
||||
item_position_it->second += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListModelAdapter::on_adaptee_items_changed(guint position, guint removed, guint added)
|
||||
{
|
||||
g_assert(position + removed <= items_.size());
|
||||
g_assert(position + added <= adaptee_->get_n_items());
|
||||
|
||||
for (auto i = 0U; i < removed; ++i)
|
||||
{
|
||||
auto const removed_position = position + removed - i - 1;
|
||||
auto info = items_.at(removed_position);
|
||||
|
||||
items_.erase(std::next(items_.begin(), removed_position));
|
||||
info.notify_tag.disconnect();
|
||||
|
||||
item_positions_.erase(info.id);
|
||||
adjust_item_positions(removed_position, -1);
|
||||
|
||||
auto path = Path();
|
||||
path.push_back(removed_position);
|
||||
|
||||
row_deleted(path);
|
||||
}
|
||||
|
||||
for (auto i = 0U; i < added; ++i)
|
||||
{
|
||||
auto const added_position = position + i;
|
||||
auto const item = adaptee_->get_object(added_position);
|
||||
auto const info = ItemInfo{
|
||||
.id = id_getter_(item),
|
||||
.notify_tag = gtr_object_signal_notify(*item.get())
|
||||
.connect(sigc::mem_fun(*this, &ListModelAdapter::on_adaptee_item_changed)),
|
||||
};
|
||||
|
||||
items_.insert(std::next(items_.begin(), added_position), info);
|
||||
|
||||
adjust_item_positions(added_position, 1);
|
||||
item_positions_.emplace(info.id, added_position);
|
||||
|
||||
auto path = Path();
|
||||
path.push_back(added_position);
|
||||
|
||||
auto iter = iterator(this);
|
||||
iter_set_stamp(iter, stamp_);
|
||||
iter_set_item_id(iter, info.id);
|
||||
|
||||
row_inserted(path, iter);
|
||||
}
|
||||
}
|
||||
|
||||
void ListModelAdapter::on_adaptee_item_changed(Glib::RefPtr<Glib::ObjectBase const> const& item)
|
||||
{
|
||||
g_return_if_fail(item != nullptr);
|
||||
|
||||
auto const item_id = id_getter_(item);
|
||||
|
||||
if (auto const position = find_item_position_by_id(item_id); position.has_value())
|
||||
{
|
||||
auto path = Path();
|
||||
path.push_back(position.value());
|
||||
|
||||
auto iter = iterator(this);
|
||||
iter_set_stamp(iter, stamp_);
|
||||
iter_set_item_id(iter, item_id);
|
||||
|
||||
row_changed(path, iter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
class ListModelAdapter
|
||||
: public Gtk::TreeModel
|
||||
, public Glib::Object
|
||||
{
|
||||
using IdGetter = std::function<int(Glib::RefPtr<Glib::ObjectBase const> const&)>;
|
||||
using ValueGetter = std::function<void(Glib::RefPtr<Glib::ObjectBase const> const&, int, Glib::ValueBase&)>;
|
||||
|
||||
struct ItemInfo
|
||||
{
|
||||
int id = 0;
|
||||
sigc::connection notify_tag;
|
||||
};
|
||||
|
||||
using TrTreeModelFlags = IF_GTKMM4(Gtk::TreeModel::Flags, Gtk::TreeModelFlags);
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
static Glib::RefPtr<ListModelAdapter> create(Glib::RefPtr<Gio::ListModel> const& adaptee)
|
||||
{
|
||||
return Glib::make_refptr_for_instance(
|
||||
new ListModelAdapter(adaptee, T::get_columns(), &T::get_item_id, &T::get_item_value));
|
||||
}
|
||||
|
||||
protected:
|
||||
// Gtk::TreeModel
|
||||
TrTreeModelFlags get_flags_vfunc() const override;
|
||||
int get_n_columns_vfunc() const override;
|
||||
GType get_column_type_vfunc(int index) const override;
|
||||
bool iter_next_vfunc(iterator const& iter, iterator& iter_next) const override;
|
||||
bool get_iter_vfunc(Path const& path, iterator& iter) const override;
|
||||
bool iter_children_vfunc(iterator const& parent, iterator& iter) const override;
|
||||
bool iter_parent_vfunc(iterator const& child, iterator& iter) const override;
|
||||
bool iter_nth_root_child_vfunc(int position, iterator& iter) const override;
|
||||
bool iter_has_child_vfunc(const_iterator const& iter) const override;
|
||||
int iter_n_root_children_vfunc() const override;
|
||||
TreeModel::Path get_path_vfunc(const_iterator const& iter) const override;
|
||||
void get_value_vfunc(const_iterator const& iter, int column, Glib::ValueBase& value) const override;
|
||||
|
||||
private:
|
||||
ListModelAdapter(
|
||||
Glib::RefPtr<Gio::ListModel> const& adaptee,
|
||||
Gtk::TreeModelColumnRecord const& columns,
|
||||
IdGetter id_getter,
|
||||
ValueGetter value_getter);
|
||||
|
||||
std::optional<guint> find_item_position_by_id(int item_id) const;
|
||||
void adjust_item_positions(guint min_position, int delta);
|
||||
|
||||
void on_adaptee_items_changed(guint position, guint removed, guint added);
|
||||
void on_adaptee_item_changed(Glib::RefPtr<Glib::ObjectBase const> const& item);
|
||||
|
||||
private:
|
||||
Glib::RefPtr<Gio::ListModel> const adaptee_;
|
||||
Gtk::TreeModelColumnRecord const& columns_;
|
||||
IdGetter const id_getter_;
|
||||
ValueGetter const value_getter_;
|
||||
|
||||
int const stamp_ = 1;
|
||||
std::vector<ItemInfo> items_;
|
||||
std::unordered_map<int, guint> mutable item_positions_;
|
||||
};
|
|
@ -13,11 +13,12 @@
|
|||
|
||||
#include "Actions.h"
|
||||
#include "FilterBar.h"
|
||||
#include "HigWorkarea.h" // GUI_PAD_SMALL
|
||||
#include "ListModelAdapter.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Prefs.h"
|
||||
#include "PrefsDialog.h"
|
||||
#include "Session.h"
|
||||
#include "Torrent.h"
|
||||
#include "TorrentCellRenderer.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -61,8 +62,13 @@ public:
|
|||
|
||||
void prefsChanged(tr_quark key);
|
||||
|
||||
auto& signal_selection_changed()
|
||||
{
|
||||
return signal_selection_changed_;
|
||||
}
|
||||
|
||||
private:
|
||||
void init_view(Gtk::TreeView* view, Glib::RefPtr<Gtk::TreeModel> const& model);
|
||||
void init_view(Gtk::TreeView* view, Glib::RefPtr<FilterBar::Model> const& model);
|
||||
|
||||
Glib::RefPtr<Gio::MenuModel> createOptionsMenu();
|
||||
Glib::RefPtr<Gio::MenuModel> createSpeedMenu(Glib::RefPtr<Gio::SimpleActionGroup> const& actions, tr_direction dir);
|
||||
|
@ -92,12 +98,17 @@ private:
|
|||
MainWindow& window_;
|
||||
Glib::RefPtr<Session> const core_;
|
||||
|
||||
sigc::signal<void()> signal_selection_changed_;
|
||||
|
||||
Glib::RefPtr<Gio::ActionGroup> options_actions_;
|
||||
Glib::RefPtr<Gio::ActionGroup> stats_actions_;
|
||||
|
||||
std::array<OptionMenuInfo, 2> speed_menu_info_;
|
||||
OptionMenuInfo ratio_menu_info_;
|
||||
|
||||
TorrentCellRenderer* renderer_ = nullptr;
|
||||
Gtk::TreeViewColumn* column_ = nullptr;
|
||||
|
||||
Gtk::ScrolledWindow* scroll_ = nullptr;
|
||||
Gtk::TreeView* view_ = nullptr;
|
||||
Gtk::Widget* toolbar_ = nullptr;
|
||||
|
@ -108,8 +119,6 @@ private:
|
|||
Gtk::Label* stats_lb_ = nullptr;
|
||||
Gtk::Image* alt_speed_image_ = nullptr;
|
||||
Gtk::ToggleButton* alt_speed_button_ = nullptr;
|
||||
TorrentCellRenderer* renderer_ = nullptr;
|
||||
Gtk::TreeViewColumn* column_ = nullptr;
|
||||
sigc::connection pref_handler_id_;
|
||||
IF_GTKMM4(Gtk::PopoverMenu*, Gtk::Menu*) popup_menu_ = nullptr;
|
||||
};
|
||||
|
@ -158,14 +167,18 @@ bool tree_view_search_equal_func(
|
|||
Glib::ustring const& key,
|
||||
Gtk::TreeModel::const_iterator const& iter)
|
||||
{
|
||||
auto const name = iter->get_value(torrent_cols.name_collated);
|
||||
static auto const& self_col = Torrent::get_columns().self;
|
||||
|
||||
auto const name = iter->get_value(self_col)->get_name_collated();
|
||||
return name.find(key.lowercase()) == Glib::ustring::npos;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MainWindow::Impl::init_view(Gtk::TreeView* view, Glib::RefPtr<Gtk::TreeModel> const& model)
|
||||
void MainWindow::Impl::init_view(Gtk::TreeView* view, Glib::RefPtr<FilterBar::Model> const& model)
|
||||
{
|
||||
static auto const& torrent_cols = Torrent::get_columns();
|
||||
|
||||
view->set_search_column(torrent_cols.name_collated);
|
||||
view->set_search_equal_func(&tree_view_search_equal_func);
|
||||
|
||||
|
@ -173,12 +186,7 @@ void MainWindow::Impl::init_view(Gtk::TreeView* view, Glib::RefPtr<Gtk::TreeMode
|
|||
|
||||
renderer_ = Gtk::make_managed<TorrentCellRenderer>();
|
||||
column_->pack_start(*renderer_, false);
|
||||
column_->add_attribute(renderer_->property_torrent(), torrent_cols.torrent);
|
||||
column_->add_attribute(renderer_->property_piece_upload_speed(), torrent_cols.speed_up);
|
||||
column_->add_attribute(renderer_->property_piece_download_speed(), torrent_cols.speed_down);
|
||||
|
||||
renderer_->property_xpad() = GUI_PAD_SMALL;
|
||||
renderer_->property_ypad() = GUI_PAD_SMALL;
|
||||
column_->add_attribute(renderer_->property_torrent(), torrent_cols.self);
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
view->signal_popup_menu().connect_notify([this]() { on_popup_menu(0, 0); });
|
||||
|
@ -198,7 +206,9 @@ void MainWindow::Impl::init_view(Gtk::TreeView* view, Glib::RefPtr<Gtk::TreeMode
|
|||
view->signal_row_activated().connect([](auto const& /*path*/, auto* /*column*/)
|
||||
{ gtr_action_activate("show-torrent-properties"); });
|
||||
|
||||
view->set_model(model);
|
||||
view->set_model(IF_GTKMM4(ListModelAdapter::create<Torrent>(model), model));
|
||||
|
||||
view->get_selection()->signal_changed().connect([this]() { signal_selection_changed_.emit(); });
|
||||
}
|
||||
|
||||
void MainWindow::Impl::prefsChanged(tr_quark const key)
|
||||
|
@ -537,7 +547,7 @@ MainWindow::Impl::Impl(
|
|||
, scroll_(gtr_get_widget<Gtk::ScrolledWindow>(builder, "torrents_view_scroll"))
|
||||
, view_(gtr_get_widget<Gtk::TreeView>(builder, "torrents_view"))
|
||||
, toolbar_(gtr_get_widget<Gtk::Widget>(builder, "toolbar"))
|
||||
, filter_(gtr_get_widget_derived<FilterBar>(builder, "filterbar", core_->get_session(), core_->get_model()))
|
||||
, filter_(gtr_get_widget_derived<FilterBar>(builder, "filterbar", core_))
|
||||
, status_(gtr_get_widget<Gtk::Widget>(builder, "statusbar"))
|
||||
, ul_lb_(gtr_get_widget<Gtk::Label>(builder, "upload_speed_label"))
|
||||
, dl_lb_(gtr_get_widget<Gtk::Label>(builder, "download_speed_label"))
|
||||
|
@ -678,12 +688,13 @@ void MainWindow::Impl::updateSpeeds()
|
|||
auto up_speed = double{};
|
||||
|
||||
auto const model = core_->get_model();
|
||||
for (auto const& row : model->children())
|
||||
for (auto i = 0U, count = model->get_n_items(); i < count; ++i)
|
||||
{
|
||||
dn_count += row.get_value(torrent_cols.active_peers_down);
|
||||
dn_speed += row.get_value(torrent_cols.speed_down);
|
||||
up_count += row.get_value(torrent_cols.active_peers_up);
|
||||
up_speed += row.get_value(torrent_cols.speed_up);
|
||||
auto const torrent = gtr_ptr_dynamic_cast<Torrent>(model->get_object(i));
|
||||
dn_count += torrent->get_active_peers_down();
|
||||
dn_speed += torrent->get_speed_down();
|
||||
up_count += torrent->get_active_peers_up();
|
||||
up_speed += torrent->get_speed_up();
|
||||
}
|
||||
|
||||
dl_lb_->set_text(fmt::format(_("{download_speed} ▼"), fmt::arg("download_speed", tr_formatter_speed_KBps(dn_speed))));
|
||||
|
@ -708,16 +719,48 @@ void MainWindow::Impl::refresh()
|
|||
}
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeSelection> MainWindow::get_selection() const
|
||||
{
|
||||
return impl_->get_selection();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeSelection> MainWindow::Impl::get_selection() const
|
||||
{
|
||||
return view_->get_selection();
|
||||
}
|
||||
|
||||
void MainWindow::for_each_selected_torrent(std::function<void(Glib::RefPtr<Torrent> const&)> callback) const
|
||||
{
|
||||
for_each_selected_torrent_until(sigc::bind_return(callback, false));
|
||||
}
|
||||
|
||||
bool MainWindow::for_each_selected_torrent_until(std::function<bool(Glib::RefPtr<Torrent> const&)> callback) const
|
||||
{
|
||||
static auto const& self_col = Torrent::get_columns().self;
|
||||
|
||||
auto const selection = impl_->get_selection();
|
||||
auto const model = selection->get_model();
|
||||
bool result = false;
|
||||
|
||||
for (auto const& path : selection->get_selected_rows())
|
||||
{
|
||||
auto const torrent = Glib::make_refptr_for_instance(model->get_iter(path)->get_value(self_col));
|
||||
torrent->reference();
|
||||
if (callback(torrent))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MainWindow::select_all()
|
||||
{
|
||||
impl_->get_selection()->select_all();
|
||||
}
|
||||
|
||||
void MainWindow::unselect_all()
|
||||
{
|
||||
impl_->get_selection()->unselect_all();
|
||||
}
|
||||
|
||||
void MainWindow::set_busy(bool isBusy)
|
||||
{
|
||||
if (get_realized())
|
||||
|
@ -733,3 +776,8 @@ void MainWindow::set_busy(bool isBusy)
|
|||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
sigc::signal<void()>& MainWindow::signal_selection_changed()
|
||||
{
|
||||
return impl_->signal_selection_changed();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <libtransmission/tr-macros.h>
|
||||
|
||||
class Session;
|
||||
class Torrent;
|
||||
|
||||
class MainWindow : public Gtk::ApplicationWindow
|
||||
{
|
||||
|
@ -31,11 +32,17 @@ public:
|
|||
Glib::RefPtr<Gio::ActionGroup> const& actions,
|
||||
Glib::RefPtr<Session> const& core);
|
||||
|
||||
Glib::RefPtr<Gtk::TreeSelection> get_selection() const;
|
||||
void for_each_selected_torrent(std::function<void(Glib::RefPtr<Torrent> const&)> callback) const;
|
||||
bool for_each_selected_torrent_until(std::function<bool(Glib::RefPtr<Torrent> const&)> callback) const;
|
||||
|
||||
void select_all();
|
||||
void unselect_all();
|
||||
|
||||
void set_busy(bool isBusy);
|
||||
void refresh();
|
||||
|
||||
sigc::signal<void()>& signal_selection_changed();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> const impl_;
|
||||
|
|
|
@ -120,7 +120,7 @@ void OptionsDialog::Impl::addResponseCB(int response)
|
|||
tr_torrentStart(tor_);
|
||||
}
|
||||
|
||||
core_->add_torrent(tor_, false);
|
||||
core_->add_torrent(Torrent::create(tor_), false);
|
||||
|
||||
if (trash_check_->get_active())
|
||||
{
|
||||
|
|
649
gtk/Session.cc
649
gtk/Session.cc
|
@ -31,10 +31,13 @@
|
|||
#include <libtransmission/variant.h>
|
||||
|
||||
#include "Actions.h"
|
||||
#include "ListModelAdapter.h"
|
||||
#include "Notify.h"
|
||||
#include "Prefs.h"
|
||||
#include "PrefsDialog.h"
|
||||
#include "Session.h"
|
||||
#include "Torrent.h"
|
||||
#include "TorrentSorter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
@ -61,44 +64,24 @@ TrVariantPtr create_variant(tr_variant& other)
|
|||
return result;
|
||||
}
|
||||
|
||||
class ScopedModelSortBlocker
|
||||
{
|
||||
public:
|
||||
explicit ScopedModelSortBlocker(Gtk::TreeSortable& model)
|
||||
: model_(model)
|
||||
{
|
||||
model_.get_sort_column_id(sort_column_id_, sort_type_);
|
||||
model_.set_sort_column(Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, TR_GTK_SORT_TYPE(ASCENDING));
|
||||
}
|
||||
|
||||
~ScopedModelSortBlocker()
|
||||
{
|
||||
model_.set_sort_column(sort_column_id_, sort_type_);
|
||||
}
|
||||
|
||||
TR_DISABLE_COPY_MOVE(ScopedModelSortBlocker)
|
||||
|
||||
private:
|
||||
Gtk::TreeSortable& model_;
|
||||
int sort_column_id_ = -1;
|
||||
Gtk::SortType sort_type_ = TR_GTK_SORT_TYPE(ASCENDING);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class Session::Impl
|
||||
{
|
||||
using SortModel = IF_GTKMM4(Gtk::SortListModel, Gtk::TreeModelSort);
|
||||
|
||||
public:
|
||||
Impl(Session& core, tr_session* session);
|
||||
~Impl();
|
||||
|
||||
tr_session* close();
|
||||
|
||||
Glib::RefPtr<Gtk::ListStore> get_raw_model() const;
|
||||
Glib::RefPtr<Gtk::TreeModelSort> get_model();
|
||||
Glib::RefPtr<Gtk::TreeModelSort const> get_model() const;
|
||||
Glib::RefPtr<Gio::ListStore<Torrent>> get_raw_model() const;
|
||||
Glib::RefPtr<SortModel> get_model();
|
||||
tr_session* get_session() const;
|
||||
|
||||
std::pair<Glib::RefPtr<Torrent>, guint> find_torrent_by_id(tr_torrent_id_t torrent_id) const;
|
||||
|
||||
size_t get_active_torrent_count() const;
|
||||
|
||||
void update();
|
||||
|
@ -106,7 +89,7 @@ public:
|
|||
|
||||
void add_files(std::vector<Glib::RefPtr<Gio::File>> const& files, bool do_start, bool do_prompt, bool do_notify);
|
||||
int add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify);
|
||||
void add_torrent(tr_torrent* tor, bool do_notify);
|
||||
void add_torrent(Glib::RefPtr<Torrent> const& torrent, bool do_notify);
|
||||
bool add_from_url(Glib::ustring const& url);
|
||||
|
||||
void send_rpc_request(tr_variant const* request, int64_t tag, std::function<void(tr_variant&)> const& response_func);
|
||||
|
@ -143,6 +126,11 @@ public:
|
|||
return signal_port_tested_;
|
||||
}
|
||||
|
||||
auto& signal_torrents_changed()
|
||||
{
|
||||
return signal_torrents_changed_;
|
||||
}
|
||||
|
||||
private:
|
||||
Glib::RefPtr<Session> get_core_ptr() const;
|
||||
|
||||
|
@ -159,9 +147,7 @@ private:
|
|||
bool do_prompt,
|
||||
bool do_notify);
|
||||
|
||||
tr_torrent* create_new_torrent(tr_ctor* ctor);
|
||||
|
||||
void set_sort_mode(std::string_view mode, bool is_reversed);
|
||||
Glib::RefPtr<Torrent> create_new_torrent(tr_ctor* ctor);
|
||||
|
||||
void maybe_inhibit_hibernation();
|
||||
void set_hibernation_allowed(bool allowed);
|
||||
|
@ -178,7 +164,7 @@ private:
|
|||
void on_pref_changed(tr_quark key);
|
||||
|
||||
void on_torrent_completeness_changed(tr_torrent* tor, tr_completeness completeness, bool was_running);
|
||||
void on_torrent_metadata_changed(tr_torrent* tor);
|
||||
void on_torrent_metadata_changed(tr_torrent* raw_torrent);
|
||||
|
||||
private:
|
||||
Session& core_;
|
||||
|
@ -189,6 +175,7 @@ private:
|
|||
sigc::signal<void(bool)> signal_busy_;
|
||||
sigc::signal<void(tr_quark)> signal_prefs_changed_;
|
||||
sigc::signal<void(bool)> signal_port_tested_;
|
||||
sigc::signal<void(std::unordered_set<tr_torrent_id_t> const&, Torrent::ChangeFlags)> signal_torrents_changed_;
|
||||
|
||||
Glib::RefPtr<Gio::FileMonitor> monitor_;
|
||||
sigc::connection monitor_tag_;
|
||||
|
@ -202,33 +189,12 @@ private:
|
|||
bool dbus_error_ = false;
|
||||
guint inhibit_cookie_ = 0;
|
||||
gint busy_count_ = 0;
|
||||
Glib::RefPtr<Gtk::ListStore> raw_model_;
|
||||
Glib::RefPtr<Gtk::TreeModelSort> sorted_model_;
|
||||
Glib::RefPtr<Gio::ListStore<Torrent>> raw_model_;
|
||||
Glib::RefPtr<SortModel> sorted_model_;
|
||||
Glib::RefPtr<TorrentSorter> sorter_ = TorrentSorter::create();
|
||||
tr_session* session_ = nullptr;
|
||||
};
|
||||
|
||||
TorrentModelColumns::TorrentModelColumns() noexcept
|
||||
{
|
||||
add(name_collated);
|
||||
add(torrent);
|
||||
add(torrent_id);
|
||||
add(speed_up);
|
||||
add(speed_down);
|
||||
add(active_peers_up);
|
||||
add(active_peers_down);
|
||||
add(recheck_progress);
|
||||
add(active);
|
||||
add(activity);
|
||||
add(finished);
|
||||
add(priority);
|
||||
add(queue_position);
|
||||
add(trackers);
|
||||
add(error);
|
||||
add(active_peer_count);
|
||||
}
|
||||
|
||||
TorrentModelColumns const torrent_cols;
|
||||
|
||||
Glib::RefPtr<Session> Session::Impl::get_core_ptr() const
|
||||
{
|
||||
core_.reference();
|
||||
|
@ -239,22 +205,22 @@ Glib::RefPtr<Session> Session::Impl::get_core_ptr() const
|
|||
****
|
||||
***/
|
||||
|
||||
Glib::RefPtr<Gtk::ListStore> Session::Impl::get_raw_model() const
|
||||
Glib::RefPtr<Gio::ListStore<Torrent>> Session::Impl::get_raw_model() const
|
||||
{
|
||||
return raw_model_;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModel> Session::get_model() const
|
||||
Glib::RefPtr<Gio::ListModel> Session::get_model() const
|
||||
{
|
||||
return impl_->get_raw_model();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Session::Model> Session::get_sorted_model() const
|
||||
{
|
||||
return impl_->get_model();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModelSort> Session::Impl::get_model()
|
||||
{
|
||||
return sorted_model_;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TreeModelSort const> Session::Impl::get_model() const
|
||||
Glib::RefPtr<Session::Impl::SortModel> Session::Impl::get_model()
|
||||
{
|
||||
return sorted_model_;
|
||||
}
|
||||
|
@ -300,278 +266,6 @@ void Session::Impl::dec_busy()
|
|||
add_to_busy(-1);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
**** SORTING THE MODEL
|
||||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int compare_generic(T const& a, T const& b)
|
||||
{
|
||||
if (a < b)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a > b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr bool is_valid_eta(time_t t)
|
||||
{
|
||||
return t != TR_ETA_NOT_AVAIL && t != TR_ETA_UNKNOWN;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int compare_eta(time_t a, time_t b)
|
||||
{
|
||||
bool const a_valid = is_valid_eta(a);
|
||||
bool const b_valid = is_valid_eta(b);
|
||||
|
||||
if (!a_valid && !b_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!a_valid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!b_valid)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -compare_generic(a, b);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int compare_ratio(double a, double b)
|
||||
{
|
||||
if (static_cast<int>(a) == TR_RATIO_INF && static_cast<int>(b) == TR_RATIO_INF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (static_cast<int>(a) == TR_RATIO_INF)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (static_cast<int>(b) == TR_RATIO_INF)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return compare_generic(a, b);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_name(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
return a->get_value(torrent_cols.name_collated).compare(b->get_value(torrent_cols.name_collated));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_queue(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto const* const sa = tr_torrentStatCached(static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent)));
|
||||
auto const* const sb = tr_torrentStatCached(static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent)));
|
||||
|
||||
return sb->queuePosition - sa->queuePosition;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_ratio(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
auto const* const sa = tr_torrentStatCached(static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent)));
|
||||
auto const* const sb = tr_torrentStatCached(static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent)));
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_ratio(sa->ratio, sb->ratio);
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_queue(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_activity(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
auto* const ta = static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent));
|
||||
auto* const tb = static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent));
|
||||
auto const aUp = a->get_value(torrent_cols.speed_up);
|
||||
auto const aDown = a->get_value(torrent_cols.speed_down);
|
||||
auto const bUp = b->get_value(torrent_cols.speed_up);
|
||||
auto const bDown = b->get_value(torrent_cols.speed_down);
|
||||
|
||||
ret = compare_generic(aUp + aDown, bUp + bDown);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
auto const* const sa = tr_torrentStatCached(ta);
|
||||
auto const* const sb = tr_torrentStatCached(tb);
|
||||
ret = compare_generic(sa->peersSendingToUs + sa->peersGettingFromUs, sb->peersSendingToUs + sb->peersGettingFromUs);
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_queue(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_age(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto* const ta = static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent));
|
||||
auto* const tb = static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent));
|
||||
int ret = compare_generic(tr_torrentStatCached(ta)->addedDate, tr_torrentStatCached(tb)->addedDate);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_name(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_size(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto const size_a = tr_torrentTotalSize(static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent)));
|
||||
auto const size_b = tr_torrentTotalSize(static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent)));
|
||||
int ret = compare_generic(size_a, size_b);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_name(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_progress(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto const* const sa = tr_torrentStatCached(static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent)));
|
||||
auto const* const sb = tr_torrentStatCached(static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent)));
|
||||
int ret = compare_generic(sa->percentComplete, sb->percentComplete);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_generic(sa->seedRatioPercentDone, sb->seedRatioPercentDone);
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_ratio(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_eta(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto const* const sa = tr_torrentStatCached(static_cast<tr_torrent*>(a->get_value(torrent_cols.torrent)));
|
||||
auto const* const sb = tr_torrentStatCached(static_cast<tr_torrent*>(b->get_value(torrent_cols.torrent)));
|
||||
int ret = compare_eta(sa->eta, sb->eta);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_name(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_state(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
|
||||
{
|
||||
auto const sa = a->get_value(torrent_cols.activity);
|
||||
auto const sb = b->get_value(torrent_cols.activity);
|
||||
int ret = compare_generic(sa, sb);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = compare_by_queue(a, b);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Session::Impl::set_sort_mode(std::string_view mode, bool is_reversed)
|
||||
{
|
||||
auto const& col = torrent_cols.torrent;
|
||||
Gtk::TreeSortable::SlotCompare sort_func;
|
||||
auto type = is_reversed ? TR_GTK_SORT_TYPE(ASCENDING) : TR_GTK_SORT_TYPE(DESCENDING);
|
||||
auto const sortable = get_model();
|
||||
|
||||
if (mode == "sort-by-activity")
|
||||
{
|
||||
sort_func = &compare_by_activity;
|
||||
}
|
||||
else if (mode == "sort-by-age")
|
||||
{
|
||||
sort_func = &compare_by_age;
|
||||
}
|
||||
else if (mode == "sort-by-progress")
|
||||
{
|
||||
sort_func = &compare_by_progress;
|
||||
}
|
||||
else if (mode == "sort-by-queue")
|
||||
{
|
||||
sort_func = &compare_by_queue;
|
||||
}
|
||||
else if (mode == "sort-by-time-left")
|
||||
{
|
||||
sort_func = &compare_by_eta;
|
||||
}
|
||||
else if (mode == "sort-by-ratio")
|
||||
{
|
||||
sort_func = &compare_by_ratio;
|
||||
}
|
||||
else if (mode == "sort-by-state")
|
||||
{
|
||||
sort_func = &compare_by_state;
|
||||
}
|
||||
else if (mode == "sort-by-size")
|
||||
{
|
||||
sort_func = &compare_by_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
sort_func = &compare_by_name;
|
||||
type = is_reversed ? TR_GTK_SORT_TYPE(DESCENDING) : TR_GTK_SORT_TYPE(ASCENDING);
|
||||
}
|
||||
|
||||
sortable->set_sort_func(col, sort_func);
|
||||
sortable->set_sort_column(col, type);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
**** WATCHDIR
|
||||
|
@ -773,13 +467,12 @@ void Session::Impl::on_pref_changed(tr_quark const key)
|
|||
switch (key)
|
||||
{
|
||||
case TR_KEY_sort_mode:
|
||||
sorter_->set_mode(gtr_pref_string_get(TR_KEY_sort_mode));
|
||||
break;
|
||||
|
||||
case TR_KEY_sort_reversed:
|
||||
{
|
||||
auto const mode = gtr_pref_string_get(TR_KEY_sort_mode);
|
||||
bool const is_reversed = gtr_pref_flag_get(TR_KEY_sort_reversed);
|
||||
set_sort_mode(mode, is_reversed);
|
||||
break;
|
||||
}
|
||||
sorter_->set_reversed(gtr_pref_flag_get(TR_KEY_sort_reversed));
|
||||
break;
|
||||
|
||||
case TR_KEY_peer_limit_global:
|
||||
tr_sessionSetPeerLimit(session_, gtr_pref_int_get(key));
|
||||
|
@ -825,10 +518,22 @@ Session::Impl::Impl(Session& core, tr_session* session)
|
|||
: core_(core)
|
||||
, session_(session)
|
||||
{
|
||||
raw_model_ = Gtk::ListStore::create(torrent_cols);
|
||||
sorted_model_ = Gtk::TreeModelSort::create(raw_model_);
|
||||
sorted_model_->set_default_sort_func(
|
||||
[](Gtk::TreeModel::const_iterator const& /*a*/, Gtk::TreeModel::const_iterator const& /*b*/) { return 0; });
|
||||
raw_model_ = Gio::ListStore<Torrent>::create();
|
||||
signal_torrents_changed_.connect(sigc::hide<0>(sigc::mem_fun(*sorter_.get(), &TorrentSorter::update)));
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sorted_model_ = Gtk::SortListModel::create(raw_model_, sorter_);
|
||||
#else
|
||||
static auto const& self_col = Torrent::get_columns().self;
|
||||
|
||||
auto const sort_func = [this](SortModel::const_iterator const& lhs, SortModel::const_iterator const& rhs)
|
||||
{
|
||||
return sorter_->compare(*lhs->get_value(self_col), *rhs->get_value(self_col));
|
||||
};
|
||||
|
||||
sorted_model_ = Gtk::TreeModelSort::create(ListModelAdapter::create<Torrent>(raw_model_));
|
||||
sorted_model_->set_default_sort_func(sort_func);
|
||||
sorter_->signal_changed().connect([this, sort_func]() { sorted_model_->set_default_sort_func(sort_func); });
|
||||
#endif
|
||||
|
||||
/* init from prefs & listen to pref changes */
|
||||
on_pref_changed(TR_KEY_sort_mode);
|
||||
|
@ -899,46 +604,48 @@ void Session::Impl::on_torrent_completeness_changed(tr_torrent* tor, tr_complete
|
|||
namespace
|
||||
{
|
||||
|
||||
Glib::ustring get_collated_name(tr_torrent const* tor)
|
||||
{
|
||||
return fmt::format("{}\t{}", Glib::ustring(tr_torrentName(tor)).lowercase(), tr_torrentView(tor).hash_string);
|
||||
}
|
||||
|
||||
struct metadata_callback_data
|
||||
{
|
||||
Session* core;
|
||||
tr_torrent_id_t torrent_id;
|
||||
};
|
||||
|
||||
Gtk::TreeModel::iterator find_row_from_torrent_id(Glib::RefPtr<Gtk::TreeModel> const& model, tr_torrent_id_t id)
|
||||
} // namespace
|
||||
|
||||
std::pair<Glib::RefPtr<Torrent>, guint> Session::Impl::find_torrent_by_id(tr_torrent_id_t torrent_id) const
|
||||
{
|
||||
for (auto& row : model->children())
|
||||
auto begin_position = 0U;
|
||||
auto end_position = raw_model_->get_n_items();
|
||||
|
||||
while (begin_position < end_position)
|
||||
{
|
||||
if (id == row.get_value(torrent_cols.torrent_id))
|
||||
auto const position = begin_position + (end_position - begin_position) / 2;
|
||||
auto const torrent = raw_model_->get_item(position);
|
||||
|
||||
if (auto const current_torrent_id = torrent->get_id(); current_torrent_id == torrent_id)
|
||||
{
|
||||
return TR_GTK_TREE_MODEL_CHILD_ITER(row);
|
||||
return { torrent, position };
|
||||
}
|
||||
else
|
||||
{
|
||||
(current_torrent_id < torrent_id ? begin_position : end_position) = position;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
|
||||
so delegate to the GTK+ thread before changing our list store... */
|
||||
void Session::Impl::on_torrent_metadata_changed(tr_torrent* tor)
|
||||
void Session::Impl::on_torrent_metadata_changed(tr_torrent* raw_torrent)
|
||||
{
|
||||
Glib::signal_idle().connect(
|
||||
[this, core = get_core_ptr(), torrent_id = tr_torrentId(tor)]()
|
||||
[this, core = get_core_ptr(), torrent_id = tr_torrentId(raw_torrent)]()
|
||||
{
|
||||
/* update the torrent's collated name */
|
||||
if (auto const* const tor2 = tr_torrentFindFromId(session_, torrent_id); tor2 != nullptr)
|
||||
if (auto const [torrent, position] = find_torrent_by_id(torrent_id); torrent != nullptr)
|
||||
{
|
||||
if (auto const iter = find_row_from_torrent_id(raw_model_, torrent_id); iter)
|
||||
{
|
||||
(*iter)[torrent_cols.name_collated] = get_collated_name(tor2);
|
||||
}
|
||||
torrent->update();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -951,70 +658,25 @@ void Session::Impl::on_torrent_metadata_changed(tr_torrent* tor)
|
|||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
void Session::add_torrent(Glib::RefPtr<Torrent> const& torrent, bool do_notify)
|
||||
{
|
||||
impl_->add_torrent(torrent, do_notify);
|
||||
}
|
||||
|
||||
unsigned int build_torrent_trackers_hash(tr_torrent* tor)
|
||||
void Session::Impl::add_torrent(Glib::RefPtr<Torrent> const& torrent, bool do_notify)
|
||||
{
|
||||
auto hash = uint64_t{};
|
||||
|
||||
for (size_t i = 0, n = tr_torrentTrackerCount(tor); i < n; ++i)
|
||||
if (torrent != nullptr)
|
||||
{
|
||||
for (auto const ch : std::string_view{ tr_torrentTracker(tor, i).announce })
|
||||
{
|
||||
hash = (hash << 4) ^ (hash >> 28) ^ ch;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool is_torrent_active(tr_stat const* st)
|
||||
{
|
||||
return st->peersSendingToUs > 0 || st->peersGettingFromUs > 0 || st->activity == TR_STATUS_CHECK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Session::add_torrent(tr_torrent* tor, bool do_notify)
|
||||
{
|
||||
ScopedModelSortBlocker const disable_sort(*impl_->get_model().get());
|
||||
impl_->add_torrent(tor, do_notify);
|
||||
}
|
||||
|
||||
void Session::Impl::add_torrent(tr_torrent* tor, bool do_notify)
|
||||
{
|
||||
if (tor != nullptr)
|
||||
{
|
||||
tr_stat const* st = tr_torrentStat(tor);
|
||||
auto const collated = get_collated_name(tor);
|
||||
auto const trackers_hash = build_torrent_trackers_hash(tor);
|
||||
auto const store = get_raw_model();
|
||||
|
||||
auto const iter = store->append();
|
||||
(*iter)[torrent_cols.name_collated] = collated;
|
||||
(*iter)[torrent_cols.torrent] = tor;
|
||||
(*iter)[torrent_cols.torrent_id] = tr_torrentId(tor);
|
||||
(*iter)[torrent_cols.speed_up] = st->pieceUploadSpeed_KBps;
|
||||
(*iter)[torrent_cols.speed_down] = st->pieceDownloadSpeed_KBps;
|
||||
(*iter)[torrent_cols.active_peers_up] = st->peersGettingFromUs;
|
||||
(*iter)[torrent_cols.active_peers_down] = st->peersSendingToUs + st->webseedsSendingToUs;
|
||||
(*iter)[torrent_cols.recheck_progress] = st->recheckProgress;
|
||||
(*iter)[torrent_cols.active] = is_torrent_active(st);
|
||||
(*iter)[torrent_cols.activity] = st->activity;
|
||||
(*iter)[torrent_cols.finished] = st->finished;
|
||||
(*iter)[torrent_cols.priority] = tr_torrentGetPriority(tor);
|
||||
(*iter)[torrent_cols.queue_position] = st->queuePosition;
|
||||
(*iter)[torrent_cols.trackers] = trackers_hash;
|
||||
raw_model_->insert_sorted(torrent, &Torrent::compare_by_id);
|
||||
|
||||
if (do_notify)
|
||||
{
|
||||
gtr_notify_torrent_added(get_core_ptr(), tr_torrentId(tor));
|
||||
gtr_notify_torrent_added(get_core_ptr(), torrent->get_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr_torrent* Session::Impl::create_new_torrent(tr_ctor* ctor)
|
||||
Glib::RefPtr<Torrent> Session::Impl::create_new_torrent(tr_ctor* ctor)
|
||||
{
|
||||
bool do_trash = false;
|
||||
|
||||
|
@ -1041,7 +703,7 @@ tr_torrent* Session::Impl::create_new_torrent(tr_ctor* ctor)
|
|||
}
|
||||
}
|
||||
|
||||
return tor;
|
||||
return Torrent::create(tor);
|
||||
}
|
||||
|
||||
int Session::Impl::add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify)
|
||||
|
@ -1068,7 +730,6 @@ int Session::Impl::add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify)
|
|||
|
||||
if (!do_prompt)
|
||||
{
|
||||
ScopedModelSortBlocker const disable_sort(*sorted_model_.get());
|
||||
add_torrent(create_new_torrent(ctor), do_notify);
|
||||
tr_ctorFree(ctor);
|
||||
return 0;
|
||||
|
@ -1249,31 +910,22 @@ void Session::Impl::torrents_added()
|
|||
|
||||
void Session::torrent_changed(tr_torrent_id_t id)
|
||||
{
|
||||
auto const model = impl_->get_raw_model();
|
||||
|
||||
if (auto const iter = find_row_from_torrent_id(model, id); iter)
|
||||
if (auto const [torrent, position] = impl_->find_torrent_by_id(id); torrent != nullptr)
|
||||
{
|
||||
model->row_changed(model->get_path(iter), iter);
|
||||
torrent->update();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::remove_torrent(tr_torrent_id_t id, bool delete_files)
|
||||
{
|
||||
auto* tor = find_torrent(id);
|
||||
|
||||
if (tor != nullptr)
|
||||
if (auto const [torrent, position] = impl_->find_torrent_by_id(id); torrent != nullptr)
|
||||
{
|
||||
/* remove from the gui */
|
||||
auto const model = impl_->get_raw_model();
|
||||
|
||||
if (auto const iter = find_row_from_torrent_id(model, id); iter)
|
||||
{
|
||||
model->erase(iter);
|
||||
}
|
||||
impl_->get_raw_model()->remove(position);
|
||||
|
||||
/* remove the torrent */
|
||||
tr_torrentRemove(
|
||||
tor,
|
||||
&torrent->get_underlying(),
|
||||
delete_files,
|
||||
[](char const* filename, void* /*user_data*/, tr_error** error)
|
||||
{ return gtr_file_trash_or_remove(filename, error); },
|
||||
|
@ -1296,109 +948,28 @@ void Session::load(bool force_paused)
|
|||
auto const n_torrents = tr_sessionLoadTorrents(session, ctor);
|
||||
tr_ctorFree(ctor);
|
||||
|
||||
ScopedModelSortBlocker const disable_sort(*impl_->get_model().get());
|
||||
auto raw_torrents = std::vector<tr_torrent*>{};
|
||||
raw_torrents.resize(n_torrents);
|
||||
tr_sessionGetAllTorrents(session, std::data(raw_torrents), std::size(raw_torrents));
|
||||
|
||||
auto torrents = std::vector<tr_torrent*>{};
|
||||
torrents.resize(n_torrents);
|
||||
tr_sessionGetAllTorrents(session, std::data(torrents), std::size(torrents));
|
||||
for (auto* tor : torrents)
|
||||
{
|
||||
impl_->add_torrent(tor, false);
|
||||
}
|
||||
auto torrents = std::vector<Glib::RefPtr<Torrent>>();
|
||||
torrents.reserve(raw_torrents.size());
|
||||
std::transform(raw_torrents.begin(), raw_torrents.end(), std::back_inserter(torrents), &Torrent::create);
|
||||
std::sort(torrents.begin(), torrents.end(), &Torrent::less_by_id);
|
||||
|
||||
auto const model = impl_->get_raw_model();
|
||||
model->splice(0, model->get_n_items(), torrents);
|
||||
}
|
||||
|
||||
void Session::clear()
|
||||
{
|
||||
impl_->get_raw_model()->clear();
|
||||
impl_->get_raw_model()->remove_all();
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int gtr_compare_double(double const a, double const b, int decimal_places)
|
||||
{
|
||||
auto const ia = int64_t(a * pow(10, decimal_places));
|
||||
auto const ib = int64_t(b * pow(10, decimal_places));
|
||||
|
||||
if (ia < ib)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ia > ib)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void update_foreach(Gtk::TreeModel::Row& row)
|
||||
{
|
||||
/* get the old states */
|
||||
auto* const tor = static_cast<tr_torrent*>(row.get_value(torrent_cols.torrent));
|
||||
auto const oldActive = row.get_value(torrent_cols.active);
|
||||
auto const oldActivePeerCount = row.get_value(torrent_cols.active_peer_count);
|
||||
auto const oldUploadPeerCount = row.get_value(torrent_cols.active_peers_up);
|
||||
auto const oldDownloadPeerCount = row.get_value(torrent_cols.active_peers_down);
|
||||
auto const oldError = row.get_value(torrent_cols.error);
|
||||
auto const oldActivity = row.get_value(torrent_cols.activity);
|
||||
auto const oldFinished = row.get_value(torrent_cols.finished);
|
||||
auto const oldPriority = row.get_value(torrent_cols.priority);
|
||||
auto const oldQueuePosition = row.get_value(torrent_cols.queue_position);
|
||||
auto const oldTrackers = row.get_value(torrent_cols.trackers);
|
||||
auto const oldUpSpeed = row.get_value(torrent_cols.speed_up);
|
||||
auto const oldRecheckProgress = row.get_value(torrent_cols.recheck_progress);
|
||||
auto const oldDownSpeed = row.get_value(torrent_cols.speed_down);
|
||||
|
||||
/* get the new states */
|
||||
auto const* const st = tr_torrentStat(tor);
|
||||
auto const newActive = is_torrent_active(st);
|
||||
auto const newActivity = st->activity;
|
||||
auto const newFinished = st->finished;
|
||||
auto const newPriority = tr_torrentGetPriority(tor);
|
||||
auto const newQueuePosition = st->queuePosition;
|
||||
auto const newTrackers = build_torrent_trackers_hash(tor);
|
||||
auto const newUpSpeed = st->pieceUploadSpeed_KBps;
|
||||
auto const newDownSpeed = st->pieceDownloadSpeed_KBps;
|
||||
auto const newRecheckProgress = st->recheckProgress;
|
||||
auto const newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
|
||||
auto const newDownloadPeerCount = st->peersSendingToUs;
|
||||
auto const newUploadPeerCount = st->peersGettingFromUs + st->webseedsSendingToUs;
|
||||
auto const newError = st->error;
|
||||
|
||||
/* updating the model triggers off resort/refresh,
|
||||
so don't do it unless something's actually changed... */
|
||||
if (newActive != oldActive || newActivity != oldActivity || newFinished != oldFinished || newPriority != oldPriority ||
|
||||
newQueuePosition != oldQueuePosition || newError != oldError || newActivePeerCount != oldActivePeerCount ||
|
||||
newDownloadPeerCount != oldDownloadPeerCount || newUploadPeerCount != oldUploadPeerCount ||
|
||||
newTrackers != oldTrackers || gtr_compare_double(newUpSpeed, oldUpSpeed, 2) != 0 ||
|
||||
gtr_compare_double(newDownSpeed, oldDownSpeed, 2) != 0 ||
|
||||
gtr_compare_double(newRecheckProgress, oldRecheckProgress, 2) != 0)
|
||||
{
|
||||
row[torrent_cols.active] = newActive;
|
||||
row[torrent_cols.active_peer_count] = newActivePeerCount;
|
||||
row[torrent_cols.active_peers_up] = newUploadPeerCount;
|
||||
row[torrent_cols.active_peers_down] = newDownloadPeerCount;
|
||||
row[torrent_cols.error] = newError;
|
||||
row[torrent_cols.activity] = newActivity;
|
||||
row[torrent_cols.finished] = newFinished;
|
||||
row[torrent_cols.priority] = newPriority;
|
||||
row[torrent_cols.queue_position] = newQueuePosition;
|
||||
row[torrent_cols.trackers] = newTrackers;
|
||||
row[torrent_cols.speed_up] = newUpSpeed;
|
||||
row[torrent_cols.speed_down] = newDownSpeed;
|
||||
row[torrent_cols.recheck_progress] = newRecheckProgress;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Session::update()
|
||||
{
|
||||
impl_->update();
|
||||
|
@ -1419,14 +990,27 @@ void Session::start_now(tr_torrent_id_t id)
|
|||
|
||||
void Session::Impl::update()
|
||||
{
|
||||
auto torrent_ids = std::unordered_set<tr_torrent_id_t>();
|
||||
auto changes = Torrent::ChangeFlags();
|
||||
|
||||
/* update the model */
|
||||
for (auto row : raw_model_->children())
|
||||
for (auto i = 0U, count = raw_model_->get_n_items(); i < count; ++i)
|
||||
{
|
||||
update_foreach(row);
|
||||
auto const torrent = raw_model_->get_item(i);
|
||||
if (auto const torrent_changes = torrent->update(); torrent_changes.any())
|
||||
{
|
||||
torrent_ids.insert(torrent->get_id());
|
||||
changes |= torrent_changes;
|
||||
}
|
||||
}
|
||||
|
||||
/* update hibernation */
|
||||
maybe_inhibit_hibernation();
|
||||
|
||||
if (changes.any())
|
||||
{
|
||||
signal_torrents_changed_.emit(torrent_ids, changes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1576,7 +1160,7 @@ void Session::set_pref(tr_quark const key, int newval)
|
|||
|
||||
void Session::set_pref(tr_quark const key, double newval)
|
||||
{
|
||||
if (gtr_compare_double(newval, gtr_pref_double_get(key), 4) != 0)
|
||||
if (std::fabs(newval - gtr_pref_double_get(key)) >= 0.0001)
|
||||
{
|
||||
gtr_pref_double_set(key, newval);
|
||||
impl_->commit_prefs_change(key);
|
||||
|
@ -1738,7 +1322,7 @@ void Session::exec(tr_variant const* request)
|
|||
|
||||
size_t Session::get_torrent_count() const
|
||||
{
|
||||
return impl_->get_raw_model()->children().size();
|
||||
return impl_->get_raw_model()->get_n_items();
|
||||
}
|
||||
|
||||
size_t Session::get_active_torrent_count() const
|
||||
|
@ -1750,9 +1334,9 @@ size_t Session::Impl::get_active_torrent_count() const
|
|||
{
|
||||
size_t activeCount = 0;
|
||||
|
||||
for (auto const& row : raw_model_->children())
|
||||
for (auto i = 0U, count = raw_model_->get_n_items(); i < count; ++i)
|
||||
{
|
||||
if (row.get_value(torrent_cols.activity) != TR_STATUS_STOPPED)
|
||||
if (raw_model_->get_item(i)->get_activity() != TR_STATUS_STOPPED)
|
||||
{
|
||||
++activeCount;
|
||||
}
|
||||
|
@ -1822,3 +1406,8 @@ sigc::signal<void(bool)>& Session::signal_port_tested()
|
|||
{
|
||||
return impl_->signal_port_tested();
|
||||
}
|
||||
|
||||
sigc::signal<void(std::unordered_set<tr_torrent_id_t> const&, Torrent::ChangeFlags)>& Session::signal_torrents_changed()
|
||||
{
|
||||
return impl_->signal_torrents_changed();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <giomm.h>
|
||||
|
@ -17,6 +18,9 @@
|
|||
#include <libtransmission/tr-macros.h>
|
||||
#include <libtransmission/variant.h>
|
||||
|
||||
#include "Torrent.h"
|
||||
#include "Utils.h"
|
||||
|
||||
class Session : public Glib::Object
|
||||
{
|
||||
public:
|
||||
|
@ -27,6 +31,8 @@ public:
|
|||
ERR_NO_MORE_TORRENTS = 1000 /* finished adding a batch */
|
||||
};
|
||||
|
||||
using Model = IF_GTKMM4(Gio::ListModel, Gtk::TreeModel);
|
||||
|
||||
public:
|
||||
~Session() override;
|
||||
|
||||
|
@ -36,8 +42,8 @@ public:
|
|||
|
||||
tr_session* close();
|
||||
|
||||
/* Return the model used without incrementing the reference count */
|
||||
Glib::RefPtr<Gtk::TreeModel> get_model() const;
|
||||
Glib::RefPtr<Gio::ListModel> get_model() const;
|
||||
Glib::RefPtr<Model> get_sorted_model() const;
|
||||
|
||||
void clear();
|
||||
|
||||
|
@ -76,7 +82,7 @@ public:
|
|||
void add_ctor(tr_ctor* ctor);
|
||||
|
||||
/** Add a torrent. */
|
||||
void add_torrent(tr_torrent*, bool do_notify);
|
||||
void add_torrent(Glib::RefPtr<Torrent> const& torrent, bool do_notify);
|
||||
|
||||
/**
|
||||
* Notifies listeners that torrents have been added.
|
||||
|
@ -128,6 +134,7 @@ public:
|
|||
sigc::signal<void(bool)>& signal_busy();
|
||||
sigc::signal<void(tr_quark)>& signal_prefs_changed();
|
||||
sigc::signal<void(bool)>& signal_port_tested();
|
||||
sigc::signal<void(std::unordered_set<tr_torrent_id_t> const&, Torrent::ChangeFlags)>& signal_torrents_changed();
|
||||
|
||||
protected:
|
||||
explicit Session(tr_session* session);
|
||||
|
@ -136,36 +143,3 @@ private:
|
|||
class Impl;
|
||||
std::unique_ptr<Impl> const impl_;
|
||||
};
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
|
||||
class TorrentModelColumns : public Gtk::TreeModelColumnRecord
|
||||
{
|
||||
public:
|
||||
TorrentModelColumns() noexcept;
|
||||
|
||||
Gtk::TreeModelColumn<Glib::ustring> name_collated;
|
||||
Gtk::TreeModelColumn<gpointer> torrent;
|
||||
Gtk::TreeModelColumn<tr_torrent_id_t> torrent_id;
|
||||
Gtk::TreeModelColumn<double> speed_up;
|
||||
Gtk::TreeModelColumn<double> speed_down;
|
||||
Gtk::TreeModelColumn<int> active_peers_up;
|
||||
Gtk::TreeModelColumn<int> active_peers_down;
|
||||
Gtk::TreeModelColumn<double> recheck_progress;
|
||||
Gtk::TreeModelColumn<bool> active;
|
||||
Gtk::TreeModelColumn<tr_torrent_activity> activity;
|
||||
Gtk::TreeModelColumn<bool> finished;
|
||||
Gtk::TreeModelColumn<tr_priority_t> priority;
|
||||
Gtk::TreeModelColumn<size_t> queue_position;
|
||||
Gtk::TreeModelColumn<unsigned int> trackers;
|
||||
/* tr_stat.error
|
||||
* Tracked because ACTIVITY_FILTER_ERROR needs the row-changed events */
|
||||
Gtk::TreeModelColumn<int> error;
|
||||
/* tr_stat.{ peersSendingToUs + peersGettingFromUs + webseedsSendingToUs }
|
||||
* Tracked because ACTIVITY_FILTER_ACTIVE needs the row-changed events */
|
||||
Gtk::TreeModelColumn<int> active_peer_count;
|
||||
};
|
||||
|
||||
extern TorrentModelColumns const torrent_cols;
|
||||
|
|
|
@ -0,0 +1,766 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#include "IconCache.h"
|
||||
#include "Torrent.h"
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
Glib::Value<T>& column_value_cast(Glib::ValueBase& value, Gtk::TreeModelColumn<T> const&)
|
||||
{
|
||||
return static_cast<Glib::Value<T>&>(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename = std::enable_if_t<!std::is_floating_point_v<T>>>
|
||||
void update_cache_value(T& value, U&& new_value, Torrent::ChangeFlags& changes, Torrent::ChangeFlag flag)
|
||||
{
|
||||
if (value != new_value)
|
||||
{
|
||||
value = std::forward<U>(new_value);
|
||||
changes.set(flag);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename = std::enable_if_t<std::is_floating_point_v<T>>>
|
||||
void update_cache_value(T& value, U new_value, T epsilon, Torrent::ChangeFlags& changes, Torrent::ChangeFlag flag)
|
||||
{
|
||||
if (std::fabs(value - new_value) >= epsilon)
|
||||
{
|
||||
value = new_value;
|
||||
changes.set(flag);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int build_torrent_trackers_hash(tr_torrent const& torrent)
|
||||
{
|
||||
auto hash = uint64_t(0);
|
||||
|
||||
for (auto i = size_t(0), n = tr_torrentTrackerCount(&torrent); i < n; ++i)
|
||||
{
|
||||
for (auto const ch : std::string_view{ tr_torrentTracker(&torrent, i).announce })
|
||||
{
|
||||
hash = (hash << 4) ^ (hash >> 28) ^ ch;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string_view get_mime_type(tr_torrent const& torrent)
|
||||
{
|
||||
auto const n_files = tr_torrentFileCount(&torrent);
|
||||
|
||||
if (n_files == 0)
|
||||
{
|
||||
return UnknownMimeType;
|
||||
}
|
||||
|
||||
if (n_files > 1)
|
||||
{
|
||||
return DirectoryMimeType;
|
||||
}
|
||||
|
||||
auto const name = std::string_view(tr_torrentFile(&torrent, 0).name);
|
||||
|
||||
return name.find('/') != std::string_view::npos ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Torrent::Columns::Columns()
|
||||
{
|
||||
add(self);
|
||||
add(name_collated);
|
||||
}
|
||||
|
||||
class Torrent::Impl
|
||||
{
|
||||
public:
|
||||
struct Cache
|
||||
{
|
||||
Glib::ustring error_message;
|
||||
Glib::ustring name;
|
||||
Glib::ustring name_collated;
|
||||
|
||||
std::string_view mime_type;
|
||||
|
||||
uint64_t have_unchecked = {};
|
||||
uint64_t have_valid = {};
|
||||
uint64_t left_until_done = {};
|
||||
uint64_t size_when_done = {};
|
||||
uint64_t total_size = {};
|
||||
uint64_t uploaded_ever = {};
|
||||
|
||||
size_t queue_position = {};
|
||||
|
||||
time_t added_date = {};
|
||||
time_t eta = {};
|
||||
|
||||
tr_torrent_activity activity = {};
|
||||
|
||||
unsigned int trackers = {};
|
||||
int active_peer_count = {};
|
||||
int active_peers_down = {};
|
||||
int active_peers_up = {};
|
||||
int error_code = {};
|
||||
|
||||
uint16_t peers_connected = {};
|
||||
uint16_t peers_getting_from_us = {};
|
||||
uint16_t peers_sending_to_us = {};
|
||||
uint16_t webseeds_sending_to_us = {};
|
||||
|
||||
float activity_percent_done = {};
|
||||
float metadata_percent_complete = {};
|
||||
float percent_complete = {};
|
||||
float percent_done = {};
|
||||
float ratio = {};
|
||||
float recheck_progress = {};
|
||||
float seed_ratio = {};
|
||||
float seed_ratio_percent_done = {};
|
||||
float speed_down = {};
|
||||
float speed_up = {};
|
||||
|
||||
tr_priority_t priority = {};
|
||||
|
||||
bool active = {};
|
||||
bool finished = {};
|
||||
bool has_metadata = {};
|
||||
bool has_seed_ratio = {};
|
||||
bool stalled = {};
|
||||
};
|
||||
|
||||
public:
|
||||
Impl(Torrent& torrent, tr_torrent* raw_torrent);
|
||||
|
||||
tr_torrent* get_raw_torrent()
|
||||
{
|
||||
return raw_torrent_;
|
||||
}
|
||||
|
||||
Cache& get_cache()
|
||||
{
|
||||
return cache_;
|
||||
}
|
||||
|
||||
ChangeFlags update_cache();
|
||||
|
||||
void notify_property_changes(ChangeFlags changes) const;
|
||||
|
||||
void get_value(int column, Glib::ValueBase& value) const;
|
||||
|
||||
Glib::RefPtr<Gio::Icon> get_icon() const;
|
||||
Glib::ustring get_short_status_text() const;
|
||||
Glib::ustring get_long_progress_text() const;
|
||||
Glib::ustring get_long_status_text() const;
|
||||
|
||||
private:
|
||||
Glib::ustring get_short_transfer_text() const;
|
||||
Glib::ustring get_error_text() const;
|
||||
Glib::ustring get_activity_text() const;
|
||||
|
||||
private:
|
||||
Torrent& torrent_;
|
||||
tr_torrent* const raw_torrent_;
|
||||
|
||||
Cache cache_;
|
||||
};
|
||||
|
||||
Torrent::Impl::Impl(Torrent& torrent, tr_torrent* raw_torrent)
|
||||
: torrent_(torrent)
|
||||
, raw_torrent_(raw_torrent)
|
||||
{
|
||||
if (raw_torrent_ != nullptr)
|
||||
{
|
||||
update_cache();
|
||||
}
|
||||
}
|
||||
|
||||
Torrent::ChangeFlags Torrent::Impl::update_cache()
|
||||
{
|
||||
auto result = ChangeFlags();
|
||||
|
||||
auto const* const stats = tr_torrentStat(raw_torrent_);
|
||||
g_return_val_if_fail(stats != nullptr, Torrent::ChangeFlags());
|
||||
|
||||
auto seed_ratio = 0.0;
|
||||
auto const has_seed_ratio = tr_torrentGetSeedRatio(raw_torrent_, &seed_ratio);
|
||||
|
||||
update_cache_value(cache_.name, tr_torrentName(raw_torrent_), result, ChangeFlag::NAME);
|
||||
update_cache_value(cache_.speed_up, stats->pieceUploadSpeed_KBps, 0.01F, result, ChangeFlag::SPEED_UP);
|
||||
update_cache_value(cache_.speed_down, stats->pieceDownloadSpeed_KBps, 0.01F, result, ChangeFlag::SPEED_DOWN);
|
||||
update_cache_value(cache_.active_peers_up, stats->peersGettingFromUs, result, ChangeFlag::ACTIVE_PEERS_UP);
|
||||
update_cache_value(
|
||||
cache_.active_peers_down,
|
||||
stats->peersSendingToUs + stats->webseedsSendingToUs,
|
||||
result,
|
||||
ChangeFlag::ACTIVE_PEERS_DOWN);
|
||||
update_cache_value(cache_.recheck_progress, stats->recheckProgress, 0.01F, result, ChangeFlag::RECHECK_PROGRESS);
|
||||
update_cache_value(
|
||||
cache_.active,
|
||||
stats->peersSendingToUs > 0 || stats->peersGettingFromUs > 0 || stats->activity == TR_STATUS_CHECK,
|
||||
result,
|
||||
ChangeFlag::ACTIVE);
|
||||
update_cache_value(cache_.activity, stats->activity, result, ChangeFlag::ACTIVITY);
|
||||
update_cache_value(
|
||||
cache_.activity_percent_done,
|
||||
std::clamp(
|
||||
stats->activity == TR_STATUS_SEED && has_seed_ratio ? stats->seedRatioPercentDone : stats->percentDone,
|
||||
0.0F,
|
||||
1.0F),
|
||||
0.01F,
|
||||
result,
|
||||
ChangeFlag::PERCENT_DONE);
|
||||
update_cache_value(cache_.finished, stats->finished, result, ChangeFlag::FINISHED);
|
||||
update_cache_value(cache_.priority, tr_torrentGetPriority(raw_torrent_), result, ChangeFlag::PRIORITY);
|
||||
update_cache_value(cache_.queue_position, stats->queuePosition, result, ChangeFlag::QUEUE_POSITION);
|
||||
update_cache_value(cache_.trackers, build_torrent_trackers_hash(*raw_torrent_), result, ChangeFlag::TRACKERS);
|
||||
update_cache_value(cache_.error_code, stats->error, result, ChangeFlag::ERROR_CODE);
|
||||
update_cache_value(cache_.error_message, stats->errorString, result, ChangeFlag::ERROR_MESSAGE);
|
||||
update_cache_value(
|
||||
cache_.active_peer_count,
|
||||
stats->peersSendingToUs + stats->peersGettingFromUs + stats->webseedsSendingToUs,
|
||||
result,
|
||||
ChangeFlag::ACTIVE_PEER_COUNT);
|
||||
update_cache_value(cache_.mime_type, get_mime_type(*raw_torrent_), result, ChangeFlag::MIME_TYPE);
|
||||
update_cache_value(cache_.has_metadata, tr_torrentHasMetadata(raw_torrent_), result, ChangeFlag::HAS_METADATA);
|
||||
update_cache_value(cache_.stalled, stats->isStalled, result, ChangeFlag::STALLED);
|
||||
update_cache_value(cache_.ratio, stats->ratio, 0.01F, result, ChangeFlag::RATIO);
|
||||
|
||||
update_cache_value(cache_.added_date, stats->addedDate, result, ChangeFlag::ADDED_DATE);
|
||||
update_cache_value(cache_.eta, stats->eta, result, ChangeFlag::ETA);
|
||||
update_cache_value(cache_.percent_complete, stats->percentComplete, 0.01F, result, ChangeFlag::PERCENT_COMPLETE);
|
||||
update_cache_value(
|
||||
cache_.seed_ratio_percent_done,
|
||||
stats->seedRatioPercentDone,
|
||||
0.01F,
|
||||
result,
|
||||
ChangeFlag::SEED_RATIO_PERCENT_DONE);
|
||||
update_cache_value(cache_.total_size, tr_torrentTotalSize(raw_torrent_), result, ChangeFlag::TOTAL_SIZE);
|
||||
|
||||
update_cache_value(cache_.has_seed_ratio, has_seed_ratio, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.have_unchecked, stats->haveUnchecked, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.have_valid, stats->haveValid, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.left_until_done, stats->leftUntilDone, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.percent_done, stats->percentDone, 0.01F, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.seed_ratio, static_cast<float>(seed_ratio), 0.01F, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.size_when_done, stats->sizeWhenDone, result, ChangeFlag::LONG_PROGRESS);
|
||||
update_cache_value(cache_.uploaded_ever, stats->uploadedEver, result, ChangeFlag::LONG_PROGRESS);
|
||||
|
||||
update_cache_value(
|
||||
cache_.metadata_percent_complete,
|
||||
stats->metadataPercentComplete,
|
||||
0.01F,
|
||||
result,
|
||||
ChangeFlag::LONG_STATUS);
|
||||
update_cache_value(cache_.peers_connected, stats->peersConnected, result, ChangeFlag::LONG_STATUS);
|
||||
update_cache_value(cache_.peers_getting_from_us, stats->peersGettingFromUs, result, ChangeFlag::LONG_STATUS);
|
||||
update_cache_value(cache_.peers_sending_to_us, stats->peersSendingToUs, result, ChangeFlag::LONG_STATUS);
|
||||
update_cache_value(cache_.webseeds_sending_to_us, stats->webseedsSendingToUs, result, ChangeFlag::LONG_STATUS);
|
||||
|
||||
if (result.test(ChangeFlag::NAME))
|
||||
{
|
||||
cache_.name_collated = fmt::format("{}\t{}", cache_.name.lowercase(), tr_torrentView(raw_torrent_).hash_string);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Torrent::Impl::notify_property_changes(ChangeFlags changes) const
|
||||
{
|
||||
// Updating the model triggers off resort/refresh, so don't notify unless something's actually changed
|
||||
if (changes.none())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gtr_object_notify_emit(torrent_);
|
||||
}
|
||||
|
||||
void Torrent::Impl::get_value(int column, Glib::ValueBase& value) const
|
||||
{
|
||||
static auto const& columns = get_columns();
|
||||
|
||||
if (column == columns.self.index())
|
||||
{
|
||||
column_value_cast(value, columns.self).set(&torrent_);
|
||||
}
|
||||
else if (column == columns.name_collated.index())
|
||||
{
|
||||
column_value_cast(value, columns.name_collated).set(cache_.name_collated);
|
||||
}
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::Icon> Torrent::Impl::get_icon() const
|
||||
{
|
||||
return gtr_get_mime_type_icon(cache_.mime_type);
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_short_status_text() const
|
||||
{
|
||||
switch (cache_.activity)
|
||||
{
|
||||
case TR_STATUS_STOPPED:
|
||||
return cache_.finished ? _("Finished") : _("Paused");
|
||||
|
||||
case TR_STATUS_CHECK_WAIT:
|
||||
return _("Queued for verification");
|
||||
|
||||
case TR_STATUS_DOWNLOAD_WAIT:
|
||||
return _("Queued for download");
|
||||
|
||||
case TR_STATUS_SEED_WAIT:
|
||||
return _("Queued for seeding");
|
||||
|
||||
case TR_STATUS_CHECK:
|
||||
return fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("Verifying local data ({percent_done}% tested)"),
|
||||
fmt::arg("percent_done", tr_truncd(cache_.recheck_progress * 100.0, 1)));
|
||||
|
||||
case TR_STATUS_DOWNLOAD:
|
||||
case TR_STATUS_SEED:
|
||||
return fmt::format(
|
||||
FMT_STRING("{:s} {:s}"),
|
||||
get_short_transfer_text(),
|
||||
fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(cache_.ratio))));
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_long_progress_text() const
|
||||
{
|
||||
Glib::ustring gstr;
|
||||
|
||||
bool const isDone = cache_.left_until_done == 0;
|
||||
auto const haveTotal = cache_.have_unchecked + cache_.have_valid;
|
||||
bool const isSeed = cache_.have_valid >= cache_.total_size;
|
||||
|
||||
if (!isDone) // downloading
|
||||
{
|
||||
// 50 MB of 200 MB (25%)
|
||||
gstr += fmt::format(
|
||||
_("{current_size} of {complete_size} ({percent_done}%)"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(cache_.size_when_done)),
|
||||
fmt::arg("percent_done", tr_strpercent(cache_.percent_done * 100.0)));
|
||||
}
|
||||
else if (!isSeed && cache_.has_seed_ratio) // partial seed, seed ratio
|
||||
{
|
||||
// 50 MB of 200 MB (25%), uploaded 30 MB (Ratio: X%, Goal: Y%)
|
||||
gstr += fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
||||
fmt::arg("percent_complete", tr_strpercent(cache_.percent_complete * 100.0)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
||||
fmt::arg("ratio", tr_strlratio(cache_.ratio)),
|
||||
fmt::arg("seed_ratio", tr_strlratio(cache_.seed_ratio)));
|
||||
}
|
||||
else if (!isSeed) // partial seed, no seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio})"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
||||
fmt::arg("percent_complete", tr_strpercent(cache_.percent_complete * 100.0)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
||||
fmt::arg("ratio", tr_strlratio(cache_.ratio)));
|
||||
}
|
||||
else if (cache_.has_seed_ratio) // seed, seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
||||
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
||||
fmt::arg("ratio", tr_strlratio(cache_.ratio)),
|
||||
fmt::arg("seed_ratio", tr_strlratio(cache_.seed_ratio)));
|
||||
}
|
||||
else // seed, no seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio})"),
|
||||
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
||||
fmt::arg("ratio", tr_strlratio(cache_.ratio)));
|
||||
}
|
||||
|
||||
// add time remaining when applicable
|
||||
if (cache_.activity == TR_STATUS_DOWNLOAD || (cache_.has_seed_ratio && cache_.activity == TR_STATUS_SEED))
|
||||
{
|
||||
gstr += " - ";
|
||||
|
||||
if (cache_.eta < 0)
|
||||
{
|
||||
gstr += _("Remaining time unknown");
|
||||
}
|
||||
else
|
||||
{
|
||||
gstr += tr_format_time_left(cache_.eta);
|
||||
}
|
||||
}
|
||||
|
||||
return gstr;
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_long_status_text() const
|
||||
{
|
||||
auto status_str = get_error_text();
|
||||
if (status_str.empty())
|
||||
{
|
||||
status_str = get_activity_text();
|
||||
}
|
||||
|
||||
switch (cache_.activity)
|
||||
{
|
||||
case TR_STATUS_CHECK_WAIT:
|
||||
case TR_STATUS_CHECK:
|
||||
case TR_STATUS_DOWNLOAD_WAIT:
|
||||
case TR_STATUS_SEED_WAIT:
|
||||
case TR_STATUS_STOPPED:
|
||||
break;
|
||||
|
||||
default:
|
||||
if (auto const buf = get_short_transfer_text(); !std::empty(buf))
|
||||
{
|
||||
status_str += fmt::format(FMT_STRING(" - {:s}"), buf);
|
||||
}
|
||||
}
|
||||
|
||||
return status_str;
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_short_transfer_text() const
|
||||
{
|
||||
if (cache_.has_metadata && cache_.active_peers_down > 0)
|
||||
{
|
||||
return fmt::format(
|
||||
_("{download_speed} ▼ {upload_speed} ▲"),
|
||||
fmt::arg("upload_speed", tr_formatter_speed_KBps(cache_.speed_up)),
|
||||
fmt::arg("download_speed", tr_formatter_speed_KBps(cache_.speed_down)));
|
||||
}
|
||||
|
||||
if (cache_.has_metadata && cache_.active_peers_up > 0)
|
||||
{
|
||||
return fmt::format(_("{upload_speed} ▲"), fmt::arg("upload_speed", tr_formatter_speed_KBps(cache_.speed_up)));
|
||||
}
|
||||
|
||||
if (cache_.stalled)
|
||||
{
|
||||
return _("Stalled");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_error_text() const
|
||||
{
|
||||
switch (cache_.error_code)
|
||||
{
|
||||
case TR_STAT_TRACKER_WARNING:
|
||||
return fmt::format(_("Tracker warning: '{warning}'"), fmt::arg("warning", cache_.error_message));
|
||||
|
||||
case TR_STAT_TRACKER_ERROR:
|
||||
return fmt::format(_("Tracker Error: '{error}'"), fmt::arg("error", cache_.error_message));
|
||||
|
||||
case TR_STAT_LOCAL_ERROR:
|
||||
return fmt::format(_("Local error: '{error}'"), fmt::arg("error", cache_.error_message));
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::Impl::get_activity_text() const
|
||||
{
|
||||
switch (cache_.activity)
|
||||
{
|
||||
case TR_STATUS_STOPPED:
|
||||
case TR_STATUS_CHECK_WAIT:
|
||||
case TR_STATUS_CHECK:
|
||||
case TR_STATUS_DOWNLOAD_WAIT:
|
||||
case TR_STATUS_SEED_WAIT:
|
||||
return get_short_status_text();
|
||||
|
||||
case TR_STATUS_DOWNLOAD:
|
||||
if (!cache_.has_metadata)
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
// xgettext:no-c-format
|
||||
"Downloading metadata from {active_count} connected peer ({percent_done}% done)",
|
||||
"Downloading metadata from {active_count} connected peers ({percent_done}% done)",
|
||||
cache_.peers_connected),
|
||||
fmt::arg("active_count", cache_.peers_connected),
|
||||
fmt::arg("percent_done", tr_strpercent(cache_.metadata_percent_complete * 100.0)));
|
||||
}
|
||||
|
||||
if (cache_.peers_sending_to_us != 0 && cache_.webseeds_sending_to_us != 0)
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} of {connected_count} connected peer and webseed",
|
||||
"Downloading from {active_count} of {connected_count} connected peers and webseeds",
|
||||
cache_.peers_connected + cache_.webseeds_sending_to_us),
|
||||
fmt::arg("active_count", cache_.peers_sending_to_us + cache_.webseeds_sending_to_us),
|
||||
fmt::arg("connected_count", cache_.peers_connected + cache_.webseeds_sending_to_us));
|
||||
}
|
||||
|
||||
if (cache_.webseeds_sending_to_us != 0)
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} webseed",
|
||||
"Downloading from {active_count} webseeds",
|
||||
cache_.webseeds_sending_to_us),
|
||||
fmt::arg("active_count", cache_.webseeds_sending_to_us));
|
||||
}
|
||||
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} of {connected_count} connected peer",
|
||||
"Downloading from {active_count} of {connected_count} connected peers",
|
||||
cache_.peers_connected),
|
||||
fmt::arg("active_count", cache_.peers_sending_to_us),
|
||||
fmt::arg("connected_count", cache_.peers_connected));
|
||||
|
||||
case TR_STATUS_SEED:
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Seeding to {active_count} of {connected_count} connected peer",
|
||||
"Seeding to {active_count} of {connected_count} connected peers",
|
||||
cache_.peers_connected),
|
||||
fmt::arg("active_count", cache_.peers_getting_from_us),
|
||||
fmt::arg("connected_count", cache_.peers_connected));
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Torrent::Torrent()
|
||||
: Glib::ObjectBase(typeid(Torrent))
|
||||
{
|
||||
}
|
||||
|
||||
Torrent::Torrent(tr_torrent* torrent)
|
||||
: Glib::ObjectBase(typeid(Torrent))
|
||||
, impl_(std::make_unique<Impl>(*this, torrent))
|
||||
{
|
||||
g_assert(torrent != nullptr);
|
||||
}
|
||||
|
||||
Glib::ustring const& Torrent::get_name_collated() const
|
||||
{
|
||||
return impl_->get_cache().name_collated;
|
||||
}
|
||||
|
||||
tr_torrent_id_t Torrent::get_id() const
|
||||
{
|
||||
return tr_torrentId(impl_->get_raw_torrent());
|
||||
}
|
||||
|
||||
tr_torrent& Torrent::get_underlying() const
|
||||
{
|
||||
return *impl_->get_raw_torrent();
|
||||
}
|
||||
|
||||
float Torrent::get_speed_up() const
|
||||
{
|
||||
return impl_->get_cache().speed_up;
|
||||
}
|
||||
|
||||
float Torrent::get_speed_down() const
|
||||
{
|
||||
return impl_->get_cache().speed_down;
|
||||
}
|
||||
|
||||
int Torrent::get_active_peers_up() const
|
||||
{
|
||||
return impl_->get_cache().active_peers_up;
|
||||
}
|
||||
|
||||
int Torrent::get_active_peers_down() const
|
||||
{
|
||||
return impl_->get_cache().active_peers_down;
|
||||
}
|
||||
|
||||
float Torrent::get_recheck_progress() const
|
||||
{
|
||||
return impl_->get_cache().recheck_progress;
|
||||
}
|
||||
|
||||
bool Torrent::get_active() const
|
||||
{
|
||||
return impl_->get_cache().active;
|
||||
}
|
||||
|
||||
tr_torrent_activity Torrent::get_activity() const
|
||||
{
|
||||
return impl_->get_cache().activity;
|
||||
}
|
||||
|
||||
bool Torrent::get_finished() const
|
||||
{
|
||||
return impl_->get_cache().finished;
|
||||
}
|
||||
|
||||
tr_priority_t Torrent::get_priority() const
|
||||
{
|
||||
return impl_->get_cache().priority;
|
||||
}
|
||||
|
||||
size_t Torrent::get_queue_position() const
|
||||
{
|
||||
return impl_->get_cache().queue_position;
|
||||
}
|
||||
|
||||
unsigned int Torrent::get_trackers() const
|
||||
{
|
||||
return impl_->get_cache().trackers;
|
||||
}
|
||||
|
||||
int Torrent::get_error_code() const
|
||||
{
|
||||
return impl_->get_cache().error_code;
|
||||
}
|
||||
|
||||
Glib::ustring const& Torrent::get_error_message() const
|
||||
{
|
||||
return impl_->get_cache().error_message;
|
||||
}
|
||||
|
||||
int Torrent::get_active_peer_count() const
|
||||
{
|
||||
return impl_->get_cache().active_peer_count;
|
||||
}
|
||||
|
||||
uint64_t Torrent::get_total_size() const
|
||||
{
|
||||
return impl_->get_cache().total_size;
|
||||
}
|
||||
|
||||
float Torrent::get_ratio() const
|
||||
{
|
||||
return impl_->get_cache().ratio;
|
||||
}
|
||||
|
||||
time_t Torrent::get_eta() const
|
||||
{
|
||||
return impl_->get_cache().eta;
|
||||
}
|
||||
|
||||
time_t Torrent::get_added_date() const
|
||||
{
|
||||
return impl_->get_cache().added_date;
|
||||
}
|
||||
|
||||
float Torrent::get_percent_complete() const
|
||||
{
|
||||
return impl_->get_cache().percent_complete;
|
||||
}
|
||||
|
||||
float Torrent::get_seed_ratio_percent_done() const
|
||||
{
|
||||
return impl_->get_cache().seed_ratio_percent_done;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gio::Icon> Torrent::get_icon() const
|
||||
{
|
||||
return impl_->get_icon();
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::get_name() const
|
||||
{
|
||||
return impl_->get_cache().name;
|
||||
}
|
||||
|
||||
float Torrent::get_percent_done() const
|
||||
{
|
||||
return impl_->get_cache().activity_percent_done;
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::get_short_status_text() const
|
||||
{
|
||||
return impl_->get_short_status_text();
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::get_long_progress_text() const
|
||||
{
|
||||
return impl_->get_long_progress_text();
|
||||
}
|
||||
|
||||
Glib::ustring Torrent::get_long_status_text() const
|
||||
{
|
||||
return impl_->get_long_status_text();
|
||||
}
|
||||
|
||||
bool Torrent::get_sensitive() const
|
||||
{
|
||||
return impl_->get_cache().activity != TR_STATUS_STOPPED;
|
||||
}
|
||||
|
||||
Torrent::ChangeFlags Torrent::update()
|
||||
{
|
||||
auto result = impl_->update_cache();
|
||||
impl_->notify_property_changes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Torrent> Torrent::create(tr_torrent* torrent)
|
||||
{
|
||||
return Glib::make_refptr_for_instance(new Torrent(torrent));
|
||||
}
|
||||
|
||||
Torrent::Columns const& Torrent::get_columns()
|
||||
{
|
||||
static Columns const columns;
|
||||
return columns;
|
||||
}
|
||||
|
||||
int Torrent::get_item_id(Glib::RefPtr<Glib::ObjectBase const> const& item)
|
||||
{
|
||||
if (auto const torrent = gtr_ptr_dynamic_cast<Torrent const>(item); torrent != nullptr)
|
||||
{
|
||||
return torrent->get_id();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Torrent::get_item_value(Glib::RefPtr<Glib::ObjectBase const> const& item, int column, Glib::ValueBase& value)
|
||||
{
|
||||
if (auto const torrent = gtr_ptr_dynamic_cast<Torrent const>(item); torrent != nullptr)
|
||||
{
|
||||
torrent->impl_->get_value(column, value);
|
||||
}
|
||||
}
|
||||
|
||||
int Torrent::compare_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs)
|
||||
{
|
||||
return gtr_compare_generic(lhs->get_id(), rhs->get_id());
|
||||
}
|
||||
|
||||
bool Torrent::less_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs)
|
||||
{
|
||||
return lhs->get_id() < rhs->get_id();
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
|
||||
#include "Flags.h"
|
||||
|
||||
class Torrent : public Glib::Object
|
||||
{
|
||||
public:
|
||||
class Columns : public Gtk::TreeModelColumnRecord
|
||||
{
|
||||
public:
|
||||
Columns();
|
||||
|
||||
Gtk::TreeModelColumn<Torrent*> self;
|
||||
Gtk::TreeModelColumn<Glib::ustring> name_collated;
|
||||
};
|
||||
|
||||
enum class ChangeFlag
|
||||
{
|
||||
ACTIVE_PEER_COUNT,
|
||||
ACTIVE_PEERS_DOWN,
|
||||
ACTIVE_PEERS_UP,
|
||||
ACTIVE,
|
||||
ACTIVITY,
|
||||
ADDED_DATE,
|
||||
ERROR_CODE,
|
||||
ERROR_MESSAGE,
|
||||
ETA,
|
||||
FINISHED,
|
||||
HAS_METADATA,
|
||||
LONG_PROGRESS,
|
||||
LONG_STATUS,
|
||||
MIME_TYPE,
|
||||
NAME,
|
||||
PERCENT_COMPLETE,
|
||||
PERCENT_DONE,
|
||||
PRIORITY,
|
||||
QUEUE_POSITION,
|
||||
RATIO,
|
||||
RECHECK_PROGRESS,
|
||||
SEED_RATIO_PERCENT_DONE,
|
||||
SPEED_DOWN,
|
||||
SPEED_UP,
|
||||
STALLED,
|
||||
TOTAL_SIZE,
|
||||
TRACKERS,
|
||||
};
|
||||
|
||||
using ChangeFlags = Flags<ChangeFlag>;
|
||||
|
||||
public:
|
||||
int get_active_peer_count() const;
|
||||
int get_active_peers_down() const;
|
||||
int get_active_peers_up() const;
|
||||
bool get_active() const;
|
||||
tr_torrent_activity get_activity() const;
|
||||
time_t get_added_date() const;
|
||||
int get_error_code() const;
|
||||
Glib::ustring const& get_error_message() const;
|
||||
time_t get_eta() const;
|
||||
bool get_finished() const;
|
||||
tr_torrent_id_t get_id() const;
|
||||
Glib::ustring const& get_name_collated() const;
|
||||
Glib::ustring get_name() const;
|
||||
float get_percent_complete() const;
|
||||
float get_percent_done() const;
|
||||
tr_priority_t get_priority() const;
|
||||
size_t get_queue_position() const;
|
||||
float get_ratio() const;
|
||||
float get_recheck_progress() const;
|
||||
float get_seed_ratio_percent_done() const;
|
||||
float get_speed_down() const;
|
||||
float get_speed_up() const;
|
||||
tr_torrent& get_underlying() const;
|
||||
uint64_t get_total_size() const;
|
||||
unsigned int get_trackers() const;
|
||||
|
||||
Glib::RefPtr<Gio::Icon> get_icon() const;
|
||||
Glib::ustring get_short_status_text() const;
|
||||
Glib::ustring get_long_progress_text() const;
|
||||
Glib::ustring get_long_status_text() const;
|
||||
bool get_sensitive() const;
|
||||
|
||||
ChangeFlags update();
|
||||
|
||||
static Glib::RefPtr<Torrent> create(tr_torrent* torrent);
|
||||
|
||||
static Columns const& get_columns();
|
||||
|
||||
static int get_item_id(Glib::RefPtr<Glib::ObjectBase const> const& item);
|
||||
static void get_item_value(Glib::RefPtr<Glib::ObjectBase const> const& item, int column, Glib::ValueBase& value);
|
||||
|
||||
static int compare_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs);
|
||||
static bool less_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs);
|
||||
|
||||
private:
|
||||
Torrent();
|
||||
explicit Torrent(tr_torrent* torrent);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> const impl_;
|
||||
};
|
||||
|
||||
DEFINE_FLAGS_OPERATORS(Torrent::ChangeFlag)
|
|
@ -19,7 +19,7 @@
|
|||
#include <libtransmission/utils.h> /* tr_truncd() */
|
||||
|
||||
#include "HigWorkarea.h" // GUI_PAD, GUI_PAD_SMALL
|
||||
#include "IconCache.h"
|
||||
#include "Torrent.h"
|
||||
#include "TorrentCellRenderer.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
@ -48,265 +48,6 @@ auto get_width(Gtk::Requisition const& req)
|
|||
return req.IF_GTKMM4(get_width(), width);
|
||||
}
|
||||
|
||||
auto getProgressString(tr_torrent const* tor, uint64_t total_size, tr_stat const* st)
|
||||
{
|
||||
Glib::ustring gstr;
|
||||
|
||||
bool const isDone = st->leftUntilDone == 0;
|
||||
uint64_t const haveTotal = st->haveUnchecked + st->haveValid;
|
||||
bool const isSeed = st->haveValid >= total_size;
|
||||
double seedRatio = 0;
|
||||
bool const hasSeedRatio = tr_torrentGetSeedRatio(tor, &seedRatio);
|
||||
|
||||
if (!isDone) // downloading
|
||||
{
|
||||
// 50 MB of 200 MB (25%)
|
||||
gstr += fmt::format(
|
||||
_("{current_size} of {complete_size} ({percent_done}%)"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(st->sizeWhenDone)),
|
||||
fmt::arg("percent_done", tr_strpercent(st->percentDone * 100.0)));
|
||||
}
|
||||
else if (!isSeed && hasSeedRatio) // partial seed, seed ratio
|
||||
{
|
||||
// 50 MB of 200 MB (25%), uploaded 30 MB (Ratio: X%, Goal: Y%)
|
||||
gstr += fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(total_size)),
|
||||
fmt::arg("percent_complete", tr_strpercent(st->percentComplete * 100.0)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(st->uploadedEver)),
|
||||
fmt::arg("ratio", tr_strlratio(st->ratio)),
|
||||
fmt::arg("seed_ratio", tr_strlratio(seedRatio)));
|
||||
}
|
||||
else if (!isSeed) // partial seed, no seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio})"),
|
||||
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
||||
fmt::arg("complete_size", tr_strlsize(total_size)),
|
||||
fmt::arg("percent_complete", tr_strpercent(st->percentComplete * 100.0)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(st->uploadedEver)),
|
||||
fmt::arg("ratio", tr_strlratio(st->ratio)));
|
||||
}
|
||||
else if (hasSeedRatio) // seed, seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
||||
fmt::arg("complete_size", tr_strlsize(total_size)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(st->uploadedEver)),
|
||||
fmt::arg("ratio", tr_strlratio(st->ratio)),
|
||||
fmt::arg("seed_ratio", tr_strlratio(seedRatio)));
|
||||
}
|
||||
else // seed, no seed ratio
|
||||
{
|
||||
gstr += fmt::format(
|
||||
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio})"),
|
||||
fmt::arg("complete_size", tr_strlsize(total_size)),
|
||||
fmt::arg("uploaded_size", tr_strlsize(st->uploadedEver)),
|
||||
fmt::arg("ratio", tr_strlratio(st->ratio)));
|
||||
}
|
||||
|
||||
// add time remaining when applicable
|
||||
if (st->activity == TR_STATUS_DOWNLOAD || (hasSeedRatio && st->activity == TR_STATUS_SEED))
|
||||
{
|
||||
int const eta = st->eta;
|
||||
gstr += " - ";
|
||||
|
||||
if (eta < 0)
|
||||
{
|
||||
gstr += _("Remaining time unknown");
|
||||
}
|
||||
else
|
||||
{
|
||||
gstr += tr_format_time_left(eta);
|
||||
}
|
||||
}
|
||||
|
||||
return gstr;
|
||||
}
|
||||
|
||||
std::string getShortTransferString(
|
||||
tr_torrent const* const tor,
|
||||
tr_stat const* const st,
|
||||
double uploadSpeed_KBps,
|
||||
double downloadSpeed_KBps)
|
||||
{
|
||||
bool const have_meta = tr_torrentHasMetadata(tor);
|
||||
|
||||
if (bool const have_down = have_meta && (st->peersSendingToUs > 0 || st->webseedsSendingToUs > 0); have_down)
|
||||
{
|
||||
return fmt::format(
|
||||
_("{download_speed} ▼ {upload_speed} ▲"),
|
||||
fmt::arg("upload_speed", tr_formatter_speed_KBps(uploadSpeed_KBps)),
|
||||
fmt::arg("download_speed", tr_formatter_speed_KBps(downloadSpeed_KBps)));
|
||||
}
|
||||
|
||||
if (bool const have_up = have_meta && st->peersGettingFromUs > 0; have_up)
|
||||
{
|
||||
return fmt::format(_("{upload_speed} ▲"), fmt::arg("upload_speed", tr_formatter_speed_KBps(uploadSpeed_KBps)));
|
||||
}
|
||||
|
||||
if (st->isStalled)
|
||||
{
|
||||
return _("Stalled");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string getShortStatusString(
|
||||
tr_torrent const* const tor,
|
||||
tr_stat const* const st,
|
||||
double uploadSpeed_KBps,
|
||||
double downloadSpeed_KBps)
|
||||
{
|
||||
switch (st->activity)
|
||||
{
|
||||
case TR_STATUS_STOPPED:
|
||||
return st->finished ? _("Finished") : _("Paused");
|
||||
|
||||
case TR_STATUS_CHECK_WAIT:
|
||||
return _("Queued for verification");
|
||||
|
||||
case TR_STATUS_DOWNLOAD_WAIT:
|
||||
return _("Queued for download");
|
||||
|
||||
case TR_STATUS_SEED_WAIT:
|
||||
return _("Queued for seeding");
|
||||
|
||||
case TR_STATUS_CHECK:
|
||||
return fmt::format(
|
||||
// xgettext:no-c-format
|
||||
_("Verifying local data ({percent_done}% tested)"),
|
||||
fmt::arg("percent_done", tr_truncd(st->recheckProgress * 100.0, 1)));
|
||||
|
||||
case TR_STATUS_DOWNLOAD:
|
||||
case TR_STATUS_SEED:
|
||||
return fmt::format(
|
||||
FMT_STRING("{:s} {:s}"),
|
||||
getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps),
|
||||
fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(st->ratio))));
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> getErrorString(tr_stat const* st)
|
||||
{
|
||||
switch (st->error)
|
||||
{
|
||||
case TR_STAT_TRACKER_WARNING:
|
||||
return fmt::format(_("Tracker warning: '{warning}'"), fmt::arg("warning", st->errorString));
|
||||
|
||||
case TR_STAT_TRACKER_ERROR:
|
||||
return fmt::format(_("Tracker Error: '{error}'"), fmt::arg("error", st->errorString));
|
||||
|
||||
case TR_STAT_LOCAL_ERROR:
|
||||
return fmt::format(_("Local error: '{error}'"), fmt::arg("error", st->errorString));
|
||||
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto getActivityString(
|
||||
tr_torrent const* const tor,
|
||||
tr_stat const* const st,
|
||||
double const uploadSpeed_KBps,
|
||||
double const downloadSpeed_KBps)
|
||||
{
|
||||
switch (st->activity)
|
||||
{
|
||||
case TR_STATUS_STOPPED:
|
||||
case TR_STATUS_CHECK_WAIT:
|
||||
case TR_STATUS_CHECK:
|
||||
case TR_STATUS_DOWNLOAD_WAIT:
|
||||
case TR_STATUS_SEED_WAIT:
|
||||
return getShortStatusString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps);
|
||||
|
||||
case TR_STATUS_DOWNLOAD:
|
||||
if (!tr_torrentHasMetadata(tor))
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
// xgettext:no-c-format
|
||||
"Downloading metadata from {active_count} connected peer ({percent_done}% done)",
|
||||
"Downloading metadata from {active_count} connected peers ({percent_done}% done)",
|
||||
st->peersConnected),
|
||||
fmt::arg("active_count", st->peersConnected),
|
||||
fmt::arg("percent_done", tr_strpercent(st->metadataPercentComplete * 100.0)));
|
||||
}
|
||||
|
||||
if (st->peersSendingToUs != 0 && st->webseedsSendingToUs != 0)
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} of {connected_count} connected peer and webseed",
|
||||
"Downloading from {active_count} of {connected_count} connected peers and webseeds",
|
||||
st->peersConnected + st->webseedsSendingToUs),
|
||||
fmt::arg("active_count", st->peersSendingToUs + st->webseedsSendingToUs),
|
||||
fmt::arg("connected_count", st->peersConnected + st->webseedsSendingToUs));
|
||||
}
|
||||
|
||||
if (st->webseedsSendingToUs != 0)
|
||||
{
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} webseed",
|
||||
"Downloading from {active_count} webseeds",
|
||||
st->webseedsSendingToUs),
|
||||
fmt::arg("active_count", st->webseedsSendingToUs));
|
||||
}
|
||||
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Downloading from {active_count} of {connected_count} connected peer",
|
||||
"Downloading from {active_count} of {connected_count} connected peers",
|
||||
st->peersConnected),
|
||||
fmt::arg("active_count", st->peersSendingToUs),
|
||||
fmt::arg("connected_count", st->peersConnected));
|
||||
|
||||
case TR_STATUS_SEED:
|
||||
return fmt::format(
|
||||
ngettext(
|
||||
"Seeding to {active_count} of {connected_count} connected peer",
|
||||
"Seeding to {active_count} of {connected_count} connected peers",
|
||||
st->peersConnected),
|
||||
fmt::arg("active_count", st->peersGettingFromUs),
|
||||
fmt::arg("connected_count", st->peersConnected));
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
|
||||
std::string getStatusString(
|
||||
tr_torrent const* tor,
|
||||
tr_stat const* st,
|
||||
double const uploadSpeed_KBps,
|
||||
double const downloadSpeed_KBps,
|
||||
bool ignore_errors = false)
|
||||
{
|
||||
auto status_str = (ignore_errors ? std::nullopt : getErrorString(st))
|
||||
.value_or(getActivityString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps));
|
||||
|
||||
if (st->activity != TR_STATUS_CHECK_WAIT && st->activity != TR_STATUS_CHECK && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
|
||||
st->activity != TR_STATUS_SEED_WAIT && st->activity != TR_STATUS_STOPPED)
|
||||
{
|
||||
if (auto const buf = getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps); !std::empty(buf))
|
||||
{
|
||||
status_str += fmt::format(FMT_STRING(" - {:s}"), buf);
|
||||
}
|
||||
}
|
||||
|
||||
return status_str;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/***
|
||||
|
@ -348,16 +89,6 @@ public:
|
|||
return property_bar_height_;
|
||||
}
|
||||
|
||||
auto& property_upload_speed_KBps()
|
||||
{
|
||||
return property_upload_speed_KBps_;
|
||||
}
|
||||
|
||||
auto& property_download_speed_KBps()
|
||||
{
|
||||
return property_download_speed_KBps_;
|
||||
}
|
||||
|
||||
auto& property_compact()
|
||||
{
|
||||
return property_compact_;
|
||||
|
@ -383,10 +114,8 @@ private:
|
|||
private:
|
||||
TorrentCellRenderer& renderer_;
|
||||
|
||||
Glib::Property<gpointer> property_torrent_;
|
||||
Glib::Property<Torrent*> property_torrent_;
|
||||
Glib::Property<int> property_bar_height_;
|
||||
Glib::Property<double> property_upload_speed_KBps_;
|
||||
Glib::Property<double> property_download_speed_KBps_;
|
||||
Glib::Property<bool> property_compact_;
|
||||
|
||||
Gtk::CellRendererText* text_renderer_ = nullptr;
|
||||
|
@ -398,37 +127,6 @@ private:
|
|||
****
|
||||
***/
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Glib::RefPtr<Gio::Icon> get_icon(tr_torrent const* tor)
|
||||
{
|
||||
auto mime_type = std::string_view{};
|
||||
|
||||
if (auto const n_files = tr_torrentFileCount(tor); n_files == 0)
|
||||
{
|
||||
mime_type = UnknownMimeType;
|
||||
}
|
||||
else if (n_files > 1)
|
||||
{
|
||||
mime_type = DirectoryMimeType;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const* const name = tr_torrentFile(tor, 0).name;
|
||||
|
||||
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
|
||||
}
|
||||
|
||||
return gtr_get_mime_type_icon(mime_type);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void TorrentCellRenderer::Impl::set_icon(
|
||||
Gtk::CellRendererPixbuf& renderer,
|
||||
Glib::RefPtr<Gio::Icon> const& icon,
|
||||
|
@ -451,16 +149,11 @@ Gtk::Requisition TorrentCellRenderer::Impl::get_size_compact(Gtk::Widget& widget
|
|||
Gtk::Requisition name_size;
|
||||
Gtk::Requisition stat_size;
|
||||
|
||||
auto* const tor = static_cast<tr_torrent*>(property_torrent_.get_value());
|
||||
auto const* const st = tr_torrentStatCached(tor);
|
||||
auto const& torrent = *property_torrent_.get_value();
|
||||
|
||||
auto const icon = get_icon(tor);
|
||||
auto const name = Glib::ustring(tr_torrentName(tor));
|
||||
auto const gstr_stat = getShortStatusString(
|
||||
tor,
|
||||
st,
|
||||
property_upload_speed_KBps_.get_value(),
|
||||
property_download_speed_KBps_.get_value());
|
||||
auto const icon = torrent.get_icon();
|
||||
auto const name = torrent.get_name();
|
||||
auto const gstr_stat = torrent.get_short_status_text();
|
||||
renderer_.get_padding(xpad, ypad);
|
||||
|
||||
/* get the idealized cell dimensions */
|
||||
|
@ -492,19 +185,12 @@ Gtk::Requisition TorrentCellRenderer::Impl::get_size_full(Gtk::Widget& widget) c
|
|||
Gtk::Requisition stat_size;
|
||||
Gtk::Requisition prog_size;
|
||||
|
||||
auto* const tor = static_cast<tr_torrent*>(property_torrent_.get_value());
|
||||
auto const* const st = tr_torrentStatCached(tor);
|
||||
auto const total_size = tr_torrentTotalSize(tor);
|
||||
auto const& torrent = *property_torrent_.get_value();
|
||||
|
||||
auto const icon = get_icon(tor);
|
||||
auto const name = Glib::ustring(tr_torrentName(tor));
|
||||
auto const gstr_stat = getStatusString(
|
||||
tor,
|
||||
st,
|
||||
property_upload_speed_KBps_.get_value(),
|
||||
property_download_speed_KBps_.get_value(),
|
||||
true);
|
||||
auto const gstr_prog = getProgressString(tor, total_size, st);
|
||||
auto const icon = torrent.get_icon();
|
||||
auto const name = torrent.get_name();
|
||||
auto const gstr_stat = torrent.get_long_status_text();
|
||||
auto const gstr_prog = torrent.get_long_progress_text();
|
||||
renderer_.get_padding(xpad, ypad);
|
||||
|
||||
/* get the idealized cell dimensions */
|
||||
|
@ -558,20 +244,21 @@ void TorrentCellRenderer::get_preferred_height_vfunc(Gtk::Widget& widget, int& m
|
|||
namespace
|
||||
{
|
||||
|
||||
int get_percent_done(tr_torrent const* tor, tr_stat const* st)
|
||||
{
|
||||
auto const seed = st->activity == TR_STATUS_SEED && tr_torrentGetSeedRatio(tor, nullptr);
|
||||
return static_cast<int>((seed ? std::max(0.0F, st->seedRatioPercentDone) : std::max(0.0F, st->percentDone)) * 100);
|
||||
}
|
||||
|
||||
Gdk::RGBA const& get_progress_bar_color(tr_stat const& st)
|
||||
Gdk::RGBA const& get_progress_bar_color(Torrent const& torrent)
|
||||
{
|
||||
static auto const steelblue_color = Gdk::RGBA("steelblue");
|
||||
static auto const forestgreen_color = Gdk::RGBA("forestgreen");
|
||||
static auto const silver_color = Gdk::RGBA("silver");
|
||||
|
||||
return st.activity == TR_STATUS_DOWNLOAD ? steelblue_color :
|
||||
(st.activity == TR_STATUS_SEED ? forestgreen_color : silver_color);
|
||||
switch (torrent.get_activity())
|
||||
{
|
||||
case TR_STATUS_DOWNLOAD:
|
||||
return steelblue_color;
|
||||
case TR_STATUS_SEED:
|
||||
return forestgreen_color;
|
||||
default:
|
||||
return silver_color;
|
||||
}
|
||||
}
|
||||
|
||||
Cairo::RefPtr<Cairo::Surface> get_mask_surface(Cairo::RefPtr<Cairo::Surface> const& surface, Gdk::Rectangle const& area)
|
||||
|
@ -690,19 +377,11 @@ void TorrentCellRenderer::Impl::render_compact(
|
|||
int min_width = 0;
|
||||
int width = 0;
|
||||
|
||||
auto* const tor = static_cast<tr_torrent*>(property_torrent_.get_value());
|
||||
auto const* const st = tr_torrentStatCached(tor);
|
||||
bool const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
|
||||
st->activity != TR_STATUS_SEED_WAIT;
|
||||
auto const percent_done = get_percent_done(tor, st);
|
||||
bool const sensitive = active || st->error != 0;
|
||||
auto const& torrent = *property_torrent_.get_value();
|
||||
auto const percent_done = static_cast<int>(torrent.get_percent_done() * 100);
|
||||
bool const sensitive = torrent.get_sensitive();
|
||||
|
||||
if (st->activity == TR_STATUS_STOPPED)
|
||||
{
|
||||
flags |= TR_GTK_CELL_RENDERER_STATE(INSENSITIVE);
|
||||
}
|
||||
|
||||
if (st->error != 0 && (flags & TR_GTK_CELL_RENDERER_STATE(SELECTED)) == Gtk::CellRendererState{})
|
||||
if (torrent.get_error_code() != 0 && (flags & TR_GTK_CELL_RENDERER_STATE(SELECTED)) == Gtk::CellRendererState{})
|
||||
{
|
||||
text_renderer_->property_foreground() = "red";
|
||||
}
|
||||
|
@ -711,14 +390,10 @@ void TorrentCellRenderer::Impl::render_compact(
|
|||
text_renderer_->property_foreground_set() = false;
|
||||
}
|
||||
|
||||
auto const icon = get_icon(tor);
|
||||
auto const name = Glib::ustring(tr_torrentName(tor));
|
||||
auto const& progress_color = get_progress_bar_color(*st);
|
||||
auto const gstr_stat = getShortStatusString(
|
||||
tor,
|
||||
st,
|
||||
property_upload_speed_KBps_.get_value(),
|
||||
property_download_speed_KBps_.get_value());
|
||||
auto const icon = torrent.get_icon();
|
||||
auto const name = torrent.get_name();
|
||||
auto const& progress_color = get_progress_bar_color(torrent);
|
||||
auto const gstr_stat = torrent.get_short_status_text();
|
||||
renderer_.get_padding(xpad, ypad);
|
||||
|
||||
auto fill_area = background_area;
|
||||
|
@ -777,6 +452,7 @@ void TorrentCellRenderer::Impl::render_compact(
|
|||
text_renderer_->property_text() = gstr_stat;
|
||||
text_renderer_->property_scale() = SmallScale;
|
||||
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(END);
|
||||
text_renderer_->property_sensitive() = sensitive;
|
||||
render_impl(*text_renderer_, snapshot, widget, stat_area, stat_area, flags);
|
||||
|
||||
text_renderer_->property_text() = name;
|
||||
|
@ -795,20 +471,11 @@ void TorrentCellRenderer::Impl::render_full(
|
|||
Gtk::Requisition min_size;
|
||||
Gtk::Requisition size;
|
||||
|
||||
auto* const tor = static_cast<tr_torrent*>(property_torrent_.get_value());
|
||||
auto const* const st = tr_torrentStatCached(tor);
|
||||
auto const total_size = tr_torrentTotalSize(tor);
|
||||
bool const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
|
||||
st->activity != TR_STATUS_SEED_WAIT;
|
||||
auto const percent_done = get_percent_done(tor, st);
|
||||
bool const sensitive = active || st->error != 0;
|
||||
auto const& torrent = *property_torrent_.get_value();
|
||||
auto const percent_done = static_cast<int>(torrent.get_percent_done() * 100);
|
||||
bool const sensitive = torrent.get_sensitive();
|
||||
|
||||
if (st->activity == TR_STATUS_STOPPED)
|
||||
{
|
||||
flags |= TR_GTK_CELL_RENDERER_STATE(INSENSITIVE);
|
||||
}
|
||||
|
||||
if (st->error != 0 && (flags & TR_GTK_CELL_RENDERER_STATE(SELECTED)) == Gtk::CellRendererState{})
|
||||
if (torrent.get_error_code() != 0 && (flags & TR_GTK_CELL_RENDERER_STATE(SELECTED)) == Gtk::CellRendererState{})
|
||||
{
|
||||
text_renderer_->property_foreground() = "red";
|
||||
}
|
||||
|
@ -817,15 +484,11 @@ void TorrentCellRenderer::Impl::render_full(
|
|||
text_renderer_->property_foreground_set() = false;
|
||||
}
|
||||
|
||||
auto const icon = get_icon(tor);
|
||||
auto const name = Glib::ustring(tr_torrentName(tor));
|
||||
auto const& progress_color = get_progress_bar_color(*st);
|
||||
auto const gstr_prog = getProgressString(tor, total_size, st);
|
||||
auto const gstr_stat = getStatusString(
|
||||
tor,
|
||||
st,
|
||||
property_upload_speed_KBps_.get_value(),
|
||||
property_download_speed_KBps_.get_value());
|
||||
auto const icon = torrent.get_icon();
|
||||
auto const name = torrent.get_name();
|
||||
auto const& progress_color = get_progress_bar_color(torrent);
|
||||
auto const gstr_prog = torrent.get_long_progress_text();
|
||||
auto const gstr_stat = torrent.get_long_status_text();
|
||||
renderer_.get_padding(xpad, ypad);
|
||||
|
||||
/* get the idealized cell dimensions */
|
||||
|
@ -913,6 +576,7 @@ void TorrentCellRenderer::Impl::render_full(
|
|||
text_renderer_->property_scale() = 1.0;
|
||||
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(END);
|
||||
text_renderer_->property_weight() = TR_PANGO_WEIGHT(BOLD);
|
||||
text_renderer_->property_sensitive() = sensitive;
|
||||
render_impl(*text_renderer_, snapshot, widget, name_area, name_area, flags);
|
||||
|
||||
text_renderer_->property_text() = gstr_prog;
|
||||
|
@ -977,32 +641,23 @@ TorrentCellRenderer::Impl::Impl(TorrentCellRenderer& renderer)
|
|||
: renderer_(renderer)
|
||||
, property_torrent_(renderer, "torrent", nullptr)
|
||||
, property_bar_height_(renderer, "bar-height", DefaultBarHeight)
|
||||
, property_upload_speed_KBps_(renderer, "piece-upload-speed", 0)
|
||||
, property_download_speed_KBps_(renderer, "piece-download-speed", 0)
|
||||
, property_compact_(renderer, "compact", false)
|
||||
, text_renderer_(Gtk::make_managed<Gtk::CellRendererText>())
|
||||
, progress_renderer_(Gtk::make_managed<Gtk::CellRendererProgress>())
|
||||
, icon_renderer_(Gtk::make_managed<Gtk::CellRendererPixbuf>())
|
||||
{
|
||||
renderer_.property_xpad() = GUI_PAD_SMALL;
|
||||
renderer_.property_ypad() = GUI_PAD_SMALL;
|
||||
|
||||
text_renderer_->property_xpad() = 0;
|
||||
text_renderer_->property_ypad() = 0;
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<gpointer> TorrentCellRenderer::property_torrent()
|
||||
Glib::PropertyProxy<Torrent*> TorrentCellRenderer::property_torrent()
|
||||
{
|
||||
return impl_->property_torrent().get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<double> TorrentCellRenderer::property_piece_upload_speed()
|
||||
{
|
||||
return impl_->property_upload_speed_KBps().get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<double> TorrentCellRenderer::property_piece_download_speed()
|
||||
{
|
||||
return impl_->property_download_speed_KBps().get_proxy();
|
||||
}
|
||||
|
||||
Glib::PropertyProxy<int> TorrentCellRenderer::property_bar_height()
|
||||
{
|
||||
return impl_->property_bar_height().get_proxy();
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "Utils.h"
|
||||
|
||||
struct tr_torrent;
|
||||
class Torrent;
|
||||
|
||||
class TorrentCellRenderer : public Gtk::CellRenderer
|
||||
{
|
||||
|
@ -26,16 +26,7 @@ public:
|
|||
|
||||
TR_DISABLE_COPY_MOVE(TorrentCellRenderer)
|
||||
|
||||
Glib::PropertyProxy<gpointer> property_torrent();
|
||||
|
||||
/* Use this instead of tr_stat.pieceUploadSpeed so that the model can
|
||||
control when the speed displays get updated. This is done to keep
|
||||
the individual torrents' speeds and the status bar's overall speed
|
||||
in sync even if they refresh at slightly different times */
|
||||
Glib::PropertyProxy<double> property_piece_upload_speed();
|
||||
|
||||
/* @see property_piece_upload_speed */
|
||||
Glib::PropertyProxy<double> property_piece_download_speed();
|
||||
Glib::PropertyProxy<Torrent*> property_torrent();
|
||||
|
||||
Glib::PropertyProxy<int> property_bar_height();
|
||||
Glib::PropertyProxy<bool> property_compact();
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
|
||||
#include "Torrent.h"
|
||||
#include "TorrentFilter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
void TorrentFilter::set_activity(Activity type)
|
||||
{
|
||||
if (activity_type_ == type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto change = Change::DIFFERENT;
|
||||
if (activity_type_ == Activity::ALL)
|
||||
{
|
||||
change = Change::MORE_STRICT;
|
||||
}
|
||||
else if (type == Activity::ALL)
|
||||
{
|
||||
change = Change::LESS_STRICT;
|
||||
}
|
||||
|
||||
activity_type_ = type;
|
||||
changed(change);
|
||||
}
|
||||
|
||||
void TorrentFilter::set_tracker(Tracker type, Glib::ustring const& host)
|
||||
{
|
||||
if (tracker_type_ == type && tracker_host_ == host)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto change = Change::DIFFERENT;
|
||||
if (tracker_type_ != type)
|
||||
{
|
||||
if (tracker_type_ == Tracker::ALL)
|
||||
{
|
||||
change = Change::MORE_STRICT;
|
||||
}
|
||||
else if (type == Tracker::ALL)
|
||||
{
|
||||
change = Change::LESS_STRICT;
|
||||
}
|
||||
}
|
||||
else // tracker_host_ != host
|
||||
{
|
||||
if (tracker_host_.empty() || host.find(tracker_host_) != Glib::ustring::npos)
|
||||
{
|
||||
change = Change::MORE_STRICT;
|
||||
}
|
||||
else if (host.empty() || tracker_host_.find(host) != Glib::ustring::npos)
|
||||
{
|
||||
change = Change::LESS_STRICT;
|
||||
}
|
||||
}
|
||||
|
||||
tracker_type_ = type;
|
||||
tracker_host_ = host;
|
||||
changed(change);
|
||||
}
|
||||
|
||||
void TorrentFilter::set_text(Glib::ustring const& text)
|
||||
{
|
||||
auto const normalized_text = gtr_str_strip(text.casefold());
|
||||
if (text_ == normalized_text)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto change = Change::DIFFERENT;
|
||||
if (text_.empty() || normalized_text.find(text_) != Glib::ustring::npos)
|
||||
{
|
||||
change = Change::MORE_STRICT;
|
||||
}
|
||||
else if (normalized_text.empty() || text_.find(normalized_text) != Glib::ustring::npos)
|
||||
{
|
||||
change = Change::LESS_STRICT;
|
||||
}
|
||||
|
||||
text_ = normalized_text;
|
||||
changed(change);
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_activity(Torrent const& torrent) const
|
||||
{
|
||||
return match_activity(torrent, activity_type_);
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_tracker(Torrent const& torrent) const
|
||||
{
|
||||
return match_tracker(torrent, tracker_type_, tracker_host_);
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_text(Torrent const& torrent) const
|
||||
{
|
||||
return match_text(torrent, text_);
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(Torrent const& torrent) const
|
||||
{
|
||||
return match_activity(torrent) && match_tracker(torrent) && match_text(torrent);
|
||||
}
|
||||
|
||||
void TorrentFilter::update(Torrent::ChangeFlags changes)
|
||||
{
|
||||
using Flag = Torrent::ChangeFlag;
|
||||
|
||||
bool refilter_needed = false;
|
||||
|
||||
if (activity_type_ != Activity::ALL)
|
||||
{
|
||||
static auto const activity_flags = std::map<Activity, Torrent::ChangeFlags>({
|
||||
{ Activity::DOWNLOADING, Flag::ACTIVITY },
|
||||
{ Activity::SEEDING, Flag::ACTIVITY },
|
||||
{ Activity::ACTIVE, Flag::ACTIVE_PEER_COUNT | Flag::ACTIVITY },
|
||||
{ Activity::PAUSED, Flag::ACTIVITY },
|
||||
{ Activity::FINISHED, Flag::FINISHED },
|
||||
{ Activity::VERIFYING, Flag::ACTIVITY },
|
||||
{ Activity::ERROR, Flag::ERROR_CODE },
|
||||
});
|
||||
|
||||
auto const activity_flags_it = activity_flags.find(activity_type_);
|
||||
refilter_needed = activity_flags_it != activity_flags.end() && changes.test(activity_flags_it->second);
|
||||
}
|
||||
|
||||
if (!refilter_needed)
|
||||
{
|
||||
refilter_needed = tracker_type_ != Tracker::ALL && changes.test(Flag::TRACKERS);
|
||||
}
|
||||
|
||||
if (!refilter_needed)
|
||||
{
|
||||
refilter_needed = !text_.empty() && changes.test(Flag::NAME);
|
||||
}
|
||||
|
||||
if (refilter_needed)
|
||||
{
|
||||
changed(Change::DIFFERENT);
|
||||
}
|
||||
}
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
|
||||
sigc::signal<void()>& TorrentFilter::signal_changed()
|
||||
{
|
||||
return signal_changed_;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Glib::RefPtr<TorrentFilter> TorrentFilter::create()
|
||||
{
|
||||
return Glib::make_refptr_for_instance(new TorrentFilter());
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_activity(Torrent const& torrent, Activity type)
|
||||
{
|
||||
auto activity = tr_torrent_activity();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Activity::ALL:
|
||||
return true;
|
||||
|
||||
case Activity::DOWNLOADING:
|
||||
activity = torrent.get_activity();
|
||||
return activity == TR_STATUS_DOWNLOAD || activity == TR_STATUS_DOWNLOAD_WAIT;
|
||||
|
||||
case Activity::SEEDING:
|
||||
activity = torrent.get_activity();
|
||||
return activity == TR_STATUS_SEED || activity == TR_STATUS_SEED_WAIT;
|
||||
|
||||
case Activity::ACTIVE:
|
||||
return torrent.get_active_peer_count() > 0 || torrent.get_activity() == TR_STATUS_CHECK;
|
||||
|
||||
case Activity::PAUSED:
|
||||
return torrent.get_activity() == TR_STATUS_STOPPED;
|
||||
|
||||
case Activity::FINISHED:
|
||||
return torrent.get_finished();
|
||||
|
||||
case Activity::VERIFYING:
|
||||
activity = torrent.get_activity();
|
||||
return activity == TR_STATUS_CHECK || activity == TR_STATUS_CHECK_WAIT;
|
||||
|
||||
case Activity::ERROR:
|
||||
return torrent.get_error_code() != 0;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_tracker(Torrent const& torrent, Tracker type, Glib::ustring const& host)
|
||||
{
|
||||
if (type == Tracker::ALL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
g_assert(type == Tracker::HOST);
|
||||
|
||||
auto const& raw_torrent = torrent.get_underlying();
|
||||
|
||||
for (auto i = size_t{ 0 }, n = tr_torrentTrackerCount(&raw_torrent); i < n; ++i)
|
||||
{
|
||||
if (auto const tracker = tr_torrentTracker(&raw_torrent, i); std::data(tracker.sitename) == host)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match_text(Torrent const& torrent, Glib::ustring const& text)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (text.empty())
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& raw_torrent = torrent.get_underlying();
|
||||
|
||||
/* test the torrent name... */
|
||||
ret = torrent.get_name().casefold().find(text) != Glib::ustring::npos;
|
||||
|
||||
/* test the files... */
|
||||
for (auto i = size_t{ 0 }, n = tr_torrentFileCount(&raw_torrent); i < n && !ret; ++i)
|
||||
{
|
||||
ret = Glib::ustring(tr_torrentFile(&raw_torrent, i).name).casefold().find(text) != Glib::ustring::npos;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
|
||||
bool TorrentFilter::match_vfunc(Glib::RefPtr<Glib::ObjectBase> const& item)
|
||||
{
|
||||
auto const torrent = gtr_ptr_dynamic_cast<Torrent>(item);
|
||||
g_return_val_if_fail(torrent != nullptr, false);
|
||||
|
||||
return match(*torrent);
|
||||
}
|
||||
|
||||
TorrentFilter::Match TorrentFilter::get_strictness_vfunc()
|
||||
{
|
||||
return activity_type_ == Activity::ALL && tracker_type_ == Tracker::ALL && text_.empty() ? Match::ALL : Match::SOME;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void TorrentFilter::changed(Change /*change*/)
|
||||
{
|
||||
signal_changed_.emit();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TorrentFilter::TorrentFilter()
|
||||
: Glib::ObjectBase(typeid(TorrentFilter))
|
||||
{
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
class Torrent;
|
||||
|
||||
class TorrentFilter : public IF_GTKMM4(Gtk::Filter, Glib::Object)
|
||||
{
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
enum class Change{
|
||||
DIFFERENT,
|
||||
LESS_STRICT,
|
||||
MORE_STRICT,
|
||||
};
|
||||
#endif
|
||||
|
||||
public:
|
||||
enum class Activity
|
||||
{
|
||||
ALL,
|
||||
DOWNLOADING,
|
||||
SEEDING,
|
||||
ACTIVE,
|
||||
PAUSED,
|
||||
FINISHED,
|
||||
VERIFYING,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
enum class Tracker
|
||||
{
|
||||
ALL,
|
||||
HOST,
|
||||
};
|
||||
|
||||
public:
|
||||
void set_activity(Activity type);
|
||||
void set_tracker(Tracker type, Glib::ustring const& host);
|
||||
void set_text(Glib::ustring const& text);
|
||||
|
||||
bool match_activity(Torrent const& torrent) const;
|
||||
bool match_tracker(Torrent const& torrent) const;
|
||||
bool match_text(Torrent const& torrent) const;
|
||||
|
||||
bool match(Torrent const& torrent) const;
|
||||
|
||||
void update(Torrent::ChangeFlags changes);
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sigc::signal<void()>& signal_changed();
|
||||
#endif
|
||||
|
||||
static Glib::RefPtr<TorrentFilter> create();
|
||||
|
||||
static bool match_activity(Torrent const& torrent, Activity type);
|
||||
static bool match_tracker(Torrent const& torrent, Tracker type, Glib::ustring const& host);
|
||||
static bool match_text(Torrent const& torrent, Glib::ustring const& text);
|
||||
|
||||
protected:
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
bool match_vfunc(Glib::RefPtr<Glib::ObjectBase> const& item) override;
|
||||
Match get_strictness_vfunc() override;
|
||||
#else
|
||||
void changed(Change change);
|
||||
#endif
|
||||
|
||||
private:
|
||||
TorrentFilter();
|
||||
|
||||
private:
|
||||
Activity activity_type_ = Activity::ALL;
|
||||
Tracker tracker_type_ = Tracker::ALL;
|
||||
Glib::ustring tracker_host_;
|
||||
Glib::ustring text_;
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sigc::signal<void()> signal_changed_;
|
||||
#endif
|
||||
};
|
|
@ -0,0 +1,299 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
|
||||
#include "Torrent.h"
|
||||
#include "TorrentSorter.h"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr bool is_valid_eta(time_t value)
|
||||
{
|
||||
return value != TR_ETA_NOT_AVAIL && value != TR_ETA_UNKNOWN;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int compare_eta(time_t lhs, time_t rhs)
|
||||
{
|
||||
bool const lhs_valid = is_valid_eta(lhs);
|
||||
bool const rhs_valid = is_valid_eta(rhs);
|
||||
|
||||
if (!lhs_valid && !rhs_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lhs_valid)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!rhs_valid)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return -gtr_compare_generic(lhs, rhs);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int compare_ratio(double lhs, double rhs)
|
||||
{
|
||||
if (static_cast<int>(lhs) == TR_RATIO_INF && static_cast<int>(rhs) == TR_RATIO_INF)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (static_cast<int>(lhs) == TR_RATIO_INF)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (static_cast<int>(rhs) == TR_RATIO_INF)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return gtr_compare_generic(lhs, rhs);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_name(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
return lhs.get_name_collated().compare(rhs.get_name_collated());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_queue(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
return gtr_compare_generic(lhs.get_queue_position(), rhs.get_queue_position());
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_ratio(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -compare_ratio(lhs.get_ratio(), rhs.get_ratio()); // default descending
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_queue(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_activity(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -gtr_compare_generic(
|
||||
lhs.get_speed_up() + lhs.get_speed_down(),
|
||||
rhs.get_speed_up() + rhs.get_speed_down()); // default descending
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = -gtr_compare_generic(lhs.get_active_peer_count(), rhs.get_active_peer_count()); // default descending
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_queue(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_age(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -gtr_compare_generic(lhs.get_added_date(), rhs.get_added_date()); // default descending
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_name(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_size(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -gtr_compare_generic(lhs.get_total_size(), rhs.get_total_size()); // default descending
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_name(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_progress(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -gtr_compare_generic(lhs.get_percent_complete(), rhs.get_percent_complete()); // default descending
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = -gtr_compare_generic(
|
||||
lhs.get_seed_ratio_percent_done(),
|
||||
rhs.get_seed_ratio_percent_done()); // default descending
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_ratio(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_eta(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = compare_eta(lhs.get_eta(), rhs.get_eta());
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_name(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
int compare_by_state(Torrent const& lhs, Torrent const& rhs)
|
||||
{
|
||||
auto result = -gtr_compare_generic(lhs.get_activity(), rhs.get_activity());
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
result = compare_by_queue(lhs, rhs);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void TorrentSorter::set_mode(std::string_view mode)
|
||||
{
|
||||
static auto const compare_funcs = std::map<std::string_view, CompareFunc>({
|
||||
{ "sort-by-activity"sv, &compare_by_activity },
|
||||
{ "sort-by-age"sv, &compare_by_age },
|
||||
{ "sort-by-name"sv, &compare_by_name },
|
||||
{ "sort-by-progress"sv, &compare_by_progress },
|
||||
{ "sort-by-queue"sv, &compare_by_queue },
|
||||
{ "sort-by-ratio"sv, &compare_by_ratio },
|
||||
{ "sort-by-size"sv, &compare_by_size },
|
||||
{ "sort-by-state"sv, &compare_by_state },
|
||||
{ "sort-by-time-left"sv, &compare_by_eta },
|
||||
});
|
||||
|
||||
auto compare_func = &compare_by_name;
|
||||
if (auto const compare_func_it = compare_funcs.find(mode); compare_func_it != compare_funcs.end())
|
||||
{
|
||||
compare_func = compare_func_it->second;
|
||||
}
|
||||
|
||||
if (compare_func_ == compare_func)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
compare_func_ = compare_func;
|
||||
changed(Change::DIFFERENT);
|
||||
}
|
||||
|
||||
void TorrentSorter::set_reversed(bool is_reversed)
|
||||
{
|
||||
if (is_reversed_ == is_reversed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
is_reversed_ = is_reversed;
|
||||
changed(Change::INVERTED);
|
||||
}
|
||||
|
||||
int TorrentSorter::compare(Torrent const& lhs, Torrent const& rhs) const
|
||||
{
|
||||
return compare_func_ != nullptr ? std::clamp(compare_func_(lhs, rhs), -1, 1) * (is_reversed_ ? -1 : 1) : 0;
|
||||
}
|
||||
|
||||
void TorrentSorter::update(Torrent::ChangeFlags changes)
|
||||
{
|
||||
using Flag = Torrent::ChangeFlag;
|
||||
|
||||
static auto const compare_flags = std::map<CompareFunc, Torrent::ChangeFlags>({
|
||||
{ &compare_by_activity, Flag::ACTIVE_PEER_COUNT | Flag::QUEUE_POSITION | Flag::SPEED_DOWN | Flag::SPEED_UP },
|
||||
{ &compare_by_age, Flag::ADDED_DATE | Flag::NAME },
|
||||
{ &compare_by_eta, Flag::ETA | Flag::NAME },
|
||||
{ &compare_by_name, Flag::NAME },
|
||||
{ &compare_by_progress, Flag::PERCENT_COMPLETE | Flag::QUEUE_POSITION | Flag::RATIO | Flag::SEED_RATIO_PERCENT_DONE },
|
||||
{ &compare_by_queue, Flag::QUEUE_POSITION },
|
||||
{ &compare_by_ratio, Flag::QUEUE_POSITION | Flag::RATIO },
|
||||
{ &compare_by_size, Flag::NAME | Flag::TOTAL_SIZE },
|
||||
{ &compare_by_state, Flag::ACTIVITY | Flag::QUEUE_POSITION },
|
||||
});
|
||||
|
||||
if (auto const compare_flags_it = compare_flags.find(compare_func_);
|
||||
compare_flags_it != compare_flags.end() && changes.test(compare_flags_it->second))
|
||||
{
|
||||
changed(Change::DIFFERENT);
|
||||
}
|
||||
}
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
|
||||
sigc::signal<void()>& TorrentSorter::signal_changed()
|
||||
{
|
||||
return signal_changed_;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Glib::RefPtr<TorrentSorter> TorrentSorter::create()
|
||||
{
|
||||
return Glib::make_refptr_for_instance(new TorrentSorter());
|
||||
}
|
||||
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
|
||||
Gtk::Ordering TorrentSorter::compare_vfunc(gpointer lhs, gpointer rhs)
|
||||
{
|
||||
auto const* const lhs_torrent = dynamic_cast<Torrent const*>(Glib::wrap_auto(static_cast<GObject*>(lhs)));
|
||||
g_return_val_if_fail(lhs_torrent != nullptr, Gtk::Ordering::SMALLER);
|
||||
|
||||
auto const* const rhs_torrent = dynamic_cast<Torrent const*>(Glib::wrap_auto(static_cast<GObject*>(rhs)));
|
||||
g_return_val_if_fail(rhs_torrent != nullptr, Gtk::Ordering::LARGER);
|
||||
|
||||
return Gtk::Ordering{ compare(*lhs_torrent, *rhs_torrent) };
|
||||
}
|
||||
|
||||
Gtk::Sorter::Order TorrentSorter::get_order_vfunc()
|
||||
{
|
||||
return Gtk::Sorter::Order::PARTIAL;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void TorrentSorter::changed(Change /*change*/)
|
||||
{
|
||||
signal_changed_.emit();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TorrentSorter::TorrentSorter()
|
||||
: Glib::ObjectBase(typeid(TorrentSorter))
|
||||
{
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// This file Copyright © 2022 Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glibmm.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
class Torrent;
|
||||
|
||||
class TorrentSorter : public IF_GTKMM4(Gtk::Sorter, Glib::Object)
|
||||
{
|
||||
using CompareFunc = int (*)(Torrent const&, Torrent const&);
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
enum class Change{
|
||||
DIFFERENT,
|
||||
INVERTED,
|
||||
LESS_STRICT,
|
||||
MORE_STRICT,
|
||||
};
|
||||
#endif
|
||||
|
||||
public:
|
||||
void set_mode(std::string_view mode);
|
||||
void set_reversed(bool is_reversed);
|
||||
|
||||
int compare(Torrent const& lhs, Torrent const& rhs) const;
|
||||
|
||||
void update(Torrent::ChangeFlags changes);
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sigc::signal<void()>& signal_changed();
|
||||
#endif
|
||||
|
||||
static Glib::RefPtr<TorrentSorter> create();
|
||||
|
||||
protected:
|
||||
#if GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
Gtk::Ordering compare_vfunc(gpointer lhs, gpointer rhs) override;
|
||||
Order get_order_vfunc() override;
|
||||
#else
|
||||
void changed(Change change);
|
||||
#endif
|
||||
|
||||
private:
|
||||
TorrentSorter();
|
||||
|
||||
private:
|
||||
CompareFunc compare_func_ = nullptr;
|
||||
bool is_reversed_ = false;
|
||||
|
||||
#if !GTKMM_CHECK_VERSION(4, 0, 0)
|
||||
sigc::signal<void()> signal_changed_;
|
||||
#endif
|
||||
};
|
32
gtk/Utils.cc
32
gtk/Utils.cc
|
@ -434,6 +434,38 @@ bool gtr_file_trash_or_remove(std::string const& filename, tr_error** error)
|
|||
return result;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void object_signal_notify_callback(GObject* object, GParamSpec* /*param_spec*/, gpointer data)
|
||||
{
|
||||
if (object != nullptr && data != nullptr)
|
||||
{
|
||||
if (auto const* const slot = Glib::SignalProxyBase::data_to_slot(data); slot != nullptr)
|
||||
{
|
||||
(*static_cast<sigc::slot<TrObjectSignalNotifyCallback> const*>(slot))(Glib::wrap(object, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Glib::SignalProxy<TrObjectSignalNotifyCallback> gtr_object_signal_notify(Glib::ObjectBase& object)
|
||||
{
|
||||
static auto const object_signal_notify_info = Glib::SignalProxyInfo{
|
||||
.signal_name = "notify",
|
||||
.callback = reinterpret_cast<GCallback>(&object_signal_notify_callback),
|
||||
.notify_callback = reinterpret_cast<GCallback>(&object_signal_notify_callback),
|
||||
};
|
||||
|
||||
return { &object, &object_signal_notify_info };
|
||||
}
|
||||
|
||||
void gtr_object_notify_emit(Glib::ObjectBase& object)
|
||||
{
|
||||
g_signal_emit_by_name(object.gobj(), "notify", nullptr);
|
||||
}
|
||||
|
||||
Glib::ustring gtr_get_help_uri()
|
||||
{
|
||||
static auto const uri = fmt::format("https://transmissionbt.com/help/gtk/{}.{}x", MAJOR_VERSION, MINOR_VERSION / 10);
|
||||
|
|
27
gtk/Utils.h
27
gtk/Utils.h
|
@ -25,8 +25,6 @@
|
|||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/tr-macros.h>
|
||||
|
||||
#include "Session.h"
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -78,6 +76,7 @@
|
|||
#define TR_GTK_SELECTION_MODE(Code) IF_GTKMM4(Gtk::SelectionMode::Code, Gtk::SELECTION_##Code)
|
||||
#define TR_GTK_SORT_TYPE(Code) IF_GTKMM4(Gtk::SortType::Code, Gtk::SORT_##Code)
|
||||
#define TR_GTK_STATE_FLAGS(Code) IF_GTKMM4(Gtk::StateFlags::Code, Gtk::STATE_FLAG_##Code)
|
||||
#define TR_GTK_TREE_MODEL_FLAGS(Code) IF_GTKMM4(Gtk::TreeModel::Flags::Code, Gtk::TREE_MODEL_##Code)
|
||||
#define TR_GTK_TREE_VIEW_COLUMN_SIZING(Code) IF_GTKMM4(Gtk::TreeViewColumn::Sizing::Code, Gtk::TREE_VIEW_COLUMN_##Code)
|
||||
|
||||
#define TR_GTK_TREE_MODEL_CHILD_ITER(Obj) IF_GTKMM4((Obj).get_iter(), (Obj))
|
||||
|
@ -166,6 +165,11 @@ std::string tr_format_time(time_t timestamp);
|
|||
****
|
||||
***/
|
||||
|
||||
using TrObjectSignalNotifyCallback = void(Glib::RefPtr<Glib::ObjectBase const> const&);
|
||||
|
||||
Glib::SignalProxy<TrObjectSignalNotifyCallback> gtr_object_signal_notify(Glib::ObjectBase& object);
|
||||
void gtr_object_notify_emit(Glib::ObjectBase& object);
|
||||
|
||||
void gtr_open_uri(Glib::ustring const& uri);
|
||||
|
||||
void gtr_open_file(std::string const& path);
|
||||
|
@ -242,12 +246,31 @@ inline T gtr_str_strip(T const& text)
|
|||
return new_begin == T::npos ? T() : text.substr(new_begin, new_end == T::npos ? new_end : new_end - new_begin + 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
constexpr int gtr_compare_generic(T const& lhs, T const& rhs)
|
||||
{
|
||||
if (lhs < rhs)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lhs > rhs)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string gtr_get_full_resource_path(std::string const& rel_path);
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
class Session;
|
||||
|
||||
extern size_t const max_recent_dirs;
|
||||
std::list<std::string> gtr_get_recent_dirs(std::string const& pref);
|
||||
void gtr_save_recent_dir(std::string const& pref, Glib::RefPtr<Session> const& core, std::string const& dir);
|
||||
|
|
Loading…
Reference in New Issue