diff --git a/plugins/actions/bestfit/bestfit.cc b/plugins/actions/bestfit/bestfit.cc index fc577c8b..33ded564 100644 --- a/plugins/actions/bestfit/bestfit.cc +++ b/plugins/actions/bestfit/bestfit.cc @@ -33,186 +33,219 @@ class BestFitPlugin : public Action { update_ui(); } - ~BestFitPlugin() { - deactivate(); - } - - void activate() { - se_dbg(SE_DBG_PLUGINS); - - // actions - action_group = Gtk::ActionGroup::create("BestFitPlugin"); - - action_group->add(Gtk::Action::create( - "best-fit", _("_Best Fit Subtitles"), - _("Best fit the selected subtitles between the start " - "of the first and the end of the last one.")), - sigc::mem_fun(*this, &BestFitPlugin::on_best_fit)); - - // ui - Glib::RefPtr ui = get_ui_manager(); - - ui_id = ui->new_merge_id(); - - ui->insert_action_group(action_group); - - ui->add_ui(ui_id, "/menubar/menu-timings/best-fit", "best-fit", "best-fit"); - } - - void deactivate() { - se_dbg(SE_DBG_PLUGINS); - - Glib::RefPtr ui = get_ui_manager(); - - ui->remove_ui(ui_id); - ui->remove_action_group(action_group); - } - - void update_ui() { - se_dbg(SE_DBG_PLUGINS); - - bool visible = (get_current_document() != NULL); - - action_group->get_action("best-fit")->set_sensitive(visible); - } - - protected: - void on_best_fit() { - se_dbg(SE_DBG_PLUGINS); - - Document *doc = get_current_document(); - g_return_if_fail(doc); - - // Check if the selection is contiguous and complain if it isn't. - // Can work on multiple contiguous subtitles - std::list > contiguous_selection; - if (get_contiguous_selection(contiguous_selection) == false) - return; - - doc->start_command(_("Best fit")); - - for (auto &subtitles : contiguous_selection) { - bestfit(subtitles); - } - - doc->emit_signal("subtitle-time-changed"); - doc->finish_command(); - } - - bool get_contiguous_selection( - std::list > &contiguous_selection) { - Document *doc = get_current_document(); - - std::vector selection = doc->subtitles().get_selection(); - if (selection.size() < 2) { - doc->flash_message(_("Best Fit needs at least 2 subtitles to work on.")); - return false; - } - - contiguous_selection.push_back(std::vector()); - - guint last_id = 0; - - for (const auto &sub : selection) { - // Is the next subtitle? - if (sub.get_num() == last_id + 1) { - contiguous_selection.back().push_back(sub); - ++last_id; - } else { - // Create new list only if the previous is empty. - if (!contiguous_selection.back().empty()) { - contiguous_selection.push_back(std::vector()); - } - contiguous_selection.back().push_back(sub); - - last_id = sub.get_num(); - } - } - - // We check if we have at least one contiguous subtitles. - for (const auto &subs : contiguous_selection) { - if (subs.size() >= 2) - return true; - } - doc->flash_message( - _("Best Fit only works on an uninterrupted selection of subtitles.")); - return false; - } - - void bestfit(std::vector &subtitles) { - if (subtitles.size() < 2) - return; + ~BestFitPlugin() + { + deactivate(); + } + + /* + */ + void activate() + { + se_dbg(SE_DBG_PLUGINS); + + // actions + action_group = Gtk::ActionGroup::create("BestFitPlugin"); + + action_group->add( + Gtk::Action::create("best-fit", _("_Best Fit Subtitles"), + _("Best fit the selected subtitles between the start of the first and the end of the last one.")), + sigc::mem_fun(*this, &BestFitPlugin::on_best_fit)); + + // ui + Glib::RefPtr ui = get_ui_manager(); + + ui_id = ui->new_merge_id(); + + ui->insert_action_group(action_group); + + ui->add_ui(ui_id, "/menubar/menu-timings/best-fit", "best-fit", "best-fit"); + } + + /* + */ + void deactivate() + { + se_dbg(SE_DBG_PLUGINS); + + Glib::RefPtr ui = get_ui_manager(); + + ui->remove_ui(ui_id); + ui->remove_action_group(action_group); + } + + /* + */ + void update_ui() + { + se_dbg(SE_DBG_PLUGINS); + + bool visible = (get_current_document() != NULL); + + action_group->get_action("best-fit")->set_sensitive(visible); + } + +protected: + + /* + */ + void on_best_fit() + { + se_dbg(SE_DBG_PLUGINS); + + Document *doc = get_current_document(); + g_return_if_fail(doc); + + // Check if the selection is contiguous and complain if it isn't. + // Can work on multiple contiguous subtitles + std::list< std::vector > contiguous_selection; + if(get_contiguous_selection(contiguous_selection) == false) + return; + + doc->start_command(_("Best fit")); + + for(std::list< std::vector >::iterator it = contiguous_selection.begin(); it != contiguous_selection.end(); ++it) + { + bestfit(*it); + } + + doc->emit_signal("subtitle-time-changed"); + doc->finish_command(); + } + + /* + */ + bool get_contiguous_selection(std::list< std::vector > &contiguous_selection) + { + Document* doc = get_current_document(); + + std::vector selection = doc->subtitles().get_selection(); + if(selection.size() < 2) + { + doc->flash_message(_("Best Fit needs at least 2 subtitles to work on.")); + return false; + } + + contiguous_selection.push_back( std::vector () ); + + guint last_id = 0; + + for(guint i=0; i () ); + + contiguous_selection.back().push_back( sub ); + + last_id = sub.get_num(); + } + } + + // We check if we have at least one contiguous subtitles. + for(std::list< std::vector >::iterator it = contiguous_selection.begin(); it != contiguous_selection.end(); ++it) + { + if((*it).size() >= 2) + return true; + } + doc->flash_message(_("Best Fit only works on an uninterrupted selection of subtitles.")); + return false; + } + + /* + */ + void bestfit(std::vector &subtitles) + { + if(subtitles.size() < 2) + return; // Get relevant preferences SubtitleTime gap = cfg::get_int("timing", "min-gap-between-subtitles"); - double mincps = cfg::get_double("timing", "min-characters-per-second"); + SubtitleTime minlen = cfg::get_int("timing", "min-display"); + long minmsecs = minlen.totalmsecs; - // SubtitleTime minlen = cfg::get_int("timing", "min-display"); + // double mincps = cfg::get_double("timing", "min-characters-per-second"); // long maxcpl = cfg::get_int("timing", "max-characters-per-line"); // long maxcps = cfg::get_int("timing", "max-characters-per-second"); - SubtitleTime startime = subtitles.front().get_start(); - SubtitleTime endtime = subtitles.back().get_end(); - SubtitleTime grosstime = endtime - startime; - SubtitleTime allgaps = gap * (subtitles.size() - 1); - SubtitleTime nettime = grosstime - allgaps; - - // Get the total of characters counts - long totalchars = 0; - for (const auto &sub : subtitles) { - totalchars += utility::get_text_length_for_timing(sub.get_text()); - } - - // Avoid divide by zero - // Fix bug #23151 : Using best fit subtitles on zero-length subtitles - // crashes subtitleeditor - if (totalchars == 0) - return; - - // Distribute available time between selected subtitles in proportion to the - // length of their text - long subchars = 0; - long prevchars = 0; - SubtitleTime intime; - SubtitleTime prevend; - SubtitleTime dur; - SubtitleTime maxdur; - - for (unsigned int i = 0; i < subtitles.size(); ++i) { - Subtitle &sub = subtitles[i]; - - subchars = utility::get_text_length_for_timing(sub.get_text()); - - // give this subtitle a fair share of the net time available - dur = (nettime * subchars) / totalchars; - // calculate proportionate start time - intime = startime + ((grosstime * prevchars) / totalchars); - - // make sure we're not under the minimum cps - maxdur.totalmsecs = static_cast( - floor((1000.0 * static_cast(subchars)) / mincps)); - - if (dur > maxdur) - dur = maxdur; - - // make sure minimum gap is preserved - // (rounding errors could've shortened it) - if (i > 0 && (intime - prevend) < gap) { - intime = prevend + gap; - } - - sub.set_start_and_end(intime, intime + dur); - - prevend = intime + dur; - } - // reset the end time of the last subtitle to make sure rounding errors - // didn't move it - subtitles.back().set_end(endtime); - } - - protected: - Gtk::UIManager::ui_merge_id ui_id; - Glib::RefPtr action_group; + SubtitleTime startime = subtitles.front().get_start(); + SubtitleTime endtime = subtitles.back().get_end(); + SubtitleTime grosstime = endtime - startime; + long allgaps = gap.totalmsecs * (subtitles.size()-1); + long nettime = grosstime.totalmsecs - allgaps; + + std::vector durations( subtitles.size() ); + std::vector charcounts( subtitles.size() ); + + // Get the total character count + long totalchars = 0; + for(guint i=0; i< subtitles.size(); ++i) + { + charcounts[i] = utility::get_text_length_for_timing( subtitles[i].get_text() ); + durations[i] = 0; + totalchars += charcounts[i]; + } + + // Avoid divide by zero + // Fix bug #23151 : Using best fit subtitles on zero-length subtitles crashes subtitleeditor + if(totalchars == 0) + return; + + long charsleft = totalchars; + long timeleft = nettime; + bool done = false; //we're done if no subtitle duration hit the minimum limit + bool hopeless = false; //if all durations hit the minimum limit, it's hopeless + while( !done && !hopeless ) { + done = true; + hopeless = true; + for( guint i = 0; i < durations.size(); ++i ) { + if( charcounts[i] >= 0 ) { + long d = charcounts[i] * timeleft / charsleft; + if( d < minmsecs ) { + charsleft -= charcounts[i]; + timeleft -= minmsecs; + durations[i] = minmsecs; + charcounts[i] = -1; + done = false; + } else { + durations[i] = d; + hopeless = false; + } + } + } + } + + if( hopeless ) { + //forget subtitle minimum duration and just distribute the time evenly + for( guint i = 0; i < durations.size(); ++i ) { + durations[i] = utility::get_text_length_for_timing( subtitles[i].get_text() ) * nettime / totalchars; + } + } + + //time subtitles according to calculated durations + SubtitleTime intime = subtitles[0].get_start(); + for( guint i = 0; i < durations.size(); ++i ) { + subtitles[i].set_start_and_end( intime, intime + durations[i] ); + intime.totalmsecs += durations[i] + gap.totalmsecs; + } + + //reset the end time of the last time to original + //in case rounding errors made it drift away + subtitles[ subtitles.size() - 1 ].set_end( endtime ); + } + +protected: + Gtk::UIManager::ui_merge_id ui_id; + Glib::RefPtr action_group; }; REGISTER_EXTENSION(BestFitPlugin) diff --git a/plugins/actions/clipboard/clipboard.cc b/plugins/actions/clipboard/clipboard.cc index 00d215e3..d4373f27 100644 --- a/plugins/actions/clipboard/clipboard.cc +++ b/plugins/actions/clipboard/clipboard.cc @@ -97,6 +97,11 @@ class ClipboardPlugin : public Action { _("Create a new document and paste the contents of " "the clipboard into it.")), sigc::mem_fun(*this, &ClipboardPlugin::on_paste_as_new_document)); + action_group->add( + Gtk::Action::create("clipboard-paste-over-text", + _("Paste Over Text"), + _("Overwrite subtitle text with clipboard content.")), + sigc::mem_fun(*this, &ClipboardPlugin::on_paste_over_text)); // ui Glib::RefPtr ui = get_ui_manager(); @@ -118,6 +123,7 @@ class ClipboardPlugin : public Action { + @@ -207,6 +213,7 @@ class ClipboardPlugin : public Action { bool paste_visible = false; bool paste_now_visible = false; + bool paste_over_visible = false; if (chosen_clipboard_target != "") { paste_visible = true; @@ -215,11 +222,17 @@ class ClipboardPlugin : public Action { Player::NONE); } + Document * doc = get_current_document(); + paste_over_visible = (doc) ? !doc->subtitles().get_selection().empty() : false; + action_group->get_action("clipboard-paste")->set_sensitive(paste_visible); action_group->get_action("clipboard-paste-at-player-position") ->set_sensitive(paste_now_visible); action_group->get_action("clipboard-paste-as-new-document") ->set_sensitive(paste_visible); + action_group->get_action("clipboard-paste-over-text") + ->set_sensitive( paste_over_visible ); + } void on_player_message(Player::Message) { @@ -569,38 +582,56 @@ class ClipboardPlugin : public Action { if (is_something_to_paste() == false) return; - paste_after = where_to_paste(subtitles); - - // We get the new subtitles in the new_subtitles array - create_and_insert_paste_subtitles(subtitles, paste_after, new_subtitles); + if( (flags & PASTE_OVER_TEXT) != 0 ) + { + new_subtitles = subtitles.get_selection(); + int howmany = std::min( (int)new_subtitles.size(), (int)clipdoc->subtitles().size() ); + Subtitle clip_sub = clipdoc->subtitles().get_first(); + Subtitle oversub = subtitles.get_first(); + + for( int i = 0; i < howmany; i++ ) + { + //overwrite + new_subtitles[i].set_text( clip_sub.get_text() ); + ++clip_sub; + } - calculate_and_apply_timeshift(subtitles, paste_after, new_subtitles, flags); + //tell the user what happened + doc->flash_message(_("%i subtitle(s) overwritten with clipboard text."), howmany ); + } else { + paste_after = where_to_paste(subtitles); + + // We get the new subtitles in the new_subtitles array + create_and_insert_paste_subtitles(subtitles, paste_after, new_subtitles); + + calculate_and_apply_timeshift(subtitles, paste_after, new_subtitles, flags); + + // We can now remove the old selected subtitles, only if the selection is > + // 1 + std::vector selection = subtitles.get_selection(); + if (selection.size() > 1) + subtitles.remove(selection); + + // We select the pasted subtitles, this way the user see where are the new + // subtitles + subtitles.unselect_all(); + subtitles.select(new_subtitles); + + // show the pasted subtitles + // FIXME tomas-kitone: this is a clumsy implementation. + // I think we should add a show_subtitle( Subtitle &sub ) function to class + // SubtitleView or at least get_iter() or get_path() to class Subtitle + SubtitleView *view = reinterpret_cast(doc->widget()); + if (view != NULL) { + int sub_num = new_subtitles[0].get_num() - 1; + Gtk::TreeModel::Path sub_path = + Gtk::TreeModel::Path(Glib::ustring::compose("%1", sub_num)); + view->scroll_to_row(sub_path, 0.25); + } - // We can now remove the old selected subtitles, only if the selection is > - // 1 - std::vector selection = subtitles.get_selection(); - if (selection.size() > 1) - subtitles.remove(selection); - - // We select the pasted subtitles, this way the user see where are the new - // subtitles - subtitles.unselect_all(); - subtitles.select(new_subtitles); - - // show the pasted subtitles - // FIXME tomas-kitone: this is a clumsy implementation. - // I think we should add a show_subtitle( Subtitle &sub ) function to class - // SubtitleView or at least get_iter() or get_path() to class Subtitle - SubtitleView *view = reinterpret_cast(doc->widget()); - if (view != NULL) { - int sub_num = new_subtitles[0].get_num() - 1; - Gtk::TreeModel::Path sub_path = - Gtk::TreeModel::Path(Glib::ustring::compose("%1", sub_num)); - view->scroll_to_row(sub_path, 0.25); + // tell the user what happened + doc->flash_message(_("%i subtitle(s) pasted."), new_subtitles.size()); } - - // tell the user what happened - doc->flash_message(_("%i subtitle(s) pasted."), new_subtitles.size()); } bool is_something_to_paste() { @@ -714,6 +745,13 @@ class ClipboardPlugin : public Action { paste_common(PASTE_AS_NEW_DOCUMENT); } + void on_paste_over_text() + { + se_dbg(SE_DBG_PLUGINS); + + paste_common( PASTE_OVER_TEXT ); + }; + void paste_common(unsigned long flags) { se_dbg(SE_DBG_PLUGINS); @@ -794,7 +832,8 @@ class ClipboardPlugin : public Action { PASTE_TIMING_AFTER = 0x01, // snap the pasted subtitles after the preceding subtitle PASTE_TIMING_PLAYER = 0x02, // paste at the current player position - PASTE_AS_NEW_DOCUMENT = 0x04 + PASTE_AS_NEW_DOCUMENT = 0x04, + PASTE_OVER_TEXT = 0x08 // keep current timing but overwrite the text }; unsigned long paste_flags;