Add support for GTK 4 (#3916)

* Make compact mode switch work for both GTK 3 and 4

* Implement GTK 4-specific view gesture handling

* Fix torrents view context menu on GTK 4

* Explicitly show/hide menubar on startup/teardown

* Switch from `Gtk::Pixbuf` to `Gio::Icon` for views

* Support GTK 4 exceptions based on `std::exception`

* Fix options menu setup with GTK 4

* Use `delete-event` (GTK 3) and `close-request` (GTK 4) signals to handle window clousure

* Add custom file chooser button implementation

GTK 4 drops FileChooserButton widget and suggests implementing it using
Button.

* Add helpers to set X11 hints with GTK 4

* Remove `HigWorkarea` class that's no longer used

* Make main menu shortcuts work with GTK 4

* Make drops work in main window and make dialog with GTK 4

* Remove unused `gtr_action_get_widget()` helper

* Fix text direction mark setup with GTK 4 (due to switch to enum class)

* Fix file tree font size calculation with GTK 4

* Fix crash during shutdown with GTK 4

* Switch from `RadioButton` to `CheckButton` for compatibility with GTK 4

* Fix opening files with GTK 4

* Rework torrent cell renderer to support both GTK 3 and 4

* Disable system tray icon support with GTK 4

* Fix windows positioning with GTK 4

* Fix focus event handling with GTK 4

* Adapt to tree model row/iterator changes in GTK 4

* Adapt to toplevel/root window changes in GTK 4

* Adapt to clipboard changes in GTK 4

* Adapt to icon/theme changes in GTK 4

* Adapt to file/path changes in GTK 4

* Random leftover fixes for GTK 4 compatibility

* Clean up unused code

* Move GTK 3 *.ui files into a subdirectory

* Add GTK 4 *.ui files

* Search for both GTK 3 and 4 during configuration
This commit is contained in:
Mike Gelfand 2022-10-08 15:50:03 -07:00 committed by GitHub
parent e3b871216f
commit c75c6bf5c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 6329 additions and 849 deletions

View File

@ -39,7 +39,7 @@ set(PSL_MINIMUM 0.21.1)
set(QT_MINIMUM 5.6)
option(ENABLE_DAEMON "Build daemon" ON)
tr_auto_option(ENABLE_GTK "Build GTK+ client" AUTO)
tr_auto_option(ENABLE_GTK "Build GTK client" AUTO)
tr_auto_option(ENABLE_QT "Build Qt client" AUTO)
tr_auto_option(ENABLE_MAC "Build Mac client" AUTO)
option(ENABLE_WEB "Build Web client" OFF)
@ -60,6 +60,7 @@ tr_auto_option(USE_SYSTEM_NATPMP "Use system natpmp library" AUTO)
tr_auto_option(USE_SYSTEM_UTP "Use system utp library" AUTO)
tr_auto_option(USE_SYSTEM_B64 "Use system b64 library" AUTO)
tr_auto_option(USE_SYSTEM_PSL "Use system psl library" AUTO)
tr_list_option(USE_GTK_VERSION "Use specific GTK version" AUTO 3 4)
tr_list_option(USE_QT_VERSION "Use specific Qt version" AUTO 5 6)
tr_list_option(WITH_CRYPTO "Use specified crypto library" AUTO ccrypto cyassl mbedtls openssl polarssl wolfssl)
tr_auto_option(WITH_INOTIFY "Enable inotify support (on systems that support it)" AUTO)
@ -266,17 +267,34 @@ endif()
if(ENABLE_GTK)
tr_get_required_flag(ENABLE_GTK GTK_IS_REQUIRED)
pkg_check_modules(GTK ${GTK_IS_REQUIRED}
gtkmm-3.0>=${GTKMM_MINIMUM}
glibmm-2.4>=${GLIBMM_MINIMUM}
giomm-2.4>=${GIOMM_MINIMUM})
if(USE_GTK_VERSION STREQUAL "AUTO" OR USE_GTK_VERSION EQUAL 4)
pkg_check_modules(GTK
gtkmm-4.0>=${GTKMM_MINIMUM}
glibmm-2.68>=${GLIBMM_MINIMUM}
giomm-2.68>=${GIOMM_MINIMUM})
set(GTK_VERSION 4)
endif()
if(NOT GTK_FOUND AND (USE_GTK_VERSION STREQUAL "AUTO" OR USE_GTK_VERSION EQUAL 3))
pkg_check_modules(GTK ${GTK_IS_REQUIRED}
gtkmm-3.0>=${GTKMM_MINIMUM}
glibmm-2.4>=${GLIBMM_MINIMUM}
giomm-2.4>=${GIOMM_MINIMUM})
set(GTK_VERSION 3)
endif()
if(GTK_IS_REQUIRED AND NOT GTK_FOUND)
message(FATAL_ERROR "GTK is required but wasn't found")
endif()
tr_fixup_auto_option(ENABLE_GTK GTK_FOUND GTK_IS_REQUIRED)
if(ENABLE_GTK AND WITH_LIBAPPINDICATOR)
if(ENABLE_GTK AND WITH_LIBAPPINDICATOR AND GTK_VERSION EQUAL 3)
tr_get_required_flag(WITH_LIBAPPINDICATOR LIBAPPINDICATOR_IS_REQUIRED)
pkg_check_modules(LIBAPPINDICATOR appindicator3-0.1>=${LIBAPPINDICATOR_MINIMUM})
tr_fixup_auto_option(WITH_LIBAPPINDICATOR LIBAPPINDICATOR_FOUND LIBAPPINDICATOR_IS_REQUIRED)
else()
set(WITH_LIBAPPINDICATOR OFF)
endif()
else()
set(WITH_LIBAPPINDICATOR OFF)

View File

@ -4,6 +4,7 @@
// License text can be found in the licenses/ folder.
#include <array>
#include <stack>
#include <string>
#include <string_view>
#include <unordered_map>
@ -21,6 +22,8 @@
using namespace std::string_view_literals;
using VariantString = Glib::Variant<Glib::ustring>;
namespace
{
@ -184,14 +187,57 @@ void gtr_action_set_toggled(Glib::ustring const& name, bool b)
get_action(name)->set_state(Glib::Variant<bool>::create(b));
}
Gtk::Widget* gtr_action_get_widget(Glib::ustring const& name)
{
Gtk::Widget* widget;
myBuilder->get_widget(name, widget);
return widget;
}
Glib::RefPtr<Glib::Object> gtr_action_get_object(Glib::ustring const& name)
{
return myBuilder->get_object(name);
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
Glib::RefPtr<Gio::ListModel> gtr_shortcuts_get_from_menu(Glib::RefPtr<Gio::MenuModel> const& menu)
{
auto result = Gio::ListStore<Gtk::Shortcut>::create();
std::stack<Glib::RefPtr<Gio::MenuModel>> links;
links.push(menu);
while (!links.empty())
{
auto const link = links.top();
links.pop();
for (int i = 0; i < link->get_n_items(); ++i)
{
Glib::ustring action_name;
Glib::ustring action_accel;
for (auto it = link->iterate_item_attributes(i); it->next();)
{
if (auto const name = it->get_name(); name == "action")
{
action_name = Glib::VariantBase::cast_dynamic<VariantString>(it->get_value()).get();
}
else if (name == "accel")
{
action_accel = Glib::VariantBase::cast_dynamic<VariantString>(it->get_value()).get();
}
}
if (!action_name.empty() && !action_accel.empty())
{
result->append(Gtk::Shortcut::create(
Gtk::ShortcutTrigger::parse_string(action_accel),
Gtk::NamedAction::create(action_name)));
}
for (auto it = link->iterate_item_links(i); it->next();)
{
links.push(it->get_value());
}
}
}
return result;
}
#endif

View File

@ -18,14 +18,11 @@ void gtr_actions_handler(Glib::ustring const& action_name, gpointer user_data);
void gtr_action_activate(Glib::ustring const& action_name);
void gtr_action_set_sensitive(Glib::ustring const& action_name, bool is_sensitive);
void gtr_action_set_toggled(Glib::ustring const& action_name, bool is_toggled);
Gtk::Widget* gtr_action_get_widget(Glib::ustring const& name);
Glib::RefPtr<Glib::Object> gtr_action_get_object(Glib::ustring const& name);
template<typename T>
inline T* gtr_action_get_widget(Glib::ustring const& name)
{
return static_cast<T*>(gtr_action_get_widget(name));
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
Glib::RefPtr<Gio::ListModel> gtr_shortcuts_get_from_menu(Glib::RefPtr<Gio::MenuModel> const& menu);
#endif
template<typename T>
inline Glib::RefPtr<T> gtr_action_get_object(Glib::ustring const& name)

View File

@ -41,6 +41,7 @@
#include "MakeDialog.h"
#include "MessageLogWindow.h"
#include "OptionsDialog.h"
#include "PathButton.h"
#include "Prefs.h"
#include "PrefsDialog.h"
#include "RelocateDialog.h"
@ -51,6 +52,13 @@
using namespace std::literals;
#if GTKMM_CHECK_VERSION(4, 0, 0)
using FileListValue = Glib::Value<GSList*>;
using FileListHandler = Glib::SListHandler<Glib::RefPtr<Gio::File>>;
using StringValue = Glib::Value<Glib::ustring>;
#endif
#define SHOW_LICENSE
namespace
@ -103,9 +111,12 @@ private:
bool refresh_actions();
void refresh_actions_soon();
void on_main_window_size_allocated(Gtk::Allocation& alloc);
bool on_main_window_focus_in(GdkEventFocus* event);
void on_main_window_size_allocated();
void on_main_window_focus_in();
#if GTKMM_CHECK_VERSION(4, 0, 0)
bool on_drag_data_received(Glib::ValueBase const& value, double x, double y);
#else
void on_drag_data_received(
Glib::RefPtr<Gdk::DragContext> const& drag_context,
gint x,
@ -113,6 +124,7 @@ private:
Gtk::SelectionData const& selection_data,
guint info,
guint time_);
#endif
bool on_rpc_changed_idle(tr_rpc_callback_type type, tr_torrent_id_t torrent_id);
@ -121,7 +133,7 @@ private:
void hideMainWindow();
void toggleMainWindow();
bool winclose(GdkEventAny* event);
bool winclose();
void rowChangedCB(Gtk::TreePath const& path, Gtk::TreeModel::iterator const& iter);
void app_setup();
@ -189,7 +201,7 @@ namespace
template<typename T>
void gtr_window_present(T const& window)
{
window->present(gtk_get_current_event_time());
window->present(GDK_CURRENT_TIME);
}
/***
@ -234,7 +246,7 @@ void Application::Impl::show_details_dialog_for_selected_torrents()
{
auto dialog = DetailsDialog::create(*wind_, core_);
dialog->set_torrents(ids);
dialog->signal_hide().connect([this, key]() { details_.erase(key); });
gtr_window_on_close(*dialog, [this, key]() { details_.erase(key); });
dialog_it = details_.try_emplace(key, std::move(dialog)).first;
dialog_it->second->show();
}
@ -372,23 +384,34 @@ void ensure_magnet_handler_exists()
} // namespace
void Application::Impl::on_main_window_size_allocated(Gtk::Allocation& /*alloc*/)
void Application::Impl::on_main_window_size_allocated()
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
bool const is_maximized = wind_->is_maximized();
#else
auto const gdk_window = wind_->get_window();
bool const is_maximized = gdk_window != nullptr && (gdk_window->get_state() & Gdk::WINDOW_STATE_MAXIMIZED) != 0;
#endif
gtr_pref_int_set(TR_KEY_main_window_is_maximized, is_maximized);
if (!is_maximized)
{
#if !GTKMM_CHECK_VERSION(4, 0, 0)
int x;
int y;
int w;
int h;
wind_->get_position(x, y);
wind_->get_size(w, h);
gtr_pref_int_set(TR_KEY_main_window_x, x);
gtr_pref_int_set(TR_KEY_main_window_y, y);
#endif
int w;
int h;
#if GTKMM_CHECK_VERSION(4, 0, 0)
wind_->get_default_size(w, h);
#else
wind_->get_size(w, h);
#endif
gtr_pref_int_set(TR_KEY_main_window_width, w);
gtr_pref_int_set(TR_KEY_main_window_height, h);
}
@ -534,18 +557,20 @@ void Application::on_startup()
void Application::Impl::on_startup()
{
Gtk::IconTheme::get_default()->add_resource_path(gtr_get_full_resource_path("icons"s));
IF_GTKMM4(Gtk::IconTheme::get_for_display(Gdk::Display::get_default()), Gtk::IconTheme::get_default())
->add_resource_path(gtr_get_full_resource_path("icons"s));
Gtk::Window::set_default_icon_name(AppIconName);
/* Add style provider to the window. */
auto css_provider = Gtk::CssProvider::create();
css_provider->load_from_resource(gtr_get_full_resource_path("transmission-ui.css"));
Gtk::StyleContext::add_provider_for_screen(
Gdk::Screen::get_default(),
Gtk::StyleContext::IF_GTKMM4(add_provider_for_display, add_provider_for_screen)(
IF_GTKMM4(Gdk::Display::get_default(), Gdk::Screen::get_default()),
css_provider,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
std::ignore = FilterBar();
std::ignore = PathButton();
tr_session* session;
@ -576,11 +601,26 @@ void Application::Impl::on_startup()
ui_builder_ = Gtk::Builder::create_from_resource(gtr_get_full_resource_path("transmission-ui.xml"s));
auto const actions = gtr_actions_init(ui_builder_, this);
app_.set_menubar(gtr_action_get_object<Gio::Menu>("main-window-menu"));
auto const main_menu = gtr_action_get_object<Gio::Menu>("main-window-menu");
app_.set_menubar(main_menu);
/* create main window now to be a parent to any error dialogs */
wind_ = MainWindow::create(app_, actions, core_);
wind_->signal_size_allocate().connect(sigc::mem_fun(*this, &Impl::on_main_window_size_allocated));
wind_->set_show_menubar(true);
#if GTKMM_CHECK_VERSION(4, 0, 0)
wind_->property_maximized().signal_changed().connect(sigc::mem_fun(*this, &Impl::on_main_window_size_allocated));
wind_->property_default_width().signal_changed().connect(sigc::mem_fun(*this, &Impl::on_main_window_size_allocated));
wind_->property_default_height().signal_changed().connect(sigc::mem_fun(*this, &Impl::on_main_window_size_allocated));
#else
wind_->signal_size_allocate().connect(sigc::hide<0>(sigc::mem_fun(*this, &Impl::on_main_window_size_allocated)));
#endif
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto const shortcut_controller = Gtk::ShortcutController::create(gtr_shortcuts_get_from_menu(main_menu));
shortcut_controller->set_scope(Gtk::ShortcutScope::GLOBAL);
wind_->add_controller(shortcut_controller);
#endif
app_.hold();
app_setup();
tr_sessionSetRPCCallback(session, &Impl::on_rpc_changed, this);
@ -712,7 +752,7 @@ void Application::Impl::app_setup()
}
else
{
wind_->set_skip_taskbar_hint(icon_ != nullptr);
gtr_window_set_skip_taskbar_hint(*wind_, icon_ != nullptr);
is_iconified_ = false; // ensure that the next toggle iconifies
gtr_action_set_toggled("toggle-main-window", false);
}
@ -750,8 +790,12 @@ void Application::Impl::app_setup()
void Application::Impl::placeWindowFromPrefs()
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
wind_->set_default_size((int)gtr_pref_int_get(TR_KEY_main_window_width), (int)gtr_pref_int_get(TR_KEY_main_window_height));
#else
wind_->resize((int)gtr_pref_int_get(TR_KEY_main_window_width), (int)gtr_pref_int_get(TR_KEY_main_window_height));
wind_->move((int)gtr_pref_int_get(TR_KEY_main_window_x), (int)gtr_pref_int_get(TR_KEY_main_window_y));
#endif
}
void Application::Impl::presentMainWindow()
@ -762,7 +806,7 @@ void Application::Impl::presentMainWindow()
{
is_iconified_ = false;
wind_->set_skip_taskbar_hint(false);
gtr_window_set_skip_taskbar_hint(*wind_, false);
}
if (!wind_->get_visible())
@ -772,14 +816,14 @@ void Application::Impl::presentMainWindow()
}
gtr_window_present(wind_);
wind_->get_window()->raise();
gtr_window_raise(*wind_);
}
void Application::Impl::hideMainWindow()
{
gtr_action_set_toggled("toggle-main-window", false);
wind_->set_skip_taskbar_hint(true);
gtr_window_set_skip_taskbar_hint(*wind_, true);
gtr_widget_set_visible(*wind_, false);
is_iconified_ = true;
}
@ -796,7 +840,7 @@ void Application::Impl::toggleMainWindow()
}
}
bool Application::Impl::winclose(GdkEventAny* /*event*/)
bool Application::Impl::winclose()
{
if (icon_ != nullptr)
{
@ -818,6 +862,32 @@ void Application::Impl::rowChangedCB(Gtk::TreePath const& path, Gtk::TreeModel::
}
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
bool Application::Impl::on_drag_data_received(Glib::ValueBase const& value, double /*x*/, double /*y*/)
{
if (G_VALUE_HOLDS(value.gobj(), GDK_TYPE_FILE_LIST))
{
FileListValue files_value;
files_value.init(value.gobj());
open_files(FileListHandler::slist_to_vector(files_value.get(), Glib::OwnershipType::OWNERSHIP_NONE));
return true;
}
else if (G_VALUE_HOLDS(value.gobj(), StringValue::value_type()))
{
StringValue string_value;
string_value.init(value.gobj());
if (auto const text = gtr_str_strip(string_value.get()); !text.empty())
{
return core_->add_from_url(text);
}
}
return false;
}
#else
void Application::Impl::on_drag_data_received(
Glib::RefPtr<Gdk::DragContext> const& drag_context,
gint /*x*/,
@ -847,6 +917,8 @@ void Application::Impl::on_drag_data_received(
drag_context->drag_finish(true, false, time_);
}
#endif
void Application::Impl::main_window_setup()
{
// g_assert(nullptr == cbdata->wind);
@ -857,14 +929,21 @@ void Application::Impl::main_window_setup()
refresh_actions_soon();
auto const model = core_->get_model();
model->signal_row_changed().connect(sigc::mem_fun(*this, &Impl::rowChangedCB));
wind_->signal_delete_event().connect(sigc::mem_fun(*this, &Impl::winclose));
gtr_window_on_close(*wind_, sigc::mem_fun(*this, &Impl::winclose));
refresh_actions();
/* register to handle URIs that get dragged onto our main window */
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto drop_controller = Gtk::DropTarget::create(G_TYPE_INVALID, Gdk::DragAction::COPY);
drop_controller->set_gtypes({ StringValue::value_type(), GDK_TYPE_FILE_LIST });
drop_controller->signal_drop().connect(sigc::mem_fun(*this, &Impl::on_drag_data_received), false);
wind_->add_controller(drop_controller);
#else
wind_->drag_dest_set(Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
wind_->drag_dest_add_uri_targets();
wind_->drag_dest_add_text_targets(); /* links dragged from browsers are text */
wind_->signal_drag_data_received().connect(sigc::mem_fun(*this, &Impl::on_drag_data_received));
#endif
}
bool Application::Impl::on_session_closed()
@ -900,16 +979,26 @@ void Application::Impl::on_app_exit()
/* stop the refresh-actions timer */
refresh_actions_tag_.disconnect();
#if !GTKMM_CHECK_VERSION(4, 0, 0)
auto* c = static_cast<Gtk::Container*>(wind_.get());
c->remove(*static_cast<Gtk::Bin*>(c)->get_child());
#endif
wind_->set_show_menubar(false);
auto* p = Gtk::make_managed<Gtk::Grid>();
p->set_column_spacing(GUI_PAD_BIG);
p->set_halign(TR_GTK_ALIGN(CENTER));
p->set_valign(TR_GTK_ALIGN(CENTER));
#if GTKMM_CHECK_VERSION(4, 0, 0)
wind_->set_child(*p);
#else
c->add(*p);
#endif
auto* icon = Gtk::make_managed<Gtk::Image>("network-workgroup", Gtk::ICON_SIZE_DIALOG);
auto* icon = Gtk::make_managed<Gtk::Image>();
icon->property_icon_name() = "network-workgroup";
icon->property_icon_size() = IF_GTKMM4(Gtk::IconSize::LARGE, Gtk::ICON_SIZE_DIALOG);
p->attach(*icon, 0, 0, 1, 2);
auto* top_label = Gtk::make_managed<Gtk::Label>();
@ -930,7 +1019,9 @@ void Application::Impl::on_app_exit()
button->signal_clicked().connect([]() { ::exit(0); });
p->attach(*button, 1, 2, 1, 1);
#if !GTKMM_CHECK_VERSION(4, 0, 0)
p->show_all();
#endif
button->grab_focus();
/* clear the UI */
@ -1000,7 +1091,7 @@ void Application::Impl::on_core_error(Session::ErrorCode code, Glib::ustring con
switch (code)
{
case Session::ERR_ADD_TORRENT_ERR:
error_list_.push_back(Glib::path_get_basename(msg));
error_list_.push_back(Glib::path_get_basename(msg.raw()));
break;
case Session::ERR_ADD_TORRENT_DUP:
@ -1017,14 +1108,12 @@ void Application::Impl::on_core_error(Session::ErrorCode code, Glib::ustring con
}
}
bool Application::Impl::on_main_window_focus_in(GdkEventFocus* /*event*/)
void Application::Impl::on_main_window_focus_in()
{
if (wind_ != nullptr)
{
wind_->set_urgency_hint(false);
gtr_window_set_urgency_hint(*wind_, false);
}
return false;
}
void Application::Impl::on_add_torrent(tr_ctor* ctor)
@ -1032,12 +1121,19 @@ void Application::Impl::on_add_torrent(tr_ctor* ctor)
auto w = std::shared_ptr<OptionsDialog>(
OptionsDialog::create(*wind_, core_, std::unique_ptr<tr_ctor, decltype(&tr_ctorFree)>(ctor, &tr_ctorFree)));
w->signal_hide().connect([w]() mutable { w.reset(); });
w->signal_focus_in_event().connect(sigc::mem_fun(*this, &Impl::on_main_window_focus_in));
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto focus_controller = Gtk::EventControllerFocus::create();
focus_controller->signal_enter().connect(sigc::mem_fun(*this, &Impl::on_main_window_focus_in));
w->add_controller(focus_controller);
#else
w->signal_focus_in_event().connect_notify(sigc::hide<0>(sigc::mem_fun(*this, &Impl::on_main_window_focus_in)));
#endif
if (wind_ != nullptr)
{
wind_->set_urgency_hint(true);
gtr_window_set_urgency_hint(*wind_, true);
}
w->show();
@ -1080,7 +1176,7 @@ void Application::Impl::on_prefs_changed(tr_quark const key)
case TR_KEY_show_notification_area_icon:
if (bool const show = gtr_pref_flag_get(key); show && icon_ == nullptr)
{
icon_ = std::make_unique<SystemTrayIcon>(*wind_, core_);
icon_ = SystemTrayIcon::create(*wind_, core_);
}
else if (!show && icon_ != nullptr)
{
@ -1327,10 +1423,8 @@ void Application::Impl::show_about_dialog()
#endif
d->set_transient_for(*wind_);
d->set_modal(true);
#if GTKMM_CHECK_VERSION(4, 0, 0)
d->signal_close_request().connect_notify([d]() mutable { d.reset(); });
#else
d->signal_delete_event().connect_notify([d](void* /*event*/) mutable { d.reset(); });
gtr_window_on_close(*d, [d]() mutable { d.reset(); });
#if !GTKMM_CHECK_VERSION(4, 0, 0)
d->signal_response().connect_notify([&dref = *d](int /*response*/) { dref.close(); });
#endif
d->show();
@ -1420,18 +1514,13 @@ void Application::Impl::copy_magnet_link_to_clipboard(tr_torrent* tor) const
{
auto const magnet = tr_torrentGetMagnetLink(tor);
auto const display = wind_->get_display();
GdkAtom selection;
Glib::RefPtr<Gtk::Clipboard> clipboard;
/* this is The Right Thing for copy/paste... */
selection = GDK_SELECTION_CLIPBOARD;
clipboard = Gtk::Clipboard::get_for_display(display, selection);
clipboard->set_text(magnet);
IF_GTKMM4(display->get_clipboard(), Gtk::Clipboard::get_for_display(display, GDK_SELECTION_CLIPBOARD))->set_text(magnet);
/* ...but people using plain ol' X need this instead */
selection = GDK_SELECTION_PRIMARY;
clipboard = Gtk::Clipboard::get_for_display(display, selection);
clipboard->set_text(magnet);
IF_GTKMM4(display->get_primary_clipboard(), Gtk::Clipboard::get_for_display(display, GDK_SELECTION_PRIMARY))
->set_text(magnet);
}
void gtr_actions_handler(Glib::ustring const& action_name, gpointer user_data)
@ -1446,19 +1535,19 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
if (action_name == "open-torrent-from-url")
{
auto w = std::shared_ptr<TorrentUrlChooserDialog>(TorrentUrlChooserDialog::create(*wind_, core_));
w->signal_hide().connect([w]() mutable { w.reset(); });
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
w->show();
}
else if (action_name == "open-torrent")
{
auto w = std::shared_ptr<TorrentFileChooserDialog>(TorrentFileChooserDialog::create(*wind_, core_));
w->signal_hide().connect([w]() mutable { w.reset(); });
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
w->show();
}
else if (action_name == "show-stats")
{
auto dialog = std::shared_ptr<StatsDialog>(StatsDialog::create(*wind_, core_));
dialog->signal_hide().connect([dialog]() mutable { dialog.reset(); });
gtr_window_on_close(*dialog, [dialog]() mutable { dialog.reset(); });
dialog->show();
}
else if (action_name == "donate")
@ -1489,7 +1578,7 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
if (!ids.empty())
{
auto w = std::shared_ptr<RelocateDialog>(RelocateDialog::create(*wind_, core_, ids));
w->signal_hide().connect([w]() mutable { w.reset(); });
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
w->show();
}
}
@ -1512,7 +1601,7 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
else if (action_name == "new-torrent")
{
auto w = std::shared_ptr<MakeDialog>(MakeDialog::create(*wind_, core_));
w->signal_hide().connect([w]() mutable { w.reset(); });
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
w->show();
}
else if (action_name == "remove-torrent")
@ -1540,7 +1629,7 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
if (prefs_ == nullptr)
{
prefs_ = PrefsDialog::create(*wind_, core_);
prefs_->signal_hide().connect([this]() { prefs_.reset(); });
gtr_window_on_close(*prefs_, [this]() { prefs_.reset(); });
}
gtr_window_present(prefs_);
@ -1550,12 +1639,20 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
if (msgwin_ == nullptr)
{
msgwin_ = MessageLogWindow::create(*wind_, core_);
msgwin_->signal_hide().connect([this]() { msgwin_.reset(); });
gtr_window_on_close(
*msgwin_,
[this]()
{
gtr_action_set_toggled("toggle-message-log", false);
msgwin_.reset();
});
gtr_action_set_toggled("toggle-message-log", true);
msgwin_->show();
}
else
{
msgwin_->hide();
msgwin_->close();
}
}
else if (action_name == "show-about-dialog")

View File

@ -20,51 +20,66 @@ endif()
find_program(APPSTREAM appstreamcli)
if(GTK_VERSION EQUAL 4)
set(UI_SUBDIR ui/gtk4)
else()
set(UI_SUBDIR ui/gtk3)
endif()
set(${PROJECT_NAME}_UI_FILES
AddTrackerDialog.ui
DetailsDialog.ui
EditTrackersDialog.ui
FilterBar.ui
MainWindow.ui
MakeDialog.ui
MakeProgressDialog.ui
MessageLogWindow.ui
OptionsDialog.ui
PrefsDialog.ui
RelocateDialog.ui
StatsDialog.ui
TorrentUrlChooserDialog.ui
${UI_SUBDIR}/AddTrackerDialog.ui
${UI_SUBDIR}/DetailsDialog.ui
${UI_SUBDIR}/EditTrackersDialog.ui
${UI_SUBDIR}/FilterBar.ui
${UI_SUBDIR}/MainWindow.ui
${UI_SUBDIR}/MakeDialog.ui
${UI_SUBDIR}/MakeProgressDialog.ui
${UI_SUBDIR}/MessageLogWindow.ui
${UI_SUBDIR}/OptionsDialog.ui
${UI_SUBDIR}/PrefsDialog.ui
${UI_SUBDIR}/RelocateDialog.ui
${UI_SUBDIR}/StatsDialog.ui
${UI_SUBDIR}/TorrentUrlChooserDialog.ui
)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.c
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.h
COMMAND
${GLIB_COMPILE_RESOURCES_EXECUTABLE}
--target=${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.c
--sourcedir=${CMAKE_CURRENT_SOURCE_DIR}
--generate-source
--c-name transmission
transmission.gresource.xml
COMMAND
${GLIB_COMPILE_RESOURCES_EXECUTABLE}
--target=${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.h
--sourcedir=${CMAKE_CURRENT_SOURCE_DIR}
--generate-header
--c-name transmission
transmission.gresource.xml
DEPENDS
icons/hicolor_apps_scalable_transmission.svg
icons/lock.svg
icons/options-symbolic.svg
icons/ratio-symbolic.svg
icons/turtle-symbolic.svg
transmission-ui.xml
transmission.gresource.xml
${${PROJECT_NAME}_UI_FILES}
WORKING_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}
macro(gtr_compile_resources NAME INPUT_DIR INPUT_FILE OUTPUT_FILE_BASE)
add_custom_command(
OUTPUT
"${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_BASE}.c"
"${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_BASE}.h"
COMMAND
"${GLIB_COMPILE_RESOURCES_EXECUTABLE}"
"--target=${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_BASE}.c"
"--sourcedir=${INPUT_DIR}"
--generate-source
--c-name "${NAME}"
"${INPUT_DIR}/${INPUT_FILE}"
COMMAND
"${GLIB_COMPILE_RESOURCES_EXECUTABLE}"
"--target=${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_FILE_BASE}.h"
"--sourcedir=${INPUT_DIR}"
--generate-header
--c-name "${NAME}"
"${INPUT_DIR}/${INPUT_FILE}"
DEPENDS
"${INPUT_DIR}/${INPUT_FILE}"
${ARGN}
WORKING_DIRECTORY
"${INPUT_DIR}"
)
endmacro()
gtr_compile_resources(transmission ${CMAKE_CURRENT_SOURCE_DIR} transmission.gresource.xml transmission-resources
icons/hicolor_apps_scalable_transmission.svg
icons/lock.svg
icons/options-symbolic.svg
icons/ratio-symbolic.svg
icons/turtle-symbolic.svg
transmission-ui.css
transmission-ui.xml
)
gtr_compile_resources(transmission_ui ${CMAKE_CURRENT_SOURCE_DIR}/${UI_SUBDIR} transmission-ui.gresource.xml transmission-ui-resources
${${PROJECT_NAME}_UI_FILES}
)
if(ENABLE_NLS)
@ -94,7 +109,6 @@ set(${PROJECT_NAME}_SOURCES
FileList.cc
FilterBar.cc
FreeSpaceLabel.cc
HigWorkarea.cc
IconCache.cc
main.cc
MainWindow.cc
@ -102,6 +116,7 @@ set(${PROJECT_NAME}_SOURCES
MessageLogWindow.cc
Notify.cc
OptionsDialog.cc
PathButton.cc
Prefs.cc
PrefsDialog.cc
RelocateDialog.cc
@ -111,6 +126,7 @@ set(${PROJECT_NAME}_SOURCES
TorrentCellRenderer.cc
Utils.cc
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.c
${CMAKE_CURRENT_BINARY_DIR}/transmission-ui-resources.c
)
set(${PROJECT_NAME}_HEADERS
@ -129,6 +145,7 @@ set(${PROJECT_NAME}_HEADERS
MessageLogWindow.h
Notify.h
OptionsDialog.h
PathButton.h
Prefs.h
PrefsDialog.h
RelocateDialog.h
@ -138,6 +155,7 @@ set(${PROJECT_NAME}_HEADERS
TorrentCellRenderer.h
Utils.h
${CMAKE_CURRENT_BINARY_DIR}/transmission-resources.h
${CMAKE_CURRENT_BINARY_DIR}/transmission-ui-resources.h
)
include_directories(

View File

@ -55,7 +55,7 @@ private:
void tracker_page_init(Glib::RefPtr<Gtk::Builder> const& builder);
void options_page_init(Glib::RefPtr<Gtk::Builder> const& builder);
void on_details_window_size_allocated(Gtk::Allocation& alloc);
void on_details_window_size_allocated();
bool onPeerViewQueryTooltip(int x, int y, bool keyboard_tip, Glib::RefPtr<Gtk::Tooltip> const& tooltip);
void onMorePeerInfoToggled();
@ -191,7 +191,7 @@ std::vector<tr_torrent*> DetailsDialog::Impl::getTorrents() const
namespace
{
void set_togglebutton_if_different(Gtk::ToggleButton* toggle, sigc::connection& tag, bool value)
void set_togglebutton_if_different(Gtk::CheckButton* toggle, sigc::connection& tag, bool value)
{
bool const currentValue = toggle->get_active();
@ -1150,7 +1150,11 @@ public:
PeerModelColumns const peer_cols;
void initPeerRow(Gtk::TreeIter const& iter, std::string_view key, std::string_view torrent_name, tr_peer_stat const* peer)
void initPeerRow(
Gtk::TreeModel::iterator const& iter,
std::string_view key,
std::string_view torrent_name,
tr_peer_stat const* peer)
{
g_return_if_fail(peer != nullptr);
@ -1173,7 +1177,7 @@ void initPeerRow(Gtk::TreeIter const& iter, std::string_view key, std::string_vi
(*iter)[peer_cols.torrent_name] = Glib::ustring{ std::data(torrent_name), std::size(torrent_name) };
}
void refreshPeerRow(Gtk::TreeIter const& iter, tr_peer_stat const* peer)
void refreshPeerRow(Gtk::TreeModel::iterator const& iter, tr_peer_stat const* peer)
{
std::string up_speed;
std::string down_speed;
@ -1268,7 +1272,7 @@ void DetailsDialog::Impl::refreshPeerList(std::vector<tr_torrent*> const& torren
}
/* step 2: mark all the peers in the list as not-updated */
for (auto const& row : store->children())
for (auto& row : store->children())
{
row[peer_cols.was_updated] = false;
}
@ -1347,7 +1351,7 @@ void DetailsDialog::Impl::refreshWebseedList(std::vector<tr_torrent*> const& tor
};
/* step 1: mark all webseeds as not-updated */
for (auto const& row : store->children())
for (auto& row : store->children())
{
row[webseed_cols.was_updated] = false;
}
@ -1698,7 +1702,10 @@ void DetailsDialog::Impl::peer_page_init(Glib::RefPtr<Gtk::Builder> const& build
webseed_store_ = Gtk::ListStore::create(webseed_cols);
auto* v = gtr_get_widget<Gtk::TreeView>(builder, "webseeds_view");
v->set_model(webseed_store_);
v->signal_button_release_event().connect([v](GdkEventButton* event) { return on_tree_view_button_released(v, event); });
setup_tree_view_button_event_handling(
*v,
{},
[v](double view_x, double view_y) { return on_tree_view_button_released(*v, view_x, view_y); });
{
auto* r = Gtk::make_managed<Gtk::CellRendererText>();
@ -1726,9 +1733,11 @@ void DetailsDialog::Impl::peer_page_init(Glib::RefPtr<Gtk::Builder> const& build
peer_view_->set_model(m);
peer_view_->set_has_tooltip(true);
peer_view_->signal_query_tooltip().connect(sigc::mem_fun(*this, &Impl::onPeerViewQueryTooltip));
peer_view_->signal_button_release_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_released(peer_view_, event); });
peer_view_->signal_query_tooltip().connect(sigc::mem_fun(*this, &Impl::onPeerViewQueryTooltip), false);
setup_tree_view_button_event_handling(
*peer_view_,
{},
[this](double view_x, double view_y) { return on_tree_view_button_released(*peer_view_, view_x, view_y); });
setPeerViewColumns(peer_view_);
@ -1756,10 +1765,12 @@ std::array<std::string_view, 3> const text_dir_mark = { ""sv, "\u200E"sv, "\u200
void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::TextDirection direction, std::ostream& gstr)
{
auto const dir_mark = text_dir_mark[static_cast<int>(direction)];
if (tracker.hasAnnounced && tracker.announceState != TR_TRACKER_INACTIVE)
{
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
auto const time_span_ago = tr_format_time_relative(now, tracker.lastAnnounceTime);
if (tracker.lastAnnounceSucceeded)
@ -1800,13 +1811,13 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
{
case TR_TRACKER_INACTIVE:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << _("No updates scheduled");
break;
case TR_TRACKER_WAITING:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << fmt::format(
_("Asking for more peers {time_span_from_now}"),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextAnnounceTime)));
@ -1814,13 +1825,13 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
case TR_TRACKER_QUEUED:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << _("Queued to ask for more peers");
break;
case TR_TRACKER_ACTIVE:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround time_span_ago
_("Asked for more peers {markup_begin}{time_span_ago}{markup_end}"),
@ -1836,10 +1847,12 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::TextDirection direction, std::ostream& gstr)
{
auto const dir_mark = text_dir_mark[static_cast<int>(direction)];
if (tracker.hasScraped)
{
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
auto const time_span_ago = tr_format_time_relative(now, tracker.lastScrapeTime);
if (tracker.lastScrapeSucceeded)
@ -1874,7 +1887,7 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
case TR_TRACKER_WAITING:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << fmt::format(
_("Asking for peer counts in {time_span_from_now}"),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextScrapeTime)));
@ -1882,13 +1895,13 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
case TR_TRACKER_QUEUED:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << _("Queued to ask for peer counts");
break;
case TR_TRACKER_ACTIVE:
gstr << '\n';
gstr << text_dir_mark[direction];
gstr << dir_mark;
gstr << fmt::format(
_("Asked for peer counts {markup_begin}{time_span_ago}{markup_end}"),
fmt::arg("markup_begin", "<small>"),
@ -1909,7 +1922,7 @@ void buildTrackerSummary(
Gtk::TextDirection direction)
{
// hostname
gstr << text_dir_mark[direction];
gstr << text_dir_mark[static_cast<int>(direction)];
gstr << (tracker.isBackup ? "<i>" : "<b>");
gstr << Glib::Markup::escape_text(!key.empty() ? fmt::format(FMT_STRING("{:s} - {:s}"), tracker.host, key) : tracker.host);
gstr << (tracker.isBackup ? "</i>" : "</b>");
@ -2027,7 +2040,7 @@ void DetailsDialog::Impl::refreshTracker(std::vector<tr_torrent*> const& torrent
}
/* step 2: mark all the trackers in the list as not-updated */
for (auto const& row : store->children())
for (auto& row : store->children())
{
row[tracker_cols.was_updated] = false;
}
@ -2203,7 +2216,7 @@ void EditTrackersDialog::on_response(int response)
if (do_destroy)
{
hide();
close();
}
}
@ -2214,7 +2227,7 @@ void DetailsDialog::Impl::on_edit_trackers()
if (auto const* const tor = tracker_list_get_current_torrent(); tor != nullptr)
{
auto d = std::shared_ptr<EditTrackersDialog>(EditTrackersDialog::create(dialog_, core_, tor));
d->signal_hide().connect([d]() mutable { d.reset(); });
gtr_window_on_close(*d, [d]() mutable { d.reset(); });
d->show();
}
}
@ -2322,7 +2335,7 @@ void AddTrackerDialog::on_response(int response)
if (destroy)
{
hide();
close();
}
}
@ -2333,7 +2346,7 @@ void DetailsDialog::Impl::on_tracker_list_add_button_clicked()
if (auto const* const tor = tracker_list_get_current_torrent(); tor != nullptr)
{
auto d = std::shared_ptr<AddTrackerDialog>(AddTrackerDialog::create(dialog_, core_, tor));
d->signal_hide().connect([d]() mutable { d.reset(); });
gtr_window_on_close(*d, [d]() mutable { d.reset(); });
d->show();
}
}
@ -2375,10 +2388,11 @@ void DetailsDialog::Impl::tracker_page_init(Glib::RefPtr<Gtk::Builder> const& /*
trackers_filtered_->set_visible_func(sigc::mem_fun(*this, &Impl::trackerVisibleFunc));
tracker_view_->set_model(trackers_filtered_);
tracker_view_->signal_button_press_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_pressed(tracker_view_, event); });
tracker_view_->signal_button_release_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_released(tracker_view_, event); });
setup_tree_view_button_event_handling(
*tracker_view_,
[this](guint /*button*/, TrGdkModifierType /*state*/, double view_x, double view_y, bool context_menu_requested)
{ return on_tree_view_button_pressed(*tracker_view_, view_x, view_y, context_menu_requested); },
[this](double view_x, double view_y) { return on_tree_view_button_released(*tracker_view_, view_x, view_y); });
auto sel = tracker_view_->get_selection();
sel->signal_changed().connect(sigc::mem_fun(*this, &Impl::on_tracker_list_selection_changed));
@ -2436,11 +2450,15 @@ void DetailsDialog::Impl::refresh()
}
}
void DetailsDialog::Impl::on_details_window_size_allocated(Gtk::Allocation& /*alloc*/)
void DetailsDialog::Impl::on_details_window_size_allocated()
{
int w = 0;
int h = 0;
#if GTKMM_CHECK_VERSION(4, 0, 0)
dialog_.get_default_size(w, h);
#else
dialog_.get_size(w, h);
#endif
gtr_pref_int_set(TR_KEY_details_window_width, w);
gtr_pref_int_set(TR_KEY_details_window_height, h);
}
@ -2511,10 +2529,18 @@ DetailsDialog::Impl::Impl(DetailsDialog& dialog, Glib::RefPtr<Gtk::Builder> cons
, file_label_(gtr_get_widget<Gtk::Label>(builder, "files_label"))
{
/* return saved window size */
dialog_.resize((int)gtr_pref_int_get(TR_KEY_details_window_width), (int)gtr_pref_int_get(TR_KEY_details_window_height));
dialog_.signal_size_allocate().connect(sigc::mem_fun(*this, &Impl::on_details_window_size_allocated));
auto const width = (int)gtr_pref_int_get(TR_KEY_details_window_width);
auto const height = (int)gtr_pref_int_get(TR_KEY_details_window_height);
#if GTKMM_CHECK_VERSION(4, 0, 0)
dialog_.set_default_size(width, height);
dialog_.property_default_width().signal_changed().connect(sigc::mem_fun(*this, &Impl::on_details_window_size_allocated));
dialog_.property_default_height().signal_changed().connect(sigc::mem_fun(*this, &Impl::on_details_window_size_allocated));
#else
dialog_.resize(width, height);
dialog_.signal_size_allocate().connect(sigc::hide<0>(sigc::mem_fun(*this, &Impl::on_details_window_size_allocated)));
#endif
dialog_.signal_response().connect(sigc::hide<0>(sigc::mem_fun(dialog_, &DetailsDialog::hide)));
dialog_.signal_response().connect(sigc::hide<0>(sigc::mem_fun(dialog_, &DetailsDialog::close)));
info_page_init(builder);
peer_page_init(builder);

View File

@ -129,5 +129,5 @@ void gtr_confirm_remove(
d.reset();
});
d->show_all();
d->show();
}

View File

@ -61,7 +61,7 @@ public:
add(enabled);
}
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> icon;
Gtk::TreeModelColumn<Glib::RefPtr<Gio::Icon>> icon;
Gtk::TreeModelColumn<Glib::ustring> label;
Gtk::TreeModelColumn<Glib::ustring> label_esc;
Gtk::TreeModelColumn<int> prog;
@ -81,8 +81,6 @@ FileModelColumns const file_cols;
class FileList::Impl
{
public:
Impl(FileList& widget, Gtk::TreeView* view, Glib::RefPtr<Session> const& core, tr_torrent_id_t torrent_id);
Impl(FileList& widget, Glib::RefPtr<Session> const& core, tr_torrent_id_t torrent_id);
Impl(
FileList& widget,
Glib::RefPtr<Gtk::Builder> const& builder,
@ -99,13 +97,13 @@ private:
void clearData();
void refresh();
bool getAndSelectEventPath(GdkEventButton const* event, Gtk::TreeViewColumn*& col, Gtk::TreeModel::Path& path);
bool getAndSelectEventPath(double view_x, double view_y, Gtk::TreeViewColumn*& col, Gtk::TreeModel::Path& path);
std::vector<tr_file_index_t> getActiveFilesForPath(Gtk::TreeModel::Path const& path) const;
std::vector<tr_file_index_t> getSelectedFilesAndDescendants() const;
std::vector<tr_file_index_t> getSubtree(Gtk::TreeModel::Path const& path) const;
bool onViewButtonPressed(GdkEventButton const* event);
bool onViewButtonPressed(guint button, TrGdkModifierType state, double view_x, double view_y);
bool onViewPathToggled(Gtk::TreeViewColumn* col, Gtk::TreeModel::Path const& path);
void onRowActivated(Gtk::TreeModel::Path const& path, Gtk::TreeViewColumn* col);
void cell_edited_callback(Glib::ustring const& path_string, Glib::ustring const& newname);
@ -276,9 +274,9 @@ void gtr_tree_model_foreach_postorder_subtree(
Gtk::TreeModel::iterator const& parent,
Gtk::TreeModel::SlotForeachIter const& func)
{
for (auto const& child : parent->children())
for (auto& child : parent->children())
{
gtr_tree_model_foreach_postorder_subtree(child, func);
gtr_tree_model_foreach_postorder_subtree(TR_GTK_TREE_MODEL_CHILD_ITER(child), func);
}
if (parent)
@ -289,9 +287,9 @@ void gtr_tree_model_foreach_postorder_subtree(
void gtr_tree_model_foreach_postorder(Glib::RefPtr<Gtk::TreeModel> const& model, Gtk::TreeModel::SlotForeachIter const& func)
{
for (auto const& iter : model->children())
for (auto& iter : model->children())
{
gtr_tree_model_foreach_postorder_subtree(iter, func);
gtr_tree_model_foreach_postorder_subtree(TR_GTK_TREE_MODEL_CHILD_ITER(iter), func);
}
}
@ -452,7 +450,7 @@ void buildTree(FileRowNode& node, build_data& build)
bool const isLeaf = node.child_count() == 0;
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(child_data.name.raw()) : DirectoryMimeType;
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *build.w);
auto const icon = gtr_get_mime_type_icon(mime_type);
auto const file = isLeaf ? tr_torrentFile(build.tor, child_data.index) : tr_file_view{};
int const priority = isLeaf ? file.priority : 0;
bool const enabled = isLeaf ? file.wanted : true;
@ -573,14 +571,14 @@ void FileList::Impl::set_torrent(tr_torrent_id_t tor_id)
namespace
{
void renderDownload(Gtk::CellRenderer* renderer, Gtk::TreeModel::iterator const& iter)
void renderDownload(Gtk::CellRenderer* renderer, Gtk::TreeModel::const_iterator const& iter)
{
auto const enabled = iter->get_value(file_cols.enabled);
static_cast<Gtk::CellRendererToggle*>(renderer)->property_inconsistent() = enabled == MIXED;
static_cast<Gtk::CellRendererToggle*>(renderer)->property_active() = enabled == true;
}
void renderPriority(Gtk::CellRenderer* renderer, Gtk::TreeModel::iterator const& iter)
void renderPriority(Gtk::CellRenderer* renderer, Gtk::TreeModel::const_iterator const& iter)
{
Glib::ustring text;
@ -609,13 +607,14 @@ void renderPriority(Gtk::CellRenderer* renderer, Gtk::TreeModel::iterator const&
/* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */
std::string buildFilename(tr_torrent const* tor, Gtk::TreeModel::iterator const& iter)
{
std::list<std::string> tokens;
std::vector<std::string> tokens;
for (auto child = iter; child; child = child->parent())
{
tokens.push_front(child->get_value(file_cols.label));
tokens.push_back(child->get_value(file_cols.label));
}
tokens.emplace_front(tr_torrentGetCurrentDir(tor));
tokens.emplace_back(tr_torrentGetCurrentDir(tor));
std::reverse(tokens.begin(), tokens.end());
return Glib::build_filename(tokens);
}
@ -709,12 +708,12 @@ bool FileList::Impl::onViewPathToggled(Gtk::TreeViewColumn* col, Gtk::TreeModel:
/**
* @note 'col' and 'path' are assumed not to be nullptr.
*/
bool FileList::Impl::getAndSelectEventPath(GdkEventButton const* event, Gtk::TreeViewColumn*& col, Gtk::TreeModel::Path& path)
bool FileList::Impl::getAndSelectEventPath(double view_x, double view_y, Gtk::TreeViewColumn*& col, Gtk::TreeModel::Path& path)
{
int cell_x;
int cell_y;
if (view_->get_path_at_pos(event->x, event->y, path, col, cell_x, cell_y))
if (view_->get_path_at_pos(view_x, view_y, path, col, cell_x, cell_y))
{
if (auto const sel = view_->get_selection(); !sel->is_selected(path))
{
@ -728,14 +727,15 @@ bool FileList::Impl::getAndSelectEventPath(GdkEventButton const* event, Gtk::Tre
return false;
}
bool FileList::Impl::onViewButtonPressed(GdkEventButton const* event)
bool FileList::Impl::onViewButtonPressed(guint button, TrGdkModifierType state, double view_x, double view_y)
{
Gtk::TreeViewColumn* col;
Gtk::TreeModel::Path path;
bool handled = false;
if (event->type == GDK_BUTTON_PRESS && event->button == 1 && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) == 0 &&
getAndSelectEventPath(event, col, path))
if (button == GDK_BUTTON_PRIMARY &&
(state & (TR_GDK_MODIFIED_TYPE(SHIFT_MASK) | TR_GDK_MODIFIED_TYPE(CONTROL_MASK))) == TrGdkModifierType{} &&
getAndSelectEventPath(view_x, view_y, col, path))
{
handled = onViewPathToggled(col, path);
}
@ -758,7 +758,7 @@ bool FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib:
{
bool const isLeaf = iter->children().empty();
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(newname.raw()) : DirectoryMimeType;
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *view_);
auto const icon = gtr_get_mime_type_icon(mime_type);
(*iter)[file_cols.label] = newname;
(*iter)[file_cols.icon] = icon;
@ -772,7 +772,7 @@ bool FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib:
else
{
auto w = std::make_shared<Gtk::MessageDialog>(
*static_cast<Gtk::Window*>(widget_.get_toplevel()),
*static_cast<Gtk::Window*>(TR_GTK_WIDGET_GET_ROOT(widget_)),
fmt::format(
_("Couldn't rename '{old_path}' as '{path}': {error} ({error_code})"),
fmt::arg("old_path", path_string),
@ -842,12 +842,6 @@ void FileList::Impl::cell_edited_callback(Glib::ustring const& path_string, Glib
rename_data.release());
}
FileList::FileList(Glib::RefPtr<Session> const& core, tr_torrent_id_t tor_id)
: Gtk::ScrolledWindow()
, impl_(std::make_unique<Impl>(*this, core, tor_id))
{
}
FileList::FileList(
BaseObjectType* cast_item,
Glib::RefPtr<Gtk::Builder> const& builder,
@ -859,19 +853,33 @@ FileList::FileList(
{
}
FileList::Impl::Impl(FileList& widget, Gtk::TreeView* view, Glib::RefPtr<Session> const& core, tr_torrent_id_t torrent_id)
FileList::Impl::Impl(
FileList& widget,
Glib::RefPtr<Gtk::Builder> const& builder,
Glib::ustring const& view_name,
Glib::RefPtr<Session> const& core,
tr_torrent_id_t torrent_id)
: widget_(widget)
, core_(core)
, view_(view)
, view_(gtr_get_widget<Gtk::TreeView>(builder, view_name))
{
/* create the view */
view_->signal_button_press_event().connect(sigc::mem_fun(*this, &Impl::onViewButtonPressed), false);
view_->signal_row_activated().connect(sigc::mem_fun(*this, &Impl::onRowActivated));
view_->signal_button_release_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_released(view_, event); });
setup_tree_view_button_event_handling(
*view_,
[this](guint button, TrGdkModifierType state, double view_x, double view_y, bool /*context_menu_requested*/)
{ return onViewButtonPressed(button, state, view_x, view_y); },
[this](double view_x, double view_y) { return on_tree_view_button_released(*view_, view_x, view_y); });
auto pango_font_description = view_->create_pango_context()->get_font_description();
pango_font_description.set_size(pango_font_description.get_size() * 0.8);
if (auto const new_size = pango_font_description.get_size() * 0.8; pango_font_description.get_size_is_absolute())
{
pango_font_description.set_absolute_size(new_size);
}
else
{
pango_font_description.set_size(new_size);
}
/* set up view */
auto const sel = view_->get_selection();
@ -887,7 +895,12 @@ FileList::Impl::Impl(FileList& widget, Gtk::TreeView* view, Glib::RefPtr<Session
col->set_resizable(true);
auto* icon_rend = Gtk::make_managed<Gtk::CellRendererPixbuf>();
col->pack_start(*icon_rend, false);
col->add_attribute(icon_rend->property_pixbuf(), file_cols.icon);
col->add_attribute(icon_rend->property_gicon(), file_cols.icon);
#if GTKMM_CHECK_VERSION(4, 0, 0)
icon_rend->property_icon_size() = Gtk::IconSize::NORMAL;
#else
icon_rend->property_stock_size() = Gtk::ICON_SIZE_MENU;
#endif
/* add text renderer */
auto* text_rend = Gtk::make_managed<Gtk::CellRendererText>();
text_rend->property_editable() = true;
@ -974,26 +987,4 @@ FileList::Impl::Impl(FileList& widget, Gtk::TreeView* view, Glib::RefPtr<Session
set_torrent(torrent_id);
}
FileList::Impl::Impl(FileList& widget, Glib::RefPtr<Session> const& core, tr_torrent_id_t torrent_id)
: Impl(widget, Gtk::make_managed<Gtk::TreeView>(), core, torrent_id)
{
view_->set_border_width(GUI_PAD_BIG);
/* create the scrolled window and stick the view in it */
widget_.set_policy(TR_GTK_POLICY_TYPE(AUTOMATIC), TR_GTK_POLICY_TYPE(AUTOMATIC));
widget_.set_shadow_type(Gtk::SHADOW_IN);
widget_.add(*view_);
widget_.set_size_request(-1, 200);
}
FileList::Impl::Impl(
FileList& widget,
Glib::RefPtr<Gtk::Builder> const& builder,
Glib::ustring const& view_name,
Glib::RefPtr<Session> const& core,
tr_torrent_id_t torrent_id)
: Impl(widget, gtr_get_widget<Gtk::TreeView>(builder, view_name), core, torrent_id)
{
}
FileList::~FileList() = default;

View File

@ -16,7 +16,6 @@ class Session;
class FileList : public Gtk::ScrolledWindow
{
public:
FileList(Glib::RefPtr<Session> const& core, tr_torrent_id_t torrent_id);
FileList(
BaseObjectType* cast_item,
Glib::RefPtr<Gtk::Builder> const& builder,

View File

@ -72,6 +72,11 @@ private:
sigc::connection torrent_model_row_inserted_tag_;
sigc::connection torrent_model_row_deleted_cb_tag_;
sigc::connection filter_model_row_deleted_tag_;
sigc::connection filter_model_row_inserted_tag_;
sigc::connection update_count_label_tag_;
Glib::ustring filter_text_;
};
@ -232,7 +237,7 @@ bool tracker_filter_model_update(Glib::RefPtr<Gtk::TreeStore> const& tracker_mod
else
{
auto const sitename = iter->get_value(tracker_filter_cols.sitename);
int const cmp = sitename.compare(sites_v.at(i).sitename);
int const cmp = sitename.raw().compare(sites_v.at(i).sitename);
if (cmp < 0)
{
@ -294,7 +299,7 @@ Glib::RefPtr<Gtk::TreeStore> tracker_filter_model_new(Glib::RefPtr<Gtk::TreeMode
return store;
}
bool is_it_a_separator(Glib::RefPtr<Gtk::TreeModel> const& /*model*/, Gtk::TreeIter const& iter)
bool is_it_a_separator(Glib::RefPtr<Gtk::TreeModel> const& /*model*/, Gtk::TreeModel::const_iterator const& iter)
{
return iter->get_value(tracker_filter_cols.type) == TRACKER_FILTER_TYPE_SEPARATOR;
}
@ -430,7 +435,7 @@ public:
ActivityFilterModelColumns const activity_filter_cols;
bool activity_is_it_a_separator(Glib::RefPtr<Gtk::TreeModel> const& /*model*/, Gtk::TreeIter const& iter)
bool activity_is_it_a_separator(Glib::RefPtr<Gtk::TreeModel> const& /*model*/, Gtk::TreeModel::const_iterator const& iter)
{
return iter->get_value(activity_filter_cols.type) == ACTIVITY_FILTER_SEPARATOR;
}
@ -468,7 +473,7 @@ bool test_torrent_activity(tr_torrent* tor, int type)
}
}
void status_model_update_count(Gtk::TreeIter const& iter, int n)
void status_model_update_count(Gtk::TreeModel::iterator const& iter, int n)
{
if (n != iter->get_value(activity_filter_cols.count))
{
@ -495,7 +500,7 @@ bool activity_filter_model_update(Glib::RefPtr<Gtk::ListStore> const& activity_m
}
}
status_model_update_count(row, hits);
status_model_update_count(TR_GTK_TREE_MODEL_CHILD_ITER(row), hits);
}
return false;
@ -539,7 +544,7 @@ Glib::RefPtr<Gtk::ListStore> activity_filter_model_new(Glib::RefPtr<Gtk::TreeMod
return store;
}
void render_activity_pixbuf_func(Gtk::CellRendererPixbuf* cell_renderer, Gtk::TreeModel::iterator const& iter)
void 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;
@ -724,7 +729,7 @@ void FilterBar::Impl::update_count_label_idle()
if (!pending)
{
show_lb_->set_data(DIRTY_KEY, GINT_TO_POINTER(1));
Glib::signal_idle().connect(sigc::mem_fun(*this, &Impl::update_count_label));
update_count_label_tag_ = Glib::signal_idle().connect(sigc::mem_fun(*this, &Impl::update_count_label));
}
}
@ -787,9 +792,10 @@ FilterBar::Impl::Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::
tracker_combo_box_init(tracker_, tmodel);
filter_model_ = Gtk::TreeModelFilter::create(tmodel);
filter_model_->signal_row_deleted().connect([this](auto const& /*path*/) { update_count_label_idle(); });
filter_model_->signal_row_inserted().connect([this](auto const& /*path*/, auto const& /*iter*/)
{ update_count_label_idle(); });
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(); });
static_cast<Gtk::TreeStore*>(tracker_->get_model().get())->set_data(SESSION_KEY, session);
@ -798,7 +804,11 @@ FilterBar::Impl::Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::
tracker_->signal_changed().connect(sigc::mem_fun(*this, &Impl::selection_changed_cb));
activity_->signal_changed().connect(sigc::mem_fun(*this, &Impl::selection_changed_cb));
#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();
@ -807,6 +817,14 @@ FilterBar::Impl::Impl(FilterBar& widget, tr_session* session, Glib::RefPtr<Gtk::
FilterBar::Impl::~Impl()
{
if (update_count_label_tag_.connected())
{
update_count_label_tag_.disconnect();
}
filter_model_row_deleted_tag_.disconnect();
filter_model_row_inserted_tag_.disconnect();
torrent_model_row_deleted_cb_tag_.disconnect();
torrent_model_row_inserted_tag_.disconnect();
torrent_model_row_changed_tag_.disconnect();

View File

@ -1,143 +0,0 @@
// This file Copyright © 2007-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/tr-macros.h>
#include "HigWorkarea.h"
#include "Utils.h"
HigWorkarea::HigWorkarea()
{
set_border_width(GUI_PAD_BIG);
set_row_spacing(GUI_PAD);
set_column_spacing(GUI_PAD_BIG);
}
void HigWorkarea::add_section_divider(guint& row)
{
auto* w = Gtk::make_managed<Gtk::Fixed>();
w->set_size_request(0U, 6U);
attach(*w, 0, row, 2, 1);
++row;
}
void HigWorkarea::add_section_title_widget(guint& row, Gtk::Widget& w)
{
w.set_hexpand(true);
attach(w, 0, row, 2, 1);
++row;
}
void HigWorkarea::add_section_title(guint& row, Glib::ustring const& section_title)
{
auto* l = Gtk::make_managed<Gtk::Label>(gtr_sprintf("<b>%s</b>", section_title));
l->set_halign(TR_GTK_ALIGN(START));
l->set_valign(TR_GTK_ALIGN(CENTER));
l->set_use_markup(true);
add_section_title_widget(row, *l);
}
void HigWorkarea::add_wide_control(guint& row, Gtk::Widget& w)
{
w.set_hexpand(true);
w.set_margin_start(18);
attach(w, 0, row, 2, 1);
++row;
}
void HigWorkarea::add_wide_tall_control(guint& row, Gtk::Widget& w)
{
w.set_hexpand(true);
w.set_vexpand(true);
add_wide_control(row, w);
}
Gtk::CheckButton* HigWorkarea::add_wide_checkbutton(guint& row, Glib::ustring const& mnemonic_string, bool is_active)
{
auto* w = Gtk::make_managed<Gtk::CheckButton>(mnemonic_string, true);
w->set_active(is_active);
add_wide_control(row, *w);
return w;
}
void HigWorkarea::add_label_w(guint row, Gtk::Widget& w)
{
w.set_margin_start(18);
if (auto* label = dynamic_cast<Gtk::Label*>(&w); label != nullptr)
{
label->set_halign(TR_GTK_ALIGN(START));
label->set_valign(TR_GTK_ALIGN(CENTER));
label->set_use_markup(true);
}
attach(w, 0, row, 1, 1);
}
void HigWorkarea::add_tall_control(guint row, Gtk::Widget& control)
{
if (auto* label = dynamic_cast<Gtk::Label*>(&control); label != nullptr)
{
label->set_halign(TR_GTK_ALIGN(START));
label->set_valign(TR_GTK_ALIGN(CENTER));
}
control.set_hexpand(true);
control.set_vexpand(true);
attach(control, 1, row, 1, 1);
}
void HigWorkarea::add_control(guint row, Gtk::Widget& control)
{
if (auto* label = dynamic_cast<Gtk::Label*>(&control); label != nullptr)
{
label->set_halign(TR_GTK_ALIGN(START));
label->set_valign(TR_GTK_ALIGN(CENTER));
}
control.set_hexpand(true);
attach(control, 1, row, 1, 1);
}
void HigWorkarea::add_row_w(guint& row, Gtk::Widget& label_widget, Gtk::Widget& control, Gtk::Widget* mnemonic)
{
add_label_w(row, label_widget);
add_control(row, control);
if (auto* label = dynamic_cast<Gtk::Label*>(&label_widget); label != nullptr)
{
label->set_mnemonic_widget(mnemonic != nullptr ? *mnemonic : control);
}
++row;
}
Gtk::Label* HigWorkarea::add_row(guint& row, Glib::ustring const& mnemonic_string, Gtk::Widget& control, Gtk::Widget* mnemonic)
{
auto* l = Gtk::make_managed<Gtk::Label>(mnemonic_string, true);
add_row_w(row, *l, control, mnemonic);
return l;
}
Gtk::Label* HigWorkarea::add_tall_row(
guint& row,
Glib::ustring const& mnemonic_string,
Gtk::Widget& control,
Gtk::Widget* mnemonic)
{
auto* l = Gtk::make_managed<Gtk::Label>(mnemonic_string, true);
auto* h = Gtk::make_managed<Gtk::Box>(TR_GTK_ORIENTATION(HORIZONTAL), 0);
auto* v = Gtk::make_managed<Gtk::Box>(TR_GTK_ORIENTATION(VERTICAL), 0);
h->pack_start(*l, false, false, 0);
v->pack_start(*h, false, false, GUI_PAD_SMALL);
add_label_w(row, *v);
add_tall_control(row, control);
l->set_mnemonic_widget(mnemonic ? *mnemonic : control);
++row;
return l;
}

View File

@ -5,50 +5,6 @@
#pragma once
#include <gtkmm.h>
#include <libtransmission/tr-macros.h>
/**
*** utility code for making dialog layout that follows the Gnome HIG.
*** see section 8.2.2, Visual Design > Window Layout > Dialogs.
**/
class HigWorkarea : public Gtk::Grid
{
public:
HigWorkarea();
TR_DISABLE_COPY_MOVE(HigWorkarea)
void add_section_divider(guint& row);
void add_section_title_widget(guint& row, Gtk::Widget& w);
void add_section_title(guint& row, Glib::ustring const& section_title);
void add_wide_tall_control(guint& row, Gtk::Widget& w);
void add_wide_control(guint& row, Gtk::Widget& w);
Gtk::CheckButton* add_wide_checkbutton(guint& row, Glib::ustring const& mnemonic_string, bool is_active);
void add_label_w(guint row, Gtk::Widget& label_widget);
Gtk::Label* add_tall_row(
guint& row,
Glib::ustring const& mnemonic_string,
Gtk::Widget& control,
Gtk::Widget* mnemonic_or_null_for_control = nullptr);
Gtk::Label* add_row(
guint& row,
Glib::ustring const& mnemonic_string,
Gtk::Widget& control,
Gtk::Widget* mnemonic_or_null_for_control = nullptr);
void add_row_w(
guint& row,
Gtk::Widget& label_widget,
Gtk::Widget& control,
Gtk::Widget* mnemonic_or_null_for_control = nullptr);
private:
void add_tall_control(guint row, Gtk::Widget& control);
void add_control(guint row, Gtk::Widget& control);
};
auto inline constexpr GUI_PAD_SMALL = int{ 3 };
auto inline constexpr GUI_PAD = int{ 6 };
auto inline constexpr GUI_PAD_BIG = int{ 12 };

View File

@ -6,9 +6,7 @@
*/
#include <algorithm>
#include <array>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
@ -21,104 +19,14 @@
using namespace std::literals;
using IconCache = std::map<std::string, Glib::RefPtr<Gio::Icon>, std::less<>>;
std::string_view const DirectoryMimeType = "folder"sv;
std::string_view const UnknownMimeType = "unknown"sv;
namespace
Glib::RefPtr<Gio::Icon> gtr_get_mime_type_icon(std::string_view mime_type)
{
auto const VoidPixbufKey = "void-pixbuf"s;
struct IconCache
{
Glib::RefPtr<Gtk::IconTheme> icon_theme;
int icon_size;
std::map<std::string, Glib::RefPtr<Gdk::Pixbuf>, std::less<>> cache;
};
std::array<std::unique_ptr<IconCache>, 7> icon_cache;
Glib::RefPtr<Gdk::Pixbuf> create_void_pixbuf(int width, int height)
{
auto const p = Gdk::Pixbuf::create(TR_GDK_COLORSPACE(RGB), true, 8, width, height);
p->fill(0xFFFFFF00);
return p;
}
int get_size_in_pixels(Gtk::IconSize icon_size)
{
int width = 0;
int height = 0;
Gtk::IconSize::lookup(icon_size, width, height);
return std::max(width, height);
}
std::unique_ptr<IconCache> icon_cache_new(Gtk::Widget& for_widget, Gtk::IconSize icon_size)
{
auto icons = std::make_unique<IconCache>();
icons->icon_theme = Gtk::IconTheme::get_for_screen(for_widget.get_screen());
icons->icon_size = get_size_in_pixels(icon_size);
icons->cache.try_emplace(VoidPixbufKey, create_void_pixbuf(icons->icon_size, icons->icon_size));
return icons;
}
Glib::RefPtr<Gdk::Pixbuf> get_themed_icon_pixbuf(Gio::ThemedIcon& icon, int size, Gtk::IconTheme& icon_theme)
{
auto const icon_names = icon.get_names();
auto icon_info = icon_theme.choose_icon(icon_names, size);
if (!bool{ icon_info })
{
icon_info = icon_theme.lookup_icon("text-x-generic", size, Gtk::ICON_LOOKUP_USE_BUILTIN);
}
try
{
return icon_info.load_icon();
}
catch (Glib::Error const& e)
{
g_warning("could not load icon pixbuf: %s\n", e.what().c_str());
return {};
}
}
Glib::RefPtr<Gdk::Pixbuf> get_file_icon_pixbuf(Gio::FileIcon& icon, int size)
{
try
{
return Gdk::Pixbuf::create_from_file(icon.get_file()->get_path(), size, -1, false);
}
catch (Glib::Error const&)
{
return {};
}
}
Glib::RefPtr<Gdk::Pixbuf> _get_icon_pixbuf(Glib::RefPtr<Gio::Icon> const& icon, int size, Gtk::IconTheme& theme)
{
if (icon == nullptr)
{
return {};
}
if (auto* const ticon = dynamic_cast<Gio::ThemedIcon*>(icon.get()); ticon != nullptr)
{
return get_themed_icon_pixbuf(*ticon, size, theme);
}
if (auto* const ficon = dynamic_cast<Gio::FileIcon*>(icon.get()); ficon != nullptr)
{
return get_file_icon_pixbuf(*ficon, size);
}
return {};
}
Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, std::string_view mime_type)
{
auto& cache = icons.cache;
static IconCache cache;
if (auto mime_it = cache.find(mime_type); mime_it != std::end(cache))
{
@ -127,56 +35,10 @@ Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, std::s
auto mime_type_str = std::string{ mime_type };
auto icon = Gio::content_type_get_icon(mime_type_str);
auto pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *icons.icon_theme.get());
if (pixbuf != nullptr)
if (icon != nullptr)
{
cache.try_emplace(std::move(mime_type_str), pixbuf);
cache.try_emplace(std::move(mime_type_str), icon);
}
return pixbuf;
}
} // namespace
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
{
int n;
switch (icon_size)
{
case Gtk::ICON_SIZE_MENU:
n = 1;
break;
case Gtk::ICON_SIZE_SMALL_TOOLBAR:
n = 2;
break;
case Gtk::ICON_SIZE_LARGE_TOOLBAR:
n = 3;
break;
case Gtk::ICON_SIZE_BUTTON:
n = 4;
break;
case Gtk::ICON_SIZE_DND:
n = 5;
break;
case Gtk::ICON_SIZE_DIALOG:
n = 6;
break;
default: /*GTK_ICON_SIZE_INVALID*/
n = 0;
break;
}
if (icon_cache[n] == nullptr)
{
icon_cache[n] = icon_cache_new(for_widget, icon_size);
}
return icon_cache_get_mime_type_icon(*icon_cache[n], mime_type);
return icon;
}

View File

@ -14,4 +14,4 @@
extern std::string_view const DirectoryMimeType;
extern std::string_view const UnknownMimeType;
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget);
Glib::RefPtr<Gio::Icon> gtr_get_mime_type_icon(std::string_view mime_type);

View File

@ -70,7 +70,7 @@ private:
Glib::RefPtr<Gio::MenuModel> createStatsMenu();
void on_popup_menu(GdkEventButton* event);
void on_popup_menu(double view_x, double view_y);
void onSpeedToggled(std::string const& action_name, tr_direction dir, bool enabled);
void onSpeedSet(tr_direction dir, int KBps);
@ -111,22 +111,37 @@ private:
TorrentCellRenderer* renderer_ = nullptr;
Gtk::TreeViewColumn* column_ = nullptr;
sigc::connection pref_handler_id_;
Gtk::Menu* popup_menu_ = nullptr;
IF_GTKMM4(Gtk::PopoverMenu*, Gtk::Menu*) popup_menu_ = nullptr;
};
/***
****
***/
void MainWindow::Impl::on_popup_menu(GdkEventButton* event)
void MainWindow::Impl::on_popup_menu([[maybe_unused]] double view_x, [[maybe_unused]] double view_y)
{
if (popup_menu_ == nullptr)
{
popup_menu_ = Gtk::make_managed<Gtk::Menu>(gtr_action_get_object<Gio::Menu>("main-window-popup"));
auto const menu = gtr_action_get_object<Gio::Menu>("main-window-popup");
#if GTKMM_CHECK_VERSION(4, 0, 0)
popup_menu_ = Gtk::make_managed<Gtk::PopoverMenu>(menu);
popup_menu_->set_parent(window_);
#else
popup_menu_ = Gtk::make_managed<Gtk::Menu>(menu);
popup_menu_->attach_to_widget(window_);
#endif
}
popup_menu_->popup_at_pointer(reinterpret_cast<GdkEvent*>(event));
#if GTKMM_CHECK_VERSION(4, 0, 0)
double window_x = 0;
double window_y = 0;
view_->translate_coordinates(window_, view_x, view_y, window_x, window_y);
popup_menu_->set_pointing_to(Gdk::Rectangle(window_x, window_y, 1, 1));
popup_menu_->popup();
#else
popup_menu_->popup_at_pointer(nullptr);
#endif
}
namespace
@ -136,7 +151,7 @@ bool tree_view_search_equal_func(
Glib::RefPtr<Gtk::TreeModel> const& /*model*/,
int /*column*/,
Glib::ustring const& key,
Gtk::TreeModel::iterator const& iter)
Gtk::TreeModel::const_iterator const& iter)
{
auto const name = iter->get_value(torrent_cols.name_collated);
return name.find(key.lowercase()) == Glib::ustring::npos;
@ -160,13 +175,21 @@ void MainWindow::Impl::init_view(Gtk::TreeView* view, Glib::RefPtr<Gtk::TreeMode
renderer_->property_xpad() = GUI_PAD_SMALL;
renderer_->property_ypad() = GUI_PAD_SMALL;
view->signal_popup_menu().connect_notify([this]() { on_popup_menu(nullptr); });
view->signal_button_press_event().connect(
[this, view](GdkEventButton* event)
{ return on_tree_view_button_pressed(view, event, sigc::mem_fun(*this, &Impl::on_popup_menu)); },
false);
view->signal_button_release_event().connect([view](GdkEventButton* event)
{ return on_tree_view_button_released(view, event); });
#if !GTKMM_CHECK_VERSION(4, 0, 0)
view->signal_popup_menu().connect_notify([this]() { on_popup_menu(0, 0); });
#endif
setup_tree_view_button_event_handling(
*view,
[this, view](guint /*button*/, TrGdkModifierType /*state*/, double view_x, double view_y, bool context_menu_requested)
{
return on_tree_view_button_pressed(
*view,
view_x,
view_y,
context_menu_requested,
sigc::mem_fun(*this, &Impl::on_popup_menu));
},
[view](double view_x, double view_y) { return on_tree_view_button_released(*view, view_x, view_y); });
view->signal_row_activated().connect([](auto const& /*path*/, auto* /*column*/)
{ gtr_action_activate("show-torrent-properties"); });
@ -181,8 +204,11 @@ void MainWindow::Impl::prefsChanged(tr_quark const key)
renderer_->property_compact() = gtr_pref_flag_get(key);
/* since the cell size has changed, we need gtktreeview to revalidate
* its fixed-height mode values. Unfortunately there's not an API call
* for that, but it *does* revalidate when it thinks the style's been tweaked */
g_signal_emit_by_name(Glib::unwrap(view_), "style-updated", nullptr, nullptr);
* for that, but this seems to work for both GTK 3 and 4 */
view_->set_fixed_height_mode(false);
view_->set_row_separator_func({});
view_->unset_row_separator_func();
view_->set_fixed_height_mode(true);
break;
case TR_KEY_show_statusbar:
@ -227,9 +253,6 @@ void MainWindow::Impl::syncAltSpeedButton()
{
bool const b = gtr_pref_flag_get(TR_KEY_alt_speed_enabled);
alt_speed_button_->set_active(b);
alt_speed_image_->set_from_icon_name("turtle-symbolic", Gtk::BuiltinIconSize::ICON_SIZE_MENU);
alt_speed_button_->set_halign(TR_GTK_ALIGN(CENTER));
alt_speed_button_->set_valign(TR_GTK_ALIGN(CENTER));
alt_speed_button_->set_tooltip_text(fmt::format(
b ? _("Click to disable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)") :
_("Click to enable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)"),
@ -516,7 +539,9 @@ MainWindow::Impl::Impl(
/* make the window */
window.set_title(Glib::get_application_name());
window.set_default_size(gtr_pref_int_get(TR_KEY_main_window_width), gtr_pref_int_get(TR_KEY_main_window_height));
#if !GTKMM_CHECK_VERSION(4, 0, 0)
window.move(gtr_pref_int_get(TR_KEY_main_window_x), gtr_pref_int_get(TR_KEY_main_window_y));
#endif
if (gtr_pref_flag_get(TR_KEY_main_window_is_maximized))
{
@ -532,7 +557,18 @@ MainWindow::Impl::Impl(
/* gear */
auto* gear_button = gtr_get_widget<Gtk::MenuButton>(builder, "gear_button");
gear_button->set_menu_model(createOptionsMenu());
#if GTKMM_CHECK_VERSION(4, 0, 0)
for (auto* child = gear_button->get_first_child(); child != nullptr; child = child->get_next_sibling())
{
if (auto* popover = dynamic_cast<Gtk::Popover*>(child); popover != nullptr)
{
popover->signal_show().connect([this]() { onOptionsClicked(); }, false);
break;
}
}
#else
gear_button->signal_clicked().connect([this]() { onOptionsClicked(); }, false);
#endif
/* turtle */
alt_speed_button_->signal_toggled().connect(sigc::mem_fun(*this, &Impl::alt_speed_toggled_cb));
@ -574,6 +610,7 @@ MainWindow::Impl::Impl(
refresh();
#if !GTKMM_CHECK_VERSION(4, 0, 0)
/* prevent keyboard events being sent to the window first */
window.signal_key_press_event().connect(
[this](GdkEventKey* event) { return gtk_window_propagate_key_event(static_cast<Gtk::Window&>(window_).gobj(), event); },
@ -581,6 +618,7 @@ MainWindow::Impl::Impl(
window.signal_key_release_event().connect(
[this](GdkEventKey* event) { return gtk_window_propagate_key_event(static_cast<Gtk::Window&>(window_).gobj(), event); },
false);
#endif
}
void MainWindow::Impl::updateStats()
@ -675,10 +713,14 @@ void MainWindow::set_busy(bool isBusy)
{
if (get_realized())
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto const cursor = isBusy ? Gdk::Cursor::create("wait") : Glib::RefPtr<Gdk::Cursor>();
set_cursor(cursor);
#else
auto const display = get_display();
auto const cursor = isBusy ? Gdk::Cursor::create(display, Gdk::WATCH) : Glib::RefPtr<Gdk::Cursor>();
get_window()->set_cursor(cursor);
display->flush();
#endif
}
}

View File

@ -21,6 +21,7 @@
#include <libtransmission/makemeta.h>
#include <libtransmission/utils.h> /* tr_formatter_mem_B() */
#include "PathButton.h"
#include "MakeDialog.h"
#include "PrefsDialog.h"
#include "Session.h"
@ -28,6 +29,11 @@
using namespace std::literals;
#if GTKMM_CHECK_VERSION(4, 0, 0)
using FileListValue = Glib::Value<GSList*>;
using FileListHandler = Glib::SListHandler<Glib::RefPtr<Gio::File>>;
#endif
namespace
{
@ -81,10 +87,13 @@ public:
TR_DISABLE_COPY_MOVE(Impl)
private:
void onSourceToggled2(Gtk::ToggleButton* tb, Gtk::FileChooserButton* chooser);
void onChooserChosen(Gtk::FileChooserButton* chooser);
void onSourceToggled2(Gtk::CheckButton* tb, PathButton* chooser);
void onChooserChosen(PathButton* chooser);
void onResponse(int response);
#if GTKMM_CHECK_VERSION(4, 0, 0)
bool on_drag_data_received(Glib::ValueBase const& value, double x, double y);
#else
void on_drag_data_received(
Glib::RefPtr<Gdk::DragContext> const& drag_context,
int x,
@ -92,6 +101,9 @@ private:
Gtk::SelectionData const& selection_data,
guint info,
guint time_);
#endif
bool set_dropped_source_path(std::string const& filename);
void updatePiecesLabel();
@ -105,13 +117,13 @@ private:
MakeDialog& dialog_;
Glib::RefPtr<Session> const core_;
Gtk::RadioButton* file_radio_ = nullptr;
Gtk::FileChooserButton* file_chooser_ = nullptr;
Gtk::RadioButton* folder_radio_ = nullptr;
Gtk::FileChooserButton* folder_chooser_ = nullptr;
Gtk::CheckButton* file_radio_ = nullptr;
PathButton* file_chooser_ = nullptr;
Gtk::CheckButton* folder_radio_ = nullptr;
PathButton* folder_chooser_ = nullptr;
Gtk::Label* pieces_lb_ = nullptr;
Gtk::Scale* piece_size_scale_ = nullptr;
Gtk::FileChooserButton* destination_chooser_ = nullptr;
PathButton* destination_chooser_ = nullptr;
Gtk::CheckButton* comment_check_ = nullptr;
Gtk::Entry* comment_entry_ = nullptr;
Gtk::CheckButton* private_check_ = nullptr;
@ -221,7 +233,7 @@ void MakeProgressDialog::onProgressDialogResponse(int response)
{
case TR_GTK_RESPONSE_TYPE(CANCEL):
builder_.cancelChecksums();
hide();
close();
break;
case TR_GTK_RESPONSE_TYPE(ACCEPT):
@ -229,7 +241,7 @@ void MakeProgressDialog::onProgressDialogResponse(int response)
[[fallthrough]];
case TR_GTK_RESPONSE_TYPE(CLOSE):
hide();
close();
break;
default:
@ -273,14 +285,15 @@ void MakeDialog::Impl::makeProgressDialog(std::string_view target, std::future<t
std::move(future),
target,
core_));
progress_dialog_->signal_hide().connect(
gtr_window_on_close(
*progress_dialog_,
[this]()
{
auto const success = progress_dialog_->success();
progress_dialog_.reset();
if (success)
{
dialog_.hide();
dialog_.close();
}
});
progress_dialog_->show();
@ -322,7 +335,7 @@ void MakeDialog::Impl::onResponse(int response)
}
else if (response == TR_GTK_RESPONSE_TYPE(CLOSE))
{
dialog_.hide();
dialog_.close();
}
}
@ -333,7 +346,7 @@ void MakeDialog::Impl::onResponse(int response)
namespace
{
void onSourceToggled(Gtk::ToggleButton* tb, Gtk::Widget* widget)
void onSourceToggled(Gtk::CheckButton* tb, Gtk::Widget* widget)
{
widget->set_sensitive(tb->get_active());
}
@ -391,13 +404,13 @@ void MakeDialog::Impl::setFilename(std::string_view filename)
updatePiecesLabel();
}
void MakeDialog::Impl::onChooserChosen(Gtk::FileChooserButton* chooser)
void MakeDialog::Impl::onChooserChosen(PathButton* chooser)
{
chooser->set_data(FileChosenKey, GINT_TO_POINTER(true));
setFilename(chooser->get_filename());
}
void MakeDialog::Impl::onSourceToggled2(Gtk::ToggleButton* tb, Gtk::FileChooserButton* chooser)
void MakeDialog::Impl::onSourceToggled2(Gtk::CheckButton* tb, PathButton* chooser)
{
if (tb->get_active())
{
@ -412,6 +425,47 @@ void MakeDialog::Impl::onSourceToggled2(Gtk::ToggleButton* tb, Gtk::FileChooserB
}
}
bool MakeDialog::Impl::set_dropped_source_path(std::string const& filename)
{
if (Glib::file_test(filename, TR_GLIB_FILE_TEST(IS_DIR)))
{
/* a directory was dragged onto the dialog... */
folder_radio_->set_active(true);
folder_chooser_->set_filename(filename);
return true;
}
if (Glib::file_test(filename, TR_GLIB_FILE_TEST(IS_REGULAR)))
{
/* a file was dragged on to the dialog... */
file_radio_->set_active(true);
file_chooser_->set_filename(filename);
return true;
}
return false;
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
bool MakeDialog::Impl::on_drag_data_received(Glib::ValueBase const& value, double /*x*/, double /*y*/)
{
if (G_VALUE_HOLDS(value.gobj(), GDK_TYPE_FILE_LIST))
{
FileListValue files_value;
files_value.init(value.gobj());
if (auto const files = FileListHandler::slist_to_vector(files_value.get(), Glib::OwnershipType::OWNERSHIP_NONE);
!files.empty())
{
return set_dropped_source_path(files.front()->get_path());
}
}
return false;
}
#else
void MakeDialog::Impl::on_drag_data_received(
Glib::RefPtr<Gdk::DragContext> const& drag_context,
int /*x*/,
@ -424,28 +478,14 @@ void MakeDialog::Impl::on_drag_data_received(
if (auto const uris = selection_data.get_uris(); !uris.empty())
{
auto const& uri = uris.front();
auto const filename = Glib::filename_from_uri(uri);
if (Glib::file_test(filename, TR_GLIB_FILE_TEST(IS_DIR)))
{
/* a directory was dragged onto the dialog... */
folder_radio_->set_active(true);
folder_chooser_->set_current_folder(filename);
success = true;
}
else if (Glib::file_test(filename, TR_GLIB_FILE_TEST(IS_REGULAR)))
{
/* a file was dragged on to the dialog... */
file_radio_->set_active(true);
file_chooser_->set_filename(filename);
success = true;
}
success = set_dropped_source_path(Glib::filename_from_uri(uris.front()));
}
drag_context->drag_finish(success, false, time_);
}
#endif
MakeDialog::MakeDialog(
BaseObjectType* cast_item,
Glib::RefPtr<Gtk::Builder> const& builder,
@ -468,13 +508,13 @@ std::unique_ptr<MakeDialog> MakeDialog::create(Gtk::Window& parent, Glib::RefPtr
MakeDialog::Impl::Impl(MakeDialog& dialog, Glib::RefPtr<Gtk::Builder> const& builder, Glib::RefPtr<Session> const& core)
: dialog_(dialog)
, core_(core)
, file_radio_(gtr_get_widget<Gtk::RadioButton>(builder, "source_file_radio"))
, file_chooser_(gtr_get_widget<Gtk::FileChooserButton>(builder, "source_file_button"))
, folder_radio_(gtr_get_widget<Gtk::RadioButton>(builder, "source_folder_radio"))
, folder_chooser_(gtr_get_widget<Gtk::FileChooserButton>(builder, "source_folder_button"))
, file_radio_(gtr_get_widget<Gtk::CheckButton>(builder, "source_file_radio"))
, file_chooser_(gtr_get_widget_derived<PathButton>(builder, "source_file_button"))
, folder_radio_(gtr_get_widget<Gtk::CheckButton>(builder, "source_folder_radio"))
, folder_chooser_(gtr_get_widget_derived<PathButton>(builder, "source_folder_button"))
, pieces_lb_(gtr_get_widget<Gtk::Label>(builder, "source_size_label"))
, piece_size_scale_(gtr_get_widget<Gtk::Scale>(builder, "piece_size_scale"))
, destination_chooser_(gtr_get_widget<Gtk::FileChooserButton>(builder, "destination_button"))
, destination_chooser_(gtr_get_widget_derived<PathButton>(builder, "destination_button"))
, comment_check_(gtr_get_widget<Gtk::CheckButton>(builder, "comment_check"))
, comment_entry_(gtr_get_widget<Gtk::Entry>(builder, "comment_entry"))
, private_check_(gtr_get_widget<Gtk::CheckButton>(builder, "private_check"))
@ -484,7 +524,7 @@ MakeDialog::Impl::Impl(MakeDialog& dialog, Glib::RefPtr<Gtk::Builder> const& bui
{
dialog_.signal_response().connect(sigc::mem_fun(*this, &Impl::onResponse));
destination_chooser_->set_current_folder(Glib::get_user_special_dir(TR_GLIB_USER_DIRECTORY(DESKTOP)));
destination_chooser_->set_filename(Glib::get_user_special_dir(TR_GLIB_USER_DIRECTORY(DESKTOP)));
folder_radio_->set_active(false);
folder_radio_->signal_toggled().connect([this]() { onSourceToggled2(folder_radio_, folder_chooser_); });
@ -510,9 +550,15 @@ MakeDialog::Impl::Impl(MakeDialog& dialog, Glib::RefPtr<Gtk::Builder> const& bui
source_entry_->set_sensitive(false);
source_check_->signal_toggled().connect([this]() { onSourceToggled(source_check_, source_entry_); });
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto drop_controller = Gtk::DropTarget::create(GDK_TYPE_FILE_LIST, Gdk::DragAction::COPY);
drop_controller->signal_drop().connect(sigc::mem_fun(*this, &Impl::on_drag_data_received), false);
dialog_.add_controller(drop_controller);
#else
dialog_.drag_dest_set(Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
dialog_.drag_dest_add_uri_targets();
dialog_.signal_drag_data_received().connect(sigc::mem_fun(*this, &Impl::on_drag_data_received));
#endif
}
void MakeDialog::Impl::onPieceSizeUpdated()

View File

@ -113,7 +113,7 @@ bool MessageLogWindow::Impl::is_pinned_to_new() const
if (auto const iter = sort_->children()[row_count - 1]; iter)
{
pinned_to_new = last_visible == sort_->get_path(iter);
pinned_to_new = last_visible == sort_->get_path(TR_GTK_TREE_MODEL_CHILD_ITER(iter));
}
}
}
@ -129,7 +129,7 @@ void MessageLogWindow::Impl::scroll_to_bottom()
if (auto const iter = sort_->children()[row_count - 1]; iter)
{
view_->scroll_to_row(sort_->get_path(iter), 1);
view_->scroll_to_row(sort_->get_path(TR_GTK_TREE_MODEL_CHILD_ITER(iter)), 1);
}
}
}
@ -218,7 +218,7 @@ void MessageLogWindow::Impl::onSaveDialogResponse(std::shared_ptr<Gtk::FileChoos
{
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
doSave(*d, d->get_filename());
doSave(*d, d->get_file()->get_path());
}
d.reset();
@ -277,7 +277,7 @@ void setForegroundColor(Gtk::CellRendererText* renderer, tr_log_level level)
void renderText(
Gtk::CellRendererText* renderer,
Gtk::TreeModel::iterator const& iter,
Gtk::TreeModel::const_iterator const& iter,
Gtk::TreeModelColumn<Glib::ustring> const& col)
{
auto const* const node = iter->get_value(message_log_cols.tr_msg);
@ -286,7 +286,7 @@ void renderText(
setForegroundColor(renderer, node->level);
}
void renderTime(Gtk::CellRendererText* renderer, Gtk::TreeModel::iterator const& iter)
void renderTime(Gtk::CellRendererText* renderer, Gtk::TreeModel::const_iterator const& iter)
{
auto const* const node = iter->get_value(message_log_cols.tr_msg);
renderer->property_text() = Glib::DateTime::create_now_local(node->when).format("%T");
@ -356,7 +356,8 @@ tr_log_message* addMessages(Glib::RefPtr<Gtk::ListStore> const& store, tr_log_me
{
char const* name = !std::empty(i->name) ? i->name.c_str() : default_name.c_str();
auto const row = *store->prepend();
auto row_it = store->prepend();
auto& row = *row_it;
row[message_log_cols.tr_msg] = i;
row[message_log_cols.name] = name;
row[message_log_cols.message] = i->message;
@ -493,8 +494,10 @@ MessageLogWindow::Impl::Impl(
filter_->set_visible_func(sigc::mem_fun(*this, &Impl::isRowVisible));
view_->set_model(sort_);
view_->signal_button_release_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_released(view_, event); });
setup_tree_view_button_event_handling(
*view_,
{},
[this](double view_x, double view_y) { return on_tree_view_button_released(*view_, view_x, view_y); });
appendColumn(view_, message_log_cols.sequence);
appendColumn(view_, message_log_cols.name);
appendColumn(view_, message_log_cols.message);
@ -504,17 +507,4 @@ MessageLogWindow::Impl::Impl(
SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS);
scroll_to_bottom();
window_.show_all_children();
}
void MessageLogWindow::on_show()
{
Gtk::Window::on_show();
gtr_action_set_toggled("toggle-message-log", true);
}
void MessageLogWindow::on_hide()
{
Gtk::Window::on_hide();
gtr_action_set_toggled("toggle-message-log", false);
}

View File

@ -27,10 +27,6 @@ public:
static std::unique_ptr<MessageLogWindow> create(Gtk::Window& parent, Glib::RefPtr<Session> const& core);
protected:
void on_show() override;
void on_hide() override;
private:
class Impl;
std::unique_ptr<Impl> const impl_;

View File

@ -15,6 +15,7 @@
#include "FileList.h"
#include "FreeSpaceLabel.h"
#include "OptionsDialog.h"
#include "PathButton.h"
#include "Prefs.h"
#include "PrefsDialog.h"
#include "Session.h"
@ -27,6 +28,8 @@
namespace
{
auto const ShowOptionsDialogChoice = Glib::ustring("show_options_dialog");
std::string get_source_file(tr_ctor& ctor)
{
if (char const* source_file = tr_ctorGetSourceFile(&ctor); source_file != nullptr)
@ -67,8 +70,8 @@ public:
TR_DISABLE_COPY_MOVE(Impl)
private:
void sourceChanged(Gtk::FileChooserButton* b);
void downloadDirChanged(Gtk::FileChooserButton* b);
void sourceChanged(PathButton* b);
void downloadDirChanged(PathButton* b);
void removeOldTorrent();
void updateTorrent();
@ -105,11 +108,7 @@ void OptionsDialog::Impl::addResponseCB(int response)
{
if (tor_ != nullptr)
{
if (response != TR_GTK_RESPONSE_TYPE(ACCEPT))
{
removeOldTorrent();
}
else
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
tr_torrentSetPriority(tor_, gtr_priority_combo_get_value(*priority_combo_));
@ -127,9 +126,13 @@ void OptionsDialog::Impl::addResponseCB(int response)
gtr_save_recent_dir("download", core_, downloadDir_);
}
else if (response == TR_GTK_RESPONSE_TYPE(CANCEL))
{
removeOldTorrent();
}
}
dialog_.hide();
dialog_.close();
}
void OptionsDialog::Impl::updateTorrent()
@ -158,7 +161,7 @@ void OptionsDialog::Impl::updateTorrent()
* The `filename' tests here are to prevent us from losing the current
* metadata when that happens.
*/
void OptionsDialog::Impl::sourceChanged(Gtk::FileChooserButton* b)
void OptionsDialog::Impl::sourceChanged(PathButton* b)
{
auto const filename = b->get_filename();
@ -193,7 +196,7 @@ void OptionsDialog::Impl::sourceChanged(Gtk::FileChooserButton* b)
}
}
void OptionsDialog::Impl::downloadDirChanged(Gtk::FileChooserButton* b)
void OptionsDialog::Impl::downloadDirChanged(PathButton* b)
{
auto const fname = b->get_filename();
@ -209,7 +212,8 @@ void OptionsDialog::Impl::downloadDirChanged(Gtk::FileChooserButton* b)
namespace
{
void addTorrentFilters(Gtk::FileChooser* chooser)
template<typename FileChooserT>
void addTorrentFilters(FileChooserT* chooser)
{
auto filter = Gtk::FileFilter::create();
filter->set_name(_("Torrent files"));
@ -274,22 +278,13 @@ OptionsDialog::Impl::Impl(
gtr_priority_combo_init(*priority_combo_);
gtr_priority_combo_set_value(*priority_combo_, TR_PRI_NORMAL);
auto* source_chooser = gtr_get_widget<Gtk::FileChooserButton>(builder, "source_button");
auto* source_chooser = gtr_get_widget_derived<PathButton>(builder, "source_button");
addTorrentFilters(source_chooser);
source_chooser->signal_selection_changed().connect([this, source_chooser]() { sourceChanged(source_chooser); });
auto* destination_chooser = gtr_get_widget<Gtk::FileChooserButton>(builder, "destination_button");
if (!destination_chooser->set_current_folder(downloadDir_))
{
g_warning("couldn't select '%s'", downloadDir_.c_str());
}
for (auto const& folder : gtr_get_recent_dirs("download"))
{
destination_chooser->remove_shortcut_folder(folder);
destination_chooser->add_shortcut_folder(folder);
}
auto* destination_chooser = gtr_get_widget_derived<PathButton>(builder, "destination_button");
destination_chooser->set_filename(downloadDir_);
destination_chooser->set_shortcut_folders(gtr_get_recent_dirs("download"));
destination_chooser->signal_selection_changed().connect([this, destination_chooser]()
{ downloadDirChanged(destination_chooser); });
@ -329,21 +324,30 @@ OptionsDialog::Impl::Impl(
void TorrentFileChooserDialog::onOpenDialogResponse(int response, Glib::RefPtr<Session> const& core)
{
/* remember this folder the next time we use this dialog */
gtr_pref_string_set(TR_KEY_open_dialog_dir, get_current_folder());
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
auto const* const tb = static_cast<Gtk::CheckButton*>(get_extra_widget());
/* remember this folder the next time we use this dialog */
gtr_pref_string_set(TR_KEY_open_dialog_dir, IF_GTKMM4(get_current_folder, get_current_folder_file)()->get_path());
bool const do_start = gtr_pref_flag_get(TR_KEY_start_added_torrents);
bool const do_prompt = tb->get_active();
bool const do_prompt = get_choice(ShowOptionsDialogChoice) == "true";
bool const do_notify = false;
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto files = std::vector<Glib::RefPtr<Gio::File>>();
auto files_model = get_files();
for (auto i = guint{ 0 }; i < files_model->get_n_items(); ++i)
{
files.push_back(gtr_ptr_dynamic_cast<Gio::File>(files_model->get_object(i)));
}
#else
auto const files = get_files();
#endif
core->add_files(files, do_start, do_prompt, do_notify);
}
hide();
close();
}
std::unique_ptr<TorrentFileChooserDialog> TorrentFileChooserDialog::create(
@ -367,13 +371,11 @@ TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::Re
if (auto const folder = gtr_pref_string_get(TR_KEY_open_dialog_dir); !folder.empty())
{
set_current_folder(folder);
IF_GTKMM4(set_current_folder, set_current_folder_file)(Gio::File::create_for_path(folder));
}
auto* c = Gtk::make_managed<Gtk::CheckButton>(_("Show _options dialog"), true);
c->set_active(gtr_pref_flag_get(TR_KEY_show_options_window));
set_extra_widget(*c);
c->show();
add_choice(ShowOptionsDialogChoice, _("Show options dialog"));
set_choice(ShowOptionsDialogChoice, gtr_pref_flag_get(TR_KEY_show_options_window) ? "true" : "false");
}
/***
@ -385,7 +387,7 @@ void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const&
if (response == TR_GTK_RESPONSE_TYPE(CANCEL))
{
hide();
close();
}
else if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
@ -398,7 +400,7 @@ void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const&
if (core->add_from_url(url))
{
hide();
close();
}
else
{

266
gtk/PathButton.cc Normal file
View File

@ -0,0 +1,266 @@
// 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 <vector>
#include <glibmm.h>
#include <glibmm/i18n.h>
#include "PathButton.h"
class PathButton::Impl
{
public:
Impl(PathButton& widget);
TR_DISABLE_COPY_MOVE(Impl)
#if GTKMM_CHECK_VERSION(4, 0, 0)
std::string const& get_filename() const;
void set_filename(std::string const& value);
void set_shortcut_folders(std::list<std::string> const& value);
void add_filter(Glib::RefPtr<Gtk::FileFilter> const& value);
Glib::Property<Gtk::FileChooser::Action>& property_action();
Glib::Property<Glib::ustring>& property_title();
sigc::signal<void()>& signal_selection_changed();
#endif
private:
#if GTKMM_CHECK_VERSION(4, 0, 0)
void show_dialog();
void update();
void update_mode();
#endif
private:
PathButton& widget_;
#if GTKMM_CHECK_VERSION(4, 0, 0)
Glib::Property<Gtk::FileChooser::Action> action_;
Glib::Property<Glib::ustring> title_;
sigc::signal<void()> selection_changed_;
Gtk::Image* const image_ = nullptr;
Gtk::Label* const label_ = nullptr;
Gtk::Image* const mode_ = nullptr;
std::string current_file_;
std::list<std::string> shortcut_folders_;
std::vector<Glib::RefPtr<Gtk::FileFilter>> filters_;
#endif
};
PathButton::Impl::Impl(PathButton& widget)
: widget_(widget)
#if GTKMM_CHECK_VERSION(4, 0, 0)
, action_(widget, "action", Gtk::FileChooser::Action::OPEN)
, title_(widget, "title", {})
, image_(Gtk::make_managed<Gtk::Image>())
, label_(Gtk::make_managed<Gtk::Label>())
, mode_(Gtk::make_managed<Gtk::Image>())
#endif
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
action_.get_proxy().signal_changed().connect([this]() { update_mode(); });
label_->set_ellipsize(Pango::EllipsizeMode::END);
label_->set_hexpand(true);
label_->set_halign(Gtk::Align::START);
auto* const layout = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
layout->append(*image_);
layout->append(*label_);
layout->append(*Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::VERTICAL));
layout->append(*mode_);
widget_.set_child(*layout);
widget_.signal_clicked().connect(sigc::mem_fun(*this, &Impl::show_dialog));
update();
update_mode();
#endif
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
std::string const& PathButton::Impl::get_filename() const
{
return current_file_;
}
void PathButton::Impl::set_filename(std::string const& value)
{
current_file_ = value;
update();
selection_changed_.emit();
}
void PathButton::Impl::set_shortcut_folders(std::list<std::string> const& value)
{
shortcut_folders_ = value;
}
void PathButton::Impl::add_filter(Glib::RefPtr<Gtk::FileFilter> const& value)
{
filters_.push_back(value);
}
Glib::Property<Gtk::FileChooser::Action>& PathButton::Impl::property_action()
{
return action_;
}
Glib::Property<Glib::ustring>& PathButton::Impl::property_title()
{
return title_;
}
sigc::signal<void()>& PathButton::Impl::signal_selection_changed()
{
return selection_changed_;
}
void PathButton::Impl::show_dialog()
{
auto const title = title_.get_value();
auto dialog = std::make_shared<Gtk::FileChooserDialog>(!title.empty() ? title : _("Select a File"), action_.get_value());
dialog->set_transient_for(*static_cast<Gtk::Window*>(widget_.get_root()));
dialog->add_button(_("_Cancel"), Gtk::ResponseType::CANCEL);
dialog->add_button(_("_Open"), Gtk::ResponseType::ACCEPT);
dialog->set_modal(true);
if (!current_file_.empty())
{
dialog->set_file(Gio::File::create_for_path(current_file_));
}
for (auto const& folder : shortcut_folders_)
{
dialog->remove_shortcut_folder(Gio::File::create_for_path(folder));
dialog->add_shortcut_folder(Gio::File::create_for_path(folder));
}
for (auto const& filter : filters_)
{
dialog->add_filter(filter);
}
dialog->signal_response().connect(
[this, dialog](int response) mutable
{
if (response == Gtk::ResponseType::ACCEPT)
{
set_filename(dialog->get_file()->get_path());
selection_changed_.emit();
}
dialog.reset();
});
dialog->show();
}
void PathButton::Impl::update()
{
if (!current_file_.empty())
{
auto const file = Gio::File::create_for_path(current_file_);
try
{
image_->set(file->query_info()->get_icon());
}
catch (Glib::Error const&)
{
image_->set_from_icon_name("image-missing");
}
label_->set_text(file->get_basename());
}
else
{
image_->set_from_icon_name("image-missing");
label_->set_text(_("(None)"));
}
widget_.set_tooltip_text(current_file_);
}
void PathButton::Impl::update_mode()
{
mode_->set_from_icon_name(
action_.get_value() == Gtk::FileChooser::Action::SELECT_FOLDER ? "folder-open-symbolic" : "document-open-symbolic");
}
#endif
PathButton::PathButton()
: Glib::ObjectBase(typeid(PathButton))
, impl_(std::make_unique<Impl>(*this))
{
}
PathButton::PathButton(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> const& /*builder*/)
: Glib::ObjectBase(typeid(PathButton))
, BaseWidgetType(cast_item)
, impl_(std::make_unique<Impl>(*this))
{
}
PathButton::~PathButton() = default;
void PathButton::set_shortcut_folders(std::list<std::string> const& value)
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
impl_->set_shortcut_folders(value);
#else
for (auto const& folder : value)
{
remove_shortcut_folder(folder);
add_shortcut_folder(folder);
}
#endif
}
#if GTKMM_CHECK_VERSION(4, 0, 0)
std::string PathButton::get_filename() const
{
return impl_->get_filename();
}
void PathButton::set_filename(std::string const& value)
{
impl_->set_filename(value);
}
void PathButton::add_filter(Glib::RefPtr<Gtk::FileFilter> const& value)
{
impl_->add_filter(value);
}
Glib::PropertyProxy<Gtk::FileChooser::Action> PathButton::property_action()
{
return impl_->property_action().get_proxy();
}
Glib::PropertyProxy<Glib::ustring> PathButton::property_title()
{
return impl_->property_title().get_proxy();
}
sigc::signal<void()>& PathButton::signal_selection_changed()
{
return impl_->signal_selection_changed();
}
#endif

46
gtk/PathButton.h Normal file
View File

@ -0,0 +1,46 @@
// 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 <list>
#include <memory>
#include <string>
#include <gtkmm.h>
#include <libtransmission/tr-macros.h>
#include "Utils.h"
class PathButton : public IF_GTKMM4(Gtk::Button, Gtk::FileChooserButton)
{
using BaseWidgetType = IF_GTKMM4(Gtk::Button, Gtk::FileChooserButton);
public:
PathButton();
PathButton(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> const& builder);
~PathButton() override;
TR_DISABLE_COPY_MOVE(PathButton)
void set_shortcut_folders(std::list<std::string> const& value);
#if GTKMM_CHECK_VERSION(4, 0, 0)
std::string get_filename() const;
void set_filename(std::string const& value);
void add_filter(Glib::RefPtr<Gtk::FileFilter> const& value);
Glib::PropertyProxy<Gtk::FileChooser::Action> property_action();
Glib::PropertyProxy<Glib::ustring> property_title();
sigc::signal<void()>& signal_selection_changed();
#endif
private:
class Impl;
std::unique_ptr<Impl> const impl_;
};

View File

@ -18,9 +18,11 @@
#include <libtransmission/web-utils.h>
#include "FreeSpaceLabel.h"
#include "PathButton.h"
#include "Prefs.h"
#include "PrefsDialog.h"
#include "Session.h"
#include "SystemTrayIcon.h"
#include "Utils.h"
/**
@ -55,7 +57,7 @@ void PrefsDialog::Impl::response_cb(int response)
if (response == TR_GTK_RESPONSE_TYPE(CLOSE))
{
dialog_.hide();
dialog_.close();
}
}
@ -161,21 +163,27 @@ void init_text_view(Gtk::TextView& view, tr_quark const key, Glib::RefPtr<Sessio
auto buffer = view.get_buffer();
buffer->set_text(gtr_pref_string_get(key));
auto const save_buffer = [buffer, key, core]()
{
core->set_pref(key, buffer->get_text());
};
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto focus_controller = Gtk::EventControllerFocus::create();
focus_controller->signal_leave().connect(save_buffer);
view.add_controller(focus_controller);
#else
view.add_events(Gdk::FOCUS_CHANGE_MASK);
view.signal_focus_out_event().connect(
[buffer, key, core](GdkEventFocus* /*event*/)
{
core->set_pref(key, buffer->get_text());
return false;
});
view.signal_focus_out_event().connect_notify(sigc::hide<0>(save_buffer));
#endif
}
void chosen_cb(Gtk::FileChooser* w, tr_quark const key, Glib::RefPtr<Session> const& core)
void chosen_cb(PathButton* w, tr_quark const key, Glib::RefPtr<Session> const& core)
{
core->set_pref(key, w->get_filename());
}
void init_chooser_button(Gtk::FileChooserButton& button, tr_quark const key, Glib::RefPtr<Session> const& core)
void init_chooser_button(PathButton& button, tr_quark const key, Glib::RefPtr<Session> const& core)
{
if (auto const path = gtr_pref_string_get(key); !path.empty())
{
@ -185,7 +193,7 @@ void init_chooser_button(Gtk::FileChooserButton& button, tr_quark const key, Gli
button.signal_selection_changed().connect([&button, key, core]() { chosen_cb(&button, key, core); });
}
void target_cb(Gtk::ToggleButton* tb, Gtk::Widget* target)
void target_cb(Gtk::CheckButton* tb, Gtk::Widget* target)
{
target->set_sensitive(tb->get_active());
}
@ -248,7 +256,7 @@ DownloadingPage::DownloadingPage(
{
auto* l = gtr_get_widget<Gtk::CheckButton>(builder, "watch_dir_check");
init_check_button(*l, TR_KEY_watch_dir_enabled, core_);
auto* w = gtr_get_widget<Gtk::FileChooserButton>(builder, "watch_dir_chooser");
auto* w = gtr_get_widget_derived<PathButton>(builder, "watch_dir_chooser");
init_chooser_button(*w, TR_KEY_watch_dir, core_);
w->set_sensitive(gtr_pref_flag_get(TR_KEY_watch_dir_enabled));
l->signal_toggled().connect([l, w]() { target_cb(l, w); });
@ -266,7 +274,7 @@ DownloadingPage::DownloadingPage(
TR_KEY_trash_original_torrent_files,
core_);
init_chooser_button(*gtr_get_widget<Gtk::FileChooserButton>(builder, "download_dir_chooser"), TR_KEY_download_dir, core_);
init_chooser_button(*gtr_get_widget_derived<PathButton>(builder, "download_dir_chooser"), TR_KEY_download_dir, core_);
init_spin_button(
*gtr_get_widget<Gtk::SpinButton>(builder, "max_active_downloads_spin"),
@ -292,7 +300,7 @@ DownloadingPage::DownloadingPage(
{
auto* l = gtr_get_widget<Gtk::CheckButton>(builder, "incomplete_dir_check");
init_check_button(*l, TR_KEY_incomplete_dir_enabled, core_);
auto* w = gtr_get_widget<Gtk::FileChooserButton>(builder, "incomplete_dir_chooser");
auto* w = gtr_get_widget_derived<PathButton>(builder, "incomplete_dir_chooser");
init_chooser_button(*w, TR_KEY_incomplete_dir, core_);
w->set_sensitive(gtr_pref_flag_get(TR_KEY_incomplete_dir_enabled));
l->signal_toggled().connect([l, w]() { target_cb(l, w); });
@ -301,7 +309,7 @@ DownloadingPage::DownloadingPage(
{
auto* l = gtr_get_widget<Gtk::CheckButton>(builder, "download_done_script_check");
init_check_button(*l, TR_KEY_script_torrent_done_enabled, core_);
auto* w = gtr_get_widget<Gtk::FileChooserButton>(builder, "download_done_script_chooser");
auto* w = gtr_get_widget_derived<PathButton>(builder, "download_done_script_chooser");
init_chooser_button(*w, TR_KEY_script_torrent_done_filename, core_);
w->set_sensitive(gtr_pref_flag_get(TR_KEY_script_torrent_done_enabled));
l->signal_toggled().connect([l, w]() { target_cb(l, w); });
@ -358,7 +366,7 @@ SeedingPage::SeedingPage(
{
auto* l = gtr_get_widget<Gtk::CheckButton>(builder, "seeding_done_script_check");
init_check_button(*l, TR_KEY_script_torrent_done_seeding_enabled, core_);
auto* w = gtr_get_widget<Gtk::FileChooserButton>(builder, "seeding_done_script_choose");
auto* w = gtr_get_widget_derived<PathButton>(builder, "seeding_done_script_choose");
init_chooser_button(*w, TR_KEY_script_torrent_done_seeding_filename, core_);
w->set_sensitive(gtr_pref_flag_get(TR_KEY_script_torrent_done_seeding_enabled));
l->signal_toggled().connect([l, w]() { target_cb(l, w); });
@ -397,10 +405,15 @@ DesktopPage::DesktopPage(
TR_KEY_inhibit_desktop_hibernation,
core_);
init_check_button(
*gtr_get_widget<Gtk::CheckButton>(builder, "show_systray_icon_check"),
TR_KEY_show_notification_area_icon,
core_);
if (auto* const show_systray_icon_check = gtr_get_widget<Gtk::CheckButton>(builder, "show_systray_icon_check");
SystemTrayIcon::is_available())
{
init_check_button(*show_systray_icon_check, TR_KEY_show_notification_area_icon, core_);
}
else
{
show_systray_icon_check->hide();
}
init_check_button(
*gtr_get_widget<Gtk::CheckButton>(builder, "notify_on_torrent_add_check"),
@ -500,7 +513,7 @@ void PrivacyPage::onBlocklistUpdated(int n)
void PrivacyPage::onBlocklistUpdate()
{
updateBlocklistDialog_ = std::make_unique<Gtk::MessageDialog>(
*static_cast<Gtk::Window*>(get_toplevel()),
*static_cast<Gtk::Window*>(TR_GTK_WIDGET_GET_ROOT(*this)),
_("Update Blocklist"),
false,
TR_GTK_MESSAGE_TYPE(INFO),
@ -736,7 +749,7 @@ RemotePage::RemotePage(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> con
{
/* "enabled" checkbutton */
init_check_button(*rpc_tb_, TR_KEY_rpc_enabled, core_);
rpc_tb_->signal_clicked().connect([this]() { refreshRPCSensitivity(); });
rpc_tb_->signal_toggled().connect([this]() { refreshRPCSensitivity(); });
auto* const open_button = gtr_get_widget<Gtk::Button>(builder, "open_web_client_button");
widgets_.push_back(open_button);
open_button->signal_clicked().connect(&onLaunchClutchCB);
@ -750,7 +763,7 @@ RemotePage::RemotePage(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> con
/* require authentication */
init_check_button(*auth_tb_, TR_KEY_rpc_authentication_required, core_);
widgets_.push_back(auth_tb_);
auth_tb_->signal_clicked().connect([this]() { refreshRPCSensitivity(); });
auth_tb_->signal_toggled().connect([this]() { refreshRPCSensitivity(); });
/* username */
auto* username_entry = gtr_get_widget<Gtk::Entry>(builder, "rpc_username_entry");
@ -767,15 +780,17 @@ RemotePage::RemotePage(BaseObjectType* cast_item, Glib::RefPtr<Gtk::Builder> con
/* require authentication */
init_check_button(*whitelist_tb_, TR_KEY_rpc_whitelist_enabled, core_);
widgets_.push_back(whitelist_tb_);
whitelist_tb_->signal_clicked().connect([this]() { refreshRPCSensitivity(); });
whitelist_tb_->signal_toggled().connect([this]() { refreshRPCSensitivity(); });
/* access control list */
{
store_ = whitelist_tree_model_new(gtr_pref_string_get(TR_KEY_rpc_whitelist));
view_->set_model(store_);
view_->signal_button_release_event().connect([this](GdkEventButton* event)
{ return on_tree_view_button_released(view_, event); });
setup_tree_view_button_event_handling(
*view_,
{},
[this](double view_x, double view_y) { return on_tree_view_button_released(*view_, view_x, view_y); });
whitelist_widgets_.push_back(view_);
auto const sel = view_->get_selection();
@ -887,7 +902,7 @@ auto SpeedPage::get_weekday_string(Glib::Date::Weekday weekday)
{
auto date = Glib::Date{};
date.set_time_current();
date.add_days(weekday - date.get_weekday());
date.add_days(static_cast<int>(weekday) - static_cast<int>(date.get_weekday()));
return date.format_string("%A");
}

View File

@ -13,6 +13,7 @@
#include <libtransmission/transmission.h>
#include "PathButton.h"
#include "Prefs.h" /* gtr_pref_string_get */
#include "RelocateDialog.h"
#include "Session.h"
@ -52,8 +53,8 @@ private:
bool do_move_ = false;
sigc::connection timer_;
std::unique_ptr<Gtk::MessageDialog> message_dialog_;
Gtk::FileChooserButton* chooser_ = nullptr;
Gtk::RadioButton* move_tb_ = nullptr;
PathButton* chooser_ = nullptr;
Gtk::CheckButton* move_tb_ = nullptr;
};
RelocateDialog::Impl::~Impl()
@ -113,7 +114,7 @@ bool RelocateDialog::Impl::onTimer()
}
else
{
dialog_.hide();
dialog_.close();
}
}
@ -153,7 +154,7 @@ void RelocateDialog::Impl::onResponse(int response)
}
else
{
dialog_.hide();
dialog_.close();
}
}
@ -189,8 +190,8 @@ RelocateDialog::Impl::Impl(
: dialog_(dialog)
, core_(core)
, torrent_ids_(torrent_ids)
, chooser_(gtr_get_widget<Gtk::FileChooserButton>(builder, "new_location_button"))
, move_tb_(gtr_get_widget<Gtk::RadioButton>(builder, "move_data_radio"))
, chooser_(gtr_get_widget_derived<PathButton>(builder, "new_location_button"))
, move_tb_(gtr_get_widget<Gtk::CheckButton>(builder, "move_data_radio"))
{
dialog_.set_default_response(TR_GTK_RESPONSE_TYPE(CANCEL));
dialog_.signal_response().connect(sigc::mem_fun(*this, &Impl::onResponse));
@ -199,19 +200,15 @@ RelocateDialog::Impl::Impl(
if (recent_dirs.empty())
{
/* default to download dir */
chooser_->set_current_folder(gtr_pref_string_get(TR_KEY_download_dir));
chooser_->set_filename(gtr_pref_string_get(TR_KEY_download_dir));
}
else
{
/* set last used as target */
chooser_->set_current_folder(recent_dirs.front());
chooser_->set_filename(recent_dirs.front());
recent_dirs.pop_front();
/* add remaining as shortcut */
for (auto const& folder : recent_dirs)
{
chooser_->remove_shortcut_folder(folder);
chooser_->add_shortcut_folder(folder);
}
chooser_->set_shortcut_folders(recent_dirs);
}
}

View File

@ -402,12 +402,12 @@ int compare_time(time_t a, time_t b)
return ret;
}
int compare_by_name(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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));
}
int compare_by_queue(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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)));
@ -415,7 +415,7 @@ int compare_by_queue(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator
return sb->queuePosition - sa->queuePosition;
}
int compare_by_ratio(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
int compare_by_ratio(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
{
int ret = 0;
@ -435,7 +435,7 @@ int compare_by_ratio(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator
return ret;
}
int compare_by_activity(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
int compare_by_activity(Gtk::TreeModel::const_iterator const& a, Gtk::TreeModel::const_iterator const& b)
{
int ret = 0;
@ -463,7 +463,7 @@ int compare_by_activity(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::itera
return ret;
}
int compare_by_age(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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));
@ -477,7 +477,7 @@ int compare_by_age(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator c
return ret;
}
int compare_by_size(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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)));
@ -491,7 +491,7 @@ int compare_by_size(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator
return ret;
}
int compare_by_progress(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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)));
@ -510,7 +510,7 @@ int compare_by_progress(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::itera
return ret;
}
int compare_by_eta(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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)));
@ -524,7 +524,7 @@ int compare_by_eta(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator c
return ret;
}
int compare_by_state(Gtk::TreeModel::iterator const& a, Gtk::TreeModel::iterator const& b)
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);
@ -820,8 +820,8 @@ Session::Impl::Impl(Session& core, tr_session* session)
{
raw_model_ = Gtk::ListStore::create(torrent_cols);
sorted_model_ = Gtk::TreeModelSort::create(raw_model_);
sorted_model_->set_default_sort_func([](Gtk::TreeModel::iterator const& /*a*/, Gtk::TreeModel::iterator const& /*b*/)
{ return 0; });
sorted_model_->set_default_sort_func(
[](Gtk::TreeModel::const_iterator const& /*a*/, Gtk::TreeModel::const_iterator const& /*b*/) { return 0; });
/* init from prefs & listen to pref changes */
on_pref_changed(TR_KEY_sort_mode);
@ -900,11 +900,11 @@ struct metadata_callback_data
Gtk::TreeModel::iterator find_row_from_torrent_id(Glib::RefPtr<Gtk::TreeModel> const& model, tr_torrent_id_t id)
{
for (auto const& row : model->children())
for (auto& row : model->children())
{
if (id == row.get_value(torrent_cols.torrent_id))
{
return row;
return TR_GTK_TREE_MODEL_CHILD_ITER(row);
}
}
@ -1325,7 +1325,7 @@ int gtr_compare_double(double const a, double const b, int decimal_places)
return 0;
}
void update_foreach(Gtk::TreeModel::Row const& row)
void update_foreach(Gtk::TreeModel::Row& row)
{
/* get the old states */
auto* const tor = static_cast<tr_torrent*>(row.get_value(torrent_cols.torrent));
@ -1407,7 +1407,7 @@ void Session::start_now(tr_torrent_id_t id)
void Session::Impl::update()
{
/* update the model */
for (auto const& row : raw_model_->children())
for (auto row : raw_model_->children())
{
update_foreach(row);
}

View File

@ -122,7 +122,7 @@ void StatsDialog::Impl::dialogResponse(int response)
if (response == TR_GTK_RESPONSE_TYPE(CLOSE))
{
dialog_.hide();
dialog_.close();
}
}

View File

@ -8,7 +8,6 @@
// We're using deprecated Gtk::StatusItem ourselves as well
#undef GTKMM_DISABLE_DEPRECATED
#include <memory>
#include <string>
#include <glibmm.h>
@ -26,14 +25,31 @@
#include "SystemTrayIcon.h"
#include "Utils.h"
#define TR_SYS_TRAY_IMPL_NONE 0
#define TR_SYS_TRAY_IMPL_APPINDICATOR 1
#define TR_SYS_TRAY_IMPL_STATUS_ICON 2
#ifdef HAVE_LIBAPPINDICATOR
#define TR_SYS_TRAY_IMPL TR_SYS_TRAY_IMPL_APPINDICATOR
#elif !GTKMM_CHECK_VERSION(4, 0, 0)
#define TR_SYS_TRAY_IMPL TR_SYS_TRAY_IMPL_STATUS_ICON
#else
#define TR_SYS_TRAY_IMPL TR_SYS_TRAY_IMPL_NONE
#endif
using namespace std::literals;
namespace
{
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
auto const TrayIconName = Glib::ustring("transmission-tray-icon"s);
auto const AppIconName = Glib::ustring("transmission"s);
#endif
#if TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_APPINDICATOR
auto const AppName = Glib::ustring("transmission-gtk"s);
#endif
} // namespace
@ -56,16 +72,18 @@ private:
private:
Glib::RefPtr<Session> const core_;
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
Gtk::Menu* menu_;
#endif
#ifdef HAVE_LIBAPPINDICATOR
#if TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_APPINDICATOR
AppIndicator* indicator_;
#else
#elif TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_STATUS_ICON
Glib::RefPtr<Gtk::StatusIcon> icon_;
#endif
};
#ifdef HAVE_LIBAPPINDICATOR
#if TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_APPINDICATOR
SystemTrayIcon::Impl::~Impl()
{
@ -76,7 +94,7 @@ void SystemTrayIcon::Impl::refresh()
{
}
#else
#elif TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_STATUS_ICON
SystemTrayIcon::Impl::~Impl() = default;
@ -95,11 +113,17 @@ void SystemTrayIcon::Impl::refresh()
icon_->set_tooltip_text(make_tooltip_text());
}
#else
SystemTrayIcon::Impl::~Impl() = default;
#endif
namespace
{
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
Glib::ustring getIconName()
{
Glib::ustring icon_name;
@ -121,6 +145,8 @@ Glib::ustring getIconName()
return icon_name;
}
#endif
} // namespace
SystemTrayIcon::SystemTrayIcon(Gtk::Window& main_window, Glib::RefPtr<Session> const& core)
@ -132,29 +158,43 @@ SystemTrayIcon::~SystemTrayIcon() = default;
void SystemTrayIcon::refresh()
{
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
impl_->refresh();
#endif
}
SystemTrayIcon::Impl::Impl(Gtk::Window& main_window, Glib::RefPtr<Session> const& core)
bool SystemTrayIcon::is_available()
{
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
return true;
#else
return false;
#endif
}
std::unique_ptr<SystemTrayIcon> SystemTrayIcon::create(Gtk::Window& main_window, Glib::RefPtr<Session> const& core)
{
return is_available() ? std::make_unique<SystemTrayIcon>(main_window, core) : nullptr;
}
SystemTrayIcon::Impl::Impl([[maybe_unused]] Gtk::Window& main_window, Glib::RefPtr<Session> const& core)
: core_(core)
{
#if TR_SYS_TRAY_IMPL != TR_SYS_TRAY_IMPL_NONE
auto const icon_name = getIconName();
menu_ = Gtk::make_managed<Gtk::Menu>(gtr_action_get_object<Gio::Menu>("icon-popup"));
menu_->attach_to_widget(main_window);
#endif
#ifdef HAVE_LIBAPPINDICATOR
#if TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_APPINDICATOR
indicator_ = app_indicator_new(AppName.c_str(), icon_name.c_str(), APP_INDICATOR_CATEGORY_SYSTEM_SERVICES);
app_indicator_set_status(indicator_, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_menu(indicator_, Glib::unwrap(menu_));
app_indicator_set_title(indicator_, Glib::get_application_name().c_str());
#else
#elif TR_SYS_TRAY_IMPL == TR_SYS_TRAY_IMPL_STATUS_ICON
icon_ = Gtk::StatusIcon::create(icon_name);
icon_->signal_activate().connect(sigc::mem_fun(*this, &Impl::activated));
icon_->signal_popup_menu().connect(sigc::mem_fun(*this, &Impl::popup));
#endif
}

View File

@ -23,6 +23,9 @@ public:
void refresh();
static bool is_available();
static std::unique_ptr<SystemTrayIcon> create(Gtk::Window& main_window, Glib::RefPtr<Session> const& core);
private:
class Impl;
std::unique_ptr<Impl> const impl_;

View File

@ -30,14 +30,17 @@
****
***/
#define REQ_HEIGHT(Obj) IF_GTKMM4((Obj).get_height(), (Obj).height)
#define REQ_WIDTH(Obj) IF_GTKMM4((Obj).get_width(), (Obj).width)
namespace
{
auto const DefaultBarHeight = 12;
auto const CompactBarWidth = 50;
auto const SmallScale = 0.9;
auto const CompactIconSize = Gtk::ICON_SIZE_MENU;
auto const FullIconSize = Gtk::ICON_SIZE_DND;
auto const CompactIconSize = IF_GTKMM4(Gtk::IconSize::NORMAL, Gtk::ICON_SIZE_MENU);
auto const FullIconSize = IF_GTKMM4(Gtk::IconSize::LARGE, Gtk::ICON_SIZE_DND);
auto getProgressString(tr_torrent const* tor, uint64_t total_size, tr_stat const* st)
{
@ -280,9 +283,11 @@ std::string getStatusString(
tr_torrent const* tor,
tr_stat const* st,
double const uploadSpeed_KBps,
double const downloadSpeed_KBps)
double const downloadSpeed_KBps,
bool ignore_errors = false)
{
auto status_str = getErrorString(st).value_or(getActivityString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps));
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)
@ -304,6 +309,9 @@ std::string getStatusString(
class TorrentCellRenderer::Impl
{
using ContextPtr = TorrentCellRenderer::ContextPtr;
using IconSize = IF_GTKMM4(Gtk::IconSize, Gtk::BuiltinIconSize);
public:
explicit Impl(TorrentCellRenderer& renderer);
~Impl();
@ -314,12 +322,12 @@ public:
void get_size_full(Gtk::Widget& widget, int& width, int& height) const;
void render_compact(
Cairo::RefPtr<Cairo::Context> const& cr,
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gtk::CellRendererState flags);
void render_full(
Cairo::RefPtr<Cairo::Context> const& cr,
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gtk::CellRendererState flags);
@ -339,6 +347,9 @@ public:
Glib::Property<bool> compact;
private:
static void set_icon(Gtk::CellRendererPixbuf& renderer, Glib::RefPtr<Gio::Icon> const& icon, IconSize icon_size);
private:
TorrentCellRenderer& renderer_;
@ -354,7 +365,7 @@ private:
namespace
{
Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
Glib::RefPtr<Gio::Icon> get_icon(tr_torrent const* tor)
{
auto mime_type = std::string_view{};
@ -373,7 +384,7 @@ Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_siz
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
}
return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
return gtr_get_mime_type_icon(mime_type);
}
} // namespace
@ -382,6 +393,19 @@ Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_siz
****
***/
void TorrentCellRenderer::Impl::set_icon(
Gtk::CellRendererPixbuf& renderer,
Glib::RefPtr<Gio::Icon> const& icon,
IconSize icon_size)
{
renderer.property_gicon() = icon;
#if GTKMM_CHECK_VERSION(4, 0, 0)
renderer.property_icon_size() = icon_size;
#else
renderer.property_stock_size() = icon_size;
#endif
}
void TorrentCellRenderer::Impl::get_size_compact(Gtk::Widget& widget, int& width, int& height) const
{
int xpad;
@ -394,13 +418,13 @@ void TorrentCellRenderer::Impl::get_size_compact(Gtk::Widget& widget, int& width
auto* const tor = static_cast<tr_torrent*>(torrent.get_value());
auto const* const st = tr_torrentStatCached(tor);
auto const icon = get_icon(tor, CompactIconSize, widget);
auto const icon = get_icon(tor);
auto const name = Glib::ustring(tr_torrentName(tor));
auto const gstr_stat = getShortStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value());
renderer_.get_padding(xpad, ypad);
/* get the idealized cell dimensions */
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, CompactIconSize);
icon_renderer_->get_preferred_size(widget, min_size, icon_size);
text_renderer_->property_text() = name;
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(NONE);
@ -414,8 +438,8 @@ void TorrentCellRenderer::Impl::get_size_compact(Gtk::Widget& widget, int& width
*** LAYOUT
**/
width = xpad * 2 + icon_size.width + GUI_PAD + CompactBarWidth + GUI_PAD + stat_size.width;
height = ypad * 2 + std::max(name_size.height, bar_height.get_value());
width = xpad * 2 + REQ_WIDTH(icon_size) + GUI_PAD + CompactBarWidth + GUI_PAD + REQ_WIDTH(stat_size);
height = ypad * 2 + std::max(REQ_HEIGHT(name_size), bar_height.get_value());
}
void TorrentCellRenderer::Impl::get_size_full(Gtk::Widget& widget, int& width, int& height) const
@ -432,14 +456,14 @@ void TorrentCellRenderer::Impl::get_size_full(Gtk::Widget& widget, int& width, i
auto const* const st = tr_torrentStatCached(tor);
auto const total_size = tr_torrentTotalSize(tor);
auto const icon = get_icon(tor, FullIconSize, widget);
auto const icon = get_icon(tor);
auto const name = Glib::ustring(tr_torrentName(tor));
auto const gstr_stat = getStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value());
auto const gstr_stat = getStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value(), true);
auto const gstr_prog = getProgressString(tor, total_size, st);
renderer_.get_padding(xpad, ypad);
/* get the idealized cell dimensions */
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, FullIconSize);
icon_renderer_->get_preferred_size(widget, min_size, icon_size);
text_renderer_->property_text() = name;
text_renderer_->property_weight() = TR_PANGO_WEIGHT(BOLD);
@ -457,9 +481,9 @@ void TorrentCellRenderer::Impl::get_size_full(Gtk::Widget& widget, int& width, i
*** LAYOUT
**/
width = xpad * 2 + icon_size.width + GUI_PAD + std::max(prog_size.width, stat_size.width);
height = ypad * 2 + name_size.height + prog_size.height + GUI_PAD_SMALL + bar_height.get_value() + GUI_PAD_SMALL +
stat_size.height;
width = xpad * 2 + REQ_WIDTH(icon_size) + GUI_PAD + std::max(REQ_WIDTH(prog_size), REQ_WIDTH(stat_size));
height = ypad * 2 + REQ_HEIGHT(name_size) + REQ_HEIGHT(prog_size) + GUI_PAD_SMALL + bar_height.get_value() + GUI_PAD_SMALL +
REQ_HEIGHT(stat_size);
}
void TorrentCellRenderer::get_preferred_width_vfunc(Gtk::Widget& widget, int& minimum_width, int& natural_width) const
@ -507,24 +531,6 @@ void TorrentCellRenderer::get_preferred_height_vfunc(Gtk::Widget& widget, int& m
namespace
{
Gdk::RGBA get_text_color(Gtk::Widget& w, tr_stat const* st)
{
static auto const red = Gdk::RGBA("red");
if (st->error != 0)
{
return red;
}
else if (st->activity == TR_STATUS_STOPPED)
{
return w.get_style_context()->get_color(Gtk::STATE_FLAG_INSENSITIVE);
}
else
{
return w.get_style_context()->get_color(Gtk::STATE_FLAG_NORMAL);
}
}
double get_percent_done(tr_torrent const* tor, tr_stat const* st, bool* seed)
{
double d;
@ -543,10 +549,16 @@ double get_percent_done(tr_torrent const* tor, tr_stat const* st, bool* seed)
return d;
}
template<typename... Ts>
void render_impl(Gtk::CellRenderer& renderer, Ts&&... args)
{
renderer.IF_GTKMM4(snapshot, render)(std::forward<Ts>(args)...);
}
} // namespace
void TorrentCellRenderer::Impl::render_compact(
Cairo::RefPtr<Cairo::Context> const& cr,
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gtk::CellRendererState flags)
@ -564,11 +576,24 @@ void TorrentCellRenderer::Impl::render_compact(
auto const percentDone = get_percent_done(tor, st, &seed);
bool const sensitive = active || st->error;
auto const icon = get_icon(tor, CompactIconSize, widget);
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{})
{
text_renderer_->property_foreground() = "red";
}
else
{
text_renderer_->property_foreground_set() = false;
}
auto const icon = get_icon(tor);
auto const name = Glib::ustring(tr_torrentName(tor));
auto const gstr_stat = getShortStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value());
renderer_.get_padding(xpad, ypad);
auto const text_color = get_text_color(widget, st);
auto fill_area = background_area;
fill_area.set_x(fill_area.get_x() + xpad);
@ -577,7 +602,7 @@ void TorrentCellRenderer::Impl::render_compact(
fill_area.set_height(fill_area.get_height() - ypad * 2);
auto icon_area = fill_area;
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, CompactIconSize);
icon_renderer_->get_preferred_width(widget, min_width, width);
icon_area.set_width(width);
@ -614,30 +639,28 @@ void TorrentCellRenderer::Impl::render_compact(
*** RENDER
**/
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, CompactIconSize);
icon_renderer_->property_sensitive() = sensitive;
icon_renderer_->render(cr, widget, icon_area, icon_area, flags);
render_impl(*icon_renderer_, context, widget, icon_area, icon_area, flags);
auto const percent_done = static_cast<int>(percentDone * 100.0);
progress_renderer_->property_value() = percent_done;
progress_renderer_->property_text() = fmt::format(FMT_STRING("{:d}%"), percent_done);
progress_renderer_->property_sensitive() = sensitive;
progress_renderer_->render(cr, widget, prog_area, prog_area, flags);
render_impl(*progress_renderer_, context, widget, prog_area, prog_area, flags);
text_renderer_->property_text() = gstr_stat;
text_renderer_->property_scale() = SmallScale;
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(END);
text_renderer_->property_foreground_rgba() = text_color;
text_renderer_->render(cr, widget, stat_area, stat_area, flags);
render_impl(*text_renderer_, context, widget, stat_area, stat_area, flags);
text_renderer_->property_text() = name;
text_renderer_->property_scale() = 1.0;
text_renderer_->property_foreground_rgba() = text_color;
text_renderer_->render(cr, widget, name_area, name_area, flags);
render_impl(*text_renderer_, context, widget, name_area, name_area, flags);
}
void TorrentCellRenderer::Impl::render_full(
Cairo::RefPtr<Cairo::Context> const& cr,
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gtk::CellRendererState flags)
@ -656,19 +679,32 @@ void TorrentCellRenderer::Impl::render_full(
auto const percentDone = get_percent_done(tor, st, &seed);
bool const sensitive = active || st->error;
auto const icon = get_icon(tor, FullIconSize, widget);
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{})
{
text_renderer_->property_foreground() = "red";
}
else
{
text_renderer_->property_foreground_set() = false;
}
auto const icon = get_icon(tor);
auto const name = Glib::ustring(tr_torrentName(tor));
auto const gstr_prog = getProgressString(tor, total_size, st);
auto const gstr_stat = getStatusString(tor, st, upload_speed_KBps.get_value(), download_speed_KBps.get_value());
renderer_.get_padding(xpad, ypad);
auto const text_color = get_text_color(widget, st);
/* get the idealized cell dimensions */
Gdk::Rectangle icon_area;
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, FullIconSize);
icon_renderer_->get_preferred_size(widget, min_size, size);
icon_area.set_width(size.width);
icon_area.set_height(size.height);
icon_area.set_width(REQ_WIDTH(size));
icon_area.set_height(REQ_HEIGHT(size));
Gdk::Rectangle name_area;
text_renderer_->property_text() = name;
@ -676,19 +712,19 @@ void TorrentCellRenderer::Impl::render_full(
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(NONE);
text_renderer_->property_scale() = 1.0;
text_renderer_->get_preferred_size(widget, min_size, size);
name_area.set_height(size.height);
name_area.set_height(REQ_HEIGHT(size));
Gdk::Rectangle prog_area;
text_renderer_->property_text() = gstr_prog;
text_renderer_->property_weight() = TR_PANGO_WEIGHT(NORMAL);
text_renderer_->property_scale() = SmallScale;
text_renderer_->get_preferred_size(widget, min_size, size);
prog_area.set_height(size.height);
prog_area.set_height(REQ_HEIGHT(size));
Gdk::Rectangle stat_area;
text_renderer_->property_text() = gstr_stat;
text_renderer_->get_preferred_size(widget, min_size, size);
stat_area.set_height(size.height);
stat_area.set_height(REQ_HEIGHT(size));
Gdk::Rectangle prct_area;
@ -740,34 +776,32 @@ void TorrentCellRenderer::Impl::render_full(
*** RENDER
**/
icon_renderer_->property_pixbuf() = icon;
set_icon(*icon_renderer_, icon, FullIconSize);
icon_renderer_->property_sensitive() = sensitive;
icon_renderer_->render(cr, widget, icon_area, icon_area, flags);
render_impl(*icon_renderer_, context, widget, icon_area, icon_area, flags);
text_renderer_->property_text() = name;
text_renderer_->property_scale() = 1.0;
text_renderer_->property_foreground_rgba() = text_color;
text_renderer_->property_ellipsize() = TR_PANGO_ELLIPSIZE_MODE(END);
text_renderer_->property_weight() = TR_PANGO_WEIGHT(BOLD);
text_renderer_->render(cr, widget, name_area, name_area, flags);
render_impl(*text_renderer_, context, widget, name_area, name_area, flags);
text_renderer_->property_text() = gstr_prog;
text_renderer_->property_scale() = SmallScale;
text_renderer_->property_weight() = TR_PANGO_WEIGHT(NORMAL);
text_renderer_->render(cr, widget, prog_area, prog_area, flags);
render_impl(*text_renderer_, context, widget, prog_area, prog_area, flags);
progress_renderer_->property_value() = static_cast<int>(percentDone * 100.0);
progress_renderer_->property_text() = Glib::ustring();
progress_renderer_->property_sensitive() = sensitive;
progress_renderer_->render(cr, widget, prct_area, prct_area, flags);
render_impl(*progress_renderer_, context, widget, prct_area, prct_area, flags);
text_renderer_->property_text() = gstr_stat;
text_renderer_->property_foreground_rgba() = text_color;
text_renderer_->render(cr, widget, stat_area, stat_area, flags);
render_impl(*text_renderer_, context, widget, stat_area, stat_area, flags);
}
void TorrentCellRenderer::render_vfunc(
Cairo::RefPtr<Cairo::Context> const& cr,
void TorrentCellRenderer::IF_GTKMM4(snapshot_vfunc, render_vfunc)(
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gdk::Rectangle const& /*cell_area*/,
@ -782,11 +816,11 @@ void TorrentCellRenderer::render_vfunc(
{
if (impl_->compact.get_value())
{
impl_->render_compact(cr, widget, background_area, flags);
impl_->render_compact(context, widget, background_area, flags);
}
else
{
impl_->render_full(cr, widget, background_area, flags);
impl_->render_full(context, widget, background_area, flags);
}
}

View File

@ -12,10 +12,14 @@
#include <libtransmission/tr-macros.h>
#include "Utils.h"
struct tr_torrent;
class TorrentCellRenderer : public Gtk::CellRenderer
{
using ContextPtr = IF_GTKMM4(Glib::RefPtr<Gtk::Snapshot>, Cairo::RefPtr<Cairo::Context>);
public:
TorrentCellRenderer();
~TorrentCellRenderer() override;
@ -31,8 +35,8 @@ public:
protected:
void get_preferred_width_vfunc(Gtk::Widget& widget, int& minimum_width, int& natural_width) const override;
void get_preferred_height_vfunc(Gtk::Widget& widget, int& minimum_height, int& natural_height) const override;
void render_vfunc(
Cairo::RefPtr<Cairo::Context> const& cr,
void IF_GTKMM4(snapshot_vfunc, render_vfunc)(
ContextPtr const& context,
Gtk::Widget& widget,
Gdk::Rectangle const& background_area,
Gdk::Rectangle const& cell_area,

View File

@ -14,6 +14,12 @@
#include <giomm.h> /* g_file_trash() */
#include <glibmm/i18n.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
#if GTK_CHECK_VERSION(4, 0, 0) && defined(GDK_WINDOWING_X11)
#include <gdk/x11/gdkx.h>
#endif
#include <fmt/core.h>
#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
@ -265,22 +271,24 @@ void gtr_add_torrent_error_dialog(Gtk::Widget& child, tr_torrent* duplicate_torr
TR_GTK_BUTTONS_TYPE(CLOSE));
w->set_secondary_text(secondary);
w->signal_response().connect([w](int /*response*/) mutable { w.reset(); });
w->show_all();
w->show();
}
/* pop up the context menu if a user right-clicks.
if the row they right-click on isn't selected, select it. */
bool on_tree_view_button_pressed(
Gtk::TreeView* view,
GdkEventButton* event,
std::function<void(GdkEventButton*)> const& callback)
Gtk::TreeView& view,
double view_x,
double view_y,
bool context_menu_requested,
std::function<void(double, double)> const& callback)
{
if (event->type == GDK_BUTTON_PRESS && event->button == 3)
if (context_menu_requested)
{
Gtk::TreeModel::Path path;
auto const selection = view->get_selection();
auto const selection = view.get_selection();
if (view->get_path_at_pos((int)event->x, (int)event->y, path) && !selection->is_selected(path))
if (view.get_path_at_pos((int)view_x, (int)view_y, path) && !selection->is_selected(path))
{
selection->unselect_all();
selection->select(path);
@ -288,7 +296,7 @@ bool on_tree_view_button_pressed(
if (callback)
{
callback(event);
callback(view_x, view_y);
}
return true;
@ -299,16 +307,75 @@ bool on_tree_view_button_pressed(
/* if the user clicked in an empty area of the list,
* clear all the selections. */
bool on_tree_view_button_released(Gtk::TreeView* view, GdkEventButton* event)
bool on_tree_view_button_released(Gtk::TreeView& view, double view_x, double view_y)
{
if (Gtk::TreeModel::Path path; !view->get_path_at_pos((int)event->x, (int)event->y, path))
if (Gtk::TreeModel::Path path; !view.get_path_at_pos((int)view_x, (int)view_y, path))
{
view->get_selection()->unselect_all();
view.get_selection()->unselect_all();
}
return false;
}
void setup_tree_view_button_event_handling(
Gtk::TreeView& view,
std::function<bool(guint, TrGdkModifierType, double, double, bool)> const& press_callback,
std::function<bool(double, double)> const& release_callback)
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto controller = Gtk::GestureClick::create();
controller->set_button(0);
controller->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
if (press_callback)
{
controller->signal_pressed().connect(
[&view, press_callback, controller](int /*n_press*/, double event_x, double event_y)
{
auto* const sequence = controller->get_current_sequence();
auto const event = controller->get_last_event(sequence);
if (event->get_event_type() == TR_GDK_EVENT_TYPE(BUTTON_PRESS) &&
press_callback(
event->get_button(),
event->get_modifier_state(),
event_x,
event_y,
event->triggers_context_menu()))
{
controller->set_sequence_state(sequence, Gtk::EventSequenceState::CLAIMED);
}
},
false);
}
if (release_callback)
{
controller->signal_released().connect(
[&view, release_callback, controller](int /*n_press*/, double event_x, double event_y)
{
auto* const sequence = controller->get_current_sequence();
auto const event = controller->get_last_event(sequence);
if (event->get_event_type() == TR_GDK_EVENT_TYPE(BUTTON_RELEASE) && release_callback(event_x, event_y))
{
controller->set_sequence_state(sequence, Gtk::EventSequenceState::CLAIMED);
}
});
}
view.add_controller(controller);
#else
if (press_callback)
{
view.signal_button_press_event().connect(
[press_callback](GdkEventButton* event)
{ return press_callback(event->button, event->state, event->x, event->y, event->button == GDK_BUTTON_SECONDARY); },
false);
}
if (release_callback)
{
view.signal_button_release_event().connect([release_callback](GdkEventButton* event)
{ return release_callback(event->x, event->y); });
}
#endif
}
bool gtr_file_trash_or_remove(std::string const& filename, tr_error** error)
{
bool trashed = false;
@ -326,8 +393,8 @@ bool gtr_file_trash_or_remove(std::string const& filename, tr_error** error)
}
catch (Glib::Error const& e)
{
g_message("Unable to trash file \"%s\": %s", filename.c_str(), e.what().c_str());
tr_error_set(error, e.code(), e.what().raw());
g_message("Unable to trash file \"%s\": %s", filename.c_str(), TR_GLIB_EXCEPTION_WHAT(e));
tr_error_set(error, e.code(), TR_GLIB_EXCEPTION_WHAT(e));
}
}
@ -339,9 +406,9 @@ bool gtr_file_trash_or_remove(std::string const& filename, tr_error** error)
}
catch (Glib::Error const& e)
{
g_message("Unable to delete file \"%s\": %s", filename.c_str(), e.what().c_str());
g_message("Unable to delete file \"%s\": %s", filename.c_str(), TR_GLIB_EXCEPTION_WHAT(e));
tr_error_clear(error);
tr_error_set(error, e.code(), e.what().raw());
tr_error_set(error, e.code(), TR_GLIB_EXCEPTION_WHAT(e));
result = false;
}
}
@ -438,7 +505,7 @@ void gtr_combo_box_set_active_enum(Gtk::ComboBox& combo_box, int value)
{
if (row.get_value(column) == value)
{
combo_box.set_active(row);
combo_box.set_active(TR_GTK_TREE_MODEL_CHILD_ITER(row));
return;
}
}
@ -552,11 +619,33 @@ void gtr_widget_set_visible(Gtk::Widget& w, bool b)
w.set_visible(b);
}
void gtr_dialog_set_content(Gtk::Dialog& dialog, Gtk::Widget& content)
void gtr_window_set_skip_taskbar_hint([[maybe_unused]] Gtk::Window& window, [[maybe_unused]] bool value)
{
auto* vbox = dialog.get_content_area();
vbox->pack_start(content, true, true, 0);
content.show_all();
#if GTK_CHECK_VERSION(4, 0, 0)
#if defined(GDK_WINDOWING_X11)
gdk_x11_surface_set_skip_taskbar_hint(Glib::unwrap(window.get_surface()), value ? TRUE : FALSE);
#endif
#else
window.set_skip_taskbar_hint(value);
#endif
}
void gtr_window_set_urgency_hint([[maybe_unused]] Gtk::Window& window, [[maybe_unused]] bool value)
{
#if GTK_CHECK_VERSION(4, 0, 0)
#if defined(GDK_WINDOWING_X11)
gdk_x11_surface_set_urgency_hint(Glib::unwrap(window.get_surface()), value ? TRUE : FALSE);
#endif
#else
window.set_urgency_hint(value);
#endif
}
void gtr_window_raise([[maybe_unused]] Gtk::Window& window)
{
#if !GTKMM_CHECK_VERSION(4, 0, 0)
window.get_window()->raise();
#endif
}
/***
@ -596,16 +685,43 @@ void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
void gtr_paste_clipboard_url_into_entry(Gtk::Entry& entry)
{
auto const process = [&entry](Glib::ustring const& text)
{
auto const sv = tr_strvStrip(text.raw());
if (!sv.empty() && (tr_urlIsValid(sv) || tr_magnet_metainfo{}.parseMagnet(sv)))
{
entry.set_text(text);
return true;
}
return false;
};
#if GTKMM_CHECK_VERSION(4, 0, 0)
auto const request = [](Glib::RefPtr<Gdk::Clipboard> const& clipboard, auto&& callback)
{
clipboard->read_text_async([clipboard, callback](Glib::RefPtr<Gio::AsyncResult>& result)
{ callback(clipboard->read_text_finish(result)); });
};
request(
Gdk::Display::get_default()->get_primary_clipboard(),
[request, process](Glib::ustring const& text)
{
if (!process(text))
{
request(Gdk::Display::get_default()->get_clipboard(), process);
}
});
#else
for (auto const& str : { Gtk::Clipboard::get(GDK_SELECTION_PRIMARY)->wait_for_text(),
Gtk::Clipboard::get(GDK_SELECTION_CLIPBOARD)->wait_for_text() })
{
auto const sv = tr_strvStrip(str.raw());
if (!sv.empty() && (tr_urlIsValid(sv) || tr_magnet_metainfo{}.parseMagnet(sv)))
if (process(str))
{
entry.set_text(str);
return;
break;
}
}
#endif
}
/***

View File

@ -10,6 +10,7 @@
#include <functional>
#include <list>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
@ -68,6 +69,7 @@
#define TR_GTK_ALIGN(Code) IF_GTKMM4(Gtk::Align::Code, Gtk::ALIGN_##Code)
#define TR_GTK_BUTTONS_TYPE(Code) IF_GTKMM4(Gtk::ButtonsType::Code, Gtk::BUTTONS_##Code)
#define TR_GTK_CELL_RENDERER_STATE(Code) IF_GTKMM4(Gtk::CellRendererState::Code, Gtk::CELL_RENDERER_##Code)
#define TR_GTK_FILE_CHOOSER_ACTION(Code) IF_GTKMM4(Gtk::FileChooser::Action::Code, Gtk::FILE_CHOOSER_ACTION_##Code)
#define TR_GTK_MESSAGE_TYPE(Code) IF_GTKMM4(Gtk::MessageType::Code, Gtk::MESSAGE_##Code)
#define TR_GTK_ORIENTATION(Code) IF_GTKMM4(Gtk::Orientation::Code, Gtk::ORIENTATION_##Code)
@ -78,14 +80,21 @@
#define TR_GTK_STATE_FLAGS(Code) IF_GTKMM4(Gtk::StateFlags::Code, Gtk::STATE_FLAG_##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))
#define TR_GTK_WIDGET_GET_ROOT(Obj) IF_GTKMM4((Obj).get_root(), (Obj).get_toplevel())
#define TR_GDK_COLORSPACE(Code) IF_GTKMM4(Gdk::Colorspace::Code, Gdk::COLORSPACE_##Code)
#define TR_GDK_EVENT_TYPE(Code) IF_GTKMM4(Gdk::Event::Type::Code, GdkEventType::GDK_##Code)
#define TR_GDK_DRAG_ACTION(Code) IF_GTKMM4(Gdk::DragAction::Code, Gdk::ACTION_##Code)
#define TR_GDK_MODIFIED_TYPE(Code) IF_GTKMM4(Gdk::ModifierType::Code, GdkModifierType::GDK_##Code)
#define TR_GLIB_FILE_TEST(Code) IF_GLIBMM2_68(Glib::FileTest::Code, Glib::FILE_TEST_##Code)
#define TR_GLIB_NODE_TREE_TRAVERSE_FLAGS(Cls, Code) IF_GLIBMM2_68(Cls::TraverseFlags::Code, Cls::TRAVERSE_##Code)
#define TR_GLIB_SPAWN_FLAGS(Code) IF_GLIBMM2_68(Glib::SpawnFlags::Code, Glib::SPAWN_##Code)
#define TR_GLIB_USER_DIRECTORY(Code) IF_GLIBMM2_68(Glib::UserDirectory::Code, Glib::USER_DIRECTORY_##Code)
#define TR_GLIB_EXCEPTION_WHAT(Obj) IF_GLIBMM2_68((Obj).what(), (Obj).what().c_str())
#define TR_GIO_APP_INFO_CREATE_FLAGS(Code) IF_GLIBMM2_68(Gio::AppInfo::CreateFlags::Code, Gio::APP_INFO_CREATE_##Code)
#define TR_GIO_APPLICATION_FLAGS(Code) IF_GLIBMM2_68(Gio::Application::Flags::Code, Gio::APPLICATION_##Code)
#define TR_GIO_DBUS_BUS_TYPE(Code) IF_GLIBMM2_68(Gio::DBus::BusType::Code, Gio::DBus::BUS_TYPE_##Code)
@ -155,7 +164,9 @@ Glib::ustring gtr_get_help_uri();
/* backwards-compatible wrapper around gtk_widget_set_visible() */
void gtr_widget_set_visible(Gtk::Widget&, bool);
void gtr_dialog_set_content(Gtk::Dialog& dialog, Gtk::Widget& content);
void gtr_window_set_skip_taskbar_hint(Gtk::Window& window, bool value);
void gtr_window_set_urgency_hint(Gtk::Window& window, bool value);
void gtr_window_raise(Gtk::Window& window);
/***
****
@ -182,12 +193,21 @@ void gtr_add_torrent_error_dialog(Gtk::Widget& window_or_child, tr_torrent* dupl
/* pop up the context menu if a user right-clicks.
if the row they right-click on isn't selected, select it. */
bool on_tree_view_button_pressed(
Gtk::TreeView* view,
GdkEventButton* event,
std::function<void(GdkEventButton*)> const& callback = {});
Gtk::TreeView& view,
double view_x,
double view_y,
bool context_menu_requested,
std::function<void(double, double)> const& callback = {});
/* if the click didn't specify a row, clear the selection */
bool on_tree_view_button_released(Gtk::TreeView* view, GdkEventButton* event);
bool on_tree_view_button_released(Gtk::TreeView& view, double view_x, double view_y);
using TrGdkModifierType = IF_GTKMM4(Gdk::ModifierType, guint);
void setup_tree_view_button_event_handling(
Gtk::TreeView& view,
std::function<bool(guint, TrGdkModifierType, double, double, bool)> const& press_callback,
std::function<bool(double, double)> const& release_callback);
/* move a file to the trashcan if GIO is available; otherwise, delete it */
bool gtr_file_trash_or_remove(std::string const& filename, tr_error** error);
@ -276,6 +296,16 @@ inline Glib::RefPtr<T> gtr_ptr_static_cast(Glib::RefPtr<U> const& ptr)
#endif
}
template<typename T, typename U>
inline Glib::RefPtr<T> gtr_ptr_dynamic_cast(Glib::RefPtr<U> const& ptr)
{
#if G_ENCODE_VERSION(GLIBMM_MAJOR_VERSION, GLIBMM_MINOR_VERSION) < G_ENCODE_VERSION(2, 68)
return Glib::RefPtr<T>::cast_dynamic(ptr);
#else
return std::dynamic_pointer_cast<T>(ptr);
#endif
}
template<>
struct std::hash<Glib::ustring>
{
@ -295,20 +325,51 @@ struct fmt::formatter<Glib::ustring> : formatter<std::string>
}
};
template<typename T, typename... ArgTs>
T* gtr_get_widget(Glib::RefPtr<Gtk::Builder> const& builder, Glib::ustring const& name, ArgTs&&... args)
template<typename T>
T* gtr_get_widget(Glib::RefPtr<Gtk::Builder> const& builder, Glib::ustring const& name)
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
return builder->get_widget<T>(name);
#else
T* widget = nullptr;
builder->get_widget(name, widget, std::forward<ArgTs>(args)...);
builder->get_widget(name, widget);
return widget;
#endif
}
template<typename T, typename... ArgTs>
T* gtr_get_widget_derived(Glib::RefPtr<Gtk::Builder> const& builder, Glib::ustring const& name, ArgTs&&... args)
{
#if GTKMM_CHECK_VERSION(4, 0, 0)
return Gtk::Builder::get_widget_derived<T>(builder, name, std::forward<ArgTs>(args)...);
#else
T* widget = nullptr;
builder->get_widget_derived(name, widget, std::forward<ArgTs>(args)...);
return widget;
#endif
}
template<typename F>
void gtr_window_on_close(Gtk::Window& widget, F&& callback)
{
auto bool_callback = [callback]() mutable -> bool
{
if constexpr (std::is_same_v<void, std::invoke_result_t<decltype(callback)>>)
{
callback();
return false;
}
else
{
return callback();
}
};
#if GTKMM_CHECK_VERSION(4, 0, 0)
widget.signal_close_request().connect(bool_callback, false);
#else
widget.signal_delete_event().connect(sigc::hide<0>(bool_callback), false);
#endif
}
namespace Glib

View File

@ -45,9 +45,17 @@ int main(int argc, char** argv)
textdomain(AppTranslationDomainName);
/* init glib/gtk */
Gio::init();
Glib::init();
Glib::set_application_name(_("Transmission"));
/* Workaround "..." */
Gio::File::create_for_path(".");
Glib::wrap_register(
g_type_from_name("GLocalFile"),
[](GObject* object) -> Glib::ObjectBase* { return new Gio::File((GFile*)object); });
g_type_ensure(Gio::File::get_type());
/* default settings */
std::string config_dir;
bool show_version = false;
@ -68,7 +76,9 @@ int main(int argc, char** argv)
Glib::OptionContext option_context(_("[torrent files or urls]"));
option_context.set_main_group(main_group);
#if !GTKMM_CHECK_VERSION(4, 0, 0)
Gtk::Main::add_gtk_option_group(option_context);
#endif
option_context.set_translation_domain(GETTEXT_PACKAGE);
try
@ -77,7 +87,10 @@ int main(int argc, char** argv)
}
catch (Glib::OptionError const& e)
{
g_print(_("%s\nRun '%s --help' to see a full list of available command line options.\n"), e.what().c_str(), argv[0]);
g_print(
_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
TR_GLIB_EXCEPTION_WHAT(e),
argv[0]);
return 1;
}

View File

@ -4,6 +4,30 @@
border-radius: 0;
}
.tr-message-log.frame {
border-left-width: 0;
border-right-width: 0;
border-bottom-width: 0;
border-radius: 0;
}
.tr-pad-small {
padding: 3px;
}
.tr-pad-normal {
padding: 6px;
}
.tr-pad-large {
padding: 12px;
}
.tr-button-box,
.tr-dialog-content {
margin: 6px;
}
.tr-small {
font-size: small;
}

View File

@ -8,18 +8,5 @@
<file alias="icons/scalable/apps/transmission.svg" compressed="true" preprocess="xml-stripblanks">icons/hicolor_apps_scalable_transmission.svg</file>
<file compressed="true">transmission-ui.css</file>
<file compressed="true" preprocess="xml-stripblanks">transmission-ui.xml</file>
<file compressed="true" preprocess="xml-stripblanks">AddTrackerDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">DetailsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">EditTrackersDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">FilterBar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MainWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeProgressDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MessageLogWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">OptionsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">PrefsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">RelocateDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">StatsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">TorrentUrlChooserDialog.ui</file>
</gresource>
</gresources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/transmissionbt/transmission">
<file compressed="true" preprocess="xml-stripblanks">AddTrackerDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">DetailsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">EditTrackersDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">FilterBar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MainWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeProgressDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MessageLogWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">OptionsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">PrefsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">RelocateDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">StatsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">TorrentUrlChooserDialog.ui</file>
</gresource>
</gresources>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="AddTrackerDialog">
<property name="modal">1</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="url_section_label">
<property name="label" translatable="1">Tracker</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="url_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="url_label">
<property name="label" translatable="1">_Announce URL:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">url_entry</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="width-request">400</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="open_button">
<property name="label" translatable="1">_Add</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-3">open_button</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -0,0 +1,880 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="DetailsDialog">
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkNotebook" id="dialog_pages">
<property name="vexpand">1</property>
<property name="focusable">1</property>
<child>
<object class="GtkNotebookPage">
<property name="child">
<object class="GtkBox" id="info_page_layout">
<property name="css-classes">tr-pad-large</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="activity_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Activity</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="activity_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="torrent_size_label">
<property name="label" translatable="1">Torrent size:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="have_label">
<property name="label" translatable="1">Have:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="uploaded_label">
<property name="label" translatable="1">Uploaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="downloaded_label">
<property name="label" translatable="1">Downloaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="state_label">
<property name="label" translatable="1">State:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="running_time_label">
<property name="label" translatable="1">Running time:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="remaining_time_label">
<property name="label" translatable="1">Remaining time:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">6</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="last_activity_label">
<property name="label" translatable="1">Last activity:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">7</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="error_label">
<property name="label" translatable="1">Error:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">8</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="torrent_size_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="have_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="uploaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="downloaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="state_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="running_time_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="remaining_time_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">6</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="last_activity_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">7</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="error_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="wrap">1</property>
<property name="max-width-chars">1</property>
<property name="selectable">1</property>
<property name="ellipsize">end</property>
<property name="lines">10</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">8</property>
</layout>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="height-request">6</property>
</object>
</child>
<child>
<object class="GtkLabel" id="details_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Details</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="details_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="location_label">
<property name="label" translatable="1">Location:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="hash_label">
<property name="label" translatable="1">Hash:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="privacy_label">
<property name="label" translatable="1">Privacy:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="origin_label">
<property name="label" translatable="1">Origin:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="added_label">
<property name="label" translatable="1">Added:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="comment_label">
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<property name="label" translatable="1">Comment:</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="location_value_label">
<property name="label">...</property>
<property name="selectable">1</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="hash_value_label">
<property name="label">...</property>
<property name="selectable">1</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="privacy_value_label">
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="origin_value_label">
<property name="label">...</property>
<property name="selectable">1</property>
<property name="ellipsize">end</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="added_value_label">
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="comment_value_view_scroll">
<property name="width-request">350</property>
<property name="height-request">36</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTextView" id="comment_value_view">
<property name="focusable">1</property>
<property name="editable">0</property>
<property name="wrap-mode">word</property>
</object>
</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</property>
<property name="tab">
<object class="GtkLabel" id="info_page_label">
<property name="label" translatable="1">Information</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkNotebookPage">
<property name="position">1</property>
<property name="child">
<object class="GtkBox" id="peers_page_layout">
<property name="css-classes">tr-pad-large</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkPaned" id="peers_page_panes">
<property name="vexpand">1</property>
<property name="resize-start-child">0</property>
<property name="focusable">1</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="webseeds_view_scroll">
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="webseeds_view">
<property name="focusable">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="webseeds_view_selection"/>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="peers_view_scroll">
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="peers_view">
<property name="focusable">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="peers_view_selection"/>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkCheckButton" id="more_peer_details_check">
<property name="label" translatable="1">Show _more details</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</property>
<property name="tab">
<object class="GtkLabel" id="peers_page_label">
<property name="label" translatable="1">Peers</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkNotebookPage">
<property name="position">2</property>
<property name="child">
<object class="GtkBox" id="trackers_page_layout">
<property name="css-classes">tr-pad-large</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkBox" id="trackers_layout">
<property name="vexpand">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkScrolledWindow" id="trackers_view_scroll">
<property name="hexpand">1</property>
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="trackers_view">
<property name="focusable">1</property>
<property name="headers-visible">0</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkBox" id="tracker_buttons_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="add_tracker_button">
<property name="label" translatable="1">_Add</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="edit_tracker_button">
<property name="label" translatable="1">_Edit</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="remove_tracker_button">
<property name="label" translatable="1">_Remove</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkCheckButton" id="more_tracker_details_check">
<property name="label" translatable="1">Show _more details</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkCheckButton" id="backup_trackers_check">
<property name="label" translatable="1">Show _backup trackers</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</property>
<property name="tab">
<object class="GtkLabel" id="trackers_page_label">
<property name="label" translatable="1">Trackers</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkNotebookPage">
<property name="position">3</property>
<property name="child">
<object class="GtkBox" id="files_page_layout">
<property name="css-classes">tr-pad-large</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="files_view_scroll">
<property name="vexpand">1</property>
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="files_view">
<property name="focusable">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkLabel" id="files_label">
<property name="vexpand">1</property>
<property name="label" translatable="1">File listing not available for combined torrent properties</property>
</object>
</child>
</object>
</property>
<property name="tab">
<object class="GtkLabel" id="files_page_label">
<property name="label" translatable="1">Files</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkNotebookPage">
<property name="position">4</property>
<property name="child">
<object class="GtkBox" id="options_page_layout">
<property name="css-classes">tr-pad-large</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="speed_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Speed</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="speed_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkCheckButton" id="honor_limits_check">
<property name="label" translatable="1">Honor global _limits</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="download_limit_check">
<property name="label" translatable="1">Limit _download speed ({speed_units}):</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="upload_limit_check">
<property name="label" translatable="1">Limit _upload speed ({speed_units}):</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="priority_label">
<property name="label" translatable="1">Torrent _priority:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">priority_combo</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkSpinButton" id="download_limit_spin">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkSpinButton" id="upload_limit_spin">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkComboBox" id="priority_combo">
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="height-request">6</property>
</object>
</child>
<child>
<object class="GtkLabel" id="seeding_limits_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Seeding Limits</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="seeding_limits_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="ratio_limit_label">
<property name="label" translatable="1">_Ratio:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">ratio_limit_combo</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="idle_limit_label">
<property name="label" translatable="1">_Idle:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">idle_limit_combo</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkBox" id="ratio_limit_value_layout">
<property name="hexpand">1</property>
<property name="spacing">6</property>
<child>
<object class="GtkComboBox" id="ratio_limit_combo">
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkSpinButton" id="ratio_limit_spin">
<property name="focusable">1</property>
</object>
</child>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkBox" id="idle_limit_value_layout">
<property name="hexpand">1</property>
<property name="spacing">6</property>
<child>
<object class="GtkComboBox" id="idle_limit_combo">
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkSpinButton" id="idle_limit_spin">
<property name="focusable">1</property>
</object>
</child>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="height-request">6</property>
</object>
</child>
<child>
<object class="GtkLabel" id="peer_connections_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Peer Connections</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="peer_connections_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="max_peers_label">
<property name="label" translatable="1">_Maximum peers:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">max_peers_spin</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkSpinButton" id="max_peers_spin">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</property>
<property name="tab">
<object class="GtkLabel" id="options_page_label">
<property name="label" translatable="1">Options</property>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label" translatable="1">_Close</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-7">close_button</action-widget>
</action-widgets>
</object>
<object class="GtkSizeGroup" id="info_page_labels_width_group">
<widgets>
<widget name="torrent_size_label"/>
<widget name="have_label"/>
<widget name="uploaded_label"/>
<widget name="downloaded_label"/>
<widget name="state_label"/>
<widget name="running_time_label"/>
<widget name="remaining_time_label"/>
<widget name="last_activity_label"/>
<widget name="error_label"/>
<widget name="location_label"/>
<widget name="hash_label"/>
<widget name="privacy_label"/>
<widget name="origin_label"/>
<widget name="added_label"/>
<widget name="comment_label"/>
</widgets>
</object>
<object class="GtkSizeGroup" id="options_page_labels_width_group">
<widgets>
<widget name="download_limit_check"/>
<widget name="upload_limit_check"/>
<widget name="ratio_limit_label"/>
<widget name="idle_limit_label"/>
<widget name="max_peers_label"/>
</widgets>
</object>
</interface>

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="EditTrackersDialog">
<property name="modal">1</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="urls_section_title">
<property name="label" translatable="1">Tracker Announce URLs</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="urls_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="urls_section_top_comment_label">
<property name="label" translatable="1">To add a backup URL, add it on the next line after a primary URL.
To add a new primary URL, add it after a blank line.</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="urls_view_scroll">
<property name="width-request">500</property>
<property name="height-request">166</property>
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTextView" id="urls_view">
<property name="focusable">1</property>
<property name="accepts-tab">0</property>
</object>
</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="urls_section_bottom_comment_label">
<property name="label" translatable="1">Also see Default Public Trackers in Edit &gt; Preferences &gt; Network</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="save_button">
<property name="label" translatable="1">_Save</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-3">save_button</action-widget>
</action-widgets>
</object>
</interface>

34
gtk/ui/gtk4/FilterBar.ui Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<template class="gtkmm__CustomObject_9FilterBar" parent="GtkBox">
<property name="spacing">3</property>
<child>
<object class="GtkLabel" id="show_label">
<property name="label" translatable="1">_Show:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">activity_combo</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="activity_combo">
<property name="hexpand">1</property>
<property name="margin-end">6</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="tracker_combo">
<property name="hexpand">1</property>
<property name="width-request">170</property>
<property name="margin-end">6</property>
</object>
</child>
<child>
<object class="GtkEntry" id="text_entry">
<property name="hexpand">1</property>
<property name="focusable">1</property>
<property name="secondary-icon-name">edit-clear</property>
</object>
</child>
</template>
</interface>

219
gtk/ui/gtk4/MainWindow.ui Normal file
View File

@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkApplicationWindow" id="MainWindow">
<child>
<object class="GtkBox" id="window_layout">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="toolbar">
<property name="css-classes">toolbar
horizontal</property>
<property name="can-focus">1</property>
<child>
<object class="GtkButton" id="open_file_button">
<property name="focusable">1</property>
<property name="tooltip-text" translatable="1">Open a torrent</property>
<property name="action-name">win.open-torrent</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="icon-name">document-open</property>
<property name="icon_size">normal</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="1">_Open</property>
<property name="single-line-mode">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="start_torrent_button">
<property name="focusable">1</property>
<property name="tooltip-text" translatable="1">Start torrent</property>
<property name="action-name">win.torrent-start</property>
<property name="label" translatable="1">_Start</property>
<property name="use-underline">1</property>
<property name="icon-name">media-playback-start</property>
</object>
</child>
<child>
<object class="GtkButton" id="pause_torrent_button">
<property name="focusable">1</property>
<property name="tooltip-text" translatable="1">Pause torrent</property>
<property name="action-name">win.torrent-stop</property>
<property name="label" translatable="1">_Pause</property>
<property name="use-underline">1</property>
<property name="icon-name">media-playback-pause</property>
</object>
</child>
<child>
<object class="GtkButton" id="remove_torrent_button">
<property name="focusable">1</property>
<property name="action-name">win.remove-torrent</property>
<property name="label" translatable="1">Remove torrent</property>
<property name="use-underline">1</property>
<property name="icon-name">list-remove</property>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="orientation">vertical</property>
<property name="can-focus">0</property>
</object>
</child>
<child>
<object class="GtkButton" id="torrent_properties_button">
<property name="focusable">1</property>
<property name="tooltip-text" translatable="1">Torrent properties</property>
<property name="action-name">win.show-torrent-properties</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="icon-name">document-properties</property>
<property name="icon_size">normal</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="1">_Properties</property>
<property name="single-line-mode">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_9FilterBar" id="filterbar">
<property name="can-focus">1</property>
<style>
<class name="tr-pad-small"/>
</style>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="torrents_view_scroll">
<property name="vexpand">1</property>
<property name="focusable">1</property>
<property name="hscrollbar-policy">never</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="torrents_view">
<property name="focusable">1</property>
<property name="headers-visible">0</property>
<property name="fixed-height-mode">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="torrents_view_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="torrent_column">
<property name="resizable">1</property>
<property name="sizing">fixed</property>
<property name="title" translatable="1">Torrent</property>
</object>
</child>
</object>
</property>
<style>
<class name="tr-workarea"/>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="statusbar">
<property name="spacing">3</property>
<child>
<object class="GtkMenuButton" id="gear_button">
<property name="focusable">1</property>
<property name="has-frame">0</property>
<property name="receives-default">1</property>
<property name="tooltip-text" translatable="1">Options</property>
<child>
<object class="GtkImage" id="gear_button_image">
<property name="icon-name">options-symbolic</property>
<property name="icon_size">normal</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkToggleButton" id="alt_speed_button">
<property name="focusable">1</property>
<property name="has-frame">0</property>
<property name="receives-default">1</property>
<child>
<object class="GtkImage" id="alt_speed_button_image">
<property name="icon-name">turtle-symbolic</property>
<property name="icon_size">normal</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="hexpand">1</property>
</object>
</child>
<child>
<object class="GtkLabel" id="download_speed_label">
<property name="margin-start">3</property>
<property name="label">...</property>
<property name="single-line-mode">1</property>
</object>
</child>
<child>
<object class="GtkLabel" id="upload_speed_label">
<property name="margin-start">3</property>
<property name="label">...</property>
<property name="single-line-mode">1</property>
</object>
</child>
<child>
<object class="GtkLabel" id="statistics_label">
<property name="margin-start">9</property>
<property name="margin-end">3</property>
<property name="label">...</property>
<property name="single-line-mode">1</property>
</object>
</child>
<child>
<object class="GtkMenuButton" id="ratio_button">
<property name="focusable">1</property>
<property name="has-frame">0</property>
<property name="receives-default">1</property>
<property name="tooltip-text" translatable="1">Statistics</property>
<child>
<object class="GtkImage" id="ratio_button_image">
<property name="icon-name">ratio-symbolic</property>
<property name="icon_size">normal</property>
</object>
</child>
</object>
</child>
<style>
<class name="tr-pad-small"/>
</style>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,850 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface domain="transmission-gtk">
<requires lib="gtk+" version="3.24"/>
<object class="GtkApplicationWindow" id="MainWindow">
<property name="can-focus">False</property>
<property name="role">tr-main</property>
<child>
<object class="GtkBox" id="window_layout">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="main_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="file_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="file_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="open_file_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">open-torrent-menu</property>
<property name="label" translatable="yes">_Open</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="open_url_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">open-torrent-from-url</property>
<property name="label" translatable="yes">Open _URL...</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="new_torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">new-torrent</property>
<property name="label" translatable="yes">_New...</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="start_all_torrents_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">start-all-torrents</property>
<property name="label" translatable="yes">_Start All</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="pause_all_torrents_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">pause-all-torrents</property>
<property name="label" translatable="yes">_Pause All</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="quit_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">quit</property>
<property name="label" translatable="yes">_Quit</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="edit_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="edit_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="select_all_torrents_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">select-all</property>
<property name="label" translatable="yes">Select _All</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="deselect_all_torrents_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">deselect-all</property>
<property name="label" translatable="yes">Dese_lect All</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="preferences_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">edit-preferences</property>
<property name="label" translatable="yes">_Preferences</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Torrent</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="torrent_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="torrent_properties_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-torrent-properties</property>
<property name="label" translatable="yes">_Properties</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="open_torrent_folder_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">open-torrent-folder</property>
<property name="label" translatable="yes">Open Fold_er</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="start_torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">torrent-start</property>
<property name="label" translatable="yes">_Start</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="start_torrent_now_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">torrent-start-now</property>
<property name="label" translatable="yes">Start _Now</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="ask_tracker_for_peers_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">torrent-reannounce</property>
<property name="label" translatable="yes">Ask Tracker for _Mode Peers</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="torrent_queue_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Queue</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="torrent_queue_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkMenuItem" id="move_torrent_to_queue_top_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">queue-move-top</property>
<property name="label" translatable="yes">Move to _Top</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="move_torrent_up_in_queue_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">queue-move-up</property>
<property name="label" translatable="yes">Move _Up</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="move_torrent_down_in_queue_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">queue-move-down</property>
<property name="label" translatable="yes">Move _Down</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="move_torrent_to_queue_bottom_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">queue-move-bottom</property>
<property name="label" translatable="yes">Move to _Bottom</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="pause_torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">torrent-stop</property>
<property name="label" translatable="yes">_Pause</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="set_torrent_data_location_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">relocate-torrent</property>
<property name="label" translatable="yes">Set _Location...</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="verify_local_torrent_data_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">torrent-verify</property>
<property name="label" translatable="yes">_Verify Local Data</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="copy_magnet_link_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">copy-magnet-link-to-clipboard</property>
<property name="label" translatable="yes">Copy _Magnet Link to Clipboard</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="remove_torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">remove-torrent</property>
<property name="label" translatable="yes">Remove torrent</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="delete_torrent_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">delete-torrent</property>
<property name="label" translatable="yes">_Delete Files and Remove</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="view_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="view_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkCheckMenuItem" id="compact_view_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">compact-view</property>
<property name="label" translatable="yes">_Compact View</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="toggle_toolbar_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-toolbar</property>
<property name="label" translatable="yes">_Toolbar</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="toggle_filterbar_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-filterbar</property>
<property name="label" translatable="yes">_Filterbar</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="toggle_statusbar_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-statusbar</property>
<property name="label" translatable="yes">_Statusbar</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_activity_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-activity</property>
<property name="label" translatable="yes">Sort by _Activity</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_age_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-age</property>
<property name="label" translatable="yes">Sort by A_ge</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_name_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-name</property>
<property name="label" translatable="yes">Sort by _Name</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_progress_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-progress</property>
<property name="label" translatable="yes">Sort by _Progress</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_queue_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-queue</property>
<property name="label" translatable="yes">Sort by _Queue</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_ratio_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-ratio</property>
<property name="label" translatable="yes">Sort by Rati_o</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_size_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-size</property>
<property name="label" translatable="yes">Sort by Si_ze</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_state_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-state</property>
<property name="label" translatable="yes">Sort by Stat_e</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkRadioMenuItem" id="sort_by_time_left_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-by-time-left</property>
<property name="label" translatable="yes">Sort by Time _Left</property>
<property name="use-underline">True</property>
<property name="draw-as-radio">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="reverse_sort_order_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">sort-reversed</property>
<property name="label" translatable="yes">Re_verse Sort Order</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="help_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">_Help</property>
<property name="use-underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="help_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkCheckMenuItem" id="toggle_message_log_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">toggle-message-log</property>
<property name="label" translatable="yes">Message _Log</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="statistics_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-stats</property>
<property name="label" translatable="yes">_Statistics</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="donate_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">donate</property>
<property name="label" translatable="yes">_Donate</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="help_contents_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">help</property>
<property name="label" translatable="yes">_Contents</property>
<property name="use-underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="about_menu_item">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">show-about-dialog</property>
<property name="label" translatable="yes">_About</property>
<property name="use-underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToolbar" id="toolbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkToolButton" id="open_file_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Open a torrent</property>
<property name="is-important">True</property>
<property name="action-name">open-torrent-toolbar</property>
<property name="label" translatable="yes">_Open</property>
<property name="use-underline">True</property>
<property name="icon-name">document-open</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="start_torrent_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Start torrent</property>
<property name="action-name">torrent-start</property>
<property name="label" translatable="yes">_Start</property>
<property name="use-underline">True</property>
<property name="icon-name">media-playback-start</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="pause_torrent_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Pause torrent</property>
<property name="action-name">torrent-stop</property>
<property name="label" translatable="yes">_Pause</property>
<property name="use-underline">True</property>
<property name="icon-name">media-playback-pause</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="remove_torrent_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="action-name">remove-torrent</property>
<property name="label" translatable="yes">Remove torrent</property>
<property name="use-underline">True</property>
<property name="icon-name">list-remove</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkSeparatorToolItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToolButton" id="torrent_properties_button">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Torrent properties</property>
<property name="is-important">True</property>
<property name="action-name">show-torrent-properties</property>
<property name="label" translatable="yes">_Properties</property>
<property name="use-underline">True</property>
<property name="icon-name">document-properties</property>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="gtkmm__CustomObject_9FilterBar" id="filterbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="torrents_view_scroll">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hscrollbar-policy">never</property>
<property name="shadow-type">out</property>
<child>
<object class="GtkTreeView" id="torrents_view">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="headers-visible">False</property>
<property name="fixed-height-mode">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="torrents_view_selection">
<property name="mode">multiple</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="torrent_column">
<property name="resizable">True</property>
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Torrent</property>
</object>
</child>
</object>
</child>
<style>
<class name="tr-workarea"/>
</style>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="statusbar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="border-width">3</property>
<property name="spacing">3</property>
<child>
<object class="GtkButton" id="gear_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Options</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="gear_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">options-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="alt_speed_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="alt_speed_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">turtle-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFixed">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="download_speed_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">3</property>
<property name="label">...</property>
<property name="single-line-mode">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="upload_speed_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">3</property>
<property name="label">...</property>
<property name="single-line-mode">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ratio_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Statistics</property>
<property name="relief">none</property>
<child>
<object class="GtkImage" id="ratio_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">ratio-symbolic</property>
<property name="icon_size">1</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="statistics_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">9</property>
<property name="margin-end">3</property>
<property name="label">...</property>
<property name="single-line-mode">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

309
gtk/ui/gtk4/MakeDialog.ui Normal file
View File

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="MakeDialog">
<property name="title" translatable="1">New Torrent</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="files_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Files</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="files_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="destination_label">
<property name="label" translatable="1">Sa_ve to:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">destination_button</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="destination_button">
<property name="hexpand">1</property>
<property name="action">select-folder</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="source_folder_radio">
<property name="label" translatable="1">Source F_older:</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="source_folder_button">
<property name="hexpand">1</property>
<property name="action">select-folder</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="source_file_radio">
<property name="label" translatable="1">Source _File:</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<property name="active">1</property>
<property name="group">source_folder_radio</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="source_file_button">
<property name="hexpand">1</property>
<property name="title" translatable="1"></property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="source_size_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">No source selected</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"></attribute>
</attributes>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="piece_size_label">
<property name="label" translatable="1">Piece size:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">piece_size_scale</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkScale" id="piece_size_scale">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="digits">0</property>
<property name="value-pos">left</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
<child>
<object class="GtkFixed" id="properties_section_spacer">
<property name="height-request">6</property>
</object>
</child>
<child>
<object class="GtkLabel" id="properties_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Properties</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="properties_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="trackers_label">
<property name="valign">start</property>
<property name="label" translatable="1">_Trackers:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">trackers_view</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkBox" id="trackers_layout">
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="orientation">vertical</property>
<property name="spacing">3</property>
<child>
<object class="GtkScrolledWindow" id="trackers_view_scroll">
<property name="height-request">80</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTextView" id="trackers_view">
<property name="focusable">1</property>
<property name="accepts-tab">0</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkLabel" id="trackers_description_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">To add a backup URL, add it on the next line after a primary URL.
To add a new primary URL, add it after a blank line.</property>
<property name="xalign">0</property>
</object>
</child>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="comment_check">
<property name="label" translatable="1">Co_mment:</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="comment_entry">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="source_check">
<property name="label" translatable="1">_Source:</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="source_entry">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="private_check">
<property name="label" translatable="1">_Private torrent</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label" translatable="1">_Close</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="new_button">
<property name="label" translatable="1">_New</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-7">close_button</action-widget>
<action-widget response="-3">new_button</action-widget>
</action-widgets>
</object>
<object class="GtkSizeGroup" id="labels_width_group">
<widgets>
<widget name="destination_label"/>
<widget name="source_folder_radio"/>
<widget name="source_file_radio"/>
<widget name="piece_size_label"/>
<widget name="trackers_label"/>
<widget name="comment_check"/>
<widget name="source_check"/>
</widgets>
</object>
</interface>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="MakeProgressDialog">
<property name="title" translatable="1">New Torrent</property>
<property name="modal">1</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="progress_label">
<property name="label" translatable="1">Creating torrent…</property>
<property name="xalign">0</property>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar"/>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="label" translatable="1">_Close</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="add_button">
<property name="label" translatable="1">_Add</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-7">close_button</action-widget>
<action-widget response="-3">add_button</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkWindow" id="MessageLogWindow">
<property name="title" translatable="1">Message Log</property>
<property name="default-width">560</property>
<property name="default-height">350</property>
<property name="child">
<object class="GtkBox" id="window_layout">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="toolbar">
<property name="valign">center</property>
<property name="css-classes">toolbar
horizontal</property>
<child>
<object class="GtkButton" id="save_as_button">
<property name="action-name">win.save-message-log</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="icon-name">document-save-as</property>
<property name="icon_size">normal</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="1">Save _As</property>
<property name="single-line-mode">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="clear_button">
<property name="action-name">win.clear-message-log</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="icon-name">edit-clear</property>
<property name="icon_size">normal</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="1">Clear</property>
<property name="single-line-mode">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="orientation">vertical</property>
</object>
</child>
<child>
<object class="GtkToggleButton" id="pause_button">
<property name="action-name">win.pause-message-log</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="icon-name">media-playback-pause</property>
<property name="icon_size">normal</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="1">P_ause</property>
<property name="single-line-mode">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparator">
<property name="orientation">vertical</property>
</object>
</child>
<child>
<object class="GtkLabel" id="level_label">
<property name="label" translatable="1">Level</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">level_combo</property>
<property name="css-classes">tr-pad-normal</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="level_combo">
<child>
<object class="GtkCellRendererText" id="level_combo_renderer"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="messages_view_scroll">
<property name="vexpand">1</property>
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="messages_view">
<property name="focusable">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="messages_view_selection"/>
</child>
</object>
</property>
<style>
<class name="tr-message-log"/>
</style>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="OptionsDialog">
<property name="title" translatable="1">Torrent Options</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkGrid" id="dialog_content_layout">
<property name="vexpand">1</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="source_label">
<property name="label" translatable="1">_Torrent file:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">source_button</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="source_button">
<property name="hexpand">1</property>
<property name="title" translatable="1">Select Source File</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="destination_label">
<property name="label" translatable="1">_Destination folder:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">destination_button</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="destination_button">
<property name="hexpand">1</property>
<property name="action">select-folder</property>
<property name="title" translatable="1">Select Destination Folder</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="files_view_scroll">
<property name="width-request">466</property>
<property name="height-request">300</property>
<property name="focusable">1</property>
<property name="has-frame">1</property>
<property name="child">
<object class="GtkTreeView" id="files_view">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<child internal-child="selection">
<object class="GtkTreeSelection" id="files_view_selection"/>
</child>
</object>
</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="priority_label">
<property name="label" translatable="1">Torrent _priority:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">priority_combo</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkComboBox" id="priority_combo">
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">5</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="start_check">
<property name="label" translatable="1">_Start when added</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">6</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="trash_check">
<property name="label" translatable="1">Mo_ve torrent file to the trash</property>
<property name="focusable">1</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<layout>
<property name="column">0</property>
<property name="row">7</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="free_space_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"></attribute>
</attributes>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="width-request">0</property>
<property name="height-request">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkFixed">
<property name="height-request">6</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="open_button">
<property name="label" translatable="1">_Open</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-3">open_button</action-widget>
</action-widgets>
</object>
</interface>

1516
gtk/ui/gtk4/PrefsDialog.ui Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="RelocateDialog">
<property name="title" translatable="1">Set Torrent Location</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="set_location_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Location</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="set_location_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="new_location_label">
<property name="halign">start</property>
<property name="label" translatable="1">Torrent _location:</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">new_location_button</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="gtkmm__CustomObject_10PathButton" id="new_location_button">
<property name="hexpand">1</property>
<property name="action">select-folder</property>
<property name="title" translatable="1"></property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="move_data_radio">
<property name="label" translatable="1">_Move from the current folder</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<property name="active">1</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkCheckButton" id="find_data_radio">
<property name="label" translatable="1">Local data is _already there</property>
<property name="valign">center</property>
<property name="use-underline">1</property>
<property name="group">move_data_radio</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="apply_button">
<property name="label" translatable="1">_Apply</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-10">apply_button</action-widget>
</action-widgets>
</object>
</interface>

278
gtk/ui/gtk4/StatsDialog.ui Normal file
View File

@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="StatsDialog">
<property name="title" translatable="1">Statistics</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="current_session_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Current Session</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="current_session_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="current_uploaded_label">
<property name="label" translatable="1">Uploaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_uploaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_downloaded_label">
<property name="label" translatable="1">Downloaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_downloaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_ratio_label">
<property name="label" translatable="1">Ratio:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_ratio_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_duration_label">
<property name="label" translatable="1">Duration:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="current_duration_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFixed" id="total_section_spacer">
<property name="height-request">6</property>
</object>
</child>
<child>
<object class="GtkLabel" id="total_section_label">
<property name="hexpand">1</property>
<property name="label" translatable="1">Total</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="total_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="start_count_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
<property name="column-span">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_uploaded_label">
<property name="label" translatable="1">Uploaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_uploaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">1</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_downloaded_label">
<property name="label" translatable="1">Downloaded:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_downloaded_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">2</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_ratio_label">
<property name="label" translatable="1">Ratio:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_ratio_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">3</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_duration_label">
<property name="label" translatable="1">Duration:</property>
<property name="xalign">0</property>
<layout>
<property name="column">0</property>
<property name="row">4</property>
</layout>
</object>
</child>
<child>
<object class="GtkLabel" id="total_duration_value_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<layout>
<property name="column">1</property>
<property name="row">4</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="reset_button">
<property name="label" translatable="1">_Reset</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="label" translatable="1">_Close</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="1">reset_button</action-widget>
<action-widget response="-7">close_button</action-widget>
</action-widgets>
</object>
<object class="GtkSizeGroup" id="labels_width_group">
<widgets>
<widget name="current_uploaded_label"/>
<widget name="current_downloaded_label"/>
<widget name="current_ratio_label"/>
<widget name="current_duration_label"/>
<widget name="total_uploaded_label"/>
<widget name="total_downloaded_label"/>
<widget name="total_ratio_label"/>
<widget name="total_duration_label"/>
</widgets>
</object>
</interface>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="transmission-gtk">
<requires lib="gtk" version="4.0"/>
<object class="GtkDialog" id="TorrentUrlChooserDialog">
<property name="title" translatable="1">Open URL</property>
<child internal-child="content_area">
<object class="GtkBox" id="dialog_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="css-classes">tr-dialog-content</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox" id="dialog_content_layout">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="url_section_label">
<property name="label" translatable="1">Open torrent from URL</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"></attribute>
</attributes>
</object>
</child>
<child>
<object class="GtkGrid" id="url_section_layout">
<property name="margin-start">18</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<child>
<object class="GtkLabel" id="url_label">
<property name="label" translatable="1">_URL</property>
<property name="use-underline">1</property>
<property name="mnemonic-widget">url_entry</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="width-request">400</property>
<property name="focusable">1</property>
<property name="hexpand">1</property>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkBox" id="dialog_buttons">
<property name="css-classes">tr-button-box</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label" translatable="1">_Cancel</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
<child>
<object class="GtkButton" id="open_button">
<property name="label" translatable="1">_Open</property>
<property name="focusable">1</property>
<property name="receives-default">1</property>
<property name="use-underline">1</property>
</object>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">cancel_button</action-widget>
<action-widget response="-3">open_button</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/transmissionbt/transmission">
<file compressed="true" preprocess="xml-stripblanks">AddTrackerDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">DetailsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">EditTrackersDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">FilterBar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MainWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MakeProgressDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">MessageLogWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">OptionsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">PrefsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">RelocateDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">StatsDialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">TorrentUrlChooserDialog.ui</file>
</gresource>
</gresources>