1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "AssetEditor.h"
5 #include "AssetManager.h"
6 #include "FileOperationsExecutor.h"
8 #include "EditableAsset.h"
9 #include "Loader/AssetLoaderHelpers.h"
10 #include "Browser/AssetBrowserDialog.h"
11 #include "Controls/SingleSelectionDialog.h"
12 #include "Controls/QuestionDialog.h"
14 #include "FilePathUtil.h"
15 #include "CryExtension/CryGUID.h"
17 #include "AssetFilesGroupProvider.h"
18 #include "ThreadingUtils.h"
20 #include <QCloseEvent>
22 namespace Private_AssetEditor
25 //! Makes a temporary copy of files in construct time
26 //! and moves them back in the destructor.
27 class CAutoFileRecovery
30 CAutoFileRecovery(const std::vector
<string
>& files
)
37 static const string tempPrefix
= GetTemporaryDirectoryPath();
39 m_files
.reserve(files
.size());
41 ICryPak
* const pCryPak
= GetISystem()->GetIPak();
42 pCryPak
->MakeDir(tempPrefix
.c_str());
43 for (const string
& file
: files
)
45 const string tempFilemane
= PathUtil::Make(tempPrefix
, CryGUID::Create().ToString(), "tmp");
46 if (!pCryPak
->CopyFileOnDisk(file
.c_str(), tempFilemane
.c_str(), false))
51 m_files
.emplace_back(file
, tempFilemane
);
54 if (files
.size() != m_files
.size())
60 bool IsValid() const { return !m_files
.empty() || !m_files
.capacity(); }
64 ICryPak
* const pCryPak
= GetISystem()->GetIPak();
65 for (const auto& file
: m_files
)
67 pCryPak
->RemoveFile(file
.second
.c_str());
72 virtual ~CAutoFileRecovery()
74 ICryPak
* const pCryPak
= GetISystem()->GetIPak();
75 for (const auto& file
: m_files
)
77 pCryPak
->CopyFileOnDisk(file
.second
.c_str(), file
.first
.c_str(), false);
78 pCryPak
->RemoveFile(file
.second
.c_str());
82 static string
GetTemporaryDirectoryPath()
84 char path
[ICryPak::g_nMaxPath
] = {};
85 return GetISystem()->GetIPak()->AdjustFileName("%USER%/temp", path
, ICryPak::FLAGS_PATH_REAL
| ICryPak::FLAGS_FOR_WRITING
| ICryPak::FLAGS_ADD_TRAILING_SLASH
);
89 std::vector
<std::pair
<string
, string
>> m_files
;
92 class CAutoAssetRecovery
: public CAutoFileRecovery
95 CAutoAssetRecovery(const CAsset
& asset
)
96 : CAutoFileRecovery(GetAssetFiles(asset
, false))
100 static std::vector
<string
> GetAssetFiles(const CAsset
& asset
, bool bIncludeSourceFile
)
102 std::vector
<string
> files
= asset
.GetType()->GetAssetFiles(asset
, bIncludeSourceFile
, true);
104 files
.erase(std::remove_if(files
.begin(), files
.end(), [](const string
& filename
)
106 return !GetISystem()->GetIPak()->IsFileExist(filename
.c_str(), ICryPak::eFileLocation_OnDisk
);
115 CAssetEditor
* CAssetEditor::OpenAssetForEdit(const char* szEditorClassName
, CAsset
* pAsset
)
118 IPane
* pPane
= GetIEditor()->CreateDockable(szEditorClassName
);
121 CAssetEditor
* assetEditor
= static_cast<CAssetEditor
*>(pPane
);
122 if (assetEditor
->OpenAsset(pAsset
))
130 CAssetEditor::CAssetEditor(const char* assetType
, QWidget
* pParent
/*= nullptr*/)
131 : CDockableEditor(pParent
)
132 , m_assetBeingEdited(nullptr)
134 auto type
= CAssetManager::GetInstance()->FindAssetType(assetType
);
135 CRY_ASSERT(type
);//type must exist
136 m_supportedAssetTypes
.push_back(type
);
141 CAssetEditor::CAssetEditor(const QStringList
& assetTypes
, QWidget
* pParent
/*= nullptr*/)
142 : CDockableEditor(pParent
)
143 , m_assetBeingEdited(nullptr)
145 m_supportedAssetTypes
.reserve(assetTypes
.size());
146 for (const QString
& typeName
: assetTypes
)
148 auto type
= CAssetManager::GetInstance()->FindAssetType(typeName
.toStdString().c_str());
149 CRY_ASSERT(type
);//type must exist
150 m_supportedAssetTypes
.push_back(type
);
156 void CAssetEditor::Init()
160 setAcceptDrops(true);
163 bool CAssetEditor::OpenAsset(CAsset
* pAsset
)
165 //An asset can only be opened once in one editor
166 if (pAsset
->IsBeingEdited())
169 if (pAsset
== m_assetBeingEdited
)
174 // User cancelled closing of currently opened asset.
178 if (!OnOpenAsset(pAsset
))
183 AddRecentFile(QString(pAsset
->GetMetadataFile()));
184 SetAssetBeingEdited(pAsset
);
186 CEditableAsset
editableAsset(*pAsset
);
187 editableAsset
.SetOpenedInAssetEditor(this);
192 bool CAssetEditor::CanOpenAsset(CAsset
* pAsset
)
194 return pAsset
&& CanOpenAsset(pAsset
->GetType());
197 bool CAssetEditor::CanOpenAsset(const CAssetType
* pType
)
202 return std::find(m_supportedAssetTypes
.begin(), m_supportedAssetTypes
.end(), pType
) != m_supportedAssetTypes
.end();
205 void CAssetEditor::InitGenericMenu()
207 AddToMenu(CEditor::MenuItems::FileMenu
);
209 AddToMenu(CEditor::MenuItems::Open
);
210 AddToMenu(CEditor::MenuItems::Close
);
211 AddToMenu(CEditor::MenuItems::Save
);
212 AddToMenu(CEditor::MenuItems::RecentFiles
);
214 //TODO: help menu doesn't always occupy last position
215 AddToMenu(CEditor::MenuItems::HelpMenu
);
216 AddToMenu(CEditor::MenuItems::Help
);
221 int CAssetEditor::GetNewableAssetCount() const
223 int newableAssets
= 0;
224 for (const auto& type
: m_supportedAssetTypes
)
226 newableAssets
+= type
->CanBeCreated();
228 return newableAssets
;
231 void CAssetEditor::UpdateWindowTitle()
233 if (m_assetBeingEdited
)
235 if (m_assetBeingEdited
->IsModified())
236 setWindowTitle(QString(m_assetBeingEdited
->GetName()) + " *");
238 setWindowTitle(m_assetBeingEdited
->GetName().c_str());
240 setWindowIcon(m_assetBeingEdited
->GetType()->GetIcon());
244 setWindowTitle(GetPaneTitle());
245 setWindowIcon(QIcon());//TODO : this should be the pane's default icon, panes already have an icon from the Tools menu
249 void CAssetEditor::SetAssetBeingEdited(CAsset
* pAsset
)
251 if (m_assetBeingEdited
== pAsset
)
254 bool bWasReadOnly
= false;
256 if (m_assetBeingEdited
)
258 m_assetBeingEdited
->signalChanged
.DisconnectObject(this);
259 bWasReadOnly
= m_assetBeingEdited
->IsReadOnly();
262 m_assetBeingEdited
= pAsset
;
268 CAssetManager::GetInstance()->signalBeforeAssetsRemoved
.Connect([this](const std::vector
<CAsset
*>& assets
)
270 if (std::find(assets
.begin(), assets
.end(), GetAssetBeingEdited()) != assets
.end())
273 CRY_ASSERT(GetAssetBeingEdited() != nullptr);
274 signalAssetClosed(GetAssetBeingEdited());
275 SetAssetBeingEdited(nullptr);
279 pAsset
->signalChanged
.Connect(this, &CAssetEditor::OnAssetChanged
);
281 if (bWasReadOnly
!= pAsset
->IsReadOnly())
286 CAssetManager::GetInstance()->signalBeforeAssetsRemoved
.DisconnectById((uintptr_t)this);
288 //No longer considered read only after closing the asset
294 bool CAssetEditor::OnAboutToCloseAssetInternal(string
& reason
) const
298 if (!m_assetBeingEdited
)
303 if (m_assetBeingEdited
->GetEditingSession())
308 if (m_assetBeingEdited
->IsModified())
310 reason
= QtUtil::ToString(tr("Asset '%1' has unsaved modifications.").arg(m_assetBeingEdited
->GetName().c_str()));
314 return OnAboutToCloseAsset(reason
);
317 bool CAssetEditor::TryCloseAsset()
319 if (!m_assetBeingEdited
)
326 if (!GetIEditor()->IsMainFrameClosing() && !OnAboutToCloseAssetInternal(reason
))
330 // Show generic modification message.
331 reason
= QtUtil::ToString(tr("Asset '%1' has unsaved modifications.").arg(m_assetBeingEdited
->GetName().c_str()));
334 const QString title
= tr("Closing %1").arg(GetEditorName());
335 const auto button
= CQuestionDialog::SQuestion(title
, QtUtil::ToQString(reason
), QDialogButtonBox::Save
| QDialogButtonBox::Discard
| QDialogButtonBox::Cancel
, QDialogButtonBox::Cancel
);
338 case QDialogButtonBox::Save
:
342 case QDialogButtonBox::Discard
:
343 DiscardAssetChanges();
346 case QDialogButtonBox::No
:
348 // "No" is returned when a user clicked the "x" in the window bar.
349 case QDialogButtonBox::Cancel
:
353 CRY_ASSERT(0 && "Unknown button");
362 CRY_ASSERT(GetAssetBeingEdited() != nullptr);
363 signalAssetClosed(GetAssetBeingEdited());
364 SetAssetBeingEdited(nullptr);
373 void CAssetEditor::OnAssetChanged(CAsset
& asset
, int changeFlags
)
375 CRY_ASSERT(&asset
== m_assetBeingEdited
);
377 if (changeFlags
& eAssetChangeFlags_ReadOnly
)
382 if (changeFlags
& eAssetChangeFlags_Modified
)
388 void CAssetEditor::InitNewMenu()
390 const int newableAssets
= GetNewableAssetCount();
396 else if (newableAssets
== 1)
398 AddToMenu(MenuItems::New
);
402 CAbstractMenu
* const pSubMenu
= GetMenu(MenuItems::FileMenu
)->CreateMenu(tr("New"), 0, 0);
403 for (const auto& type
: m_supportedAssetTypes
)
405 if (type
->CanBeCreated())
407 QAction
* const pAction
= pSubMenu
->CreateAction(tr("%1").arg(type
->GetUiTypeName()));
408 connect(pAction
, &QAction::triggered
, [this, type
]()
410 InternalNewAsset(type
);
417 bool CAssetEditor::OnNew()
419 const int newableAssetCount
= GetNewableAssetCount();
420 if (newableAssetCount
== 1)
422 InternalNewAsset(m_supportedAssetTypes
[0]);
424 else if (newableAssetCount
> 1)
426 std::vector
<string
> assetTypeNames
;
427 std::transform(m_supportedAssetTypes
.begin(), m_supportedAssetTypes
.end(), std::back_inserter(assetTypeNames
), [](const auto& t
)
429 return t
->GetTypeName();
431 CSingleSelectionDialog assetTypeSelection
;
432 assetTypeSelection
.setWindowTitle(tr("New asset type"));
433 assetTypeSelection
.SetOptions(assetTypeNames
);
434 if (assetTypeSelection
.Execute())
436 InternalNewAsset(m_supportedAssetTypes
[assetTypeSelection
.GetSelectedIndex()]);
442 void CAssetEditor::InternalNewAsset(CAssetType
* pAssetType
)
447 const string assetTypeName
= pAssetType
->GetTypeName();
449 const string assetBasePath
= CAssetBrowserDialog::CreateSingleAssetForType(assetTypeName
, CAssetBrowserDialog::OverwriteMode::NoOverwrite
);
450 if (assetBasePath
.empty())
452 return; // Operation cancelled by user.
455 const string assetPath
= assetBasePath
+ string().Format(".%s.cryasset", pAssetType
->GetFileExtension());
456 if (pAssetType
->Create(assetPath
))
458 CAsset
* const pAsset
= CAssetManager::GetInstance()->FindAssetForMetadata(assetPath
);
466 bool CAssetEditor::OnOpen()
468 std::vector
<string
> supportedAssetTypeNames
;
469 supportedAssetTypeNames
.reserve(m_supportedAssetTypes
.size());
470 for (auto& assetType
: m_supportedAssetTypes
)
472 supportedAssetTypeNames
.push_back(assetType
->GetTypeName());
475 CAsset
* const asset
= CAssetBrowserDialog::OpenSingleAssetForTypes(supportedAssetTypeNames
);
478 (void)OpenAsset(asset
);
483 bool CAssetEditor::OnOpenFile(const QString
& path
)
485 auto asset
= CAssetManager::GetInstance()->FindAssetForFile(path
.toStdString().c_str());
488 (void)OpenAsset(asset
);
493 bool CAssetEditor::Close()
495 if (!GetAssetBeingEdited())
500 return TryCloseAsset();
503 bool CAssetEditor::OnClose()
505 //Note: this is only the callback for menu action, every other place should call close()
507 return true;//returns true because the menu action is handled
510 bool CAssetEditor::CanQuit(std::vector
<string
>& unsavedChanges
)
513 if (!OnAboutToCloseAssetInternal(reason
))
515 unsavedChanges
.push_back(reason
);
521 void CAssetEditor::closeEvent(QCloseEvent
* pEvent
)
532 for (CAssetType
* pAssetType
: m_supportedAssetTypes
)
534 if (pAssetType
->GetInstantEditor() == this)
536 pAssetType
->SetInstantEditor(nullptr);
541 void CAssetEditor::dragEnterEvent(QDragEnterEvent
* pEvent
)
543 auto pDragDropData
= CDragDropData::FromMimeData(pEvent
->mimeData());
544 if (pDragDropData
->HasCustomData("Assets"))
546 QByteArray byteArray
= pDragDropData
->GetCustomData("Assets");
547 QDataStream
stream(byteArray
);
549 QVector
<quintptr
> tmp
;
552 QVector
<CAsset
*> assets
= *reinterpret_cast<QVector
<CAsset
*>*>(&tmp
);
554 if (assets
.size() == 1 && CanOpenAsset(assets
[0]))
556 CDragDropData::ShowDragText(this, tr("Open"));
557 pEvent
->acceptProposedAction();
562 if (pDragDropData
->HasCustomData("EngineFilePaths"))
564 QByteArray byteArray
= pDragDropData
->GetCustomData("EngineFilePaths");
565 QDataStream
stream(byteArray
);
567 QStringList engineFilePaths
;
568 stream
>> engineFilePaths
;
570 const auto meshType
= CAssetManager::GetInstance()->FindAssetType("Mesh");
572 if (engineFilePaths
.size() == 1)
574 CAsset
* asset
= CAssetManager::GetInstance()->FindAssetForFile(engineFilePaths
[0].toStdString().c_str());
575 if (asset
&& CanOpenAsset(asset
))
577 CDragDropData::ShowDragText(this, tr("Open"));
578 pEvent
->acceptProposedAction();
584 if (pDragDropData
->HasFilePaths())
586 const auto filePaths
= pDragDropData
->GetFilePaths();
587 const auto meshType
= CAssetManager::GetInstance()->FindAssetType("Mesh");
589 if (filePaths
.size() == 1)
591 CAsset
* asset
= CAssetManager::GetInstance()->FindAssetForFile(filePaths
[0].toStdString().c_str());
592 if (asset
&& CanOpenAsset(asset
))
594 CDragDropData::ShowDragText(this, tr("Open"));
595 pEvent
->acceptProposedAction();
602 void CAssetEditor::dropEvent(QDropEvent
* pEvent
)
604 auto pDragDropData
= CDragDropData::FromMimeData(pEvent
->mimeData());
605 if (pDragDropData
->HasCustomData("Assets"))
607 QByteArray byteArray
= pDragDropData
->GetCustomData("Assets");
608 QDataStream
stream(byteArray
);
610 QVector
<quintptr
> tmp
;
613 QVector
<CAsset
*> assets
= *reinterpret_cast<QVector
<CAsset
*>*>(&tmp
);
615 if (assets
.size() == 1 && CanOpenAsset(assets
[0]))
617 OpenAsset(assets
[0]);
618 pEvent
->acceptProposedAction();
623 if (pDragDropData
->HasCustomData("EngineFilePaths"))
625 QByteArray byteArray
= pDragDropData
->GetCustomData("EngineFilePaths");
626 QDataStream
stream(byteArray
);
628 QStringList engineFilePaths
;
629 stream
>> engineFilePaths
;
631 const auto meshType
= CAssetManager::GetInstance()->FindAssetType("Mesh");
633 if (engineFilePaths
.size() == 1)
635 CAsset
* asset
= CAssetManager::GetInstance()->FindAssetForFile(engineFilePaths
[0].toStdString().c_str());
636 if (asset
&& CanOpenAsset(asset
))
639 pEvent
->acceptProposedAction();
645 if (pDragDropData
->HasFilePaths())
647 const auto filePaths
= pDragDropData
->GetFilePaths();
648 const auto meshType
= CAssetManager::GetInstance()->FindAssetType("Mesh");
650 if (filePaths
.size() == 1)
652 CAsset
* asset
= CAssetManager::GetInstance()->FindAssetForFile(filePaths
[0].toStdString().c_str());
653 if (asset
&& CanOpenAsset(asset
))
656 pEvent
->acceptProposedAction();
663 QToolButton
* CAssetEditor::CreateLockButton()
667 return m_pLockButton
;
670 m_pLockButton
= new QToolButton();
671 m_pLockButton
->setCheckable(true);
672 m_pLockButton
->setIcon(CryIcon("icons:General/Instant_Editing.ico"));
673 m_pLockButton
->setToolTip(tr("Instant Editing"));
675 connect(m_pLockButton
, &QToolButton::toggled
, this, &CAssetEditor::SetInstantEditingMode
);
677 const bool foundInstantEditor
= std::any_of(m_supportedAssetTypes
.cbegin(), m_supportedAssetTypes
.cend(), [](const CAssetType
* pType
)
679 return pType
->GetInstantEditor() != nullptr;
682 if (!foundInstantEditor
)
684 SetInstantEditingMode(true);
687 return m_pLockButton
;
690 bool CAssetEditor::InternalSaveAsset(CAsset
* pAsset
)
692 //TODO: Figure out how to handle writing metadata generically without opening the file twice
693 //(one in OnSaveAsset implementation and one here)
695 //TODO: here the editable asset retains every metadata of the old asset, which means if it is not overwritten in OnSaveAsset, some metadata could carry over.
696 //Perhaps the safest way would be to clear it before passing it. Also means that calling things like AddFile() in there will result in warnings because the file was duplicated etc...
698 CEditableAsset
editAsset(*pAsset
);
699 if (!OnSaveAsset(editAsset
))
704 editAsset
.InvalidateThumbnail();
705 editAsset
.WriteToFile();
706 pAsset
->SetModified(false);
711 bool CAssetEditor::Save()
713 if (!m_assetBeingEdited
)
718 return InternalSaveAsset(m_assetBeingEdited
);
721 void CAssetEditor::DiscardAssetChanges()
723 CAsset
* pAsset
= GetAssetBeingEdited();
726 CEditableAsset
editAsset(*pAsset
);
727 OnDiscardAssetChanges(editAsset
);
728 pAsset
->SetModified(false);
732 bool CAssetEditor::OnSave()
738 bool CAssetEditor::OnSaveAs()
740 if (!m_assetBeingEdited
)
745 CRY_ASSERT(!m_supportedAssetTypes
.empty());
746 CAssetType
* const pAssetType
= m_supportedAssetTypes
[0];
747 const string assetTypeName
= pAssetType
->GetTypeName();
749 // #TODO: Let user save any of the supported asset types.
750 const string assetBasePath
= CAssetBrowserDialog::SaveSingleAssetForType(assetTypeName
);
751 if (assetBasePath
.empty())
753 return true; // Operation cancelled by user.
755 const string newAssetPath
= assetBasePath
+ string().Format(".%s.cryasset", pAssetType
->GetFileExtension());
757 if (newAssetPath
.Compare(m_assetBeingEdited
->GetMetadataFile()) == 0)
762 CAssetManager
* const pAssetManager
= CAssetManager::GetInstance();
763 CAsset
* pAsset
= pAssetManager
->FindAssetForMetadata(newAssetPath
);
766 // Cancel if unable to delete.
767 pAssetManager
->DeleteAssetsWithFiles({ pAsset
});
768 pAsset
= pAssetManager
->FindAssetForMetadata(newAssetPath
);
775 // Create new asset on disk.
776 if (!InternalSaveAs(newAssetPath
))
781 pAsset
= AssetLoader::CAssetFactory::LoadAssetFromXmlFile(newAssetPath
);
782 pAssetManager
->MergeAssets({ pAsset
});
786 // Close previous asset and unconditionally discard all changes.
787 DiscardAssetChanges();
789 CRY_ASSERT(GetAssetBeingEdited() != nullptr);
790 signalAssetClosed(GetAssetBeingEdited());
791 SetAssetBeingEdited(nullptr);
798 bool CAssetEditor::InternalSaveAs(const string
& newAssetPath
)
800 CRY_ASSERT(m_assetBeingEdited
);
802 using namespace Private_AssetEditor
;
804 // 1. Make a temp copy of asset files.
805 // 2. Save asset changes.
806 // 3. Move updated files under the new asset name.
807 // 4. Restore old files from the temp copy.
808 // TODO: consider a direct implementation of the SaveAs. e.g CAssetType::SaveAs(CAsset& asset, string newAssetPath)
810 CAutoAssetRecovery
tempCopy(*m_assetBeingEdited
);
811 if (!tempCopy
.IsValid() || !OnSave())
813 CryWarning(EValidatorModule::VALIDATOR_MODULE_EDITOR
, EValidatorSeverity::VALIDATOR_ERROR
, "Unable to copy asset: %s", m_assetBeingEdited
->GetName());
817 const bool result
= m_assetBeingEdited
->GetType()->CopyAsset(m_assetBeingEdited
, newAssetPath
);
819 ThreadingUtils::AsyncQueue([this]()
821 std::vector
<std::unique_ptr
<IFilesGroupProvider
>> fileGroups
;
822 fileGroups
.emplace_back(new CAssetFilesGroupProvider(m_assetBeingEdited
, false));
823 CFileOperationExecutor::GetDefaultExecutor()->Delete(std::move(fileGroups
));
824 }).wait(); // we need to wait here it needs to be finished before tempCopy gets destroyed. Hopefully this method will die soon.
829 bool CAssetEditor::SaveBackup(const string
& backupFolder
)
831 CRY_ASSERT(m_assetBeingEdited
);
832 using namespace Private_AssetEditor
;
834 // 1. Make a temporary copy of the current asset files.
835 // 2. Save asset changes.
836 // 3. Move updated files to the backupFolder.
837 // 4. Restore old files from the temp copy.
839 CAutoAssetRecovery
tempCopy(*m_assetBeingEdited
);
841 if (!tempCopy
.IsValid() || !OnSave())
846 std::vector
<string
> files
= CAutoAssetRecovery::GetAssetFiles(*m_assetBeingEdited
, false);
848 ICryPak
* const pCryPak
= GetISystem()->GetIPak();
849 pCryPak
->MakeDir(backupFolder
.c_str());
850 const string assetsRoot
= PathUtil::GetGameProjectAssetsPath();
851 for (const auto& file
: files
)
853 CRY_ASSERT(strncmp(file
.c_str(), assetsRoot
.c_str(), assetsRoot
.size()));
854 CryPathString destFile
= PathUtil::Make(backupFolder
.c_str(), file
.c_str() + assetsRoot
.size());
855 pCryPak
->MakeDir(PathUtil::GetDirectory(destFile
.c_str()));
856 PathUtil::MoveFileAllowOverwrite(file
.c_str(), destFile
.c_str());
859 // tempCopy restores asset files.
863 bool CAssetEditor::IsReadOnly() const
865 return GetAssetBeingEdited() != nullptr ? GetAssetBeingEdited()->IsReadOnly() : false;
868 void CAssetEditor::SetInstantEditingMode(bool isActive
)
872 for (CAssetType
* pAssetType
: m_supportedAssetTypes
)
874 if (pAssetType
->GetInstantEditor() == this)
879 if (pAssetType
->GetInstantEditor())
881 pAssetType
->GetInstantEditor()->SetInstantEditingMode(false);
883 pAssetType
->SetInstantEditor(this);
888 for (CAssetType
* pAssetType
: m_supportedAssetTypes
)
890 if (pAssetType
->GetInstantEditor() == this)
892 pAssetType
->SetInstantEditor(nullptr);
897 if (m_pLockButton
&& m_pLockButton
->isChecked() != isActive
)
899 m_pLockButton
->setChecked(isActive
);