Mixer Scenes: first-draft GUI for mixer scenes
This commit is contained in:
parent
00bd313e6e
commit
ca8dddcd37
@ -38,12 +38,15 @@
|
|||||||
#include <glibmm/threads.h>
|
#include <glibmm/threads.h>
|
||||||
|
|
||||||
#include <gtkmm/accelmap.h>
|
#include <gtkmm/accelmap.h>
|
||||||
|
#include <gtkmm/menu.h>
|
||||||
#include <gtkmm/offscreenwindow.h>
|
#include <gtkmm/offscreenwindow.h>
|
||||||
#include <gtkmm/stock.h>
|
#include <gtkmm/stock.h>
|
||||||
|
|
||||||
#include "pbd/convert.h"
|
#include "pbd/convert.h"
|
||||||
#include "pbd/unwind.h"
|
#include "pbd/unwind.h"
|
||||||
|
|
||||||
|
#include "context_menu_helper.h"
|
||||||
|
|
||||||
#include "ardour/amp.h"
|
#include "ardour/amp.h"
|
||||||
#include "ardour/debug.h"
|
#include "ardour/debug.h"
|
||||||
#include "ardour/audio_port.h"
|
#include "ardour/audio_port.h"
|
||||||
@ -65,7 +68,9 @@
|
|||||||
#include "gtkmm2ext/window_title.h"
|
#include "gtkmm2ext/window_title.h"
|
||||||
#include "gtkmm2ext/doi.h"
|
#include "gtkmm2ext/doi.h"
|
||||||
|
|
||||||
|
#include "widgets/prompter.h"
|
||||||
#include "widgets/tearoff.h"
|
#include "widgets/tearoff.h"
|
||||||
|
#include "widgets/tooltips.h"
|
||||||
|
|
||||||
#include "foldback_strip.h"
|
#include "foldback_strip.h"
|
||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
@ -93,6 +98,7 @@
|
|||||||
#define PX_SCALE(px) std::max ((float)px, rintf ((float)px* UIConfiguration::instance ().get_ui_scale ()))
|
#define PX_SCALE(px) std::max ((float)px, rintf ((float)px* UIConfiguration::instance ().get_ui_scale ()))
|
||||||
|
|
||||||
using namespace ARDOUR;
|
using namespace ARDOUR;
|
||||||
|
using namespace ArdourWidgets;
|
||||||
using namespace ARDOUR_UI_UTILS;
|
using namespace ARDOUR_UI_UTILS;
|
||||||
using namespace ARDOUR_PLUGIN_UTILS;
|
using namespace ARDOUR_PLUGIN_UTILS;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
@ -281,6 +287,43 @@ Mixer_UI::Mixer_UI ()
|
|||||||
favorite_plugins_vbox.pack_start (favorite_plugins_scroller, true, true);
|
favorite_plugins_vbox.pack_start (favorite_plugins_scroller, true, true);
|
||||||
favorite_plugins_vbox.pack_start (favorite_plugins_search_hbox, false, false);
|
favorite_plugins_vbox.pack_start (favorite_plugins_search_hbox, false, false);
|
||||||
|
|
||||||
|
Gtk::Label *l = manage (new Label (_("Mixer Scenes (F1...F8 to recall)")));
|
||||||
|
l->set_alignment(0, 0.5);
|
||||||
|
Gtk::Table *padder = manage (new Table ());
|
||||||
|
padder->set_border_width(4);
|
||||||
|
padder->attach (*l, 0,1,0,1, Gtk::FILL, Gtk::FILL);
|
||||||
|
|
||||||
|
_mixer_scene_vbox.pack_start(*padder, false, false);
|
||||||
|
|
||||||
|
_mixer_scene_table.set_border_width(4);
|
||||||
|
_mixer_scene_table.set_spacings(4);
|
||||||
|
_mixer_scene_table.set_homogeneous(false);
|
||||||
|
for (int column=0; column<1;column++) {
|
||||||
|
for (int row=0; row<8;row++) {
|
||||||
|
int col = column*2;
|
||||||
|
int scn_index = col*2+row;
|
||||||
|
|
||||||
|
ArdourButton* b = manage (new ArdourButton (ArdourButton::default_elements));
|
||||||
|
b->set_text(string_compose("%1", 1+ scn_index));
|
||||||
|
b->signal_button_press_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::scene_button_pressed), scn_index), false);
|
||||||
|
|
||||||
|
_mixer_scene_buttons.push_back(b);
|
||||||
|
|
||||||
|
Gtk::Label* l = manage (new Gtk::Label (""));
|
||||||
|
Gtk::EventBox* namebox = manage (new Gtk::EventBox ()); //must put the labe in a EventBox to capture double-click
|
||||||
|
namebox->add (*l);
|
||||||
|
namebox->add_events (Gdk::BUTTON_PRESS_MASK);
|
||||||
|
namebox->signal_button_press_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::scene_label_pressed), scn_index), false);
|
||||||
|
|
||||||
|
_mixer_scene_labels.push_back(l);
|
||||||
|
|
||||||
|
_mixer_scene_table.attach (*b, col, col+1, row, row+1, Gtk::FILL, Gtk::FILL); col++;
|
||||||
|
|
||||||
|
_mixer_scene_table.attach (*namebox, col, col+1, row, row+1, Gtk::FILL, Gtk::FILL); col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_mixer_scene_vbox.pack_start(_mixer_scene_table, false, false);
|
||||||
|
|
||||||
rhs_pane1.add (favorite_plugins_frame);
|
rhs_pane1.add (favorite_plugins_frame);
|
||||||
rhs_pane1.add (track_display_frame);
|
rhs_pane1.add (track_display_frame);
|
||||||
|
|
||||||
@ -289,6 +332,13 @@ Mixer_UI::Mixer_UI ()
|
|||||||
|
|
||||||
list_vpacker.pack_start (rhs_pane2, true, true);
|
list_vpacker.pack_start (rhs_pane2, true, true);
|
||||||
|
|
||||||
|
//add a spacer; this fills the area that is normally taken by the pane resizers
|
||||||
|
_mixer_scene_spacer.set_size_request (-1, 6);
|
||||||
|
list_vpacker.pack_start (_mixer_scene_spacer, false, false);
|
||||||
|
|
||||||
|
_mixer_scene_frame.add(_mixer_scene_vbox);
|
||||||
|
list_vpacker.pack_start (_mixer_scene_frame, false, false);
|
||||||
|
|
||||||
vca_label_bar.set_size_request (-1, 16 + 1); /* must match height in GroupTabs::set_size_request() + 1 border px*/
|
vca_label_bar.set_size_request (-1, 16 + 1); /* must match height in GroupTabs::set_size_request() + 1 border px*/
|
||||||
vca_vpacker.pack_start (vca_label_bar, false, false);
|
vca_vpacker.pack_start (vca_label_bar, false, false);
|
||||||
|
|
||||||
@ -405,7 +455,6 @@ struct MixerStripSorter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Mixer_UI::escape ()
|
Mixer_UI::escape ()
|
||||||
{
|
{
|
||||||
@ -1191,6 +1240,9 @@ Mixer_UI::set_session (Session* sess)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_scene_buttons();
|
||||||
|
MixerScene::Change.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::update_scene_buttons, this), gui_context());
|
||||||
|
|
||||||
refill_favorite_plugins();
|
refill_favorite_plugins();
|
||||||
|
|
||||||
XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
|
XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
|
||||||
@ -3838,22 +3890,103 @@ Mixer_UI::vca_unassign (boost::shared_ptr<VCA> vca)
|
|||||||
void
|
void
|
||||||
Mixer_UI::store_mixer_scene (size_t n)
|
Mixer_UI::store_mixer_scene (size_t n)
|
||||||
{
|
{
|
||||||
if (_session) {
|
if (!_session) {
|
||||||
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
return;
|
||||||
if (ms && !ms->empty ()) {
|
}
|
||||||
ArdourMessageDialog md (_("Scene is already set. Overwrite it?"), false, MESSAGE_QUESTION, BUTTONS_YES_NO);
|
|
||||||
if (md.run () != RESPONSE_YES) {
|
std::string str;
|
||||||
return;
|
|
||||||
}
|
/* if it already exists, prompt the user to overwrite an existing scene */
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
if (ms && !ms->empty ()) {
|
||||||
|
ArdourMessageDialog md (_("Scene is already set. Overwrite it?"), false, MESSAGE_QUESTION, BUTTONS_YES_NO);
|
||||||
|
if (md.run () != RESPONSE_YES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
str = ms->name();
|
||||||
|
} else {
|
||||||
|
time_t now;
|
||||||
|
time (&now);
|
||||||
|
Glib::DateTime tm (Glib::DateTime::create_now_local (now));
|
||||||
|
str = Glib::DateTime::create_now_local().format ("%FT%H.%M.%S");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prompt the user for a scene name */
|
||||||
|
ArdourWidgets::Prompter name_prompter (true);
|
||||||
|
string result;
|
||||||
|
bool done = false;
|
||||||
|
name_prompter.set_title (_("Store Scene"));
|
||||||
|
name_prompter.set_prompt (_("Scene name:"));
|
||||||
|
name_prompter.set_initial_text (str);
|
||||||
|
name_prompter.add_button (_("Store"), Gtk::RESPONSE_ACCEPT);
|
||||||
|
name_prompter.show_all ();
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
switch (name_prompter.run ()) {
|
||||||
|
case Gtk::RESPONSE_ACCEPT:
|
||||||
|
name_prompter.get_result (result);
|
||||||
|
name_prompter.hide ();
|
||||||
|
if (result.length()) {
|
||||||
|
_session->store_nth_mixer_scene (n);
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
ms->set_name(result);
|
||||||
|
done = true;
|
||||||
|
} else {
|
||||||
|
/* nothing entered, just get out of here */
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
_session->store_nth_mixer_scene (n);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Mixer_UI::recall_mixer_scene (size_t n)
|
Mixer_UI::recall_mixer_scene (size_t n)
|
||||||
{
|
{
|
||||||
if (_session) {
|
if (!_session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
if (!ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool do_recall = true;
|
||||||
|
|
||||||
|
XMLNode* memory_warning_node = Config->instant_xml (X_("no-scene-warning"));
|
||||||
|
if (memory_warning_node == 0) {
|
||||||
|
ArdourMessageDialog msg (string_compose ( _("Recall mixer scene \"%1\"?\n"
|
||||||
|
"This will overwrite your mixer settings!\n"
|
||||||
|
"This operation cannot be undone."), ms->name()));
|
||||||
|
|
||||||
|
msg.add_button (_("Cancel"), Gtk::RESPONSE_NO);
|
||||||
|
|
||||||
|
msg.set_default_response (RESPONSE_OK);
|
||||||
|
|
||||||
|
VBox* vbox = msg.get_vbox();
|
||||||
|
HBox hbox;
|
||||||
|
CheckButton cb (_("Do not show this window again"));
|
||||||
|
hbox.pack_start (cb, true, false);
|
||||||
|
vbox->pack_start (hbox);
|
||||||
|
cb.show();
|
||||||
|
vbox->show();
|
||||||
|
hbox.show ();
|
||||||
|
|
||||||
|
if (msg.run () != Gtk::RESPONSE_OK) {
|
||||||
|
do_recall = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb.get_active()) {
|
||||||
|
XMLNode node (X_("no-scene-warning"));
|
||||||
|
Config->add_instant_xml (node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_recall) {
|
||||||
_session->apply_nth_mixer_scene (n);
|
_session->apply_nth_mixer_scene (n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3861,10 +3994,169 @@ Mixer_UI::recall_mixer_scene (size_t n)
|
|||||||
void
|
void
|
||||||
Mixer_UI::clear_mixer_scene (size_t n)
|
Mixer_UI::clear_mixer_scene (size_t n)
|
||||||
{
|
{
|
||||||
if (_session) {
|
if (!_session) {
|
||||||
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
return;
|
||||||
if (ms) {
|
}
|
||||||
ms->clear ();
|
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
if (!ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool do_clear = false;
|
||||||
|
XMLNode* memory_warning_node = Config->instant_xml (X_("no-clear-warning"));
|
||||||
|
if (memory_warning_node == 0) {
|
||||||
|
ArdourMessageDialog msg (string_compose ( _("Clear mixer scene \"%1\"?\n"
|
||||||
|
"This operation cannot be undone."), ms->name()));
|
||||||
|
|
||||||
|
msg.add_button (_("Cancel"), Gtk::RESPONSE_NO);
|
||||||
|
|
||||||
|
msg.set_default_response (RESPONSE_OK);
|
||||||
|
|
||||||
|
VBox* vbox = msg.get_vbox();
|
||||||
|
HBox hbox;
|
||||||
|
CheckButton cb (_("Do not show this window again"));
|
||||||
|
hbox.pack_start (cb, true, false);
|
||||||
|
vbox->pack_start (hbox);
|
||||||
|
cb.show();
|
||||||
|
vbox->show();
|
||||||
|
hbox.show ();
|
||||||
|
|
||||||
|
if (msg.run () == Gtk::RESPONSE_OK) {
|
||||||
|
do_clear = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb.get_active()) {
|
||||||
|
XMLNode node (X_("no-clear-warning"));
|
||||||
|
Config->add_instant_xml (node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_clear) {
|
||||||
|
ms->clear ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Mixer_UI::rename_mixer_scene (size_t n)
|
||||||
|
{
|
||||||
|
if (!_session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
if (!ms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prompt the user for a scene name */
|
||||||
|
ArdourWidgets::Prompter name_prompter (true);
|
||||||
|
string result;
|
||||||
|
bool done = false;
|
||||||
|
name_prompter.set_title (_("Rename Scene"));
|
||||||
|
name_prompter.set_prompt (_("Scene name:"));
|
||||||
|
name_prompter.set_initial_text (ms->name());
|
||||||
|
name_prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
|
||||||
|
name_prompter.show_all ();
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
switch (name_prompter.run ()) {
|
||||||
|
case Gtk::RESPONSE_ACCEPT:
|
||||||
|
name_prompter.get_result (result);
|
||||||
|
name_prompter.hide ();
|
||||||
|
if (result.length()) {
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (n, false);
|
||||||
|
ms->set_name(result);
|
||||||
|
done = true;
|
||||||
|
} else {
|
||||||
|
/* nothing entered, just get out of here */
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Mixer_UI::popup_scene_menu (GdkEventButton* ev, int scn_idx)
|
||||||
|
{
|
||||||
|
Gtk::Menu* menu = ARDOUR_UI_UTILS::shared_popup_menu ();
|
||||||
|
Gtk::Menu_Helpers::MenuList& items = menu->items();
|
||||||
|
|
||||||
|
items.push_back(Gtk::Menu_Helpers::MenuElem(_("Store"), sigc::bind(sigc::mem_fun(*this, &Mixer_UI::store_mixer_scene), scn_idx)));
|
||||||
|
|
||||||
|
boost::shared_ptr<MixerScene> ms = _session->nth_mixer_scene (scn_idx);
|
||||||
|
if(ms && !ms->empty()) {
|
||||||
|
items.push_back(Gtk::Menu_Helpers::MenuElem(_("Clear"), sigc::bind(sigc::mem_fun(*this, &Mixer_UI::clear_mixer_scene), scn_idx)));
|
||||||
|
items.push_back(Gtk::Menu_Helpers::MenuElem(_("Rename"), sigc::bind(sigc::mem_fun(*this, &Mixer_UI::rename_mixer_scene), scn_idx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->popup(1, ev->time);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Mixer_UI::scene_button_pressed (GdkEventButton *ev, int idx)
|
||||||
|
{
|
||||||
|
if (!_session) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<MixerScene> scn = _session->nth_mixer_scene (idx);
|
||||||
|
|
||||||
|
if (ev->button == 1 && (scn && !scn->empty())) {
|
||||||
|
recall_mixer_scene(idx);
|
||||||
|
} else if (scn && (ev->button == 3) && Gtkmm2ext::Keyboard::modifier_state_equals (ev->state, Gtkmm2ext::Keyboard::TertiaryModifier)) {
|
||||||
|
clear_mixer_scene(idx);
|
||||||
|
} else if (Keyboard::is_context_menu_event (ev)) {
|
||||||
|
popup_scene_menu (ev, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Mixer_UI::scene_label_pressed (GdkEventButton* ev, int idx)
|
||||||
|
{
|
||||||
|
boost::shared_ptr<MixerScene> scn = _session->nth_mixer_scene (idx);
|
||||||
|
if (!scn || scn->empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ev->button == 1 && ev->type == GDK_2BUTTON_PRESS) || Keyboard::is_edit_event (ev)) {
|
||||||
|
rename_mixer_scene(idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Mixer_UI::update_scene_buttons ()
|
||||||
|
{
|
||||||
|
for (size_t idx=0; idx<12;idx++) {
|
||||||
|
if (!_mixer_scene_buttons.empty() && idx < _mixer_scene_buttons.size()) {
|
||||||
|
|
||||||
|
boost::shared_ptr<MixerScene> scn = _session->nth_mixer_scene (idx);
|
||||||
|
|
||||||
|
ArdourButton *b = _mixer_scene_buttons[idx];
|
||||||
|
if (scn && !scn->empty()) {
|
||||||
|
ArdourWidgets::set_tooltip (b, _("Click to recall this mixer scene\nRight-Click for context menu"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk::Label *l = _mixer_scene_labels[idx];
|
||||||
|
l->set_alignment(0, 0.5);
|
||||||
|
|
||||||
|
if (scn && !scn->empty()) {
|
||||||
|
l->set_text(scn->name());
|
||||||
|
} else {
|
||||||
|
if (idx==0 && scn->empty()) {
|
||||||
|
l->set_text(_("(Right-Click to Store)"));
|
||||||
|
} else {
|
||||||
|
l->set_text((""));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,7 @@ public:
|
|||||||
void store_mixer_scene (size_t);
|
void store_mixer_scene (size_t);
|
||||||
void recall_mixer_scene (size_t);
|
void recall_mixer_scene (size_t);
|
||||||
void clear_mixer_scene (size_t);
|
void clear_mixer_scene (size_t);
|
||||||
|
void rename_mixer_scene (size_t n);
|
||||||
|
|
||||||
void do_vca_assign (boost::shared_ptr<ARDOUR::VCA>);
|
void do_vca_assign (boost::shared_ptr<ARDOUR::VCA>);
|
||||||
void do_vca_unassign (boost::shared_ptr<ARDOUR::VCA>);
|
void do_vca_unassign (boost::shared_ptr<ARDOUR::VCA>);
|
||||||
@ -207,6 +208,17 @@ private:
|
|||||||
Gtk::HBox out_packer;
|
Gtk::HBox out_packer;
|
||||||
ArdourWidgets::HPane list_hpane;
|
ArdourWidgets::HPane list_hpane;
|
||||||
|
|
||||||
|
Gtk::EventBox _mixer_scene_spacer;
|
||||||
|
Gtk::Frame _mixer_scene_frame;
|
||||||
|
Gtk::Table _mixer_scene_table;
|
||||||
|
Gtk::VBox _mixer_scene_vbox;
|
||||||
|
std::vector<ArdourWidgets::ArdourButton *> _mixer_scene_buttons;
|
||||||
|
std::vector<Gtk::Label *> _mixer_scene_labels;
|
||||||
|
|
||||||
|
void popup_scene_menu (GdkEventButton* ev, int);
|
||||||
|
bool scene_button_pressed (GdkEventButton*, int);
|
||||||
|
bool scene_label_pressed (GdkEventButton* ev, int);
|
||||||
|
|
||||||
MixerGroupTabs* _group_tabs;
|
MixerGroupTabs* _group_tabs;
|
||||||
|
|
||||||
bool on_scroll_event (GdkEventScroll*);
|
bool on_scroll_event (GdkEventScroll*);
|
||||||
@ -228,6 +240,8 @@ private:
|
|||||||
|
|
||||||
void add_stripables (ARDOUR::StripableList&);
|
void add_stripables (ARDOUR::StripableList&);
|
||||||
|
|
||||||
|
void update_scene_buttons ();
|
||||||
|
|
||||||
void add_routes (ARDOUR::RouteList&);
|
void add_routes (ARDOUR::RouteList&);
|
||||||
void remove_strip (MixerStrip *);
|
void remove_strip (MixerStrip *);
|
||||||
void remove_foldback (FoldbackStrip *);
|
void remove_foldback (FoldbackStrip *);
|
||||||
|
Loading…
Reference in New Issue
Block a user