fbfccf1393
... Which will be needed for various reasons for Windows builds of certain modularizations, which will otherwise complain that they can no longer generate them as inlines. In one case, deleted copies require explicitly defaulted moves, but they will work as generated inline.
324 lines
7.7 KiB
C++
324 lines
7.7 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
AudacityLogger.cpp
|
|
|
|
******************************************************************//**
|
|
|
|
\class AudacityLogger
|
|
\brief AudacityLogger is a thread-safe logger class
|
|
|
|
Provides thread-safe logging based on the wxWidgets log facility.
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
|
|
|
#include "AudacityLogger.h"
|
|
|
|
|
|
|
|
#include "FileNames.h"
|
|
#include "Internat.h"
|
|
#include "ShuttleGui.h"
|
|
|
|
#include <mutex>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/log.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/frame.h>
|
|
#include <wx/icon.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/textctrl.h>
|
|
#include <wx/tokenzr.h>
|
|
|
|
#include "../images/AudacityLogoAlpha.xpm"
|
|
#include "widgets/AudacityMessageBox.h"
|
|
|
|
//
|
|
// AudacityLogger class
|
|
//
|
|
// Two reasons for this class instead of the wxLogWindow class (or any WX GUI logging class)
|
|
//
|
|
// 1) If wxLogWindow is used and initialized before the Mac's "root" window, then
|
|
// Audacity may crash when terminating. It's not fully understood why this occurs
|
|
// but it probably has to do with the order of deletion. However, deferring the
|
|
// creation of the log window until it is actually shown circumvents the problem.
|
|
// 2) By providing an Audacity specific logging class, it can be made thread-safe and,
|
|
// as such, can be used by the ever growing threading within Audacity.
|
|
//
|
|
enum
|
|
{
|
|
LoggerID_Save = wxID_HIGHEST + 1,
|
|
LoggerID_Clear,
|
|
LoggerID_Close
|
|
};
|
|
|
|
AudacityLogger *AudacityLogger::Get()
|
|
{
|
|
static std::once_flag flag;
|
|
std::call_once( flag, []{
|
|
// wxWidgets will clean up the logger for the main thread, so we can say
|
|
// safenew. See:
|
|
// http://docs.wxwidgets.org/3.0/classwx_log.html#a2525bf54fa3f31dc50e6e3cd8651e71d
|
|
std::unique_ptr < wxLog > // DELETE any previous logger
|
|
{ wxLog::SetActiveTarget(safenew AudacityLogger) };
|
|
} );
|
|
|
|
// Use dynamic_cast so that we get a NULL ptr in case our logger
|
|
// is no longer the target.
|
|
return dynamic_cast<AudacityLogger *>(wxLog::GetActiveTarget());
|
|
}
|
|
|
|
AudacityLogger::AudacityLogger()
|
|
: wxEvtHandler(),
|
|
wxLog()
|
|
{
|
|
mText = NULL;
|
|
mUpdated = false;
|
|
}
|
|
|
|
AudacityLogger::~AudacityLogger() = default;
|
|
|
|
void AudacityLogger::Flush()
|
|
{
|
|
if (mUpdated && mFrame && mFrame->IsShown()) {
|
|
mUpdated = false;
|
|
mText->ChangeValue(mBuffer);
|
|
}
|
|
}
|
|
|
|
void AudacityLogger::DoLogText(const wxString & str)
|
|
{
|
|
if (!wxIsMainThread()) {
|
|
wxMutexGuiEnter();
|
|
}
|
|
|
|
if (mBuffer.empty()) {
|
|
wxString stamp;
|
|
|
|
TimeStamp(&stamp);
|
|
|
|
mBuffer << stamp << _TS("Audacity ") << AUDACITY_VERSION_STRING << wxT("\n");
|
|
}
|
|
|
|
mBuffer << str << wxT("\n");
|
|
|
|
mUpdated = true;
|
|
|
|
Flush();
|
|
|
|
if (!wxIsMainThread()) {
|
|
wxMutexGuiLeave();
|
|
}
|
|
}
|
|
|
|
bool AudacityLogger::SaveLog(const wxString &fileName) const
|
|
{
|
|
wxFFile file(fileName, wxT("w"));
|
|
|
|
if (file.IsOpened()) {
|
|
file.Write(mBuffer);
|
|
file.Close();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AudacityLogger::ClearLog()
|
|
{
|
|
mBuffer = wxEmptyString;
|
|
DoLogText(wxT("Log Cleared."));
|
|
|
|
return true;
|
|
}
|
|
|
|
void AudacityLogger::Show(bool show)
|
|
{
|
|
// Hide the frame if created, otherwise do nothing
|
|
if (!show) {
|
|
if (mFrame) {
|
|
mFrame->Show(false);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the frame already exists, refresh its contents and show it
|
|
if (mFrame) {
|
|
if (!mFrame->IsShown()) {
|
|
mText->ChangeValue(mBuffer);
|
|
mText->SetInsertionPointEnd();
|
|
mText->ShowPosition(mText->GetLastPosition());
|
|
}
|
|
mFrame->Show();
|
|
mFrame->Raise();
|
|
return;
|
|
}
|
|
|
|
// This is the first use, so create the frame
|
|
Destroy_ptr<wxFrame> frame
|
|
{ safenew wxFrame(NULL, wxID_ANY, _("Audacity Log")) };
|
|
frame->SetName(frame->GetTitle());
|
|
frame->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
|
|
|
|
// loads either the XPM or the windows resource, depending on the platform
|
|
{
|
|
#if !defined(__WXMAC__) && !defined(__WXX11__)
|
|
#if defined(__WXMSW__)
|
|
wxIcon ic{wxICON(AudacityLogo)};
|
|
#elif defined(__WXGTK__)
|
|
wxIcon ic{wxICON(AudacityLogoAlpha)};
|
|
#else
|
|
wxIcon ic{};
|
|
ic.CopyFromBitmap(theTheme.Bitmap(bmpAudacityLogo48x48));
|
|
#endif
|
|
frame->SetIcon(ic);
|
|
#endif
|
|
}
|
|
|
|
// Log text
|
|
ShuttleGui S(frame.get(), eIsCreating);
|
|
|
|
S.Style(wxNO_BORDER | wxTAB_TRAVERSAL).Prop(true).StartPanel();
|
|
{
|
|
S.StartVerticalLay(true);
|
|
{
|
|
mText = S.Style(wxTE_MULTILINE | wxHSCROLL | wxTE_READONLY | wxTE_RICH)
|
|
.AddTextWindow(mBuffer);
|
|
|
|
S.AddSpace(0, 5);
|
|
S.StartHorizontalLay(wxALIGN_CENTER, 0);
|
|
{
|
|
S.AddSpace(10, 0);
|
|
S.Id(LoggerID_Save).AddButton(XXO("&Save..."));
|
|
S.Id(LoggerID_Clear).AddButton(XXO("Cl&ear"));
|
|
S.Id(LoggerID_Close).AddButton(XXO("&Close"));
|
|
S.AddSpace(10, 0);
|
|
}
|
|
S.EndHorizontalLay();
|
|
S.AddSpace(0, 3);
|
|
}
|
|
S.EndVerticalLay();
|
|
}
|
|
S.EndPanel();
|
|
|
|
// Give a place for the menu help text to go
|
|
// frame->CreateStatusBar();
|
|
|
|
frame->Layout();
|
|
|
|
// Hook into the frame events
|
|
frame->Bind(wxEVT_CLOSE_WINDOW,
|
|
wxCloseEventHandler(AudacityLogger::OnCloseWindow),
|
|
this);
|
|
|
|
frame->Bind( wxEVT_COMMAND_MENU_SELECTED,
|
|
&AudacityLogger::OnSave,
|
|
this, LoggerID_Save);
|
|
frame->Bind( wxEVT_COMMAND_MENU_SELECTED,
|
|
&AudacityLogger::OnClear,
|
|
this, LoggerID_Clear);
|
|
frame->Bind( wxEVT_COMMAND_MENU_SELECTED,
|
|
&AudacityLogger::OnClose,
|
|
this, LoggerID_Close);
|
|
frame->Bind( wxEVT_COMMAND_BUTTON_CLICKED,
|
|
&AudacityLogger::OnSave,
|
|
this, LoggerID_Save);
|
|
frame->Bind( wxEVT_COMMAND_BUTTON_CLICKED,
|
|
&AudacityLogger::OnClear,
|
|
this, LoggerID_Clear);
|
|
frame->Bind( wxEVT_COMMAND_BUTTON_CLICKED,
|
|
&AudacityLogger::OnClose,
|
|
this, LoggerID_Close);
|
|
|
|
mFrame = std::move( frame );
|
|
|
|
mFrame->Show();
|
|
|
|
Flush();
|
|
}
|
|
|
|
wxString AudacityLogger::GetLog(int count)
|
|
{
|
|
if (count == 0)
|
|
{
|
|
return mBuffer;
|
|
}
|
|
|
|
wxString buffer;
|
|
|
|
auto lines = wxStringTokenize(mBuffer, wxT("\r\n"), wxTOKEN_RET_DELIMS);
|
|
for (int index = lines.size() - 1; index >= 0 && count > 0; --index, --count)
|
|
{
|
|
buffer.Prepend(lines[index]);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void AudacityLogger::OnCloseWindow(wxCloseEvent & WXUNUSED(e))
|
|
{
|
|
#if defined(__WXMAC__)
|
|
// On the Mac, destroy the window rather than hiding it since the
|
|
// log menu will override the root windows menu if there is no
|
|
// project window open.
|
|
mFrame.reset();
|
|
#else
|
|
Show(false);
|
|
#endif
|
|
}
|
|
|
|
void AudacityLogger::OnClose(wxCommandEvent & WXUNUSED(e))
|
|
{
|
|
wxCloseEvent dummy;
|
|
OnCloseWindow(dummy);
|
|
}
|
|
|
|
void AudacityLogger::OnClear(wxCommandEvent & WXUNUSED(e))
|
|
{
|
|
ClearLog();
|
|
}
|
|
|
|
void AudacityLogger::OnSave(wxCommandEvent & WXUNUSED(e))
|
|
{
|
|
wxString fName = _("log.txt");
|
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
|
XO("Save log to:"),
|
|
wxEmptyString,
|
|
fName,
|
|
wxT("txt"),
|
|
{ FileNames::TextFiles },
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
mFrame.get());
|
|
|
|
if (fName.empty()) {
|
|
return;
|
|
}
|
|
|
|
if (!mText->SaveFile(fName)) {
|
|
AudacityMessageBox(
|
|
XO("Couldn't save log to file: %s").Format( fName ),
|
|
XO("Warning"),
|
|
wxICON_EXCLAMATION,
|
|
mFrame.get());
|
|
return;
|
|
}
|
|
}
|
|
|
|
void AudacityLogger::UpdatePrefs()
|
|
{
|
|
if (mFrame) {
|
|
bool shown = mFrame->IsShown();
|
|
if (shown) {
|
|
Show(false);
|
|
}
|
|
mFrame.reset();
|
|
if (shown) {
|
|
Show(true);
|
|
}
|
|
}
|
|
}
|