mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-09 08:14:58 +01:00
initial pass at session-renaming functionality
git-svn-id: svn://localhost/ardour2/branches/3.0@9876 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
2d83ffc45a
commit
a5efa9a500
13 changed files with 263 additions and 21 deletions
|
|
@ -16,6 +16,7 @@
|
||||||
<separator/>
|
<separator/>
|
||||||
<menuitem action='Save'/>
|
<menuitem action='Save'/>
|
||||||
<menuitem action='SaveAs'/>
|
<menuitem action='SaveAs'/>
|
||||||
|
<menuitem action='Rename'/>
|
||||||
<menuitem action='Snapshot'/>
|
<menuitem action='Snapshot'/>
|
||||||
<menuitem action='SaveTemplate'/>
|
<menuitem action='SaveTemplate'/>
|
||||||
<menu name='Metadata' action='Metadata'>
|
<menu name='Metadata' action='Metadata'>
|
||||||
|
|
|
||||||
|
|
@ -2120,7 +2120,6 @@ ARDOUR_UI::snapshot_session (bool switch_to_it)
|
||||||
prompter.set_name ("Prompter");
|
prompter.set_name ("Prompter");
|
||||||
prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
|
prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
|
||||||
prompter.set_title (_("Take Snapshot"));
|
prompter.set_title (_("Take Snapshot"));
|
||||||
prompter.set_title (_("Take Snapshot"));
|
|
||||||
prompter.set_prompt (_("Name of new snapshot"));
|
prompter.set_prompt (_("Name of new snapshot"));
|
||||||
|
|
||||||
if (!switch_to_it) {
|
if (!switch_to_it) {
|
||||||
|
|
@ -2185,6 +2184,73 @@ ARDOUR_UI::snapshot_session (bool switch_to_it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ask the user for the name of a new shapshot and then take it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void
|
||||||
|
ARDOUR_UI::rename_session ()
|
||||||
|
{
|
||||||
|
if (!_session) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArdourPrompter prompter (true);
|
||||||
|
string name;
|
||||||
|
|
||||||
|
prompter.set_name ("Prompter");
|
||||||
|
prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
|
||||||
|
prompter.set_title (_("Rename Session"));
|
||||||
|
prompter.set_prompt (_("New session name"));
|
||||||
|
|
||||||
|
again:
|
||||||
|
switch (prompter.run()) {
|
||||||
|
case RESPONSE_ACCEPT:
|
||||||
|
{
|
||||||
|
prompter.get_result (name);
|
||||||
|
|
||||||
|
bool do_rename = (name.length() != 0);
|
||||||
|
|
||||||
|
if (do_rename) {
|
||||||
|
if (name.find ('/') != string::npos) {
|
||||||
|
MessageDialog msg (_("To ensure compatibility with various systems\n"
|
||||||
|
"session names may not contain a '/' character"));
|
||||||
|
msg.run ();
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
if (name.find ('\\') != string::npos) {
|
||||||
|
MessageDialog msg (_("To ensure compatibility with various systems\n"
|
||||||
|
"session names may not contain a '\\' character"));
|
||||||
|
msg.run ();
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_session->rename (name)) {
|
||||||
|
case -1: {
|
||||||
|
MessageDialog msg (_("That name is already in use by another directory/folder. Please try again."));
|
||||||
|
msg.set_position (WIN_POS_MOUSE);
|
||||||
|
msg.run ();
|
||||||
|
goto again;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
MessageDialog msg (_("Renaming this session failed.\nThings could be seriously messed up at this point"));
|
||||||
|
msg.set_position (WIN_POS_MOUSE);
|
||||||
|
msg.run ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ARDOUR_UI::save_state (const string & name, bool switch_to_it)
|
ARDOUR_UI::save_state (const string & name, bool switch_to_it)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -578,6 +578,7 @@ class ARDOUR_UI : public Gtkmm2ext::UI, public ARDOUR::SessionHandlePtr
|
||||||
guint32 last_key_press_time;
|
guint32 last_key_press_time;
|
||||||
|
|
||||||
void snapshot_session (bool switch_to_it);
|
void snapshot_session (bool switch_to_it);
|
||||||
|
void rename_session ();
|
||||||
|
|
||||||
Mixer_UI *mixer;
|
Mixer_UI *mixer;
|
||||||
int create_mixer ();
|
int create_mixer ();
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,10 @@ ARDOUR_UI::install_actions ()
|
||||||
ActionManager::session_sensitive_actions.push_back (act);
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
ActionManager::write_sensitive_actions.push_back (act);
|
ActionManager::write_sensitive_actions.push_back (act);
|
||||||
|
|
||||||
|
act = ActionManager::register_action (main_actions, X_("Rename"), _("Rename..."), sigc::mem_fun(*this, &ARDOUR_UI::rename_session));
|
||||||
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
|
ActionManager::write_sensitive_actions.push_back (act);
|
||||||
|
|
||||||
act = ActionManager::register_action (main_actions, X_("SaveTemplate"), _("Save Template..."), sigc::mem_fun(*this, &ARDOUR_UI::save_template));
|
act = ActionManager::register_action (main_actions, X_("SaveTemplate"), _("Save Template..."), sigc::mem_fun(*this, &ARDOUR_UI::save_template));
|
||||||
ActionManager::session_sensitive_actions.push_back (act);
|
ActionManager::session_sensitive_actions.push_back (act);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
|
||||||
void remove_state (std::string snapshot_name);
|
void remove_state (std::string snapshot_name);
|
||||||
void rename_state (std::string old_name, std::string new_name);
|
void rename_state (std::string old_name, std::string new_name);
|
||||||
void remove_pending_capture_state ();
|
void remove_pending_capture_state ();
|
||||||
|
int rename (const std::string&);
|
||||||
|
|
||||||
static int rename_template (std::string old_name, std::string new_name);
|
static int rename_template (std::string old_name, std::string new_name);
|
||||||
static int delete_template (std::string name);
|
static int delete_template (std::string name);
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ public:
|
||||||
*/
|
*/
|
||||||
SessionDirectory (const PBD::sys::path& session_path);
|
SessionDirectory (const PBD::sys::path& session_path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the root path of this SessionDirectory object
|
||||||
|
*/
|
||||||
|
SessionDirectory& operator= (const std::string& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the absolute path to the root directory of the session
|
* @return the absolute path to the root directory of the session
|
||||||
*/
|
*/
|
||||||
|
|
@ -124,7 +129,7 @@ protected:
|
||||||
const std::vector<PBD::sys::path> sub_directories () const;
|
const std::vector<PBD::sys::path> sub_directories () const;
|
||||||
|
|
||||||
/// The path to the root of the session directory.
|
/// The path to the root of the session directory.
|
||||||
const PBD::sys::path m_root_path;
|
PBD::sys::path m_root_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ARDOUR
|
} // namespace ARDOUR
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,7 @@ framecnt_t
|
||||||
MidiSource::midi_write (MidiRingBuffer<framepos_t>& source, framepos_t source_start, framecnt_t duration)
|
MidiSource::midi_write (MidiRingBuffer<framepos_t>& source, framepos_t source_start, framecnt_t duration)
|
||||||
{
|
{
|
||||||
Glib::Mutex::Lock lm (_lock);
|
Glib::Mutex::Lock lm (_lock);
|
||||||
|
cerr << "MidiSource calling write unlocked\n";
|
||||||
const framecnt_t ret = write_unlocked (source, source_start, duration);
|
const framecnt_t ret = write_unlocked (source, source_start, duration);
|
||||||
_last_write_end += duration;
|
_last_write_end += duration;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -310,7 +311,7 @@ void
|
||||||
MidiSource::mark_streaming_write_completed ()
|
MidiSource::mark_streaming_write_completed ()
|
||||||
{
|
{
|
||||||
if (_model) {
|
if (_model) {
|
||||||
_model->end_write(false);
|
_model->end_write (false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_writing = false;
|
_writing = false;
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ MidiStretch::run (boost::shared_ptr<Region> r, Progress* progress)
|
||||||
new_model->append(ev, Evoral::next_event_id());
|
new_model->append(ev, Evoral::next_event_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
new_model->end_write();
|
new_model->end_write ();
|
||||||
new_model->set_edited(true);
|
new_model->set_edited (true);
|
||||||
|
|
||||||
new_src->copy_interpolation_from (src);
|
new_src->copy_interpolation_from (src);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ SessionDirectory::SessionDirectory (const path& session_path)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SessionDirectory&
|
||||||
|
SessionDirectory::operator= (const std::string& newpath)
|
||||||
|
{
|
||||||
|
m_root_path = newpath;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
SessionDirectory::create ()
|
SessionDirectory::create ()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@
|
||||||
#include <sys/mount.h>
|
#include <sys/mount.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
#include <glibmm.h>
|
#include <glibmm.h>
|
||||||
#include <glibmm/thread.h>
|
#include <glibmm/thread.h>
|
||||||
|
|
||||||
|
|
@ -817,7 +819,7 @@ Session::save_state (string snapshot_name, bool pending, bool switch_to_snapshot
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) {
|
if (::rename (tmp_path.to_string().c_str(), xml_path.to_string().c_str()) != 0) {
|
||||||
error << string_compose (_("could not rename temporary session file %1 to %2"),
|
error << string_compose (_("could not rename temporary session file %1 to %2"),
|
||||||
tmp_path.to_string(), xml_path.to_string()) << endmsg;
|
tmp_path.to_string(), xml_path.to_string()) << endmsg;
|
||||||
sys::remove (tmp_path);
|
sys::remove (tmp_path);
|
||||||
|
|
@ -2950,7 +2952,7 @@ Session::cleanup_sources (CleanupReport& rep)
|
||||||
peakpath, _path, strerror (errno))
|
peakpath, _path, strerror (errno))
|
||||||
<< endmsg;
|
<< endmsg;
|
||||||
/* try to back out */
|
/* try to back out */
|
||||||
rename (newpath.c_str(), _path.c_str());
|
::rename (newpath.c_str(), _path.c_str());
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3654,3 +3656,144 @@ Session::solo_cut_control() const
|
||||||
|
|
||||||
return _solo_cut_control;
|
return _solo_cut_control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Session::rename (const std::string& new_name)
|
||||||
|
{
|
||||||
|
string legal_name = legalize_for_path (new_name);
|
||||||
|
string newpath;
|
||||||
|
string oldstr;
|
||||||
|
string newstr;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
#define RENAME ::rename
|
||||||
|
|
||||||
|
/* Rename:
|
||||||
|
|
||||||
|
* session directory
|
||||||
|
* interchange subdirectory
|
||||||
|
* session file
|
||||||
|
* session history
|
||||||
|
|
||||||
|
* Backup files are left unchanged and not renamed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* pass one: not 100% safe check that the new directory names don't
|
||||||
|
* already exist ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
||||||
|
vector<string> v;
|
||||||
|
|
||||||
|
oldstr = (*i).path;
|
||||||
|
|
||||||
|
/* this is a stupid hack because Glib::path_get_dirname() is
|
||||||
|
* lexical-only, and so passing it /a/b/c/ gives a different
|
||||||
|
* result than passing it /a/b/c ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
|
||||||
|
oldstr = oldstr.substr (0, oldstr.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string base = Glib::path_get_dirname (oldstr);
|
||||||
|
string p = Glib::path_get_basename (oldstr);
|
||||||
|
|
||||||
|
newstr = Glib::build_filename (base, legal_name);
|
||||||
|
|
||||||
|
if (Glib::file_test (newstr, Glib::FILE_TEST_EXISTS)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Session dirs */
|
||||||
|
|
||||||
|
for (vector<space_and_path>::const_iterator i = session_dirs.begin(); i != session_dirs.end(); ++i) {
|
||||||
|
vector<string> v;
|
||||||
|
|
||||||
|
oldstr = (*i).path;
|
||||||
|
|
||||||
|
/* this is a stupid hack because Glib::path_get_dirname() is
|
||||||
|
* lexical-only, and so passing it /a/b/c/ gives a different
|
||||||
|
* result than passing it /a/b/c ...
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (oldstr[oldstr.length()-1] == G_DIR_SEPARATOR) {
|
||||||
|
oldstr = oldstr.substr (0, oldstr.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string base = Glib::path_get_dirname (oldstr);
|
||||||
|
string p = Glib::path_get_basename (oldstr);
|
||||||
|
|
||||||
|
newstr = Glib::build_filename (base, legal_name);
|
||||||
|
|
||||||
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
||||||
|
|
||||||
|
if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
(*_session_dir) = newstr;
|
||||||
|
newpath = newstr;
|
||||||
|
first = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* directory below interchange */
|
||||||
|
|
||||||
|
v.push_back (newstr);
|
||||||
|
v.push_back (interchange_dir_name);
|
||||||
|
v.push_back (p);
|
||||||
|
|
||||||
|
oldstr = Glib::build_filename (v);
|
||||||
|
|
||||||
|
v.clear ();
|
||||||
|
v.push_back (newstr);
|
||||||
|
v.push_back (interchange_dir_name);
|
||||||
|
v.push_back (legal_name);
|
||||||
|
|
||||||
|
newstr = Glib::build_filename (v);
|
||||||
|
|
||||||
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
||||||
|
|
||||||
|
if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* state file */
|
||||||
|
|
||||||
|
oldstr = Glib::build_filename (newpath, _current_snapshot_name) + statefile_suffix;
|
||||||
|
newstr= Glib::build_filename (newpath, legal_name) + statefile_suffix;
|
||||||
|
|
||||||
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
||||||
|
|
||||||
|
if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* history file */
|
||||||
|
|
||||||
|
oldstr = Glib::build_filename (newpath, _current_snapshot_name) + history_suffix;
|
||||||
|
newstr = Glib::build_filename (newpath, legal_name) + history_suffix;
|
||||||
|
|
||||||
|
cerr << "Rename " << oldstr << " => " << newstr << endl;
|
||||||
|
|
||||||
|
if (RENAME (oldstr.c_str(), newstr.c_str()) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_path = newpath;
|
||||||
|
_current_snapshot_name = new_name;
|
||||||
|
_name = new_name;
|
||||||
|
|
||||||
|
set_dirty ();
|
||||||
|
|
||||||
|
/* save state again to get everything just right */
|
||||||
|
|
||||||
|
save_state (_current_snapshot_name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
#undef RENAME
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -551,8 +551,8 @@ SMFSource::load_model (bool lock, bool force_reload)
|
||||||
have_event_id = false;
|
have_event_id = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//_model->end_write (_length_beats, false, true);
|
_model->end_write (_length_beats, false, true);
|
||||||
_model->end_write (false);
|
//_model->end_write (false);
|
||||||
_model->set_edited (false);
|
_model->set_edited (false);
|
||||||
|
|
||||||
_model_iter = _model->begin();
|
_model_iter = _model->begin();
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ public:
|
||||||
|
|
||||||
void start_write();
|
void start_write();
|
||||||
bool writing() const { return _writing; }
|
bool writing() const { return _writing; }
|
||||||
void end_write(bool delete_stuck=false);
|
void end_write (Time when=0, bool delete_stuck=false, bool resolve=false);
|
||||||
|
|
||||||
void append(const Event<Time>& ev, Evoral::event_id_t evid);
|
void append(const Event<Time>& ev, Evoral::event_id_t evid);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -623,7 +623,7 @@ Sequence<Time>::start_write()
|
||||||
*/
|
*/
|
||||||
template<typename Time>
|
template<typename Time>
|
||||||
void
|
void
|
||||||
Sequence<Time>::end_write (bool delete_stuck)
|
Sequence<Time>::end_write (Time when, bool delete_stuck, bool resolve)
|
||||||
{
|
{
|
||||||
WriteLock lock(write_lock());
|
WriteLock lock(write_lock());
|
||||||
|
|
||||||
|
|
@ -631,16 +631,33 @@ Sequence<Time>::end_write (bool delete_stuck)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resolve) {
|
||||||
|
assert (when != 0);
|
||||||
|
assert (!delete_stuck);
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes)\n", this, _notes.size()));
|
DEBUG_TRACE (DEBUG::Sequence, string_compose ("%1 : end_write (%2 notes)\n", this, _notes.size()));
|
||||||
|
|
||||||
if (!_percussive && delete_stuck) {
|
if (!_percussive) {
|
||||||
|
|
||||||
for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
|
for (typename Notes::iterator n = _notes.begin(); n != _notes.end() ;) {
|
||||||
typename Notes::iterator next = n;
|
typename Notes::iterator next = n;
|
||||||
++next;
|
++next;
|
||||||
|
|
||||||
if ((*n)->length() == 0) {
|
if ((*n)->length() == 0) {
|
||||||
|
if (delete_stuck) {
|
||||||
cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
|
cerr << "WARNING: Stuck note lost: " << (*n)->note() << endl;
|
||||||
_notes.erase(n);
|
_notes.erase(n);
|
||||||
|
} else if (resolve) {
|
||||||
|
if (when <= (*n)->time()) {
|
||||||
|
cerr << "WARNING: Stuck note resolution - end time @ "
|
||||||
|
<< when << " is before note on: " << (**n) << endl;
|
||||||
|
_notes.erase (*n);
|
||||||
|
} else {
|
||||||
|
(*n)->set_length (when - (*n)->time());
|
||||||
|
cerr << "WARNING: resolved note-on with no note-off to generate " << (**n) << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
n = next;
|
n = next;
|
||||||
|
|
@ -648,10 +665,6 @@ Sequence<Time>::end_write (bool delete_stuck)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
for (int i = 0; i < 16; ++i) {
|
||||||
if (!_write_notes[i].empty()) {
|
|
||||||
cerr << "WARNING: Sequence<Time>::end_write: Channel " << i << " has "
|
|
||||||
<< _write_notes[i].size() << " stuck notes" << endl;
|
|
||||||
}
|
|
||||||
_write_notes[i].clear();
|
_write_notes[i].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -925,7 +938,7 @@ Sequence<Time>::append_note_off_unlocked (NotePtr note)
|
||||||
nn->set_off_velocity (note->velocity());
|
nn->set_off_velocity (note->velocity());
|
||||||
|
|
||||||
_write_notes[note->channel()].erase(n);
|
_write_notes[note->channel()].erase(n);
|
||||||
DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note, length: %1\n", nn->length()));
|
DEBUG_TRACE (DEBUG::Sequence, string_compose ("resolved note @ %2 length: %1\n", nn->length(), nn->time()));
|
||||||
resolved = true;
|
resolved = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue