mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-07 23:32:53 +02:00
Merge release-3.0.3 into master
Conflicts: src/AboutDialog.cpp src/ShuttleGui.cpp src/prefs/ApplicationPrefs.cpp
This commit is contained in:
commit
d8c4f54164
@ -28,21 +28,23 @@ hold information about one contributor to Audacity.
|
|||||||
|
|
||||||
#include "AboutDialog.h"
|
#include "AboutDialog.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include <wx/dialog.h>
|
#include <wx/dialog.h>
|
||||||
#include <wx/html/htmlwin.h>
|
#include <wx/html/htmlwin.h>
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
|
#include <wx/hyperlink.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/statbmp.h>
|
#include <wx/statbmp.h>
|
||||||
#include <wx/intl.h>
|
#include <wx/intl.h>
|
||||||
#include <wx/sstream.h>
|
#include <wx/sstream.h>
|
||||||
#include <wx/txtstrm.h>
|
#include <wx/txtstrm.h>
|
||||||
|
#include <wx/statbox.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
#include "FileNames.h"
|
#include "FileNames.h"
|
||||||
#include "HelpText.h"
|
#include "HelpText.h"
|
||||||
#include "ShuttleGui.h"
|
#include "ShuttleGui.h"
|
||||||
#include "widgets/HelpSystem.h"
|
#include "widgets/HelpSystem.h"
|
||||||
|
#include "ui/AccessibleLinksFormatter.h"
|
||||||
|
|
||||||
#include "AllThemeResources.h"
|
#include "AllThemeResources.h"
|
||||||
#include "Theme.h"
|
#include "Theme.h"
|
||||||
@ -324,6 +326,7 @@ AboutDialog::AboutDialog(wxWindow * parent)
|
|||||||
.Prop(0)
|
.Prop(0)
|
||||||
.AddButton(XXO("OK"), wxALIGN_CENTER, true);
|
.AddButton(XXO("OK"), wxALIGN_CENTER, true);
|
||||||
|
|
||||||
|
Layout();
|
||||||
Fit();
|
Fit();
|
||||||
this->Centre();
|
this->Centre();
|
||||||
}
|
}
|
||||||
@ -526,9 +529,9 @@ visit our %s.")
|
|||||||
// It also makes it easier to revert to full size if we decide to.
|
// It also makes it easier to revert to full size if we decide to.
|
||||||
const float fScale = 0.5f;// smaller size.
|
const float fScale = 0.5f;// smaller size.
|
||||||
wxImage RescaledImage(logo.ConvertToImage());
|
wxImage RescaledImage(logo.ConvertToImage());
|
||||||
wxColour MainColour(
|
wxColour MainColour(
|
||||||
RescaledImage.GetRed(1,1),
|
RescaledImage.GetRed(1,1),
|
||||||
RescaledImage.GetGreen(1,1),
|
RescaledImage.GetGreen(1,1),
|
||||||
RescaledImage.GetBlue(1,1));
|
RescaledImage.GetBlue(1,1));
|
||||||
pPage->SetBackgroundColour(MainColour);
|
pPage->SetBackgroundColour(MainColour);
|
||||||
// wxIMAGE_QUALITY_HIGH not supported by wxWidgets 2.6.1, or we would use it here.
|
// wxIMAGE_QUALITY_HIGH not supported by wxWidgets 2.6.1, or we would use it here.
|
||||||
@ -560,6 +563,7 @@ visit our %s.")
|
|||||||
S.EndNotebookPage();
|
S.EndNotebookPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** \brief: Fills out the "Information" tab of the preferences dialogue
|
/** \brief: Fills out the "Information" tab of the preferences dialogue
|
||||||
*
|
*
|
||||||
* Provides as much information as possible about build-time options and
|
* Provides as much information as possible about build-time options and
|
||||||
@ -831,6 +835,8 @@ void AboutDialog::PopulateInformationPage( ShuttleGui & S )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const wxString GPL_TEXT();
|
||||||
|
|
||||||
void AboutDialog::PopulateLicensePage( ShuttleGui & S )
|
void AboutDialog::PopulateLicensePage( ShuttleGui & S )
|
||||||
{
|
{
|
||||||
#if defined(HAS_PRIVACY_POLICY)
|
#if defined(HAS_PRIVACY_POLICY)
|
||||||
@ -838,11 +844,47 @@ void AboutDialog::PopulateLicensePage( ShuttleGui & S )
|
|||||||
#else
|
#else
|
||||||
S.StartNotebookPage(XO("GPL License"));
|
S.StartNotebookPage(XO("GPL License"));
|
||||||
#endif
|
#endif
|
||||||
S.StartVerticalLay(1);
|
|
||||||
HtmlWindow *html = safenew LinkingHtmlWindow(S.GetParent(), -1,
|
#if defined(HAS_PRIVACY_POLICY)
|
||||||
wxDefaultPosition,
|
S.Prop(0).StartPanel();
|
||||||
wxSize(ABOUT_DIALOG_WIDTH, 264),
|
{
|
||||||
wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
|
S.AddSpace(0, 8);
|
||||||
|
/* i18n-hint: For "About Audacity...": Title for Privacy Policy section */
|
||||||
|
S.AddVariableText(XC("PRIVACY POLICY", "about dialog"), true);
|
||||||
|
|
||||||
|
S.AddFixedText(
|
||||||
|
XO("App update checking and error reporting require network access. "
|
||||||
|
"These features are optional."));
|
||||||
|
|
||||||
|
/* i18n-hint: %s will be replaced with "our Privacy Policy" */
|
||||||
|
AccessibleLinksFormatter privacyPolicy(XO("See %s for more info."));
|
||||||
|
|
||||||
|
privacyPolicy.FormatLink(
|
||||||
|
/* i18n-hint: Title of hyperlink to the privacy policy. This is an object of "See". */
|
||||||
|
wxT("%s"), XO("our Privacy Policy"),
|
||||||
|
"https://www.audacityteam.org/about/desktop-privacy-notice/");
|
||||||
|
|
||||||
|
privacyPolicy.Populate(S);
|
||||||
|
}
|
||||||
|
S.EndPanel();
|
||||||
|
|
||||||
|
S.AddSpace(0, 8);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
S.Prop(1).StartPanel();
|
||||||
|
{
|
||||||
|
HtmlWindow* html = safenew LinkingHtmlWindow(
|
||||||
|
S.GetParent(), -1, wxDefaultPosition, wxSize(ABOUT_DIALOG_WIDTH, 264),
|
||||||
|
wxHW_SCROLLBAR_AUTO | wxSUNKEN_BORDER);
|
||||||
|
|
||||||
|
html->SetPage(FormatHtmlText(GPL_TEXT()));
|
||||||
|
|
||||||
|
S.Prop(1).Position(wxEXPAND).AddWindow( html );
|
||||||
|
}
|
||||||
|
S.EndPanel();
|
||||||
|
|
||||||
|
S.EndNotebookPage();
|
||||||
|
}
|
||||||
|
|
||||||
// I tried using <pre> here to get a monospaced font,
|
// I tried using <pre> here to get a monospaced font,
|
||||||
// as is normally used for the GPL.
|
// as is normally used for the GPL.
|
||||||
@ -852,7 +894,7 @@ void AboutDialog::PopulateLicensePage( ShuttleGui & S )
|
|||||||
// The GPL is not to be translated....
|
// The GPL is not to be translated....
|
||||||
|
|
||||||
|
|
||||||
constexpr auto GPL_TEXT =
|
const wxString GPL_TEXT() { return
|
||||||
wxT(" <center>GNU GENERAL PUBLIC LICENSE\n</center>")
|
wxT(" <center>GNU GENERAL PUBLIC LICENSE\n</center>")
|
||||||
wxT(" <center>Version 2, June 1991\n</center>")
|
wxT(" <center>Version 2, June 1991\n</center>")
|
||||||
wxT("<p><p>")
|
wxT("<p><p>")
|
||||||
@ -1135,45 +1177,6 @@ wxT("TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n"
|
|||||||
wxT("YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n")
|
wxT("YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n")
|
||||||
wxT("PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n")
|
wxT("PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n")
|
||||||
wxT("POSSIBILITY OF SUCH DAMAGES.\n");
|
wxT("POSSIBILITY OF SUCH DAMAGES.\n");
|
||||||
|
|
||||||
#if defined(HAS_PRIVACY_POLICY)
|
|
||||||
/* i18n-hint: For "About Audacity...": Title for Privacy Policy section */
|
|
||||||
const auto privacyPolicyTitle = XC("PRIVACY POLICY", "about dialog");
|
|
||||||
|
|
||||||
/* i18n-hint: For "About Audacity...": Title of hyperlink to the privacy policy. This is an object of "See". */
|
|
||||||
const auto privacyPolicyURLText = XO("our Privacy Policy");
|
|
||||||
|
|
||||||
/* i18n-hint: %s will be replaced with "our Privacy Policy" */
|
|
||||||
const auto privacyPolicyText = XO(
|
|
||||||
"App update checking and error reporting require network access. "
|
|
||||||
"These features are optional. See %s "
|
|
||||||
"for more information.")
|
|
||||||
.Format(
|
|
||||||
Verbatim(
|
|
||||||
"[[https://www.audacityteam.org/about/desktop-privacy-notice/|%s]]")
|
|
||||||
.Format(privacyPolicyURLText));
|
|
||||||
|
|
||||||
const wxString privacyPolicyHTML = wxString(
|
|
||||||
wxT("<center>") +
|
|
||||||
privacyPolicyTitle.Translation() +
|
|
||||||
wxT("</center><p>") +
|
|
||||||
privacyPolicyText.Translation() +
|
|
||||||
wxT("</p><br/><br/>")
|
|
||||||
);
|
|
||||||
|
|
||||||
wxString PageText = FormatHtmlText(privacyPolicyHTML + GPL_TEXT);
|
|
||||||
#else
|
|
||||||
wxString PageText = FormatHtmlText(GPL_TEXT);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
html->SetPage( PageText );
|
|
||||||
|
|
||||||
S.Prop(1)
|
|
||||||
.Position( wxEXPAND )
|
|
||||||
.AddWindow( html );
|
|
||||||
|
|
||||||
S.EndVerticalLay();
|
|
||||||
S.EndNotebookPage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AboutDialog::AddCredit( const wxString &name, Role role )
|
void AboutDialog::AddCredit( const wxString &name, Role role )
|
||||||
|
@ -894,6 +894,10 @@ list( APPEND SOURCES
|
|||||||
tracks/ui/ZoomHandle.cpp
|
tracks/ui/ZoomHandle.cpp
|
||||||
tracks/ui/ZoomHandle.h
|
tracks/ui/ZoomHandle.h
|
||||||
|
|
||||||
|
# ui helpers
|
||||||
|
ui/AccessibleLinksFormatter.h
|
||||||
|
ui/AccessibleLinksFormatter.cpp
|
||||||
|
|
||||||
# Widgets
|
# Widgets
|
||||||
|
|
||||||
widgets/AButton.cpp
|
widgets/AButton.cpp
|
||||||
@ -993,6 +997,8 @@ list( APPEND SOURCES
|
|||||||
update/UpdateDataParser.cpp
|
update/UpdateDataParser.cpp
|
||||||
update/UpdateManager.h
|
update/UpdateManager.h
|
||||||
update/UpdateManager.cpp
|
update/UpdateManager.cpp
|
||||||
|
update/UpdateNoticeDialog.h
|
||||||
|
update/UpdateNoticeDialog.cpp
|
||||||
update/UpdatePopupDialog.h
|
update/UpdatePopupDialog.h
|
||||||
update/UpdatePopupDialog.cpp
|
update/UpdatePopupDialog.cpp
|
||||||
prefs/ApplicationPrefs.h
|
prefs/ApplicationPrefs.h
|
||||||
|
@ -113,6 +113,8 @@ for registering for changes.
|
|||||||
#include <wx/spinctrl.h>
|
#include <wx/spinctrl.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
#include <wx/bmpbuttn.h>
|
#include <wx/bmpbuttn.h>
|
||||||
|
#include <wx/wrapsizer.h>
|
||||||
|
|
||||||
#include "ComponentInterface.h"
|
#include "ComponentInterface.h"
|
||||||
#include "widgets/ReadOnlyText.h"
|
#include "widgets/ReadOnlyText.h"
|
||||||
#include "widgets/wxPanelWrapper.h"
|
#include "widgets/wxPanelWrapper.h"
|
||||||
@ -191,6 +193,11 @@ void ShuttleGuiBase::ResetId()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ShuttleGuiBase::GetBorder() const noexcept
|
||||||
|
{
|
||||||
|
return miBorder;
|
||||||
|
}
|
||||||
|
|
||||||
/// Used to modify an already placed FlexGridSizer to make a column stretchy.
|
/// Used to modify an already placed FlexGridSizer to make a column stretchy.
|
||||||
void ShuttleGuiBase::SetStretchyCol( int i )
|
void ShuttleGuiBase::SetStretchyCol( int i )
|
||||||
{
|
{
|
||||||
@ -289,13 +296,13 @@ void ShuttleGuiBase::AddTitle(const TranslatableString &Prompt, int wrapWidth)
|
|||||||
|
|
||||||
/// Very generic 'Add' function. We can add anything we like.
|
/// Very generic 'Add' function. We can add anything we like.
|
||||||
/// Useful for unique controls
|
/// Useful for unique controls
|
||||||
wxWindow * ShuttleGuiBase::AddWindow(wxWindow * pWindow)
|
wxWindow* ShuttleGuiBase::AddWindow(wxWindow* pWindow, int PositionFlags)
|
||||||
{
|
{
|
||||||
if( mShuttleMode != eIsCreating )
|
if( mShuttleMode != eIsCreating )
|
||||||
return pWindow;
|
return pWindow;
|
||||||
mpWind = pWindow;
|
mpWind = pWindow;
|
||||||
SetProportions( 0 );
|
SetProportions( 0 );
|
||||||
UpdateSizersCore(false, wxALIGN_CENTRE | wxALL);
|
UpdateSizersCore(false, PositionFlags | wxALL);
|
||||||
return pWindow;
|
return pWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1200,6 +1207,25 @@ void ShuttleGuiBase::EndVerticalLay()
|
|||||||
PopSizer();
|
PopSizer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShuttleGuiBase::StartWrapLay(int PositionFlags, int iProp)
|
||||||
|
{
|
||||||
|
if (mShuttleMode != eIsCreating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
miSizerProp = iProp;
|
||||||
|
mpSubSizer = std::make_unique<wxWrapSizer>(wxHORIZONTAL, 0);
|
||||||
|
|
||||||
|
UpdateSizersCore(false, PositionFlags | wxALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShuttleGuiBase::EndWrapLay()
|
||||||
|
{
|
||||||
|
if (mShuttleMode != eIsCreating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PopSizer();
|
||||||
|
}
|
||||||
|
|
||||||
void ShuttleGuiBase::StartMultiColumn(int nCols, int PositionFlags)
|
void ShuttleGuiBase::StartMultiColumn(int nCols, int PositionFlags)
|
||||||
{
|
{
|
||||||
if( mShuttleMode != eIsCreating )
|
if( mShuttleMode != eIsCreating )
|
||||||
|
@ -256,7 +256,7 @@ public:
|
|||||||
void AddPrompt(const TranslatableString &Prompt, int wrapWidth = 0);
|
void AddPrompt(const TranslatableString &Prompt, int wrapWidth = 0);
|
||||||
void AddUnits(const TranslatableString &Prompt, int wrapWidth = 0);
|
void AddUnits(const TranslatableString &Prompt, int wrapWidth = 0);
|
||||||
void AddTitle(const TranslatableString &Prompt, int wrapWidth = 0);
|
void AddTitle(const TranslatableString &Prompt, int wrapWidth = 0);
|
||||||
wxWindow * AddWindow(wxWindow * pWindow);
|
wxWindow * AddWindow(wxWindow* pWindow, int PositionFlags = wxALIGN_CENTRE);
|
||||||
wxSlider * AddSlider(
|
wxSlider * AddSlider(
|
||||||
const TranslatableString &Prompt, int pos, int Max, int Min = 0);
|
const TranslatableString &Prompt, int pos, int Max, int Min = 0);
|
||||||
wxSlider * AddVSlider(const TranslatableString &Prompt, int pos, int Max);
|
wxSlider * AddVSlider(const TranslatableString &Prompt, int pos, int Max);
|
||||||
@ -348,10 +348,14 @@ public:
|
|||||||
// and create the appropriate widget.
|
// and create the appropriate widget.
|
||||||
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1);
|
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1);
|
||||||
void EndHorizontalLay();
|
void EndHorizontalLay();
|
||||||
|
|
||||||
void StartVerticalLay(int iProp=1);
|
void StartVerticalLay(int iProp=1);
|
||||||
void StartVerticalLay(int PositionFlags, int iProp);
|
void StartVerticalLay(int PositionFlags, int iProp);
|
||||||
|
|
||||||
void EndVerticalLay();
|
void EndVerticalLay();
|
||||||
|
|
||||||
|
void StartWrapLay(int PositionFlags=wxEXPAND, int iProp = 0);
|
||||||
|
void EndWrapLay();
|
||||||
|
|
||||||
wxScrolledWindow * StartScroller(int iStyle=0);
|
wxScrolledWindow * StartScroller(int iStyle=0);
|
||||||
void EndScroller();
|
void EndScroller();
|
||||||
wxPanel * StartPanel(int iStyle=0);
|
wxPanel * StartPanel(int iStyle=0);
|
||||||
@ -483,6 +487,7 @@ public:
|
|||||||
const int min);
|
const int min);
|
||||||
//-- End of variants.
|
//-- End of variants.
|
||||||
void SetBorder( int Border ) {miBorder = Border;};
|
void SetBorder( int Border ) {miBorder = Border;};
|
||||||
|
int GetBorder() const noexcept;
|
||||||
void SetSizerProportion( int iProp ) {miSizerProp = iProp;};
|
void SetSizerProportion( int iProp ) {miSizerProp = iProp;};
|
||||||
void SetStretchyCol( int i );
|
void SetStretchyCol( int i );
|
||||||
void SetStretchyRow( int i );
|
void SetStretchyRow( int i );
|
||||||
|
@ -19,10 +19,13 @@
|
|||||||
#include "update/UpdateManager.h"
|
#include "update/UpdateManager.h"
|
||||||
|
|
||||||
#include <wx/defs.h>
|
#include <wx/defs.h>
|
||||||
|
#include <wx/hyperlink.h>
|
||||||
|
|
||||||
#include "../Prefs.h"
|
#include "../Prefs.h"
|
||||||
#include "../ShuttleGui.h"
|
#include "../ShuttleGui.h"
|
||||||
|
|
||||||
|
#include "ui/AccessibleLinksFormatter.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static ComponentInterfaceSymbol s_ComponentInterfaceSymbol{ XO("Application") };
|
static ComponentInterfaceSymbol s_ComponentInterfaceSymbol{ XO("Application") };
|
||||||
@ -68,10 +71,34 @@ void ApplicationPrefs::PopulateOrExchange(ShuttleGui & S)
|
|||||||
S.SetBorder(2);
|
S.SetBorder(2);
|
||||||
S.StartScroller();
|
S.StartScroller();
|
||||||
|
|
||||||
S.StartStatic(XO("Update Audacity"));
|
/* i18n-hint: Title for the update notifications panel in the preferences dialog. */
|
||||||
|
S.StartStatic(XO("Update notifications"));
|
||||||
{
|
{
|
||||||
S.TieCheckBox(XO("&Check for Updates"), DefaultUpdatesCheckingFlag);
|
S.TieCheckBox(
|
||||||
|
/* i18n-hint: Check-box title that configures periodic updates checking. */
|
||||||
|
XXC("&Check for updates", "application preferences"),
|
||||||
|
DefaultUpdatesCheckingFlag);
|
||||||
|
|
||||||
|
S.StartVerticalLay();
|
||||||
|
{
|
||||||
|
S.AddFixedText(XO(
|
||||||
|
"App update checking requires network access. In order to protect your privacy, Audacity does not store any personal information."),
|
||||||
|
false, 470);
|
||||||
|
|
||||||
|
/* i18n-hint: %s will be replaced with "our Privacy Policy" */
|
||||||
|
AccessibleLinksFormatter privacyPolicy(XO("See %s for more info."));
|
||||||
|
|
||||||
|
privacyPolicy.FormatLink(
|
||||||
|
/* i18n-hint: Title of hyperlink to the privacy policy. This is an object of "See". */
|
||||||
|
wxT("%s"), XO("our Privacy Policy"),
|
||||||
|
"https://www.audacityteam.org/about/desktop-privacy-notice/");
|
||||||
|
|
||||||
|
privacyPolicy.Populate(S);
|
||||||
|
}
|
||||||
|
|
||||||
|
S.EndVerticalLay();
|
||||||
}
|
}
|
||||||
|
|
||||||
S.EndStatic();
|
S.EndStatic();
|
||||||
S.EndScroller();
|
S.EndScroller();
|
||||||
}
|
}
|
||||||
|
@ -475,7 +475,9 @@ void KeyConfigPrefs::OnShow(wxShowEvent & event)
|
|||||||
{
|
{
|
||||||
event.Skip();
|
event.Skip();
|
||||||
|
|
||||||
if (event.IsShown())
|
// This is required to prevent a crash if Preferences
|
||||||
|
// were opened without a project.
|
||||||
|
if (event.IsShown() && mView != nullptr)
|
||||||
{
|
{
|
||||||
mView->Refresh();
|
mView->Refresh();
|
||||||
}
|
}
|
||||||
|
@ -782,6 +782,11 @@ void PrefsDialog::SelectPageByName(const wxString &pageName)
|
|||||||
for (size_t i = 0; i < n; i++) {
|
for (size_t i = 0; i < n; i++) {
|
||||||
if (mCategories->GetPageText(i) == pageName) {
|
if (mCategories->GetPageText(i) == pageName) {
|
||||||
mCategories->SetSelection(i);
|
mCategories->SetSelection(i);
|
||||||
|
// This covers the case, when ShowModal is called
|
||||||
|
// after selecting the page.
|
||||||
|
// ShowModal will select the page previously used by
|
||||||
|
// user
|
||||||
|
SavePreferredPage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
185
src/ui/AccessibleLinksFormatter.cpp
Normal file
185
src/ui/AccessibleLinksFormatter.cpp
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/*!********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
@file AccessibleLinksFormatter.h
|
||||||
|
@brief Define a helper class to format text with link in a way, accessible to VI users.
|
||||||
|
|
||||||
|
Dmitry Vedenko
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#include "AccessibleLinksFormatter.h"
|
||||||
|
|
||||||
|
#include "ShuttleGui.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <wx/hyperlink.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
size_t OffsetPosition(size_t position, size_t length)
|
||||||
|
{
|
||||||
|
if (position == wxString::npos)
|
||||||
|
return wxString::npos;
|
||||||
|
|
||||||
|
return position + length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessibleLinksFormatter::AccessibleLinksFormatter(TranslatableString message)
|
||||||
|
: mMessage(std::move(message))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
|
||||||
|
wxString placeholder, TranslatableString value, std::string targetURL)
|
||||||
|
{
|
||||||
|
mFormatArguments.push_back({
|
||||||
|
std::move(placeholder),
|
||||||
|
std::move(value),
|
||||||
|
{},
|
||||||
|
std::move(targetURL)
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
|
||||||
|
wxString placeholder, TranslatableString value,
|
||||||
|
LinkClickedHandler handler)
|
||||||
|
{
|
||||||
|
mFormatArguments.push_back({
|
||||||
|
std::move(placeholder),
|
||||||
|
std::move(value),
|
||||||
|
std::move(handler),
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccessibleLinksFormatter::Populate(ShuttleGui& S) const
|
||||||
|
{
|
||||||
|
// Just add the text, if there are no links to format
|
||||||
|
if (mFormatArguments.empty())
|
||||||
|
{
|
||||||
|
S.AddFixedText(mMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString translated = mMessage.Translation();
|
||||||
|
|
||||||
|
std::vector<ProcessedArgument> processedArguments =
|
||||||
|
ProcessArguments(translated);
|
||||||
|
|
||||||
|
if (processedArguments.empty())
|
||||||
|
{
|
||||||
|
S.AddFixedText(mMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int borderSize = S.GetBorder();
|
||||||
|
|
||||||
|
S.Prop(0).StartInvisiblePanel();
|
||||||
|
S.StartWrapLay();
|
||||||
|
{
|
||||||
|
size_t currentPosition = 0;
|
||||||
|
|
||||||
|
S.SetBorder(0);
|
||||||
|
|
||||||
|
if (borderSize > 0)
|
||||||
|
S.AddSpace(borderSize);
|
||||||
|
|
||||||
|
for (const ProcessedArgument& processedArgument : processedArguments)
|
||||||
|
{
|
||||||
|
const FormatArgument* argument = processedArgument.Argument;
|
||||||
|
|
||||||
|
// Add everything between currentPosition and PlaceholderPosition
|
||||||
|
|
||||||
|
if (currentPosition != processedArgument.PlaceholderPosition)
|
||||||
|
{
|
||||||
|
const size_t substrLength =
|
||||||
|
processedArgument.PlaceholderPosition - currentPosition;
|
||||||
|
|
||||||
|
S.Prop(0).AddFixedText(
|
||||||
|
Verbatim(translated.substr(currentPosition, substrLength)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add hyperlink
|
||||||
|
|
||||||
|
wxHyperlinkCtrl* hyperlink = safenew wxHyperlinkCtrl(
|
||||||
|
S.GetParent(), wxID_ANY, argument->Value.Translation(),
|
||||||
|
argument->TargetURL);
|
||||||
|
|
||||||
|
if (argument->Handler)
|
||||||
|
{
|
||||||
|
hyperlink->Bind(
|
||||||
|
wxEVT_HYPERLINK, [handler = argument->Handler](wxHyperlinkEvent& evt) {
|
||||||
|
handler();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
S.AddWindow(hyperlink, wxALIGN_TOP | wxALIGN_LEFT);
|
||||||
|
|
||||||
|
// Update the currentPostion to the first symbol after the Placeholder
|
||||||
|
|
||||||
|
currentPosition = OffsetPosition(
|
||||||
|
processedArgument.PlaceholderPosition,
|
||||||
|
argument->Placeholder.Length());
|
||||||
|
|
||||||
|
if (currentPosition >= translated.Length())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPosition < translated.Length())
|
||||||
|
S.AddFixedText(Verbatim(translated.substr(currentPosition)));
|
||||||
|
}
|
||||||
|
S.EndWrapLay();
|
||||||
|
S.EndInvisiblePanel();
|
||||||
|
|
||||||
|
S.SetBorder(borderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AccessibleLinksFormatter::ProcessedArgument>
|
||||||
|
AccessibleLinksFormatter::ProcessArguments(wxString translatedMessage) const
|
||||||
|
{
|
||||||
|
std::vector<ProcessedArgument> result;
|
||||||
|
result.reserve(mFormatArguments.size());
|
||||||
|
// Arguments with the same placeholder are processed left-to-right.
|
||||||
|
// Lets track the last known position of the placeholder
|
||||||
|
std::unordered_map<wxString, size_t> knownPlaceholderPosition;
|
||||||
|
|
||||||
|
for (const FormatArgument& argument : mFormatArguments)
|
||||||
|
{
|
||||||
|
auto it = knownPlaceholderPosition.find(argument.Placeholder);
|
||||||
|
|
||||||
|
const size_t startingPosition =
|
||||||
|
it != knownPlaceholderPosition.end() ?
|
||||||
|
OffsetPosition(it->second, argument.Placeholder.length()) :
|
||||||
|
0;
|
||||||
|
|
||||||
|
const size_t placeholderPosition =
|
||||||
|
startingPosition == wxString::npos ?
|
||||||
|
wxString::npos :
|
||||||
|
translatedMessage.find(argument.Placeholder, startingPosition);
|
||||||
|
|
||||||
|
knownPlaceholderPosition[argument.Placeholder] = placeholderPosition;
|
||||||
|
|
||||||
|
if (placeholderPosition != wxString::npos)
|
||||||
|
{
|
||||||
|
result.emplace_back(
|
||||||
|
ProcessedArgument { &argument, placeholderPosition });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
result.begin(), result.end(),
|
||||||
|
[](const ProcessedArgument& lhs, const ProcessedArgument& rhs) {
|
||||||
|
return lhs.PlaceholderPosition < rhs.PlaceholderPosition;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
79
src/ui/AccessibleLinksFormatter.h
Normal file
79
src/ui/AccessibleLinksFormatter.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*!********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
@file AccessibleLinksFormatter.h
|
||||||
|
@brief Define a helper class to format text with link in a way, accessible to VI users.
|
||||||
|
|
||||||
|
Dmitry Vedenko
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "TranslatableString.h"
|
||||||
|
|
||||||
|
class ShuttleGui;
|
||||||
|
|
||||||
|
/*! @brief A class that allows translatable text to have accessible links in it in a way
|
||||||
|
* that is friendly to translators.
|
||||||
|
*
|
||||||
|
* This class allows to replace arbitrary placeholders (like %s, %url, {} or anyting of the choice)
|
||||||
|
* with links, that are accessible from the keyboard.
|
||||||
|
*
|
||||||
|
* In case there are multiple placeholders with the same name - they will be replaced in order
|
||||||
|
* they appear in the message.
|
||||||
|
*/
|
||||||
|
class AccessibleLinksFormatter final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//! Handler to be called, when the Link is activated
|
||||||
|
using LinkClickedHandler = std::function<void()>;
|
||||||
|
|
||||||
|
/*! @brief Create AccessibleLinksFormatter using a TranslatableString.
|
||||||
|
*
|
||||||
|
* TranslatableString may have the formatting options attached.
|
||||||
|
* TranslatableString copy will be stored, so formatting options that are appended
|
||||||
|
* after AccessibleLinksFormatter is created won't have any effect on the
|
||||||
|
* AccessibleLinksFormatter instance.
|
||||||
|
*/
|
||||||
|
explicit AccessibleLinksFormatter(TranslatableString message);
|
||||||
|
|
||||||
|
//! Replace placeholder with a link, that will open URL in default browser.
|
||||||
|
AccessibleLinksFormatter& FormatLink(
|
||||||
|
wxString placeholder, TranslatableString value, std::string targetURL);
|
||||||
|
|
||||||
|
//! Replace placeholder with a link, that will call a callback provided.
|
||||||
|
AccessibleLinksFormatter& FormatLink(
|
||||||
|
wxString placeholder, TranslatableString value,
|
||||||
|
LinkClickedHandler handler);
|
||||||
|
|
||||||
|
//! Generate the UI.
|
||||||
|
void Populate(ShuttleGui& S) const;
|
||||||
|
private:
|
||||||
|
struct FormatArgument final
|
||||||
|
{
|
||||||
|
wxString Placeholder;
|
||||||
|
TranslatableString Value;
|
||||||
|
|
||||||
|
LinkClickedHandler Handler;
|
||||||
|
std::string TargetURL;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProcessedArgument final
|
||||||
|
{
|
||||||
|
const FormatArgument* Argument { nullptr };
|
||||||
|
size_t PlaceholderPosition { wxString::npos };
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Find the positions of the placeholders and sort
|
||||||
|
* arguments according to the positions.
|
||||||
|
*/
|
||||||
|
std::vector<ProcessedArgument>
|
||||||
|
ProcessArguments(wxString translatedMessage) const;
|
||||||
|
|
||||||
|
TranslatableString mMessage;
|
||||||
|
std::vector<FormatArgument> mFormatArguments;
|
||||||
|
};
|
@ -8,7 +8,9 @@
|
|||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include "UpdateManager.h"
|
#include "UpdateManager.h"
|
||||||
|
|
||||||
#include "UpdatePopupDialog.h"
|
#include "UpdatePopupDialog.h"
|
||||||
|
#include "UpdateNoticeDialog.h"
|
||||||
|
|
||||||
#include "AudioIO.h"
|
#include "AudioIO.h"
|
||||||
#include "NetworkManager.h"
|
#include "NetworkManager.h"
|
||||||
@ -26,6 +28,9 @@
|
|||||||
|
|
||||||
static const char* prefsUpdateScheduledTime = "/Update/UpdateScheduledTime";
|
static const char* prefsUpdateScheduledTime = "/Update/UpdateScheduledTime";
|
||||||
|
|
||||||
|
static BoolSetting
|
||||||
|
prefUpdatesNoticeShown(wxT("/Update/UpdateNoticeShown"), false);
|
||||||
|
|
||||||
|
|
||||||
using Clock = std::chrono::system_clock;
|
using Clock = std::chrono::system_clock;
|
||||||
using TimePoint = Clock::time_point;
|
using TimePoint = Clock::time_point;
|
||||||
@ -51,10 +56,27 @@ void UpdateManager::Start()
|
|||||||
auto& instance = GetInstance();
|
auto& instance = GetInstance();
|
||||||
|
|
||||||
static std::once_flag flag;
|
static std::once_flag flag;
|
||||||
|
|
||||||
std::call_once(flag, [&instance] {
|
std::call_once(flag, [&instance] {
|
||||||
instance.mTimer.SetOwner(&instance, ID_TIMER);
|
instance.mTimer.SetOwner(&instance, ID_TIMER);
|
||||||
instance.mTimer.StartOnce(1);
|
instance.mTimer.StartOnce(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show the dialog only once.
|
||||||
|
if (!prefUpdatesNoticeShown.Read())
|
||||||
|
{
|
||||||
|
// DefaultUpdatesCheckingFlag survives the "Reset Preferences"
|
||||||
|
// action, so check, if the updates were previously disabled as well.
|
||||||
|
if (DefaultUpdatesCheckingFlag.Read())
|
||||||
|
{
|
||||||
|
UpdateNoticeDialog notice(nullptr);
|
||||||
|
|
||||||
|
notice.ShowModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
prefUpdatesNoticeShown.Write(true);
|
||||||
|
gPrefs->Flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionPatch UpdateManager::GetVersionPatch() const
|
VersionPatch UpdateManager::GetVersionPatch() const
|
||||||
|
127
src/update/UpdateNoticeDialog.cpp
Normal file
127
src/update/UpdateNoticeDialog.cpp
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
|
||||||
|
/*!********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
@file UpdateNoticeDialog.cpp
|
||||||
|
@brief Declare a dialog to notify the user about automatic update checking.
|
||||||
|
|
||||||
|
Dmitry Vedenko
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#include "UpdateNoticeDialog.h"
|
||||||
|
|
||||||
|
#include <wx/button.h>
|
||||||
|
#include <wx/stattext.h>
|
||||||
|
|
||||||
|
#include "ShuttleGui.h"
|
||||||
|
#include "CodeConversions.h"
|
||||||
|
#include "prefs/PrefsDialog.h"
|
||||||
|
#include "ui/AccessibleLinksFormatter.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const auto title =
|
||||||
|
/* i18n-hint: Title of the app update notice dialog. */
|
||||||
|
XO("App update checking");
|
||||||
|
|
||||||
|
static const auto firstParagraph =
|
||||||
|
/* i18-hint: The first paragraph of app update notice dialog. */
|
||||||
|
XO("To stay up to date, you will receive an in-app notification whenever there is a new version of Audacity available to download.");
|
||||||
|
|
||||||
|
static const auto secondParagraph =
|
||||||
|
/* i18-hint: The second paragraph of app update notice dialog */
|
||||||
|
XO("In order to protect your privacy, Audacity does not collect any personal information. However, app update checking does require network access.");
|
||||||
|
|
||||||
|
static const auto thirdParagraph =
|
||||||
|
/* i18-hint: Hint to the user about how to turn the app update off. %s is replaced with "Preferences > Application" link*/
|
||||||
|
XO("You can turn off app update checking at any time in %s.");
|
||||||
|
|
||||||
|
|
||||||
|
BEGIN_EVENT_TABLE(UpdateNoticeDialog, wxDialogWrapper)
|
||||||
|
EVT_BUTTON(wxID_OK, UpdateNoticeDialog::OnOk)
|
||||||
|
END_EVENT_TABLE()
|
||||||
|
|
||||||
|
IMPLEMENT_CLASS(UpdateNoticeDialog, wxDialogWrapper)
|
||||||
|
|
||||||
|
UpdateNoticeDialog::UpdateNoticeDialog(wxWindow* parent)
|
||||||
|
: wxDialogWrapper(
|
||||||
|
/* i18n-hint: Title of the app update notice dialog. */
|
||||||
|
parent, -1, XO("App updates"), wxDefaultPosition, wxDefaultSize,
|
||||||
|
wxCAPTION | wxCLOSE_BOX)
|
||||||
|
{
|
||||||
|
ShuttleGui S(this, eIsCreating);
|
||||||
|
|
||||||
|
S.StartVerticalLay();
|
||||||
|
{
|
||||||
|
S.AddSpace(0, 16);
|
||||||
|
|
||||||
|
S.StartHorizontalLay();
|
||||||
|
{
|
||||||
|
S.AddSpace(24, 0);
|
||||||
|
|
||||||
|
S.StartPanel();
|
||||||
|
{
|
||||||
|
S.SetBorder(8);
|
||||||
|
|
||||||
|
wxStaticText* titleCtrl = S.AddVariableText(title, false, 0, 500);
|
||||||
|
|
||||||
|
wxFont font = titleCtrl->GetFont().MakeLarger().MakeBold();
|
||||||
|
|
||||||
|
titleCtrl->SetFont(font);
|
||||||
|
|
||||||
|
S.AddFixedText(firstParagraph, false, 500);
|
||||||
|
|
||||||
|
S.AddFixedText(secondParagraph, false, 500);
|
||||||
|
|
||||||
|
/* i18n-hint: %s will be replaced with "our Privacy Policy" */
|
||||||
|
AccessibleLinksFormatter privacyPolicy(XO("See %s for more info."));
|
||||||
|
|
||||||
|
privacyPolicy.FormatLink(
|
||||||
|
/* i18n-hint: Title of hyperlink to the privacy policy. This is an object of "See". */
|
||||||
|
wxT("%s"), XO("our Privacy Policy"),
|
||||||
|
"https://www.audacityteam.org/about/desktop-privacy-notice/");
|
||||||
|
|
||||||
|
privacyPolicy.Populate(S);
|
||||||
|
|
||||||
|
S.AddSpace(0, 8);
|
||||||
|
|
||||||
|
AccessibleLinksFormatter preferencesMessage(thirdParagraph);
|
||||||
|
|
||||||
|
preferencesMessage.FormatLink(
|
||||||
|
wxT("%s"), XO("Preferences > Application"), [this]() {
|
||||||
|
GlobalPrefsDialog dialog(this /* parent */, nullptr);
|
||||||
|
|
||||||
|
dialog.SelectPageByName(XO("Application").Translation());
|
||||||
|
dialog.ShowModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
preferencesMessage.Populate(S);
|
||||||
|
}
|
||||||
|
S.EndPanel();
|
||||||
|
|
||||||
|
S.AddSpace(24, 0);
|
||||||
|
}
|
||||||
|
S.EndHorizontalLay();
|
||||||
|
|
||||||
|
S.StartHorizontalLay(wxEXPAND, 0);
|
||||||
|
{
|
||||||
|
S.AddSpace(1, 0, 1);
|
||||||
|
|
||||||
|
S.Id(wxID_OK).AddButton(XO("&OK"))->SetFocus();
|
||||||
|
|
||||||
|
S.AddSpace(8, 0);
|
||||||
|
}
|
||||||
|
S.EndHorizontalLay();
|
||||||
|
}
|
||||||
|
|
||||||
|
S.EndVerticalLay();
|
||||||
|
|
||||||
|
Layout();
|
||||||
|
Fit();
|
||||||
|
Center();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateNoticeDialog::OnOk(wxCommandEvent&)
|
||||||
|
{
|
||||||
|
EndModal(wxOK);
|
||||||
|
}
|
30
src/update/UpdateNoticeDialog.h
Normal file
30
src/update/UpdateNoticeDialog.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*!********************************************************************
|
||||||
|
|
||||||
|
Audacity: A Digital Audio Editor
|
||||||
|
|
||||||
|
@file UpdateNoticeDialog.h
|
||||||
|
@brief Define a dialog to notify the user about automatic update checking.
|
||||||
|
|
||||||
|
Dmitry Vedenko
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "widgets/wxPanelWrapper.h"
|
||||||
|
#include "wx/string.h"
|
||||||
|
|
||||||
|
class HtmlWindow;
|
||||||
|
class wxWindow;
|
||||||
|
|
||||||
|
//! Dialog, that notifies the users about automatic updates checking
|
||||||
|
class UpdateNoticeDialog final : public wxDialogWrapper
|
||||||
|
{
|
||||||
|
DECLARE_DYNAMIC_CLASS (AboutDialog)
|
||||||
|
public:
|
||||||
|
explicit UpdateNoticeDialog (wxWindow* parent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnOk (wxCommandEvent& event);
|
||||||
|
|
||||||
|
DECLARE_EVENT_TABLE ()
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user