summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--muse2/ChangeLog6
-rw-r--r--muse2/muse/wave.cpp64
-rw-r--r--muse2/muse/wave.h8
-rw-r--r--muse2/muse/waveedit/wavecanvas.cpp169
-rw-r--r--muse2/muse/waveedit/wavecanvas.h2
-rw-r--r--muse2/muse/widgets/CMakeLists.txt3
-rw-r--r--muse2/muse/widgets/copy_on_write.cpp39
-rw-r--r--muse2/muse/widgets/copy_on_write.h42
-rw-r--r--muse2/muse/widgets/copy_on_write_base.ui102
-rw-r--r--muse2/muse/widgets/utils.cpp33
-rw-r--r--muse2/muse/widgets/utils.h4
11 files changed, 451 insertions, 21 deletions
diff --git a/muse2/ChangeLog b/muse2/ChangeLog
index 6177d2c0..8c2a1751 100644
--- a/muse2/ChangeLog
+++ b/muse2/ChangeLog
@@ -1,3 +1,9 @@
+17.09.2012:
+ * Introducing: Copy On Write for waves. (Tim)
+ It asks to copy, to the Project Directory, any wave to be modified which is either
+ not writable or is shared with another independent (non-clone) wave event.
+ TODO: Improve by asking even for writable waves outside the Project Directory
+ (add a 'copy' checkbox column to the dialog, settable only under certain conditions).
16.09.2012:
- Fixed allocation error with drag&drop of plugins (rj)
- Fixed close menu alternative in Instrument editor (rj)
diff --git a/muse2/muse/wave.cpp b/muse2/muse/wave.cpp
index 0657bbe0..c2969f6d 100644
--- a/muse2/muse/wave.cpp
+++ b/muse2/muse/wave.cpp
@@ -42,6 +42,8 @@
#include "audio.h"
///#include "sig.h"
#include "al/sig.h"
+#include "part.h"
+#include "track.h"
//#define WAVE_DEBUG
//#define WAVE_DEBUG_PRC
@@ -393,11 +395,21 @@ QString SndFile::path() const
return finfo->filePath();
}
+QString SndFile::canonicalPath() const
+ {
+ return finfo->canonicalFilePath();
+ }
+
QString SndFile::dirPath() const
{
return finfo->absolutePath();
}
+QString SndFile::canonicalDirPath() const
+ {
+ return finfo->canonicalPath();
+ }
+
QString SndFile::name() const
{
return finfo->fileName();
@@ -789,6 +801,58 @@ void SndFile::applyUndoFile(const QString& original, const QString& tmpfile, uns
MusEGlobal::audio->msgIdle(false);
}
+//---------------------------------------------------------
+// checkCopyOnWrite
+//---------------------------------------------------------
+
+bool SndFile::checkCopyOnWrite()
+{
+ QString path_this = canonicalPath();
+ if(path_this.isEmpty())
+ return false;
+
+ bool fwrite = finfo->isWritable();
+
+ // No exceptions: Even if this wave event is a clone, if it ain't writeable we gotta copy the wave.
+ if(!fwrite)
+ return true;
+
+ // Count the number of non-clone part wave events (including possibly this one) using this file.
+ // Not much choice but to search all active wave events - the sndfile ref count is not the solution for this...
+ int use_count = 0;
+ WaveTrackList* wtl = MusEGlobal::song->waves();
+ for(ciTrack it = wtl->begin(); it != wtl->end(); ++it)
+ {
+ PartList* pl = (*it)->parts();
+ for(ciPart ip = pl->begin(); ip != pl->end(); ++ip)
+ {
+ EventList* el = ip->second->events();
+ // We are looking for active independent non-clone parts
+ if(el->arefCount() > 1)
+ continue;
+ for(ciEvent ie = el->begin(); ie != el->end(); ++ie)
+ {
+ if(ie->second.type() != Wave)
+ continue;
+ const Event& ev = ie->second;
+ if(ev.empty())
+ continue;
+ const SndFileR sf = ev.sndFile();
+ QString path = sf.canonicalPath();
+ if(path.isEmpty())
+ continue;
+ if(path == path_this)
+ ++use_count;
+ // If more than one non-clone part wave event is using the file, signify that the caller should make a copy of it.
+ if(use_count > 1)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
// DELETETHIS 170
#if 0
//---------------------------------------------------------
diff --git a/muse2/muse/wave.h b/muse2/muse/wave.h
index c9c7061d..d144d342 100644
--- a/muse2/muse/wave.h
+++ b/muse2/muse/wave.h
@@ -64,7 +64,7 @@ class SndFile {
bool openFlag;
bool writeFlag;
size_t readInternal(int srcChannels, float** dst, size_t n, bool overwrite, float *buffer);
-
+
protected:
int refCount;
@@ -86,10 +86,13 @@ class SndFile {
bool isOpen() const { return openFlag; }
bool isWritable() const { return writeFlag; }
void update();
+ bool checkCopyOnWrite(); //!< check if the file should be copied before writing to it
QString basename() const; //!< filename without extension
QString dirPath() const; //!< path
+ QString canonicalDirPath() const; //!< path, resolved (no symlinks or . .. etc)
QString path() const; //!< path with filename
+ QString canonicalPath() const; //!< path with filename, resolved (no symlinks or . .. etc)
QString name() const; //!< filename
unsigned samples() const;
@@ -143,10 +146,13 @@ class SndFileR {
bool isOpen() const { return sf->isOpen(); }
bool isWritable() const { return sf->isWritable(); }
void update() { sf->update(); }
+ bool checkCopyOnWrite() { return sf->checkCopyOnWrite(); };
QString basename() const { return sf->basename(); }
QString dirPath() const { return sf->dirPath(); }
+ QString canonicalDirPath() const { return sf->canonicalDirPath(); }
QString path() const { return sf->path(); }
+ QString canonicalPath() const { return sf->canonicalPath(); }
QString name() const { return sf->name(); }
unsigned samples() const { return sf->samples(); }
diff --git a/muse2/muse/waveedit/wavecanvas.cpp b/muse2/muse/waveedit/wavecanvas.cpp
index 2139b647..8c1b0012 100644
--- a/muse2/muse/waveedit/wavecanvas.cpp
+++ b/muse2/muse/waveedit/wavecanvas.cpp
@@ -33,6 +33,7 @@
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
+#include <QFile>
#include <QInputDialog>
#include <QMouseEvent>
#include <QList>
@@ -48,6 +49,7 @@
#include <errno.h>
#include <sys/wait.h>
+#include "app.h"
#include "xml.h"
#include "wavecanvas.h"
#include "event.h"
@@ -64,6 +66,7 @@
#include "fastlog.h"
#include "utils.h"
#include "tools.h"
+#include "copy_on_write.h"
namespace MusEGui {
@@ -1885,8 +1888,7 @@ void WaveCanvas::cmd(int cmd)
// modifyWarnedYet = true;
// if(QMessageBox::warning(this, QString("Muse"),
// tr("Warning! Muse currently operates directly on the sound file.\n"
- // "Undo is supported, but NOT after exit, WITH OR WITHOUT A SAVE!\n"
- // "If you are stuck, try deleting the associated .wca file and reloading."), tr("&Ok"), tr("&Cancel"),
+ // "Undo is supported, but NOT after exit, WITH OR WITHOUT A SAVE!"), tr("&Ok"), tr("&Cancel"),
// QString::null, 0, 1 ) != 0)
// return;
//}
@@ -1947,7 +1949,7 @@ MusECore::WaveSelectionList WaveCanvas::getSelection(unsigned startpos, unsigned
//printf("Event data affected: %d->%d filename:%s\n", sx, ex, file.name().toLatin1().constData());
MusECore::WaveEventSelection s;
- s.file = file;
+ s.event = event;
s.startframe = sx;
s.endframe = ex+1;
//printf("sx=%d ex=%d\n",sx,ex);
@@ -1964,24 +1966,155 @@ MusECore::WaveSelectionList WaveCanvas::getSelection(unsigned startpos, unsigned
//---------------------------------------------------------
void WaveCanvas::modifySelection(int operation, unsigned startpos, unsigned stoppos, double paramA)
{
- MusEGlobal::song->startUndo();
+ if (operation == PASTE) {
+ // we need to redefine startpos and stoppos
+ if (copiedPart =="")
+ return;
+ MusECore::SndFile pasteFile(copiedPart);
+ pasteFile.openRead();
+ startpos = pos[0];
+ stoppos = startpos+ pasteFile.samples(); // possibly this is wrong if there are tempo changes
+ pasteFile.close();
+ pos[0]=stoppos;
+ }
- if (operation == PASTE) {
- // we need to redefine startpos and stoppos
- if (copiedPart =="")
- return;
- MusECore::SndFile pasteFile(copiedPart);
- pasteFile.openRead();
- startpos = pos[0];
- stoppos = startpos+ pasteFile.samples(); // possibly this is wrong if there are tempo changes
- pasteFile.close();
- pos[0]=stoppos;
- }
-
- MusECore::WaveSelectionList selection = getSelection(startpos, stoppos);
+ //
+ // Copy on Write: Check if some files need to be copied, either because they are not
+ // writable, or more than one independent (non-clone) wave event shares a wave file.
+ //
+
+ MusECore::WaveSelectionList selection = getSelection(startpos, stoppos);
+ std::vector<MusECore::SndFileR> copy_files_proj_dir;
+ for(MusECore::iWaveSelection i = selection.begin(); i != selection.end(); i++)
+ {
+ MusECore::WaveEventSelection w = *i;
+ MusECore::SndFileR file = w.event.sndFile();
+ if(file.checkCopyOnWrite())
+ {
+ std::vector<MusECore::SndFileR>::iterator i = copy_files_proj_dir.begin();
+ for( ; i != copy_files_proj_dir.end(); ++i)
+ {
+ if(i->canonicalPath() == file.canonicalPath())
+ break;
+ }
+ if(i == copy_files_proj_dir.end())
+ copy_files_proj_dir.push_back(file);
+ }
+ }
+ if(!copy_files_proj_dir.empty())
+ {
+ CopyOnWriteDialog* dlg = new CopyOnWriteDialog();
+ for(std::vector<MusECore::SndFileR>::iterator i = copy_files_proj_dir.begin(); i != copy_files_proj_dir.end(); ++i)
+ {
+ qint64 sz = QFile(i->canonicalPath()).size();
+ QString s;
+ if(sz > 1048576)
+ s += QString::number(sz / 1048576) + "MB ";
+ else
+ if(sz > 1024)
+ s += QString::number(sz / 1024) + "KB ";
+ else
+ s += QString::number(sz) + "B ";
+ s += i->canonicalPath();
+ dlg->addProjDirFile(s);
+ }
+ int rv = dlg->exec();
+ delete dlg;
+ if(rv != QDialog::Accepted)
+ return;
+ // Has a project been created yet?
+ if(MusEGlobal::museProject == MusEGlobal::museProjectInitPath) // && MusEGlobal::config.useProjectSaveDialog
+ {
+ // No project, we need to create one.
+ if(!MusEGlobal::muse->saveAs())
+ return; // No project, don't want to copy without one.
+ //setFocus(); // For some reason focus is given away to Arranger
+ }
+ for(MusECore::iWaveSelection i = selection.begin(); i != selection.end(); i++)
+ {
+ MusECore::WaveEventSelection w = *i;
+ MusECore::SndFileR file = w.event.sndFile();
+ if(!file.checkCopyOnWrite()) // Make sure to re-check
+ continue;
+ QString filePath = MusEGlobal::museProject + QString("/") + file.name();
+ QString newFilePath;
+ if(MusECore::getUniqueFileName(filePath, newFilePath))
+ {
+ {
+ QFile qf(file.canonicalPath());
+ if(!qf.copy(newFilePath)) // Copy the file
+ {
+ printf("MusE Error: Could not copy to new sound file (file exists?): %s\n", newFilePath.toLatin1().constData());
+ continue; // Let's not overwrite an existing file
+ }
+ }
+ QFile nqf(newFilePath);
+ // Need to make sure some permissions are set...
+ QFile::Permissions pm = nqf.permissions();
+ if(!(pm & QFile::ReadOwner))
+ {
+ pm |= QFile::ReadOwner;
+ if(!nqf.setPermissions(pm))
+ {
+ printf("MusE Error: Could not set read owner permissions on new sound file: %s\n", newFilePath.toLatin1().constData());
+ continue;
+ }
+ }
+ if(!(pm & QFile::WriteOwner))
+ {
+ pm |= QFile::WriteOwner;
+ if(!nqf.setPermissions(pm))
+ {
+ printf("MusE Error: Could not set write owner permissions on new sound file: %s\n", newFilePath.toLatin1().constData());
+ continue;
+ }
+ }
+ if(!(pm & QFile::ReadUser))
+ {
+ pm |= QFile::ReadUser;
+ if(!nqf.setPermissions(pm))
+ {
+ printf("MusE Error: Could not set read user permissions on new sound file: %s\n", newFilePath.toLatin1().constData());
+ continue;
+ }
+ }
+ if(!(pm & QFile::WriteUser))
+ {
+ pm |= QFile::WriteUser;
+ if(!nqf.setPermissions(pm))
+ {
+ printf("MusE Error: Could not set write user permissions on new sound file: %s\n", newFilePath.toLatin1().constData());
+ continue;
+ }
+ }
+ MusECore::SndFile* newSF = new MusECore::SndFile(newFilePath);
+ MusECore::SndFileR newSFR(newSF); // Create a sndFileR for the new file
+ if(newSFR.openRead())
+ {
+ printf("MusE Error: Could not open new sound file: %s\n", newSFR.canonicalPath().toLatin1().constData());
+ continue; // newSF will be deleted when newSFR goes out of scope and is deleted
+ }
+ MusEGlobal::audio->msgIdle(true);
+ w.event.sndFile().close(); // Close the old file.
+ // NOTE: For now, don't bother providing undo for this. Reason: If the user undoes
+ // and then modifies again, it will prompt to create new copies each time. There is
+ // no mechanism ("touched"?) to tell if an existing copy would be suitable to just 'drop in'.
+ // It would help if we deleted the wave file copies upon undo, but not too crazy about that.
+ // So since the copy has already been created and "there it is", we might as well use it.
+ // It means that events and even undo items BEFORE this operation will point to this
+ // NEW wave file (as if they always did). It also means the user CANNOT change back
+ // to the old file... Oh well, this IS Copy On Write.
+ // FIXME: Find a conceptual way to make undo work with or without deleting the copies.
+ w.event.setSndFile(newSFR); // Set the new file.
+ MusEGlobal::audio->msgIdle(false);
+ }
+ }
+ }
+
+ MusEGlobal::song->startUndo();
for (MusECore::iWaveSelection i = selection.begin(); i != selection.end(); i++) {
MusECore::WaveEventSelection w = *i;
- MusECore::SndFileR& file = w.file;
+ MusECore::SndFileR file = w.event.sndFile();
unsigned sx = w.startframe;
unsigned ex = w.endframe;
unsigned file_channels = file.channels();
diff --git a/muse2/muse/waveedit/wavecanvas.h b/muse2/muse/waveedit/wavecanvas.h
index 4a6ae9f9..f9553845 100644
--- a/muse2/muse/waveedit/wavecanvas.h
+++ b/muse2/muse/waveedit/wavecanvas.h
@@ -46,7 +46,7 @@ class WavePart;
class WaveTrack;
struct WaveEventSelection {
- SndFileR file;
+ Event event;
unsigned startframe;
unsigned endframe;
};
diff --git a/muse2/muse/widgets/CMakeLists.txt b/muse2/muse/widgets/CMakeLists.txt
index 88706339..0e9d369d 100644
--- a/muse2/muse/widgets/CMakeLists.txt
+++ b/muse2/muse/widgets/CMakeLists.txt
@@ -40,6 +40,7 @@ QT4_WRAP_CPP (widget_mocs
comboQuant.h
combobox.h
comment.h
+ copy_on_write.h
ctrlcombo.h
custom_widget_actions.h
dentry.h
@@ -114,6 +115,7 @@ file (GLOB widgets_ui_files
cliplisteditorbase.ui
commentbase.ui
configmidifilebase.ui
+ copy_on_write_base.ui
didyouknow.ui
editnotedialogbase.ui
editsysexdialogbase.ui
@@ -153,6 +155,7 @@ file (GLOB widgets_source_files
comboQuant.cpp
combobox.cpp
comment.cpp
+ copy_on_write.cpp
ctrlcombo.cpp
custom_widget_actions.cpp
dentry.cpp
diff --git a/muse2/muse/widgets/copy_on_write.cpp b/muse2/muse/widgets/copy_on_write.cpp
new file mode 100644
index 00000000..53e79a91
--- /dev/null
+++ b/muse2/muse/widgets/copy_on_write.cpp
@@ -0,0 +1,39 @@
+//=========================================================
+// MusE
+// Linux Music Editor
+// copy_on_write.cpp
+// (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot net)
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//
+//=========================================================
+
+#include "copy_on_write.h"
+
+namespace MusEGui {
+
+CopyOnWriteDialog::CopyOnWriteDialog(QWidget* parent): QDialog(parent)
+{
+ setupUi(this);
+
+}
+
+void CopyOnWriteDialog::addProjDirFile(const QString& s)
+{
+ projDirList->addItem(s);
+}
+
+
+} // namespace MusEGui \ No newline at end of file
diff --git a/muse2/muse/widgets/copy_on_write.h b/muse2/muse/widgets/copy_on_write.h
new file mode 100644
index 00000000..f06274eb
--- /dev/null
+++ b/muse2/muse/widgets/copy_on_write.h
@@ -0,0 +1,42 @@
+//=========================================================
+// MusE
+// Linux Music Editor
+// copy_on_write.h
+// (C) Copyright 2012 Tim E. Real (terminator356 on users dot sourceforge dot net)
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; version 2 of
+// the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//
+//=========================================================
+
+#ifndef __COPY_ON_WRITE_H__
+#define __COPY_ON_WRITE_H__
+
+#include "ui_copy_on_write_base.h"
+
+namespace MusEGui {
+
+class CopyOnWriteDialog : public QDialog, public Ui::CopyOnWriteDialogBase
+{
+ Q_OBJECT
+
+public:
+ CopyOnWriteDialog(QWidget* parent = 0);
+ void addProjDirFile(const QString&);
+};
+
+} // namespace MusEGui
+
+
+#endif
diff --git a/muse2/muse/widgets/copy_on_write_base.ui b/muse2/muse/widgets/copy_on_write_base.ui
new file mode 100644
index 00000000..d59b6325
--- /dev/null
+++ b/muse2/muse/widgets/copy_on_write_base.ui
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CopyOnWriteDialogBase</class>
+ <widget class="QDialog" name="CopyOnWriteDialogBase">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>431</width>
+ <height>344</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Copy Wave Files</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Some wave files will be copied to the Project Directory,
+either because they are not writable or because more
+than one independent Wave Event shares them.
+Multiple copies will be made in some cases.
+
+If no Project has been created yet, you will be asked to,
+giving another chance to cancel.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>These files will be copied to the Project Directory:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="projDirList"/>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CopyOnWriteDialogBase</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>CopyOnWriteDialogBase</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/muse2/muse/widgets/utils.cpp b/muse2/muse/widgets/utils.cpp
index e9fbc9e3..ab1d74fd 100644
--- a/muse2/muse/widgets/utils.cpp
+++ b/muse2/muse/widgets/utils.cpp
@@ -36,6 +36,7 @@
#include <QMimeData>
#include <QPainter>
#include <QPointF>
+#include <QFileInfo>
#include "audio.h"
#include "audiodev.h"
@@ -566,4 +567,36 @@ int get_paste_len()
return end_tick - begin_tick;
}
+//---------------------------------------------------------
+// getUniqueFileName
+// Sets newAbsFilePath to origFilepath or a new version if found
+// Return true if success
+//---------------------------------------------------------
+
+bool getUniqueFileName(const QString& origFilepath, QString& newAbsFilePath)
+ {
+ QFileInfo fi(origFilepath);
+ if(!fi.exists())
+ {
+ newAbsFilePath = fi.absoluteFilePath();
+ return true;
+ }
+
+ QString pre = fi.absolutePath() + QString('/') + fi.baseName() + QString('_');
+ QString post = QString('.') + fi.completeSuffix();
+ for(int i = 1; i < 100000; ++i)
+ {
+ fi.setFile(pre + QString::number(i) + post);
+ if(!fi.exists())
+ {
+ newAbsFilePath = fi.absoluteFilePath();
+ return true;
+ }
+ }
+
+ printf("Could not find a suitable filename (more than 100000 files based on %s - clean up!\n", origFilepath.toLatin1().constData());
+ return false;
+ }
+
+
} // namespace MusECore
diff --git a/muse2/muse/widgets/utils.h b/muse2/muse/widgets/utils.h
index 07899a84..a558e73c 100644
--- a/muse2/muse/widgets/utils.h
+++ b/muse2/muse/widgets/utils.h
@@ -58,7 +58,9 @@ extern QPainterPath roundedPath(int x, int y, int w, int h, int xrad, int yrad,
extern QIcon colorRect(const QColor& color, int width, int height);
extern int get_paste_len();
-} // namespace MusECores
+extern bool getUniqueFileName(const QString& filename, QString& newAbsFilePath);
+
+} // namespace MusECore
#endif