/********************************************************************** Audacity: A Digital Audio Editor Screenshot.cpp Dominic Mazzoni *******************************************************************//** \class ScreenFrame \brief ScreenFrame provides an alternative Gui for ScreenshotCommand. It adds a timer that allows a delay before taking a screenshot, provides lots of one-click buttons, options to resize the screen. It forwards the actual work of doing the commands to the ScreenshotCommand. ***********************************************************************/ #include "Screenshot.h" #include "MemoryX.h" #include "commands/ScreenshotCommand.h" #include "commands/CommandTargets.h" #include "commands/CommandContext.h" #include #include #include #include "ShuttleGui.h" #include #include #include #include #include #include #include #include #include #include #include #include "AudacityApp.h" #include "Project.h" #include "Prefs.h" #include "toolbars/ToolManager.h" #include "Track.h" class OldStyleCommandType; //////////////////////////////////////////////////////////////////////////////// class ScreenFrame final : public wxFrame { public: // constructors and destructors ScreenFrame(wxWindow *parent, wxWindowID id); virtual ~ScreenFrame(); bool ProcessEvent(wxEvent & event) override; private: void Populate(); void PopulateOrExchange(ShuttleGui &S); void OnCloseWindow(wxCloseEvent & event); void OnUIUpdate(wxUpdateUIEvent & event); void OnDirChoose(wxCommandEvent & event); void SizeMainWindow(int w, int h); void OnMainWindowSmall(wxCommandEvent & event); void OnMainWindowLarge(wxCommandEvent & event); void OnToggleBackgroundBlue(wxCommandEvent & event); void OnToggleBackgroundWhite(wxCommandEvent & event); void DoCapture(wxString captureMode); void OnCaptureSomething(wxCommandEvent & event); void TimeZoom(double seconds); void OnOneSec(wxCommandEvent & event); void OnTenSec(wxCommandEvent & event); void OnOneMin(wxCommandEvent & event); void OnFiveMin(wxCommandEvent & event); void OnOneHour(wxCommandEvent & event); void SizeTracks(int h); void OnShortTracks(wxCommandEvent & event); void OnMedTracks(wxCommandEvent & event); void OnTallTracks(wxCommandEvent & event); std::unique_ptr CreateCommand(); wxCheckBox *mDelayCheckBox; wxTextCtrl *mDirectoryTextBox; wxToggleButton *mBlue; wxToggleButton *mWhite; wxStatusBar *mStatus; std::unique_ptr mCommand; CommandContext mContext; DECLARE_EVENT_TABLE() }; // Static pointer to the unique ScreenFrame window. // Formerly it was parentless, therefore this was a Destroy_ptr // But now the window is owned, so just use a bare pointer, and null it when // the unique window is destroyed. using ScreenFramePtr = ScreenFrame*; ScreenFramePtr mFrame; //////////////////////////////////////////////////////////////////////////////// void OpenScreenshotTools() { if (!mFrame) { auto parent = wxGetApp().GetTopWindow(); if (!parent) { wxASSERT(false); return; } mFrame = ScreenFramePtr{ safenew ScreenFrame(parent, -1) }; } mFrame->Show(); mFrame->Raise(); } void CloseScreenshotTools() { mFrame = nullptr; } //////////////////////////////////////////////////////////////////////////////// class ScreenFrameTimer final : public wxTimer { public: ScreenFrameTimer(ScreenFrame *frame, wxEvent & event) { screenFrame = frame; evt.reset(event.Clone()); } void Notify() override { // Process timer notification just once, then destroy self evt->SetEventObject(NULL); screenFrame->ProcessEvent(*evt); delete this; } private: ScreenFrame *screenFrame; std::unique_ptr evt; }; //////////////////////////////////////////////////////////////////////////////// enum { IdMainWindowSmall = 19200, IdMainWindowLarge, IdDirectory, IdDirChoose, IdOneSec, IdTenSec, IdOneMin, IdFiveMin, IdOneHour, IdShortTracks, IdMedTracks, IdTallTracks, IdDelayCheckBox, IdCaptureFirst, // No point delaying the capture of sets of things. IdCaptureEffects= IdCaptureFirst, IdCaptureScriptables, IdCapturePreferences, IdCaptureToolbars, // Put all events that need delay between AllDelayed and LastDelayed. IdAllDelayedEvents, IdCaptureWindowContents=IdAllDelayedEvents, IdCaptureFullWindow, IdCaptureWindowPlus, IdCaptureFullScreen, IdCaptureSelectionBar, IdCaptureSpectralSelection, IdCaptureTools, IdCaptureTransport, IdCaptureMixer, IdCaptureMeter, IdCapturePlayMeter, IdCaptureRecordMeter, IdCaptureEdit, IdCaptureDevice, IdCaptureTranscription, IdCaptureScrub, IdCaptureTrackPanel, IdCaptureRuler, IdCaptureTracks, IdCaptureFirstTrack, IdCaptureSecondTrack, IdCaptureLast = IdCaptureSecondTrack, IdLastDelayedEvent, IdToggleBackgroundBlue, IdToggleBackgroundWhite, }; BEGIN_EVENT_TABLE(ScreenFrame, wxFrame) EVT_CLOSE(ScreenFrame::OnCloseWindow) EVT_UPDATE_UI(IdCaptureFullScreen, ScreenFrame::OnUIUpdate) EVT_BUTTON(IdMainWindowSmall, ScreenFrame::OnMainWindowSmall) EVT_BUTTON(IdMainWindowLarge, ScreenFrame::OnMainWindowLarge) EVT_TOGGLEBUTTON(IdToggleBackgroundBlue, ScreenFrame::OnToggleBackgroundBlue) EVT_TOGGLEBUTTON(IdToggleBackgroundWhite, ScreenFrame::OnToggleBackgroundWhite) EVT_COMMAND_RANGE(IdCaptureFirst, IdCaptureLast, wxEVT_COMMAND_BUTTON_CLICKED, ScreenFrame::OnCaptureSomething) EVT_BUTTON(IdOneSec, ScreenFrame::OnOneSec) EVT_BUTTON(IdTenSec, ScreenFrame::OnTenSec) EVT_BUTTON(IdOneMin, ScreenFrame::OnOneMin) EVT_BUTTON(IdFiveMin, ScreenFrame::OnFiveMin) EVT_BUTTON(IdOneHour, ScreenFrame::OnOneHour) EVT_BUTTON(IdShortTracks, ScreenFrame::OnShortTracks) EVT_BUTTON(IdMedTracks, ScreenFrame::OnMedTracks) EVT_BUTTON(IdTallTracks, ScreenFrame::OnTallTracks) EVT_BUTTON(IdDirChoose, ScreenFrame::OnDirChoose) END_EVENT_TABLE(); // Must not be called before CreateStatusBar! std::unique_ptr ScreenFrame::CreateCommand() { wxASSERT(mStatus != NULL); auto output = std::make_unique(std::make_unique(), std::make_shared(*mStatus), std::make_shared()); return std::make_unique();//*type, std::move(output), this); } ScreenFrame::ScreenFrame(wxWindow * parent, wxWindowID id) : wxFrame(parent, id, _("Screen Capture Frame"), wxDefaultPosition, wxDefaultSize, #if !defined(__WXMSW__) #if !defined(__WXMAC__) // bug1358 wxFRAME_TOOL_WINDOW | #endif #else wxSTAY_ON_TOP| #endif wxSYSTEM_MENU|wxCAPTION|wxCLOSE_BOX), mContext( *GetActiveProject() ) { mDelayCheckBox = NULL; mDirectoryTextBox = NULL; mStatus = CreateStatusBar(3); mCommand = CreateCommand(); Populate(); // Reset the toolbars to a known state. // Note that the audio could be playing. // The monitoring will switch off temporarily // because we've switched monitor mid play. // Bug 383 - Resetting the toolbars is not wanted. // Any that are invisible will be amde visible as/when needed. //mContext.GetProject()->GetToolManager()->Reset(); Center(); } ScreenFrame::~ScreenFrame() { if (this == mFrame) mFrame = nullptr; else // There should only be one! wxASSERT(false); } void ScreenFrame::Populate() { ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); } void ScreenFrame::PopulateOrExchange(ShuttleGui & S) { wxPanel *p = S.StartPanel(); { S.SetBorder(3); S.StartStatic(_("Choose location to save files")); { S.StartMultiColumn(3, wxEXPAND); { S.SetStretchyCol(1); wxString dir = gPrefs->Read(wxT("/ScreenshotPath"), wxFileName::GetHomeDir()); mDirectoryTextBox = S.Id(IdDirectory).AddTextBox(_("Save images to:"), dir, 30); S.Id(IdDirChoose).AddButton(_("Choose...")); } S.EndMultiColumn(); } S.EndStatic(); S.StartStatic(_("Capture entire window or screen")); { S.StartHorizontalLay(); { S.Id(IdMainWindowSmall).AddButton(_("Resize Small")); S.Id(IdMainWindowLarge).AddButton(_("Resize Large")); /* i18n-hint: Bkgnd is short for background and appears on a small button * It is OK to just translate this item as if it said 'Blue' */ wxASSERT(p); // To justify safenew mBlue = safenew wxToggleButton(p, IdToggleBackgroundBlue, _("Blue Bkgnd")); S.AddWindow(mBlue); /* i18n-hint: Bkgnd is short for background and appears on a small button * It is OK to just translate this item as if it said 'White' */ mWhite = safenew wxToggleButton(p, IdToggleBackgroundWhite, _("White Bkgnd")); S.AddWindow(mWhite); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureWindowContents).AddButton(_("Capture Window Only")); S.Id(IdCaptureFullWindow).AddButton(_("Capture Full Window")); S.Id(IdCaptureWindowPlus).AddButton(_("Capture Window Plus")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureFullScreen).AddButton(_("Capture Full Screen")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { mDelayCheckBox = S.Id(IdDelayCheckBox).AddCheckBox (_("Wait 5 seconds and capture frontmost window/dialog"), _("false")); } S.EndHorizontalLay(); } S.EndStatic(); S.StartStatic(_("Capture part of a project window")); { S.StartHorizontalLay(); { S.Id(IdCaptureToolbars).AddButton(_("All Toolbars")); S.Id(IdCaptureEffects).AddButton(_("All Effects")); S.Id(IdCaptureScriptables).AddButton(_("All Scriptables")); S.Id(IdCapturePreferences).AddButton(_("All Preferences")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureSelectionBar).AddButton(_("SelectionBar")); S.Id(IdCaptureSpectralSelection).AddButton(_("Frequency Selection")); S.Id(IdCaptureTools).AddButton(_("Tools")); S.Id(IdCaptureTransport).AddButton(_("Transport")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureMixer).AddButton(_("Mixer")); S.Id(IdCaptureMeter).AddButton(_("Meter")); S.Id(IdCapturePlayMeter).AddButton(_("Play Meter")); S.Id(IdCaptureRecordMeter).AddButton(_("Record Meter")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureEdit).AddButton(_("Edit")); S.Id(IdCaptureDevice).AddButton(_("Device")); S.Id(IdCaptureTranscription).AddButton(_("Transcription")); S.Id(IdCaptureScrub).AddButton(_("Scrub")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdCaptureTrackPanel).AddButton(_("Track Panel")); S.Id(IdCaptureRuler).AddButton(_("Ruler")); S.Id(IdCaptureTracks).AddButton(_("Tracks")); S.Id(IdCaptureFirstTrack).AddButton(_("First Track")); S.Id(IdCaptureSecondTrack).AddButton(_("Second Track")); } S.EndHorizontalLay(); } S.EndStatic(); S.StartStatic(_("Scale")); { S.StartHorizontalLay(); { S.Id(IdOneSec).AddButton(_("One Sec")); S.Id(IdTenSec).AddButton(_("Ten Sec")); S.Id(IdOneMin).AddButton(_("One Min")); S.Id(IdFiveMin).AddButton(_("Five Min")); S.Id(IdOneHour).AddButton(_("One Hour")); } S.EndHorizontalLay(); S.StartHorizontalLay(); { S.Id(IdShortTracks).AddButton(_("Short Tracks")); S.Id(IdMedTracks).AddButton(_("Medium Tracks")); S.Id(IdTallTracks).AddButton(_("Tall Tracks")); } S.EndHorizontalLay(); } S.EndStatic(); } S.EndPanel(); Layout(); GetSizer()->Fit(this); SetMinSize(GetSize()); int top = 0; #ifdef __WXMAC__ // Allow for Mac menu bar top += 20; #endif int width, height; GetSize(&width, &height); int displayWidth, displayHeight; wxDisplaySize(&displayWidth, &displayHeight); if (width > 100) { Move(displayWidth - width - 16, top + 16); } else { CentreOnParent(); } SetIcon(mContext.GetProject()->GetIcon()); } bool ScreenFrame::ProcessEvent(wxEvent & e) { int id = e.GetId(); // If split into two parts to make for easier breakpoint // when testing timer. if (mDelayCheckBox && mDelayCheckBox->GetValue() && e.IsCommandEvent() && e.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED) { if( id >= IdAllDelayedEvents && id <= IdLastDelayedEvent && e.GetEventObject() != NULL) { // safenew because it's a one-shot that deletes itself ScreenFrameTimer *timer = safenew ScreenFrameTimer(this, e); timer->Start(5000, true); return true; } } if (e.IsCommandEvent() && e.GetEventObject() == NULL) { e.SetEventObject(this); } return wxFrame::ProcessEvent(e); } void ScreenFrame::OnCloseWindow(wxCloseEvent & WXUNUSED(event)) { Destroy(); } void ScreenFrame::OnUIUpdate(wxUpdateUIEvent & WXUNUSED(event)) { #ifdef __WXMAC__ wxTopLevelWindow *top = mCommand->GetFrontWindow(GetActiveProject()); bool needupdate = false; bool enable = false; if ((!top || top->IsIconized()) && mDirectoryTextBox->IsEnabled()) { needupdate = true; enable = false; } else if ((top && !top->IsIconized()) && !mDirectoryTextBox->IsEnabled()) { needupdate = true; enable = true; } if (needupdate) { for (int i = IdMainWindowSmall; i < IdLastDelayedEvent; i++) { wxWindow *w = wxWindow::FindWindowById(i, this); if (w) { w->Enable(enable); } } } #endif } void ScreenFrame::OnDirChoose(wxCommandEvent & WXUNUSED(event)) { wxString current = mDirectoryTextBox->GetValue(); wxDirDialogWrapper dlog(this, _("Choose a location to save screenshot images"), current); dlog.ShowModal(); if (dlog.GetPath() != wxT("")) { wxFileName tmpDirPath; tmpDirPath.AssignDir(dlog.GetPath()); wxString path = tmpDirPath.GetPath(wxPATH_GET_VOLUME|wxPATH_GET_SEPARATOR); mDirectoryTextBox->SetValue(path); gPrefs->Write(wxT("/ScreenshotPath"), path); gPrefs->Flush(); mCommand->mPath = path; } } void ScreenFrame::OnToggleBackgroundBlue(wxCommandEvent & WXUNUSED(event)) { mWhite->SetValue(false); } void ScreenFrame::OnToggleBackgroundWhite(wxCommandEvent & WXUNUSED(event)) { mBlue->SetValue(false); } void ScreenFrame::SizeMainWindow(int w, int h) { int top = 20; mContext.GetProject()->Maximize(false); mContext.GetProject()->SetSize(16, 16 + top, w, h); //Bug383 - Toolbar Resets not wanted. //mContext.GetProject()->GetToolManager()->Reset(); } void ScreenFrame::OnMainWindowSmall(wxCommandEvent & WXUNUSED(event)) { SizeMainWindow(680, 450); } void ScreenFrame::OnMainWindowLarge(wxCommandEvent & WXUNUSED(event)) { SizeMainWindow(900, 600); } void ScreenFrame::DoCapture(wxString captureMode) { Hide(); //mCommand->SetParameter(wxT("FilePath"), mDirectoryTextBox->GetValue()); //mCommand->SetParameter(wxT("CaptureMode"), captureMode); mCommand->mBack = mWhite->GetValue() ? "White" : mBlue->GetValue() ? "Blue" : "None"; mCommand->mPath = mDirectoryTextBox->GetValue(); mCommand->mWhat = captureMode; if (!mCommand->Apply(mContext)) mStatus->SetStatusText(_("Capture failed!"), mainStatusBarField); Show(); } void ScreenFrame::OnCaptureSomething(wxCommandEvent & event) { int i = event.GetId() - IdCaptureFirst; wxArrayString Names; Names.Add(wxT("Effects")); Names.Add(wxT("Scriptables")); Names.Add(wxT("Preferences")); Names.Add(wxT("Toolbars")); Names.Add(wxT("Window")); Names.Add(wxT("Full_Window")); Names.Add(wxT("Window_Plus")); Names.Add(wxT("Fullscreen")); Names.Add(wxT("Selectionbar")); Names.Add(wxT("Spectral_Selection")); Names.Add(wxT("Tools")); Names.Add(wxT("Transport")); Names.Add(wxT("Mixer")); Names.Add(wxT("Meter")); Names.Add(wxT("Play_Meter")); Names.Add(wxT("Record_Meter")); Names.Add(wxT("Edit")); Names.Add(wxT("Device")); Names.Add(wxT("Transcription")); Names.Add(wxT("Scrub")); Names.Add(wxT("Trackpanel")); Names.Add(wxT("Ruler")); Names.Add(wxT("Tracks")); Names.Add(wxT("First_Track")); Names.Add(wxT("Second_Track")); DoCapture(Names[i]); } void ScreenFrame::TimeZoom(double seconds) { int width, height; mContext.GetProject()->GetClientSize(&width, &height); mContext.GetProject()->mViewInfo.SetZoom((0.75 * width) / seconds); mContext.GetProject()->RedrawProject(); } void ScreenFrame::OnOneSec(wxCommandEvent & WXUNUSED(event)) { TimeZoom(1.0); } void ScreenFrame::OnTenSec(wxCommandEvent & WXUNUSED(event)) { TimeZoom(10.0); } void ScreenFrame::OnOneMin(wxCommandEvent & WXUNUSED(event)) { TimeZoom(60.0); } void ScreenFrame::OnFiveMin(wxCommandEvent & WXUNUSED(event)) { TimeZoom(300.0); } void ScreenFrame::OnOneHour(wxCommandEvent & WXUNUSED(event)) { TimeZoom(3600.0); } void ScreenFrame::SizeTracks(int h) { TrackListIterator iter(mContext.GetProject()->GetTracks()); for (Track * t = iter.First(); t; t = iter.Next()) { if (t->GetKind() == Track::Wave) { if (t->GetLink()) { t->SetHeight(h); } else { t->SetHeight(h*2); } } } mContext.GetProject()->RedrawProject(); } void ScreenFrame::OnShortTracks(wxCommandEvent & WXUNUSED(event)) { TrackListIterator iter(mContext.GetProject()->GetTracks()); for (Track * t = iter.First(); t; t = iter.Next()) { if (t->GetKind() == Track::Wave) { t->SetHeight(t->GetMinimizedHeight()); } } mContext.GetProject()->RedrawProject(); } void ScreenFrame::OnMedTracks(wxCommandEvent & WXUNUSED(event)) { SizeTracks(60); } void ScreenFrame::OnTallTracks(wxCommandEvent & WXUNUSED(event)) { SizeTracks(85); }