Added support for "Delete session" in Kate session panel.

Signed-off-by: Michele Calgaro <michele.calgaro@yahoo.it>
pull/2/head
Michele Calgaro 8 years ago
parent 2675b2147b
commit 127ac19145

@ -157,7 +157,7 @@ Shows license information.
<listitem> <listitem>
<para> <para>
Starts &kate; with the session <parameter>name</parameter>. If the session does not exist, Starts &kate; with the session <parameter>name</parameter>. If the session does not exist,
the user is prompted whether to start a new session or not.<p> a new session with the specified name is created.<p>
If a &kate; instance running the specified session already exists, the specified files are If a &kate; instance running the specified session already exists, the specified files are
loaded in that instance. loaded in that instance.
</para> </para>
@ -522,8 +522,7 @@ session chooser, the specified session is loaded prior to the files specified
on the command line. To open files from the command line in a new, unnamed on the command line. To open files from the command line in a new, unnamed
session, configure kate to start a new session per default in the session page of session, configure kate to start a new session per default in the session page of
the configuration dialog or use <option>--start</option> with an empty string: the configuration dialog or use <option>--start</option> with an empty string:
<replaceable>''</replaceable> (you will be prompted whether to create a new session <replaceable>''</replaceable>.</para>
or not).</para>
<para>Since &kate; 2.5.1 the <acronym>PID</acronym> of the current instance is <para>Since &kate; 2.5.1 the <acronym>PID</acronym> of the current instance is
exported to the environment variable <envar>KATE_PID</envar>. When opening files exported to the environment variable <envar>KATE_PID</envar>. When opening files

@ -162,7 +162,8 @@ bool KateApp::startupKate()
{ {
if (m_args->isSet("start")) if (m_args->isSet("start"))
{ {
// the user has specified the session to open // the user has specified the session to open. If the session does not exist,
// a new session with the specified name will be created
TQCString sessName = m_args->getOption("start"); TQCString sessName = m_args->getOption("start");
int sessId = sessionManager()->getSessionIdFromName(sessName); int sessId = sessionManager()->getSessionIdFromName(sessName);
if (sessId != KateSessionManager::INVALID_SESSION) if (sessId != KateSessionManager::INVALID_SESSION)
@ -171,19 +172,7 @@ bool KateApp::startupKate()
} }
else else
{ {
int msgres = KMessageBox::warningYesNo(0, i18n("<p>The session '%1' could not be found." sessionManager()->newSession(sessName, true);
"<p>Do you want to start a new session?").arg(sessName),
i18n("Session not found!"));
if (msgres == KMessageBox::Yes)
{
sessionManager()->newSession(TQString::null, true);
}
else
{
// Kate will exit now and notify it is done
TDEStartupInfo::appStarted(startupId());
return false;
}
} }
} }
else else
@ -203,7 +192,7 @@ bool KateApp::startupKate()
} }
else if (startupOption == "new") else if (startupOption == "new")
{ {
sessionManager()->newSession(TQString::null, true); sessionManager()->newSession();
} }
else // startupOption == "manual" else // startupOption == "manual"
{ {
@ -212,14 +201,14 @@ bool KateApp::startupKate()
switch (result) switch (result)
{ {
case KateSessionChooser::RESULT_OPEN_NEW: case KateSessionChooser::RESULT_OPEN_NEW:
sessionManager()->newSession(TQString::null, true); sessionManager()->newSession();
break; break;
case KateSessionChooser::RESULT_OPEN_EXISTING: case KateSessionChooser::RESULT_OPEN_EXISTING:
if (!m_sessionManager->activateSession(chooser->getSelectedSessionId())) if (!m_sessionManager->activateSession(chooser->getSelectedSessionId()))
{ {
// Open a new session in case of error // Open a new session in case of error
sessionManager()->newSession(TQString::null, true); sessionManager()->newSession();
} }
break; break;

@ -1,4 +1,6 @@
/* This file is part of the KDE project /* This file is part of the KDE project
Copyright (C) 2015-2016 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit>
partially based on previous work from
Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org> Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org>
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
@ -36,6 +38,7 @@
#include <tdepopupmenu.h> #include <tdepopupmenu.h>
#include <tqdir.h> #include <tqdir.h>
#include <tqfile.h>
#include <tqlabel.h> #include <tqlabel.h>
#include <tqlayout.h> #include <tqlayout.h>
#include <tqvbox.h> #include <tqvbox.h>
@ -268,9 +271,9 @@ void KateSession::activate()
Kate::Document::setOpenErrorDialogsActivated(true); Kate::Document::setOpenErrorDialogsActivated(true);
} }
//END Kate session //END Kate session
//BEGIN KateSessionManager //BEGIN KateSessionManager
//------------------------------------ //------------------------------------
KateSessionManager *KateSessionManager::ksm_instance = NULL; KateSessionManager *KateSessionManager::ksm_instance = NULL;
@ -300,7 +303,7 @@ KateSessionManager::KateSessionManager() :
m_sessionsCount = m_config->readNumEntry(KSM_SESSIONS_COUNT, 0); m_sessionsCount = m_config->readNumEntry(KSM_SESSIONS_COUNT, 0);
//FIXME : if m_sessionsCount == 0, create session list from existing session files //FIXME : if m_sessionsCount == 0, create session list from existing session files
m_activeSessionId = m_config->readNumEntry(KSM_ACTIVE_SESSION_ID, 0); m_activeSessionId = m_config->readNumEntry(KSM_ACTIVE_SESSION_ID, 0);
for (int i=0; i<m_sessionsCount; ++i) for (int i=0; i<m_sessionsCount; ++i)
{ {
TQString urlStr = m_config->readEntry(TQString("URL_%1").arg(i)); TQString urlStr = m_config->readEntry(TQString("URL_%1").arg(i));
if (!urlStr.isEmpty() && TDEGlobal::dirs()->exists(urlStr)) if (!urlStr.isEmpty() && TDEGlobal::dirs()->exists(urlStr))
@ -417,7 +420,7 @@ bool KateSessionManager::activateSession(int sessionId, bool saveCurr)
if (!KateApp::self()->activeMainWindow()->queryClose_internal()) if (!KateApp::self()->activeMainWindow()->queryClose_internal())
return false; return false;
} }
if (saveCurr) if (saveCurr && m_activeSessionId != INVALID_SESSION)
{ {
m_sessions[m_activeSessionId]->save(true); m_sessions[m_activeSessionId]->save(true);
} }
@ -440,7 +443,7 @@ int KateSessionManager::newSession(const TQString &sessionName, bool activate)
emit sessionCreated(newSessionId); emit sessionCreated(newSessionId);
if (activate) if (activate)
{ {
activateSession(newSessionId, true); activateSession(newSessionId, m_activeSessionId != INVALID_SESSION);
} }
return newSessionId; return newSessionId;
} }
@ -461,11 +464,44 @@ void KateSessionManager::slotNewSession()
{ {
// FIXME: allow the user to enter a session name first // FIXME: allow the user to enter a session name first
// FIXME: allow the user to specify whether to switch to the new session or not // FIXME: allow the user to specify whether to switch to the new session or not
newSession(TQString::null, true); newSession();
} }
//-------------------------------------------
bool KateSessionManager::deleteSession(int sessionId)
{
if (sessionId < 0 || sessionId >= m_sessionsCount)
return false;
// delete session file if it exists
const TQString &filename = m_sessions[sessionId]->getSessionFilename();
if (filename != TQString::null && TQFile::exists(filename))
{
TQFile::remove(filename);
}
// delete session
m_sessions.remove(sessionId); // this also deletes the KateSession item since auto-deletion is enabled
--m_sessionsCount;
if (m_activeSessionId > sessionId)
{
--m_activeSessionId;
}
else if (m_activeSessionId == sessionId)
{
m_activeSessionId = INVALID_SESSION;
}
emit sessionDeleted(sessionId);
// if the active session was deleted, create a new unnamed session and activate it
if (m_activeSessionId == INVALID_SESSION)
{
newSession();
}
return true;
}
//END KateSessionManager //END KateSessionManager
//BEGIN KateSessionChooser //BEGIN KateSessionChooser
//------------------------------------------- //-------------------------------------------
KateSessionChooser::KateSessionChooser(TQWidget *parent) KateSessionChooser::KateSessionChooser(TQWidget *parent)
@ -545,7 +581,6 @@ void KateSessionChooser::slotSelectionChanged()
{ {
enableButton(KDialogBase::User2, m_sessionList->selectedItem()); enableButton(KDialogBase::User2, m_sessionList->selectedItem());
} }
//END KateSessionChooser //END KateSessionChooser
@ -556,12 +591,6 @@ void KateSessionChooser::slotSelectionChanged()
//------------------------------------ //------------------------------------
//------------------------------------ //------------------------------------
//------------------------------------ //------------------------------------
// Michele - to be removed with OldKateSession
bool operator<( const OldKateSession::Ptr& a, const OldKateSession::Ptr& b )
{
return a->sessionName().lower() < b->sessionName().lower();
}
OldKateSession::OldKateSession (OldKateSessionManager *manager, const TQString &fileName, const TQString &name) OldKateSession::OldKateSession (OldKateSessionManager *manager, const TQString &fileName, const TQString &name)
: m_sessionFileRel (fileName) : m_sessionFileRel (fileName)
, m_sessionName (name) , m_sessionName (name)

@ -1,4 +1,6 @@
/* This file is part of the KDE project /* This file is part of the KDE project
Copyright (C) 2015-2016 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit>
partially based on previous work from
Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org> Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org>
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
@ -32,15 +34,12 @@
#include <tqstringlist.h> #include <tqstringlist.h>
class KateViewSpace; class KateViewSpace;
class OldKateSessionManager; // Michele - to be removed with OldKateSession
class KDirWatch; class KDirWatch;
class TDEListView;
class KPushButton; class KPushButton;
class TDEListView;
class TQCheckBox; class TQCheckBox;
//BEGIN KateSession //BEGIN KateSession
class KateSession class KateSession
{ {
@ -82,9 +81,9 @@ class KateSession
void setReadOnly(bool readOnly); void setReadOnly(bool readOnly);
/** /**
* @return the session filename * @return the session filename if available, otherwise the null string
*/ */
const TQString& getSessionFilename() const { return m_filename; } const TQString& getSessionFilename() const { return m_isFullName ? m_filename : TQString::null; }
/** /**
* @return the number of documents in the session * @return the number of documents in the session
@ -114,17 +113,18 @@ class KateSession
TQString m_sessionName; TQString m_sessionName;
TQString m_filename; TQString m_filename;
bool m_isFullName; // true -> m_filename is a full filename bool m_isFullName; // true -> m_filename is a full filename
// false -> m_filename is a folder name. // false -> m_filename is a folder name.
bool m_readOnly; bool m_readOnly;
int m_docCount; // number of documents in the session int m_docCount; // number of documents in the session // FIXME remove and use m_documents.count()
TQStringList m_documents; // document URLs TQStringList m_documents; // document URLs
KSimpleConfig *m_config; // session config KSimpleConfig *m_config; // session config
}; };
//END KateSession //END KateSession
//BEGIN KateSessionManager //BEGIN KateSessionManager
//------------------------------------
//FIXME (advanced) //FIXME (advanced)
//There should be only one session manager regardless of how many instances of Kate are running. //There should be only one session manager regardless of how many instances of Kate are running.
//Changes should propagate to all session panels. Different Kate instances should run different //Changes should propagate to all session panels. Different Kate instances should run different
@ -133,7 +133,6 @@ class KateSession
//This would allow a safe use of multiple Kate instances without overwriting session information //This would allow a safe use of multiple Kate instances without overwriting session information
//among them. Currently the last instance to be closed will overwrite the information previously //among them. Currently the last instance to be closed will overwrite the information previously
//saved by other Kate instances. //saved by other Kate instances.
//------------------------------------
class KateSessionManager : public TQObject class KateSessionManager : public TQObject
{ {
Q_OBJECT Q_OBJECT
@ -219,6 +218,14 @@ class KateSessionManager : public TQObject
*/ */
void saveActiveSession() { m_sessions[m_activeSessionId]->save(true); } void saveActiveSession() { m_sessions[m_activeSessionId]->save(true); }
/**
* Delete the specified session
* @param sessionId the id of the session to delete
* @return whether the session has been deleted or not
*/
bool deleteSession(int sessionId);
signals: signals:
/** /**
* Emitted once a session has been activated * Emitted once a session has been activated
@ -229,9 +236,15 @@ class KateSessionManager : public TQObject
/** /**
* Emitted once a session has been created * Emitted once a session has been created
* @param newSessionId the id of the new session * @param sessionId the id of the new session
*/
void sessionCreated(int sessionId);
/**
* Emitted once a session has been deleted
* @param sessionId the id of the deleted session
*/ */
void sessionCreated(int newSessionId); void sessionDeleted(int sessionId);
public slots: public slots:
@ -246,7 +259,7 @@ class KateSessionManager : public TQObject
TQString m_baseDir; // folder where session files are stored TQString m_baseDir; // folder where session files are stored
TQString m_configFile; // file where the session list config is stored TQString m_configFile; // file where the session list config is stored
int m_sessionsCount; // number of sessions int m_sessionsCount; // number of sessions // FIXME remove and use m_sessions.count()
int m_activeSessionId; // index of the active session int m_activeSessionId; // index of the active session
bool m_firstActivation; // true until at least one session has been activated bool m_firstActivation; // true until at least one session has been activated
TQPtrList<KateSession> m_sessions; // session list TQPtrList<KateSession> m_sessions; // session list
@ -254,10 +267,12 @@ class KateSessionManager : public TQObject
static KateSessionManager *ksm_instance; // the only KateSessionManager instance static KateSessionManager *ksm_instance; // the only KateSessionManager instance
}; };
//END KateSessionManager //END KateSessionManager
//BEGIN KateSessionChooser //BEGIN KateSessionChooser
//FIXME subclass TQListViewItem adding a field containing the session id,
// then remove the m_columnSessionId column
//------------------------------------ //------------------------------------
class KateSessionChooser : public KDialogBase class KateSessionChooser : public KDialogBase
{ {
@ -288,7 +303,6 @@ class KateSessionChooser : public KDialogBase
TDEListView *m_sessionList; TDEListView *m_sessionList;
int m_columnSessionId; int m_columnSessionId;
}; };
//BEGIN KateSessionChooser //BEGIN KateSessionChooser
@ -298,6 +312,7 @@ class KateSessionChooser : public KDialogBase
//------------------------------------ //------------------------------------
//------------------------------------ //------------------------------------
//------------------------------------ //------------------------------------
class OldKateSessionManager;
class OldKateSession : public TDEShared class OldKateSession : public TDEShared
{ {
public: public:

@ -1,5 +1,5 @@
/* This file is part of the TDE project /* This file is part of the TDE project
Copyright (C) 2015 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit> Copyright (C) 2015-2016 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit>
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public modify it under the terms of the GNU Library General Public
@ -28,6 +28,7 @@
#include <tqlistview.h> #include <tqlistview.h>
//BEGIN KateSessionPanelToolBarParent
void KateSessionPanelToolBarParent::setToolBar(TDEToolBar *tbar) void KateSessionPanelToolBarParent::setToolBar(TDEToolBar *tbar)
{ {
m_tbar = tbar; m_tbar = tbar;
@ -42,7 +43,10 @@ void KateSessionPanelToolBarParent::resizeEvent (TQResizeEvent*)
m_tbar->resize(width(),height()); m_tbar->resize(width(),height());
} }
} }
//END KateSessionPanelToolBarParent
//BEGIN KateSessionPanel
//------------------------------------------- //-------------------------------------------
KateSessionPanel::KateSessionPanel(KateMainWindow *mainWindow, KateViewManager *viewManager, KateSessionPanel::KateSessionPanel(KateMainWindow *mainWindow, KateViewManager *viewManager,
TQWidget *parent, const char *name) TQWidget *parent, const char *name)
@ -67,6 +71,7 @@ KateSessionPanel::KateSessionPanel(KateMainWindow *mainWindow, KateViewManager *
connect(m_listview, TQT_SIGNAL(executed(TQListViewItem*)), this, TQT_SLOT(itemExecuted(TQListViewItem*))); connect(m_listview, TQT_SIGNAL(executed(TQListViewItem*)), this, TQT_SLOT(itemExecuted(TQListViewItem*)));
connect(m_sessionManager, TQT_SIGNAL(sessionActivated(int, int)), this, TQT_SLOT(slotSessionActivated(int, int))); connect(m_sessionManager, TQT_SIGNAL(sessionActivated(int, int)), this, TQT_SLOT(slotSessionActivated(int, int)));
connect(m_sessionManager, TQT_SIGNAL(sessionCreated(int)), this, TQT_SLOT(slotSessionCreated(int))); connect(m_sessionManager, TQT_SIGNAL(sessionCreated(int)), this, TQT_SLOT(slotSessionCreated(int)));
connect(m_sessionManager, TQT_SIGNAL(sessionDeleted(int)), this, TQT_SLOT(slotSessionDeleted(int)));
TQPtrList<KateSession>& sessions = m_sessionManager->getSessionsList(); TQPtrList<KateSession>& sessions = m_sessionManager->getSessionsList();
for (int idx = sessions.count()-1; idx >= 0; --idx) for (int idx = sessions.count()-1; idx >= 0; --idx)
@ -118,16 +123,16 @@ void KateSessionPanel::setup_toolbar()
TQT_TQOBJECT(this), TQT_SLOT(renameSession()), m_actionCollection, "session_rename"); TQT_TQOBJECT(this), TQT_SLOT(renameSession()), m_actionCollection, "session_rename");
a->setWhatsThis(i18n("Rename the selected session.")); a->setWhatsThis(i18n("Rename the selected session."));
a->plug(m_toolbar); a->plug(m_toolbar);
*/
a = new TDEAction(i18n("Delete"), SmallIcon("edit-delete"), 0, a = new TDEAction(i18n("Delete"), SmallIcon("edit-delete"), 0,
TQT_TQOBJECT(this), TQT_SLOT(deleteSession()), m_actionCollection, "session_delete"); TQT_TQOBJECT(this), TQT_SLOT(deleteSession()), m_actionCollection, "session_delete");
a->setWhatsThis(i18n("Delete the selected session.")); a->setWhatsThis(i18n("Delete the selected session."));
a->plug(m_toolbar); a->plug(m_toolbar);
m_toolbar->insertLineSeparator(); m_toolbar->insertLineSeparator();
*/
a = new TDEAction(i18n("Activate"), SmallIcon("forward"), 0, a = new TDEAction(i18n("Activate"), SmallIcon("forward"), 0,
TQT_TQOBJECT(this), TQT_SLOT(sessionActivate()), m_actionCollection, "session_activate"); TQT_TQOBJECT(this), TQT_SLOT(activateSession()), m_actionCollection, "session_activate");
a->setWhatsThis(i18n("Activate the selected session.")); a->setWhatsThis(i18n("Activate the selected session."));
a->plug(m_toolbar); a->plug(m_toolbar);
/* /*
@ -172,11 +177,16 @@ void KateSessionPanel::renameSession()
//------------------------------------------- //-------------------------------------------
void KateSessionPanel::deleteSession() void KateSessionPanel::deleteSession()
{ {
//TODO TQListViewItem *sessionItem = m_listview->selectedItem();
if (!sessionItem)
return;
//FIXME ask for user confirmation before proceeding
m_sessionManager->deleteSession(sessionItem->text(m_columnSessionId).toInt());
} }
//------------------------------------------- //-------------------------------------------
void KateSessionPanel::sessionActivate() void KateSessionPanel::activateSession()
{ {
TQListViewItem *newSessionItem = m_listview->selectedItem(); TQListViewItem *newSessionItem = m_listview->selectedItem();
if (!newSessionItem) if (!newSessionItem)
@ -216,7 +226,7 @@ void KateSessionPanel::itemExecuted(TQListViewItem *item)
// First level items are sessions. Executing one, will switch to that session // First level items are sessions. Executing one, will switch to that session
if (!item->parent()) if (!item->parent())
{ {
sessionActivate(); activateSession();
return; return;
} }
} }
@ -241,8 +251,32 @@ void KateSessionPanel::slotSessionActivated(int newSessionId, int oldSessionId)
m_listview->setSelected(item, true); m_listview->setSelected(item, true);
} }
void KateSessionPanel::slotSessionCreated(int newSessionId) //-------------------------------------------
void KateSessionPanel::slotSessionCreated(int sessionId)
{ {
TQPtrList<KateSession>& sessions = m_sessionManager->getSessionsList(); TQPtrList<KateSession>& sessions = m_sessionManager->getSessionsList();
new TDEListViewItem(m_listview, m_listview->lastItem(), sessions[newSessionId]->getSessionName(), TQString("%1").arg(newSessionId)); new TDEListViewItem(m_listview, m_listview->lastItem(), sessions[sessionId]->getSessionName(),
TQString("%1").arg(sessionId));
}
//-------------------------------------------
void KateSessionPanel::slotSessionDeleted(int sessionId)
{
// delete item from listview
TQListViewItem *item = m_listview->firstChild();
int idx = 0;
for (; idx < sessionId; ++idx)
{
item = item->nextSibling();
}
TQListViewItem *nextItem = item->nextSibling();
delete item;
// update session id of all following items
item = nextItem;
while (item)
{
item->setText(m_columnSessionId, TQString("%1").arg(idx++));
item = item->nextSibling();
}
} }
//END KateSessionPanel

@ -1,5 +1,5 @@
/* This file is part of the TDE project /* This file is part of the TDE project
Copyright (C) 2015 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit> Copyright (C) 2015-2016 Michele Calgaro <micheleDOTcalgaro__AT__yahooDOTit>
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public modify it under the terms of the GNU Library General Public
@ -37,6 +37,7 @@ class KateSessionManager;
class TDEActionCollection; class TDEActionCollection;
//BEGIN KateSessionPanelToolBarParent
class KateSessionPanelToolBarParent: public TQFrame class KateSessionPanelToolBarParent: public TQFrame
{ {
Q_OBJECT Q_OBJECT
@ -52,9 +53,13 @@ class KateSessionPanelToolBarParent: public TQFrame
private: private:
TDEToolBar *m_tbar; TDEToolBar *m_tbar;
}; };
//END KateSessionPanelToolBarParent
//BEGIN KateSessionPanel
//------------------------------------
//FIXME subclass TQListViewItem adding a field containing the session id,
// then remove the m_columnSessionId column
class KateSessionPanel : public TQVBox class KateSessionPanel : public TQVBox
{ {
Q_OBJECT Q_OBJECT
@ -65,19 +70,22 @@ class KateSessionPanel : public TQVBox
TQWidget *parent=0, const char *name=0); TQWidget *parent=0, const char *name=0);
~KateSessionPanel() {} ~KateSessionPanel() {}
public slots: public slots:
void saveSession(); void saveSession();
void saveSessionAs(); void saveSessionAs();
void renameSession(); void renameSession();
void deleteSession(); void deleteSession();
void sessionActivate(); void activateSession();
void sessionToggleReadOnly(); void sessionToggleReadOnly();
void sessionMoveUp(); void sessionMoveUp();
void sessionMoveDown(); void sessionMoveDown();
void itemExecuted(TQListViewItem *item); void itemExecuted(TQListViewItem *item);
void slotSessionActivated(int newSessionId, int oldSessionId); void slotSessionActivated(int newSessionId, int oldSessionId);
void slotSessionCreated(int newSessionId); void slotSessionCreated(int sessionId);
void slotSessionDeleted(int sessionId);
private: private:
void setup_toolbar(); void setup_toolbar();
@ -91,7 +99,7 @@ class KateSessionPanel : public TQVBox
int m_columnSessionId; int m_columnSessionId;
int m_columnPixmap; int m_columnPixmap;
}; };
//END KateSessionPanel
#endif //__KATE_SESSIONPANEL_H__ #endif //__KATE_SESSIONPANEL_H__
// kate: space-indent on; indent-width 2; replace-tabs on;

Loading…
Cancel
Save