Files
rtorrent/src/ui/download_list.cc
T
2026-05-14 19:24:38 +09:00

369 lines
12 KiB
C++

#include "config.h"
#include "ui/download_list.h"
#include <cassert>
#include <sstream>
#include <torrent/exceptions.h>
#include <torrent/torrent.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/log.h>
#include <torrent/utils/string_manip.h>
#include "control.h"
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "core/view.h"
#include "core/view_manager.h"
#include "display/window_log.h"
#include "display/window_title.h"
#include "input/bindings.h"
#include "input/manager.h"
#include "input/path_input.h"
#include "rpc/parse_commands.h"
#include "ui/download.h"
#include "ui/element_download_list.h"
#include "ui/element_log_complete.h"
#include "ui/element_string_list.h"
#include "ui/root.h"
namespace ui {
DownloadList::DownloadList() {
m_uiArray[DISPLAY_DOWNLOAD] = NULL;
m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList();
m_uiArray[DISPLAY_LOG] = new ElementLogComplete(control->core()->log_complete());
m_uiArray[DISPLAY_STRING_LIST] = new ElementStringList();
m_windowLog = new WLog(control->core()->log_important());
setup_keys();
}
DownloadList::~DownloadList() {
assert(!is_active() && "ui::DownloadList::~DownloadList() called on an active object");
std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; });
delete m_windowLog;
}
void
DownloadList::activate(display::Frame* frame, [[maybe_unused]] bool focus) {
if (is_active())
throw torrent::internal_error("ui::DownloadList::activate() called on an already activated object");
m_frame = frame;
control->input()->push_back(&m_bindings);
activate_display(DISPLAY_DOWNLOAD_LIST);
}
void
DownloadList::disable() {
if (!is_active())
throw std::logic_error("ui::DownloadList::disable() called on an already disabled object");
receive_exit_input(INPUT_NONE);
activate_display(DISPLAY_MAX_SIZE);
m_frame = NULL;
control->input()->erase(&m_bindings);
}
core::View*
DownloadList::current_view() {
return dynamic_cast<ElementDownloadList*>(m_uiArray[DISPLAY_DOWNLOAD_LIST])->view();
}
void
DownloadList::set_current_view(const std::string& name) {
return dynamic_cast<ElementDownloadList*>(m_uiArray[DISPLAY_DOWNLOAD_LIST])->receive_change_view(name);
}
void
DownloadList::unfocus_download(core::Download* d) {
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
activate_display(DISPLAY_DOWNLOAD_LIST);
if (*current_view()->focus() == d && current_view()->focus() < current_view()->end_visible())
current_view()->next_focus();
}
void
DownloadList::activate_display(Display displayType) {
if (!is_active())
throw torrent::internal_error("ui::DownloadList::activate_display(...) !is_active().");
if (displayType > DISPLAY_MAX_SIZE)
throw torrent::internal_error("ui::DownloadList::activate_display(...) out of bounds");
if (displayType == m_state)
return;
// Cleanup previous state.
switch (m_state) {
case DISPLAY_DOWNLOAD:
m_uiArray[m_state]->disable();
delete m_uiArray[m_state];
m_uiArray[m_state] = NULL;
break;
case DISPLAY_DOWNLOAD_LIST:
m_uiArray[DISPLAY_DOWNLOAD_LIST]->disable();
m_windowLog->set_active(false);
m_frame->frame(1)->clear();
m_frame->clear();
break;
case DISPLAY_LOG:
case DISPLAY_STRING_LIST:
m_uiArray[m_state]->disable();
break;
default:
break;
}
m_state = displayType;
// Initialize new state.
switch (displayType) {
case DISPLAY_DOWNLOAD:
// If no download has the focus, just return to the download list.
if (current_view()->focus() == current_view()->end_visible()) {
m_state = DISPLAY_MAX_SIZE;
activate_display(DISPLAY_DOWNLOAD_LIST);
return;
} else {
Download* download = new Download(*current_view()->focus());
download->activate(m_frame);
download->slot_exit(std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD_LIST));
m_uiArray[DISPLAY_DOWNLOAD] = download;
break;
}
case DISPLAY_DOWNLOAD_LIST:
m_frame->initialize_row(2);
m_uiArray[DISPLAY_DOWNLOAD_LIST]->activate(m_frame->frame(0));
m_frame->frame(1)->initialize_window(m_windowLog);
m_windowLog->set_active(true);
m_windowLog->receive_update();
break;
case DISPLAY_LOG:
case DISPLAY_STRING_LIST:
m_uiArray[displayType]->activate(m_frame);
break;
default:
break;
}
// Set title.
switch (displayType) {
case DISPLAY_DOWNLOAD_LIST:
control->ui()->window_title()->set_title("rTorrent " PACKAGE_VERSION "/" +
std::string(torrent::runtime::version()) + " - " +
rpc::call_command_string("session.name"));
break;
case DISPLAY_LOG:
control->ui()->window_title()->set_title("Log");
break;
default: break;
}
control->display()->adjust_layout();
}
void
DownloadList::receive_view_input(Input type) {
if (control->ui()->current_input() != NULL)
return;
input::PathInput* input = new input::PathInput;
const char* title;
switch (type) {
case INPUT_LOAD_DEFAULT:
title = "load.start";
break;
case INPUT_LOAD_MODIFIED:
title = "load.normal";
break;
case INPUT_CHANGE_DIRECTORY:
title = "change_directory";
input->str() = rpc::call_command_string("directory.default");
input->set_pos(input->str().length());
break;
case INPUT_COMMAND:
title = "command";
break;
case INPUT_FILTER:
{
// Do not allow to subfilter the defined excluded views
const std::string excluded_views = rpc::call_command_string("view.filter.temp.excluded");
std::stringstream ss(excluded_views);
std::string view_name_var;
while(ss.good()) {
std::getline(ss, view_name_var, ',');
if (current_view()->name() == torrent::utils::trim_spaces_str(view_name_var)) {
control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered.");
return;
}
}
title = "filter";
}
break;
default:
throw torrent::internal_error("DownloadList::receive_view_input(...) Invalid input type.");
}
ElementStringList* esl = dynamic_cast<ElementStringList*>(m_uiArray[DISPLAY_STRING_LIST]);
input->signal_show_next().push_back(std::bind(&DownloadList::activate_display, this, DISPLAY_STRING_LIST));
input->signal_show_next().push_back(std::bind(&ElementStringList::next_screen, esl));
input->signal_show_range().push_back(std::bind(&DownloadList::activate_display, this, DISPLAY_STRING_LIST));
input->signal_show_range().push_back(std::bind(&ElementStringList::set_range_dirent<utils::Directory::iterator>,
esl,
std::placeholders::_1,
std::placeholders::_2));
// reset ESC delay for input prompt
set_escdelay(0);
input->bindings()['\n'] = std::bind(&DownloadList::receive_exit_input, this, type);
input->bindings()[KEY_ENTER] = std::bind(&DownloadList::receive_exit_input, this, type);
input->bindings()['\x07'] = std::bind(&DownloadList::receive_exit_input, this, INPUT_NONE); // ^G
input->bindings()['\x1B'] = std::bind(&DownloadList::receive_exit_input, this, INPUT_NONE); // ESC , ^[
control->ui()->enable_input(title, input, type);
}
void
DownloadList::receive_exit_input(Input type) {
// set back ESC delay to default
set_escdelay(1000);
input::TextInput* input = control->ui()->current_input();
// We should check that this object is the one holding the input.
if (input == NULL)
return;
if (type != INPUT_NONE && type != INPUT_EOI)
control->ui()->add_to_input_history(type, input->str());
control->ui()->disable_input();
try {
switch (type) {
case INPUT_NONE:
break;
case INPUT_LOAD_DEFAULT:
case INPUT_LOAD_MODIFIED:
if (input->str().empty())
break;
control->core()->try_create_download_expand(input->str(), type == INPUT_LOAD_DEFAULT ? core::Manager::create_start : 0);
break;
case INPUT_CHANGE_DIRECTORY:
if (current_view()->focus() == current_view()->end_visible())
throw torrent::input_error("No download in focus to change root directory.");
if ((*current_view()->focus())->is_open())
throw torrent::input_error("Cannot change root directory on an open download.");
rpc::call_command("d.directory.set", torrent::utils::trim_spaces_str(input->str()), rpc::make_target(*current_view()->focus()));
control->core()->push_log_std("New root directory \"" + rpc::call_command_string("d.directory", rpc::make_target(*current_view()->focus())) + "\" for torrent.");
break;
case INPUT_COMMAND:
rpc::parse_command_single(current_view()->focus() != current_view()->end_visible() ?
rpc::make_target(*current_view()->focus()) : rpc::make_target(),
input->str());
break;
case INPUT_FILTER:
if (input->str().empty()) {
if (rpc::call_command_value("view.filter.temp.log"))
control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view.");
current_view()->set_filter_temp(torrent::Object());
current_view()->filter();
current_view()->sort();
} else {
std::string pattern = input->str();
if (pattern.back() != '$')
pattern = pattern + ".*";
if (pattern.front() != '^')
pattern = ".*" + pattern;
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
std::string temp_filter = "match={d.name=," + pattern + "}";
if (rpc::call_command_value("view.filter.temp.log"))
control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern);
current_view()->set_filter_temp(temp_filter);
current_view()->filter();
}
break;
default:
throw torrent::internal_error("DownloadList::receive_exit_input(...) Invalid input type.");
}
} catch (torrent::input_error& e) {
lt_log_print(torrent::LOG_WARN, "Input failed: %s", e.what());
}
activate_display(DISPLAY_DOWNLOAD_LIST);
delete input;
}
void
DownloadList::setup_keys() {
m_bindings['\x7f'] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_DEFAULT);
m_bindings[KEY_BACKSPACE] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_DEFAULT);
m_bindings['\n'] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_MODIFIED);
m_bindings[KEY_ENTER] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_MODIFIED);
m_bindings['\x0F'] = std::bind(&DownloadList::receive_view_input, this, INPUT_CHANGE_DIRECTORY);
m_bindings['X' - '@'] = std::bind(&DownloadList::receive_view_input, this, INPUT_COMMAND);
m_bindings['F'] = std::bind(&DownloadList::receive_view_input, this, INPUT_FILTER);
m_uiArray[DISPLAY_LOG]->bindings()[KEY_LEFT] =
m_uiArray[DISPLAY_LOG]->bindings()[control->ui()->navigation_key(RT_KEY_LEFT)] =
m_uiArray[DISPLAY_LOG]->bindings()[' '] = std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD_LIST);
m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[KEY_RIGHT] =
m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[control->ui()->navigation_key(RT_KEY_RIGHT)] = std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD);
m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[control->ui()->navigation_key(RT_KEY_DISPLAY_LOG)] = std::bind(&DownloadList::activate_display, this, DISPLAY_LOG);
}
}