573 lines
18 KiB
C++
573 lines
18 KiB
C++
#include <fstream>
|
|
#include <gtkmm/filechooserdialog.h>
|
|
#include <gtkmm/grid.h>
|
|
#include <gtkmm/liststore.h>
|
|
#include <gtkmm/messagedialog.h>
|
|
#include <gtkmm/scrolledwindow.h>
|
|
#include <gtkmm/textview.h>
|
|
#include <iostream>
|
|
#include <thread>
|
|
|
|
#include "databasewindow.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "functions.hpp"
|
|
#include "gtkfunctions.hpp"
|
|
#include "mainwindow.hpp"
|
|
#include "progresswindow.hpp"
|
|
#include "tv_rename.hpp"
|
|
|
|
void MainWindow::chooseFile() {
|
|
// create a dialog for choosing directory
|
|
Gtk::FileChooserDialog dialog( "Select a directory",
|
|
Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER );
|
|
dialog.set_transient_for( *this );
|
|
// add cancel and select buttons
|
|
dialog.add_button( "_Cancel", Gtk::RESPONSE_CANCEL );
|
|
dialog.add_button( "Select", Gtk::RESPONSE_OK );
|
|
|
|
auto result = dialog.run();
|
|
|
|
switch ( result ) {
|
|
case Gtk::RESPONSE_OK:
|
|
m_entry_dir->set_text( dialog.get_filename() );
|
|
break;
|
|
case Gtk::RESPONSE_CANCEL:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MainWindow::quit() {
|
|
hide();
|
|
}
|
|
|
|
void MainWindow::patternHelp() {
|
|
Gtk::MessageDialog dialog( *this, "Pattern escape sequences" );
|
|
dialog.set_secondary_text(
|
|
"%filename - original filename (without type extension)\n"
|
|
"%show - show name from thetvdb\n"
|
|
"%epname - episode name from thetvdb\n"
|
|
"%season - season number\n"
|
|
"%episode - episode number\n"
|
|
"Both season number and episode number can be padded with zeros, just "
|
|
"add width of padding"
|
|
" right after %, like this: %2season.\n"
|
|
"Default pattern is \"%filename - %epname\", you might want to change "
|
|
"this to"
|
|
" \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - "
|
|
"%epname\"" );
|
|
dialog.run();
|
|
}
|
|
|
|
void MainWindow::process() {
|
|
language_code =
|
|
( *m_combo_language->get_active() )[m_columns_language.m_col_code];
|
|
|
|
searchShow( m_entry_show, m_combo_possible, m_columns_show.m_col_show,
|
|
m_columns_show.m_col_id, language_code, this );
|
|
}
|
|
|
|
void MainWindow::getNames() {
|
|
// check required field is filled out
|
|
if ( m_entry_dir->get_text().empty() ) {
|
|
Gtk::MessageDialog dialog( *this, "Directory field is empty" );
|
|
dialog.run();
|
|
return;
|
|
}
|
|
// check directory exists
|
|
if ( !FSLib::isDirectory( m_entry_dir->get_text() ) ) {
|
|
Gtk::MessageDialog dialog( *this, "Directory doesn't exist" );
|
|
dialog.run();
|
|
return;
|
|
}
|
|
|
|
path = m_entry_dir->get_text();
|
|
|
|
selected.clear();
|
|
files.clear();
|
|
|
|
std::vector< int > options;
|
|
|
|
// get all files in path and seperate them in map `files` by season
|
|
iterateFS( files, path );
|
|
|
|
for ( auto &x : files ) {
|
|
options.push_back( x.first );
|
|
}
|
|
|
|
// create a window with possible seasons to rename
|
|
// store selected seasons in `selected`
|
|
sw.reset( new SeasonWindow( options, selected ) );
|
|
sw->signal_hide().connect(
|
|
sigc::mem_fun( *this, &MainWindow::finishedSelection ) );
|
|
|
|
app->add_window( *sw );
|
|
sw->show();
|
|
}
|
|
|
|
/* change names of original files to generated new names
|
|
* orig - original filenames
|
|
* renamed - renamed filenames (sorted in the same order as `orig`)
|
|
*/
|
|
void renameFiles( const std::vector<
|
|
std::tuple< int, std::string, std::string, std::string > >
|
|
&renamed_files ) {
|
|
for ( const auto &renamed : renamed_files ) {
|
|
FSLib::rename(
|
|
std::get< 1 >( renamed ) + "/" + std::get< 2 >( renamed ),
|
|
std::get< 1 >( renamed ) + "/" + std::get< 3 >( renamed ) );
|
|
}
|
|
}
|
|
|
|
void MainWindow::finishedSelection() {
|
|
// remove created SeasonWindow and delete it from memory
|
|
app->remove_window( *sw );
|
|
sw.reset();
|
|
|
|
auto iter = m_combo_possible->get_active();
|
|
|
|
std::string show_id =
|
|
static_cast< Glib::ustring >( ( *iter )[m_columns_show.m_col_id] );
|
|
|
|
// shouldn't ever happen, but just to be sure
|
|
if ( show_id.empty() )
|
|
return;
|
|
|
|
std::string input_pattern = m_entry_pattern->get_text();
|
|
|
|
// store pattern to cache if it's different from default
|
|
if ( input_pattern != default_pattern ) {
|
|
std::ofstream file( userHome() + "/.cache/tv_rename_pattern" );
|
|
if ( file ) {
|
|
file << input_pattern;
|
|
}
|
|
}
|
|
|
|
for ( auto &x : selected ) {
|
|
// get renamed files for given season
|
|
auto renamed_files = getRenamedFiles(
|
|
static_cast< Glib::ustring >(
|
|
( *iter )[m_columns_show.m_col_show] ),
|
|
x, show_id, language_code,
|
|
( input_pattern.empty() ? default_pattern : input_pattern ),
|
|
!m_check_linux->get_active(), files[x], m_check_dvd->get_active() );
|
|
|
|
if ( renamed_files.empty() )
|
|
continue;
|
|
|
|
// if trust checkbox is ticked, rename files
|
|
if ( m_check_trust->get_active() ) {
|
|
renameFiles( renamed_files );
|
|
continue;
|
|
}
|
|
|
|
// create a custom dialog box with textview of new episode names
|
|
std::unique_ptr< Gtk::Dialog > dialog(
|
|
new Gtk::Dialog( "Rename confirmation", *this ) );
|
|
dialog->set_default_size( 550, 350 );
|
|
dialog->set_resizable( false );
|
|
auto content = dialog->get_content_area();
|
|
|
|
std::unique_ptr< Gtk::ScrolledWindow > sc_w( new Gtk::ScrolledWindow );
|
|
std::unique_ptr< Gtk::TextView > tx( new Gtk::TextView );
|
|
content->pack_start( *sc_w );
|
|
sc_w->add( *tx );
|
|
tx->set_editable( false );
|
|
tx->set_cursor_visible( false );
|
|
|
|
dialog->add_button( "_No", Gtk::RESPONSE_CANCEL );
|
|
dialog->add_button( "_Yes", Gtk::RESPONSE_OK );
|
|
sc_w->show();
|
|
tx->show();
|
|
|
|
auto buff = tx->get_buffer();
|
|
buff->place_cursor( buff->begin() );
|
|
buff->insert_at_cursor( std::get< 2 >( renamed_files[0] ).c_str() );
|
|
buff->insert_at_cursor( " --> " );
|
|
buff->insert_at_cursor( std::get< 3 >( renamed_files[0] ).c_str() );
|
|
|
|
for ( size_t i = 1; i < renamed_files.size(); i++ ) {
|
|
buff->insert_at_cursor( "\n" );
|
|
buff->insert_at_cursor( std::get< 2 >( renamed_files[i] ).c_str() );
|
|
buff->insert_at_cursor( " --> " );
|
|
buff->insert_at_cursor( std::get< 3 >( renamed_files[i] ).c_str() );
|
|
}
|
|
|
|
auto response = dialog->run();
|
|
|
|
// if user clicked "Yes" in dialog, rename files
|
|
switch ( response ) {
|
|
case Gtk::RESPONSE_OK:
|
|
renameFiles( renamed_files );
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
MainWindow::~MainWindow() {
|
|
auto children = get_children();
|
|
freeChildren( children );
|
|
}
|
|
|
|
MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr )
|
|
: app( ptr ) {
|
|
set_title( "TV Rename" );
|
|
|
|
// create widgets
|
|
Gtk::Button *button_dir = new Gtk::Button();
|
|
Gtk::Button *button_quit = new Gtk::Button();
|
|
Gtk::Button *button_process = new Gtk::Button();
|
|
Gtk::Button *button_pattern = new Gtk::Button();
|
|
|
|
Gtk::Label *label_language = new Gtk::Label();
|
|
Gtk::Label *label_show = new Gtk::Label();
|
|
Gtk::Label *label_dir = new Gtk::Label();
|
|
Gtk::Label *label_pattern = new Gtk::Label();
|
|
|
|
set_default_size( 400, 345 );
|
|
set_resizable( false );
|
|
|
|
{
|
|
// if cached pattern exists, load that instead of default
|
|
std::ifstream file( userHome() + "/.cache/tv_rename_pattern" );
|
|
if ( file ) {
|
|
std::getline( file, default_pattern );
|
|
} else {
|
|
default_pattern = "%filename - %epname";
|
|
}
|
|
}
|
|
|
|
auto *box = new Gtk::Box( Gtk::ORIENTATION_VERTICAL );
|
|
auto *menu = new Gtk::MenuBar();
|
|
auto *inputs = new Gtk::Grid();
|
|
auto *processing = new Gtk::Grid();
|
|
auto *buttons = new Gtk::Box( Gtk::ORIENTATION_HORIZONTAL );
|
|
|
|
add( *box );
|
|
box->pack_start( *menu, false, false );
|
|
box->pack_start( *inputs, false, true );
|
|
box->pack_start( *processing, false, true );
|
|
box->pack_start( *m_check_dvd, false, true );
|
|
box->pack_start( *m_label_possible, false, true );
|
|
box->pack_start( *m_combo_possible, false, true );
|
|
box->pack_start( *buttons, false, true );
|
|
box->pack_start( *button_quit, false, true );
|
|
|
|
auto *item = new Gtk::MenuItem();
|
|
auto *submenu = new Gtk::Menu();
|
|
|
|
menu->append( *item );
|
|
|
|
// File menu
|
|
item->set_label( "File" );
|
|
item->set_submenu( *submenu );
|
|
|
|
// Exit item for File menu
|
|
item = new Gtk::MenuItem();
|
|
|
|
item->set_label( "Exit" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::quit ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
// Database menu
|
|
item = new Gtk::MenuItem();
|
|
submenu = new Gtk::Menu();
|
|
|
|
item->set_label( "Database" );
|
|
item->set_submenu( *submenu );
|
|
menu->append( *item );
|
|
|
|
// Update database
|
|
item = new Gtk::MenuItem();
|
|
item->set_label( "Update database" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbUpdate ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
// Refresh database
|
|
item = new Gtk::MenuItem();
|
|
item->set_label( "Refresh database" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbRefresh ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
// Clean database
|
|
item = new Gtk::MenuItem();
|
|
item->set_label( "Clean database" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbClean ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
// Manage database
|
|
item = new Gtk::MenuItem();
|
|
item->set_label( "Manage database" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbManage ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
item = new Gtk::MenuItem();
|
|
item->set_label( "Change pattern" );
|
|
item->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbPattern ) );
|
|
|
|
submenu->append( *item );
|
|
|
|
std::vector< Gtk::Widget * > left_aligned_widgets = {
|
|
label_show, label_language, m_entry_show, m_combo_language,
|
|
label_dir, m_entry_dir, label_pattern, m_entry_pattern,
|
|
button_pattern, button_process, m_check_linux, m_check_trust,
|
|
m_check_dvd, m_label_possible, m_combo_possible, m_button_rename,
|
|
m_button_db_add, button_dir
|
|
};
|
|
|
|
// set widgets' position in boxes
|
|
inputs->attach( *label_show, 0, 0, 1, 1 );
|
|
inputs->attach( *label_language, 1, 0, 1, 1 );
|
|
inputs->attach( *m_entry_show, 0, 1, 1, 1 );
|
|
inputs->attach( *m_combo_language, 1, 1, 1, 1 );
|
|
inputs->attach( *label_dir, 0, 2, 2, 1 );
|
|
inputs->attach( *m_entry_dir, 0, 3, 1, 1 );
|
|
inputs->attach( *button_dir, 1, 3, 1, 1 );
|
|
inputs->attach( *label_pattern, 0, 4, 2, 1 );
|
|
inputs->attach( *m_entry_pattern, 0, 5, 1, 1 );
|
|
inputs->attach( *button_pattern, 1, 5, 1, 1 );
|
|
|
|
inputs->set_column_homogeneous( true );
|
|
|
|
processing->attach( *button_process, 0, 0, 1, 2 );
|
|
processing->attach( *m_check_linux, 1, 0, 1, 1 );
|
|
processing->attach( *m_check_trust, 1, 1, 1, 1 );
|
|
|
|
buttons->pack_start( *m_button_rename, false, true );
|
|
buttons->pack_start( *m_button_db_add, false, true );
|
|
|
|
// set widgets alignment and margins
|
|
for ( auto &x : left_aligned_widgets ) {
|
|
x->set_halign( Gtk::ALIGN_START );
|
|
x->set_valign( Gtk::ALIGN_CENTER );
|
|
x->set_margin_left( 5 );
|
|
x->set_margin_top( 5 );
|
|
}
|
|
|
|
m_entry_show->set_halign( Gtk::ALIGN_FILL );
|
|
m_entry_dir->set_halign( Gtk::ALIGN_FILL );
|
|
m_entry_pattern->set_halign( Gtk::ALIGN_FILL );
|
|
|
|
button_quit->set_halign( Gtk::ALIGN_END );
|
|
button_quit->set_valign( Gtk::ALIGN_CENTER );
|
|
button_quit->set_margin_right( 5 );
|
|
button_quit->set_margin_top( 5 );
|
|
button_quit->set_margin_bottom( 5 );
|
|
|
|
// set button texts
|
|
button_process->set_label( "Process" );
|
|
button_quit->set_label( "Quit" );
|
|
button_dir->set_label( "Choose directory" );
|
|
button_pattern->set_label( "Pattern help" );
|
|
m_button_rename->set_label( "Rename" );
|
|
m_button_db_add->set_label( "Add to database" );
|
|
m_check_linux->set_label( "Replace windows-illegal characters" );
|
|
m_check_trust->set_label( "Don't ask for rename confirmation" );
|
|
m_check_dvd->set_label( "Use DVD ordering" );
|
|
|
|
// set label texts
|
|
label_show->set_label( "Show:" );
|
|
label_language->set_label( "Language:" );
|
|
label_dir->set_label( "Directory:" );
|
|
label_pattern->set_label( "Pattern:" );
|
|
m_label_possible->set_label( "Possible shows:" );
|
|
|
|
m_entry_show->set_size_request( 170, 30 );
|
|
m_entry_dir->set_size_request( 170, 30 );
|
|
|
|
button_dir->set_size_request( 80, 30 );
|
|
button_quit->set_size_request( 80, 30 );
|
|
button_process->set_size_request( 80, 30 );
|
|
m_button_rename->set_size_request( 80, 30 );
|
|
m_button_db_add->set_size_request( 80, 30 );
|
|
|
|
// set default pattern
|
|
m_entry_pattern->set_text( default_pattern );
|
|
|
|
language_map = getLangs();
|
|
// put languages in combo box
|
|
{
|
|
auto model = Gtk::ListStore::create( m_columns_language );
|
|
m_combo_language->set_model( model );
|
|
|
|
for ( const auto &x : language_map ) {
|
|
auto row = *( model->append() );
|
|
row[m_columns_language.m_col_code] = x.first;
|
|
row[m_columns_language.m_col_language] = x.second;
|
|
if ( x.first == "en" )
|
|
m_combo_language->set_active( row );
|
|
}
|
|
}
|
|
|
|
{
|
|
auto model = Gtk::ListStore::create( m_columns_show );
|
|
|
|
m_combo_possible->set_model( model );
|
|
}
|
|
|
|
// set column to be shown in comboboxes
|
|
m_combo_language->pack_start( m_columns_language.m_col_language );
|
|
m_combo_possible->pack_start( m_columns_show.m_col_show );
|
|
|
|
// set dimensions
|
|
m_combo_language->set_size_request( 120, 30 );
|
|
m_combo_possible->set_size_request( 200, 30 );
|
|
|
|
// set signals
|
|
button_dir->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::chooseFile ) );
|
|
button_quit->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::quit ) );
|
|
button_process->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::process ) );
|
|
button_pattern->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::patternHelp ) );
|
|
m_button_rename->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::getNames ) );
|
|
m_button_db_add->signal_clicked().connect(
|
|
sigc::mem_fun( *this, &MainWindow::dbAdd ) );
|
|
m_entry_show->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::process ) );
|
|
m_entry_dir->signal_activate().connect(
|
|
sigc::mem_fun( *this, &MainWindow::process ) );
|
|
|
|
// show everything except possible shows and items related to them
|
|
show_all_children();
|
|
m_check_linux->set_active( true );
|
|
m_label_possible->hide();
|
|
m_combo_possible->hide();
|
|
m_button_rename->hide();
|
|
m_button_db_add->hide();
|
|
}
|
|
|
|
void MainWindow::dbUpdate() {
|
|
auto *pw = new ProgressWindow;
|
|
app->add_window( *pw );
|
|
auto app_ptr = app;
|
|
|
|
pw->signal_hide().connect( [pw, app_ptr]() {
|
|
app_ptr->remove_window( *pw );
|
|
delete pw;
|
|
} );
|
|
std::thread t( updateDB, !m_check_linux->get_active(), pw );
|
|
t.detach();
|
|
pw->show();
|
|
}
|
|
|
|
void MainWindow::dbClean() {
|
|
cleanDB();
|
|
}
|
|
|
|
void MainWindow::dbRefresh() {
|
|
auto *pw = new ProgressWindow;
|
|
app->add_window( *pw );
|
|
auto app_ptr = app;
|
|
|
|
pw->signal_hide().connect( [pw, app_ptr]() {
|
|
app_ptr->remove_window( *pw );
|
|
delete pw;
|
|
} );
|
|
std::thread t( refreshDB, !m_check_linux->get_active(), pw );
|
|
t.detach();
|
|
pw->show();
|
|
}
|
|
|
|
void MainWindow::dbAdd() {
|
|
// check required field is filled out
|
|
if ( m_entry_dir->get_text().empty() ) {
|
|
Gtk::MessageDialog dialog( *this, "Directory field is empty" );
|
|
dialog.run();
|
|
return;
|
|
}
|
|
// check directory exists
|
|
if ( !FSLib::isDirectory( m_entry_dir->get_text() ) ) {
|
|
Gtk::MessageDialog dialog( *this, "Directory doesn't exist" );
|
|
dialog.run();
|
|
return;
|
|
}
|
|
|
|
auto iter = m_combo_possible->get_active();
|
|
std::string input_pattern = m_entry_pattern->get_text();
|
|
|
|
std::string show =
|
|
static_cast< Glib::ustring >( ( *iter )[m_columns_show.m_col_show] );
|
|
std::string language_code = static_cast< Glib::ustring >(
|
|
( *m_combo_language->get_active() )[m_columns_language.m_col_code] );
|
|
std::string show_id =
|
|
static_cast< Glib::ustring >( ( *iter )[m_columns_show.m_col_id] );
|
|
|
|
auto *pw = new ProgressWindow;
|
|
app->add_window( *pw );
|
|
auto app_ptr = app;
|
|
|
|
pw->signal_hide().connect( [pw, app_ptr]() {
|
|
app_ptr->remove_window( *pw );
|
|
delete pw;
|
|
} );
|
|
|
|
std::thread t( addToDB, std::move( show ), m_entry_dir->get_text(),
|
|
std::move( language_code ), std::move( show_id ),
|
|
m_entry_pattern->get_text(), !m_check_linux->get_active(),
|
|
m_check_dvd->get_active(), pw );
|
|
t.detach();
|
|
pw->show();
|
|
}
|
|
|
|
void MainWindow::dbManage() {
|
|
auto *dbWindow =
|
|
new DatabaseWindow( !m_check_linux->get_active(), language_map, app );
|
|
app->add_window( *dbWindow );
|
|
auto app_ptr = app;
|
|
|
|
dbWindow->signal_hide().connect( [dbWindow, app_ptr]() {
|
|
app_ptr->remove_window( *dbWindow );
|
|
delete dbWindow;
|
|
} );
|
|
|
|
dbWindow->show();
|
|
}
|
|
|
|
void MainWindow::dbPattern() {
|
|
// Create a custom dialog box for pattern change
|
|
std::unique_ptr< Gtk::Dialog > dialog(
|
|
new Gtk::Dialog( "Change pattern", *this ) );
|
|
dialog->set_default_size( 350, 0 );
|
|
auto content = dialog->get_content_area();
|
|
|
|
auto pattern = getDBPattern();
|
|
|
|
std::unique_ptr< Gtk::Entry > pattern_entry( new Gtk::Entry );
|
|
content->pack_start( *pattern_entry );
|
|
|
|
dialog->add_button( "_Cancel", Gtk::RESPONSE_CANCEL );
|
|
dialog->add_button( "_OK", Gtk::RESPONSE_OK );
|
|
dialog->show_all_children();
|
|
|
|
dialog->signal_response().connect(
|
|
[&pattern, &pattern_entry]( int response ) {
|
|
if ( response == Gtk::RESPONSE_OK )
|
|
pattern = pattern_entry->get_text();
|
|
} );
|
|
|
|
pattern_entry->set_text( pattern );
|
|
|
|
auto response = dialog->run();
|
|
|
|
if ( response == Gtk::RESPONSE_OK ) {
|
|
changeDBPattern( pattern );
|
|
}
|
|
}
|