2013-10-23 18:08:12 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
*//*******************************************************************/
|
|
|
|
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2013-10-23 18:08:12 +00:00
|
|
|
#include "AudacityLogger.h"
|
2018-11-11 02:40:37 +00:00
|
|
|
|
2020-06-19 19:43:09 +00:00
|
|
|
|
2018-11-11 17:27:44 +00:00
|
|
|
|
2017-08-02 16:41:29 +00:00
|
|
|
#include "FileNames.h"
|
2019-05-20 18:27:11 +00:00
|
|
|
#include "Internat.h"
|
2013-10-23 18:08:12 +00:00
|
|
|
#include "ShuttleGui.h"
|
|
|
|
|
2019-04-26 14:20:49 +00:00
|
|
|
#include <mutex>
|
2017-08-02 16:41:29 +00:00
|
|
|
#include <wx/filedlg.h>
|
2013-10-23 18:08:12 +00:00
|
|
|
#include <wx/log.h>
|
2020-08-11 06:48:32 +00:00
|
|
|
#include <wx/ffile.h>
|
2013-10-23 18:08:12 +00:00
|
|
|
#include <wx/frame.h>
|
2013-10-23 20:16:18 +00:00
|
|
|
#include <wx/icon.h>
|
2013-10-23 18:08:12 +00:00
|
|
|
#include <wx/settings.h>
|
2019-05-20 18:27:11 +00:00
|
|
|
#include <wx/textctrl.h>
|
2021-04-01 06:13:15 +00:00
|
|
|
#include <wx/tokenzr.h>
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2013-10-23 19:23:15 +00:00
|
|
|
#include "../images/AudacityLogoAlpha.xpm"
|
2019-05-20 18:27:11 +00:00
|
|
|
#include "widgets/AudacityMessageBox.h"
|
2013-10-23 19:23:15 +00:00
|
|
|
|
2013-10-23 18:08:12 +00:00
|
|
|
//
|
|
|
|
// 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
|
|
|
|
};
|
|
|
|
|
2019-04-26 14:20:49 +00:00
|
|
|
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());
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:08:12 +00:00
|
|
|
AudacityLogger::AudacityLogger()
|
|
|
|
: wxEvtHandler(),
|
|
|
|
wxLog()
|
|
|
|
{
|
|
|
|
mText = NULL;
|
|
|
|
mUpdated = false;
|
|
|
|
}
|
|
|
|
|
2020-09-29 12:54:22 +00:00
|
|
|
AudacityLogger::~AudacityLogger() = default;
|
|
|
|
|
2013-10-23 18:08:12 +00:00
|
|
|
void AudacityLogger::Flush()
|
|
|
|
{
|
|
|
|
if (mUpdated && mFrame && mFrame->IsShown()) {
|
|
|
|
mUpdated = false;
|
|
|
|
mText->ChangeValue(mBuffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 16:25:32 +00:00
|
|
|
void AudacityLogger::DoLogText(const wxString & str)
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
|
|
|
if (!wxIsMainThread()) {
|
|
|
|
wxMutexGuiEnter();
|
|
|
|
}
|
|
|
|
|
2019-02-12 00:10:48 +00:00
|
|
|
if (mBuffer.empty()) {
|
2015-08-25 21:14:32 +00:00
|
|
|
wxString stamp;
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2015-08-25 21:14:32 +00:00
|
|
|
TimeStamp(&stamp);
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2016-09-11 12:03:37 +00:00
|
|
|
mBuffer << stamp << _TS("Audacity ") << AUDACITY_VERSION_STRING << wxT("\n");
|
2013-10-23 18:08:12 +00:00
|
|
|
}
|
|
|
|
|
2015-08-25 21:14:32 +00:00
|
|
|
mBuffer << str << wxT("\n");
|
2013-10-23 18:08:12 +00:00
|
|
|
|
|
|
|
mUpdated = true;
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
|
|
|
|
if (!wxIsMainThread()) {
|
|
|
|
wxMutexGuiLeave();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 06:48:32 +00:00
|
|
|
bool AudacityLogger::SaveLog(const wxString &fileName) const
|
2020-08-11 05:14:56 +00:00
|
|
|
{
|
|
|
|
wxFFile file(fileName, wxT("w"));
|
|
|
|
|
|
|
|
if (file.IsOpened()) {
|
|
|
|
file.Write(mBuffer);
|
|
|
|
file.Close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-11 14:06:15 +00:00
|
|
|
bool AudacityLogger::ClearLog()
|
|
|
|
{
|
|
|
|
mBuffer = wxEmptyString;
|
|
|
|
DoLogText(wxT("Log Cleared."));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:08:12 +00:00
|
|
|
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
|
2016-08-13 02:45:20 +00:00
|
|
|
Destroy_ptr<wxFrame> frame
|
|
|
|
{ safenew wxFrame(NULL, wxID_ANY, _("Audacity Log")) };
|
2015-05-12 13:29:27 +00:00
|
|
|
frame->SetName(frame->GetTitle());
|
2013-10-23 18:08:12 +00:00
|
|
|
frame->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
|
|
|
|
|
|
|
|
// loads either the XPM or the windows resource, depending on the platform
|
2016-02-01 01:39:24 +00:00
|
|
|
{
|
2013-10-23 18:08:12 +00:00
|
|
|
#if !defined(__WXMAC__) && !defined(__WXX11__)
|
2016-02-01 01:39:24 +00:00
|
|
|
#if defined(__WXMSW__)
|
|
|
|
wxIcon ic{wxICON(AudacityLogo)};
|
|
|
|
#elif defined(__WXGTK__)
|
|
|
|
wxIcon ic{wxICON(AudacityLogoAlpha)};
|
|
|
|
#else
|
|
|
|
wxIcon ic{};
|
2013-10-23 18:08:12 +00:00
|
|
|
ic.CopyFromBitmap(theTheme.Bitmap(bmpAudacityLogo48x48));
|
|
|
|
#endif
|
2016-02-01 01:39:24 +00:00
|
|
|
frame->SetIcon(ic);
|
|
|
|
#endif
|
|
|
|
}
|
2013-10-23 18:08:12 +00:00
|
|
|
|
|
|
|
// Log text
|
2016-08-13 02:45:20 +00:00
|
|
|
ShuttleGui S(frame.get(), eIsCreating);
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2017-10-31 18:52:01 +00:00
|
|
|
S.Style(wxNO_BORDER | wxTAB_TRAVERSAL).Prop(true).StartPanel();
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
|
|
|
S.StartVerticalLay(true);
|
|
|
|
{
|
2020-04-14 21:42:00 +00:00
|
|
|
mText = S.Style(wxTE_MULTILINE | wxHSCROLL | wxTE_READONLY | wxTE_RICH)
|
2017-10-31 18:52:01 +00:00
|
|
|
.AddTextWindow(mBuffer);
|
2013-10-23 18:08:12 +00:00
|
|
|
|
|
|
|
S.AddSpace(0, 5);
|
|
|
|
S.StartHorizontalLay(wxALIGN_CENTER, 0);
|
|
|
|
{
|
|
|
|
S.AddSpace(10, 0);
|
2020-05-11 15:28:14 +00:00
|
|
|
S.Id(LoggerID_Save).AddButton(XXO("&Save..."));
|
|
|
|
S.Id(LoggerID_Clear).AddButton(XXO("Cl&ear"));
|
|
|
|
S.Id(LoggerID_Close).AddButton(XXO("&Close"));
|
2013-10-23 18:08:12 +00:00
|
|
|
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
|
2018-02-12 21:45:54 +00:00
|
|
|
frame->Bind(wxEVT_CLOSE_WINDOW,
|
2013-10-23 18:08:12 +00:00
|
|
|
wxCloseEventHandler(AudacityLogger::OnCloseWindow),
|
|
|
|
this);
|
|
|
|
|
2018-02-12 21:45:54 +00:00
|
|
|
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);
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2016-08-13 02:45:20 +00:00
|
|
|
mFrame = std::move( frame );
|
2013-10-23 18:08:12 +00:00
|
|
|
|
|
|
|
mFrame->Show();
|
|
|
|
|
|
|
|
Flush();
|
|
|
|
}
|
|
|
|
|
2021-04-01 06:13:15 +00:00
|
|
|
wxString AudacityLogger::GetLog(int count)
|
2015-04-18 10:06:28 +00:00
|
|
|
{
|
2021-04-01 06:13:15 +00:00
|
|
|
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;
|
2015-04-18 10:06:28 +00:00
|
|
|
}
|
|
|
|
|
2013-10-31 22:28:21 +00:00
|
|
|
void AudacityLogger::OnCloseWindow(wxCloseEvent & WXUNUSED(e))
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
|
|
|
#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.
|
2018-01-31 15:08:42 +00:00
|
|
|
mFrame.reset();
|
2013-10-23 18:08:12 +00:00
|
|
|
#else
|
|
|
|
Show(false);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2013-10-31 22:28:21 +00:00
|
|
|
void AudacityLogger::OnClose(wxCommandEvent & WXUNUSED(e))
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
|
|
|
wxCloseEvent dummy;
|
|
|
|
OnCloseWindow(dummy);
|
|
|
|
}
|
|
|
|
|
2013-10-31 22:28:21 +00:00
|
|
|
void AudacityLogger::OnClear(wxCommandEvent & WXUNUSED(e))
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
2020-08-11 14:06:15 +00:00
|
|
|
ClearLog();
|
2013-10-23 18:08:12 +00:00
|
|
|
}
|
|
|
|
|
2013-10-31 22:28:21 +00:00
|
|
|
void AudacityLogger::OnSave(wxCommandEvent & WXUNUSED(e))
|
2013-10-23 18:08:12 +00:00
|
|
|
{
|
|
|
|
wxString fName = _("log.txt");
|
|
|
|
|
2017-08-02 16:41:29 +00:00
|
|
|
fName = FileNames::SelectFile(FileNames::Operation::Export,
|
2019-12-27 03:48:00 +00:00
|
|
|
XO("Save log to:"),
|
|
|
|
wxEmptyString,
|
|
|
|
fName,
|
|
|
|
wxT("txt"),
|
|
|
|
{ FileNames::TextFiles },
|
|
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
|
|
|
|
mFrame.get());
|
2013-10-23 18:08:12 +00:00
|
|
|
|
2019-03-14 20:20:18 +00:00
|
|
|
if (fName.empty()) {
|
2013-10-23 18:08:12 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mText->SaveFile(fName)) {
|
2018-01-04 01:47:56 +00:00
|
|
|
AudacityMessageBox(
|
2019-12-07 19:30:07 +00:00
|
|
|
XO("Couldn't save log to file: %s").Format( fName ),
|
|
|
|
XO("Warning"),
|
2018-01-04 01:47:56 +00:00
|
|
|
wxICON_EXCLAMATION,
|
|
|
|
mFrame.get());
|
2013-10-23 18:08:12 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-14 07:25:52 +00:00
|
|
|
void AudacityLogger::UpdatePrefs()
|
|
|
|
{
|
|
|
|
if (mFrame) {
|
|
|
|
bool shown = mFrame->IsShown();
|
|
|
|
if (shown) {
|
|
|
|
Show(false);
|
|
|
|
}
|
|
|
|
mFrame.reset();
|
|
|
|
if (shown) {
|
|
|
|
Show(true);
|
|
|
|
}
|
|
|
|
}
|
2020-06-20 02:47:27 +00:00
|
|
|
}
|