diff --git a/Poedit.vcxproj b/Poedit.vcxproj
index dd5771ae74..d5df10e80d 100644
--- a/Poedit.vcxproj
+++ b/Poedit.vcxproj
@@ -119,10 +119,7 @@
-
- Create
- Create
-
+
@@ -141,6 +138,7 @@
+
@@ -152,6 +150,7 @@
+
@@ -203,6 +202,7 @@
+
@@ -216,6 +216,7 @@
+
@@ -307,6 +308,11 @@
Designer
+
+ Document
+ Create
+ Create
+
diff --git a/Poedit.vcxproj.filters b/Poedit.vcxproj.filters
index 86bc108a7a..c3f0fb3fda 100644
--- a/Poedit.vcxproj.filters
+++ b/Poedit.vcxproj.filters
@@ -177,6 +177,15 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
@@ -356,6 +365,12 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
diff --git a/Poedit.xcodeproj/project.pbxproj b/Poedit.xcodeproj/project.pbxproj
index 3e671dda94..27c2949fb3 100644
--- a/Poedit.xcodeproj/project.pbxproj
+++ b/Poedit.xcodeproj/project.pbxproj
@@ -92,8 +92,13 @@
B26897B4197A830200B42764 /* MoveApplication.strings in Resources */ = {isa = PBXBuildFile; fileRef = B26897B2197A830200B42764 /* MoveApplication.strings */; };
B26D064F182506E40069C378 /* languagectrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B26D064D182506E40069C378 /* languagectrl.cpp */; };
B26D0655182697200069C378 /* welcomescreen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B26D0653182697200069C378 /* welcomescreen.cpp */; };
- B26D06581826A22C0069C378 /* PoeditWelcome.png in Resources */ = {isa = PBXBuildFile; fileRef = B26D06561826A22C0069C378 /* PoeditWelcome.png */; };
- B26D06591826A22C0069C378 /* PoeditWelcome@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B26D06571826A22C0069C378 /* PoeditWelcome@2x.png */; };
+ B26E2C8325A244FF008D6DF1 /* icons.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B26E2C8125A244FE008D6DF1 /* icons.cpp */; };
+ B26E2C8625A24541008D6DF1 /* menus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B26E2C8425A24541008D6DF1 /* menus.cpp */; };
+ B26E2C8925A24571008D6DF1 /* titleless_window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B26E2C8825A24571008D6DF1 /* titleless_window.cpp */; };
+ B26E2C8E25A245BD008D6DF1 /* CloseButtonHoverTemplate@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B26E2C8A25A245BC008D6DF1 /* CloseButtonHoverTemplate@2x.png */; };
+ B26E2C8F25A245BD008D6DF1 /* CloseButtonTemplate@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = B26E2C8B25A245BC008D6DF1 /* CloseButtonTemplate@2x.png */; };
+ B26E2C9025A245BD008D6DF1 /* CloseButtonTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = B26E2C8C25A245BC008D6DF1 /* CloseButtonTemplate.png */; };
+ B26E2C9125A245BD008D6DF1 /* CloseButtonHoverTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = B26E2C8D25A245BD008D6DF1 /* CloseButtonHoverTemplate.png */; };
B27959DE1E85850A00DBA47D /* qa_checks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B27959DC1E85850A00DBA47D /* qa_checks.cpp */; };
B27959DF1E858F7100DBA47D /* qa_checks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B27959DC1E85850A00DBA47D /* qa_checks.cpp */; };
B280E84D1A92776D009F4A98 /* http_client_macos.mm in Sources */ = {isa = PBXBuildFile; fileRef = B280E84B1A92776D009F4A98 /* http_client_macos.mm */; };
@@ -387,8 +392,16 @@
B26D064E182506E40069C378 /* languagectrl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = languagectrl.h; sourceTree = ""; };
B26D0653182697200069C378 /* welcomescreen.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = welcomescreen.cpp; sourceTree = ""; };
B26D0654182697200069C378 /* welcomescreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = welcomescreen.h; sourceTree = ""; };
- B26D06561826A22C0069C378 /* PoeditWelcome.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = PoeditWelcome.png; sourceTree = ""; };
- B26D06571826A22C0069C378 /* PoeditWelcome@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "PoeditWelcome@2x.png"; sourceTree = ""; };
+ B26E2C8125A244FE008D6DF1 /* icons.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = icons.cpp; sourceTree = ""; };
+ B26E2C8225A244FF008D6DF1 /* icons.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = icons.h; sourceTree = ""; };
+ B26E2C8425A24541008D6DF1 /* menus.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = menus.cpp; sourceTree = ""; };
+ B26E2C8525A24541008D6DF1 /* menus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menus.h; sourceTree = ""; };
+ B26E2C8725A24571008D6DF1 /* titleless_window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = titleless_window.h; sourceTree = ""; };
+ B26E2C8825A24571008D6DF1 /* titleless_window.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = titleless_window.cpp; sourceTree = ""; };
+ B26E2C8A25A245BC008D6DF1 /* CloseButtonHoverTemplate@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "CloseButtonHoverTemplate@2x.png"; path = "macos/CloseButtonHoverTemplate@2x.png"; sourceTree = ""; };
+ B26E2C8B25A245BC008D6DF1 /* CloseButtonTemplate@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "CloseButtonTemplate@2x.png"; path = "macos/CloseButtonTemplate@2x.png"; sourceTree = ""; };
+ B26E2C8C25A245BC008D6DF1 /* CloseButtonTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CloseButtonTemplate.png; path = macos/CloseButtonTemplate.png; sourceTree = ""; };
+ B26E2C8D25A245BD008D6DF1 /* CloseButtonHoverTemplate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CloseButtonHoverTemplate.png; path = macos/CloseButtonHoverTemplate.png; sourceTree = ""; };
B27959DC1E85850A00DBA47D /* qa_checks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = qa_checks.cpp; sourceTree = ""; };
B27959DD1E85850A00DBA47D /* qa_checks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qa_checks.h; sourceTree = ""; };
B27D1FC519EFFA2800AB1913 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; };
@@ -766,8 +779,12 @@
B28F1CC516F629D30018AF7E /* gexecute.h */,
B230E2261A73F81400FB1E57 /* hidpi.cpp */,
B230E2271A73F81400FB1E57 /* hidpi.h */,
+ B26E2C8125A244FE008D6DF1 /* icons.cpp */,
+ B26E2C8225A244FF008D6DF1 /* icons.h */,
B28F1CCA16F629D30018AF7E /* manager.cpp */,
B28F1CCB16F629D30018AF7E /* manager.h */,
+ B26E2C8425A24541008D6DF1 /* menus.cpp */,
+ B26E2C8525A24541008D6DF1 /* menus.h */,
B28F1CCC16F629D30018AF7E /* macos_helpers.h */,
B28F1CCD16F629D30018AF7E /* macos_helpers.mm */,
B28F1CD016F629D30018AF7E /* prefsdlg.cpp */,
@@ -788,6 +805,8 @@
B2E2184B199A76B100EA2784 /* syntaxhighlighter.h */,
B2E11F101A2C66FB00E4E42C /* text_control.cpp */,
B2E11F111A2C66FB00E4E42C /* text_control.h */,
+ B26E2C8825A24571008D6DF1 /* titleless_window.cpp */,
+ B26E2C8725A24571008D6DF1 /* titleless_window.h */,
B28F1CDE16F629D30018AF7E /* utility.cpp */,
B28F1CDF16F629D30018AF7E /* utility.h */,
B26D064D182506E40069C378 /* languagectrl.cpp */,
@@ -1010,6 +1029,10 @@
B2B1EB0619E45FE7009A59B7 /* Toolbar */,
B2166F4019AE4024000A6AA0 /* Preferences */,
B24ACD8316F6263900399242 /* Poedit.iconset */,
+ B26E2C8D25A245BD008D6DF1 /* CloseButtonHoverTemplate.png */,
+ B26E2C8A25A245BC008D6DF1 /* CloseButtonHoverTemplate@2x.png */,
+ B26E2C8C25A245BC008D6DF1 /* CloseButtonTemplate.png */,
+ B26E2C8B25A245BC008D6DF1 /* CloseButtonTemplate@2x.png */,
B290F9E22166543800741842 /* DownvoteTemplate.png */,
B290F9E12166543800741842 /* DownvoteTemplate@2x.png */,
B2E836E21709ED2A00F31559 /* poedit-status-cat-mid.png */,
@@ -1027,8 +1050,6 @@
B24D19681E84503B00C6DD8D /* StatusWarning@2x.png */,
B20F31CE216654DA005B7037 /* StatusWarningBlack.png */,
B20F31CF216654DA005B7037 /* StatusWarningBlack@2x.png */,
- B26D06561826A22C0069C378 /* PoeditWelcome.png */,
- B26D06571826A22C0069C378 /* PoeditWelcome@2x.png */,
B209005F19CAD64A00D6382E /* SuggestionErrorTemplate.png */,
B2E7F16D1E045343005FA992 /* SuggestionErrorTemplate@2x.png */,
B2D52B841DE88A3D00E27B35 /* SuggestionPerfectMatch.png */,
@@ -1231,6 +1252,7 @@
B29A1C331A9F8C0A00BC3006 /* poedit-sync.png in Resources */,
B2E836EB1709ED2A00F31559 /* poedit-status-cat-mid.png in Resources */,
B20F31CD216654D2005B7037 /* StatusErrorBlack.png in Resources */,
+ B26E2C9025A245BD008D6DF1 /* CloseButtonTemplate.png in Resources */,
B2E836EC1709ED2A00F31559 /* poedit-status-cat-no.png in Resources */,
B2E836ED1709ED2A00F31559 /* poedit-status-cat-ok.png in Resources */,
B22A5C86184E4B870034BEFD /* document-properties@2x.png in Resources */,
@@ -1243,11 +1265,13 @@
B2E837021709ED6300F31559 /* poedit-update.png in Resources */,
B2E837031709ED6300F31559 /* poedit-validate.png in Resources */,
B267EAFD1E045959005541E7 /* StatusError.png in Resources */,
+ B26E2C8E25A245BD008D6DF1 /* CloseButtonHoverTemplate@2x.png in Resources */,
B290F9E32166543800741842 /* DownvoteTemplate@2x.png in Resources */,
B292667421664C9500DC536C /* ItemCommentTemplate.png in Resources */,
B290F9E42166543800741842 /* DownvoteTemplate.png in Resources */,
B29AE89017103992008D1F8A /* comment.xrc in Resources */,
B22A5C83184E4B870034BEFD /* edit-delete@2x.png in Resources */,
+ B26E2C8F25A245BD008D6DF1 /* CloseButtonTemplate@2x.png in Resources */,
B292667621664C9500DC536C /* ItemBookmarkTemplate@2x.png in Resources */,
B2A012B321BEE4C5008051FD /* SuggestionTMTemplate@2x.png in Resources */,
B20F31D0216654DA005B7037 /* StatusWarningBlack.png in Resources */,
@@ -1266,12 +1290,11 @@
B29AE89617103992008D1F8A /* properties.xrc in Resources */,
B29AE89717103992008D1F8A /* summary.xrc in Resources */,
B29AE89A17103992008D1F8A /* toolbar.xrc in Resources */,
+ B26E2C9125A245BD008D6DF1 /* CloseButtonHoverTemplate.png in Resources */,
B2CE2FEC1A94EBDE0020A620 /* CrowdinLogoTemplate@2x.png in Resources */,
B2EC60A41812D9D40059756A /* icudt57l.dat in Resources */,
B25DB35E1E3E5102006D7228 /* ExtractorsGNUgettext@2x.png in Resources */,
B24D196A1E84503B00C6DD8D /* StatusWarning@2x.png in Resources */,
- B26D06581826A22C0069C378 /* PoeditWelcome.png in Resources */,
- B26D06591826A22C0069C378 /* PoeditWelcome@2x.png in Resources */,
B26897B4197A830200B42764 /* MoveApplication.strings in Resources */,
B292667521664C9500DC536C /* ItemCommentTemplate@2x.png in Resources */,
B22A5C82184E4B870034BEFD /* poedit-validate@2x.png in Resources */,
@@ -1458,7 +1481,9 @@
B295C6031E2A81C200CD71CD /* extractor.cpp in Sources */,
B28F1CEE16F629D30018AF7E /* edlistctrl.cpp in Sources */,
B28F1CF016F629D30018AF7E /* fileviewer.cpp in Sources */,
+ B26E2C8925A24571008D6DF1 /* titleless_window.cpp in Sources */,
B2132FDA19B3672000326B16 /* customcontrols.cpp in Sources */,
+ B26E2C8325A244FF008D6DF1 /* icons.cpp in Sources */,
B295C6021E2A81C200CD71CD /* extractor_legacy.cpp in Sources */,
B28F1CF116F629D30018AF7E /* findframe.cpp in Sources */,
B28F1CF216F629D30018AF7E /* gexecute.cpp in Sources */,
@@ -1484,6 +1509,7 @@
B280E84D1A92776D009F4A98 /* http_client_macos.mm in Sources */,
B22C5F0A17DDC67400ECAFD1 /* language.cpp in Sources */,
B2E2184C199A76B100EA2784 /* syntaxhighlighter.cpp in Sources */,
+ B26E2C8625A24541008D6DF1 /* menus.cpp in Sources */,
B2377A202159179B0085E9C4 /* catalog_xliff.cpp in Sources */,
B2284A53183BE3B300E097C7 /* PFMoveApplication.m in Sources */,
B25D94941AE3D7E3003BC368 /* concurrency.cpp in Sources */,
diff --git a/Poedit2.xcworkspace/xcshareddata/xcschemes/Poedit.xcscheme b/Poedit2.xcworkspace/xcshareddata/xcschemes/Poedit.xcscheme
index 86bf7773a6..ca7e885f5a 100644
--- a/Poedit2.xcworkspace/xcshareddata/xcschemes/Poedit.xcscheme
+++ b/Poedit2.xcworkspace/xcshareddata/xcschemes/Poedit.xcscheme
@@ -96,7 +96,7 @@
+ isEnabled = "NO">
AddSpacer(PX(6));
topsizer->Add(loginSizer, wxSizerFlags().Right().PXDoubleBorder(wxLEFT|wxRIGHT));
- auto loginText = new SecondaryLabel(this, "");
- auto loginImage = new AvatarIcon(this, wxSize(PX(24), PX(24)));
- loginSizer->Add(loginText, wxSizerFlags().ReserveSpaceEvenIfHidden().Center().Border(wxRIGHT, PX(5)));
- loginSizer->Add(loginImage, wxSizerFlags().ReserveSpaceEvenIfHidden().Center());
- FetchLoginInfo(loginText, loginImage);
+ m_loginText = new SecondaryLabel(this, "");
+ m_loginImage = new AvatarIcon(this, wxSize(PX(24), PX(24)));
+ loginSizer->Add(m_loginText, wxSizerFlags().ReserveSpaceEvenIfHidden().Center().Border(wxRIGHT, PX(5)));
+ loginSizer->Add(m_loginImage, wxSizerFlags().ReserveSpaceEvenIfHidden().Center());
auto pickers = new wxFlexGridSizer(2, wxSize(PX(5),PX(6)));
pickers->AddGrowableCol(1);
@@ -452,7 +451,6 @@ class CrowdinOpenDialog : public wxDialog
#endif
SetSizerAndFit(topsizer);
- CenterOnParent();
m_project->Bind(wxEVT_CHOICE, [=](wxCommandEvent&){ OnProjectSelected(); });
ok->Bind(wxEVT_UPDATE_UI, &CrowdinOpenDialog::OnUpdateOK, this);
@@ -460,38 +458,42 @@ class CrowdinOpenDialog : public wxDialog
ok->Disable();
EnableAllChoices(false);
+ }
+ void LoadFromCrowdin()
+ {
FetchProjects();
+ FetchLoginInfo();
}
wxString OutLocalFilename;
private:
- void FetchLoginInfo(SecondaryLabel *label, AvatarIcon *icon)
+ void FetchLoginInfo()
{
- label->Hide();
- icon->Hide();
+ m_loginText->Hide();
+ m_loginImage->Hide();
CrowdinClient::Get().GetUserInfo()
.then_on_window(this, [=](CrowdinClient::UserInfo u)
{
- label->SetLabel(_("Signed in as:") + " " + u.name);
- icon->SetUserName(u.name);
+ m_loginText->SetLabel(_("Signed in as:") + " " + u.name);
+ m_loginImage->SetUserName(u.name);
if (u.avatar.empty())
{
- icon->Show();
+ m_loginImage->Show();
}
else
{
http_client::download_from_anywhere(u.avatar)
.then_on_window(this, [=](downloaded_file f)
{
- icon->LoadIcon(f.filename());
- icon->Show();
+ m_loginImage->LoadIcon(f.filename());
+ m_loginImage->Show();
});
}
Layout();
- label->Show();
+ m_loginText->Show();
})
.catch_all(m_activity->HandleError);
}
@@ -656,6 +658,8 @@ class CrowdinOpenDialog : public wxDialog
}
private:
+ SecondaryLabel *m_loginText;
+ AvatarIcon *m_loginImage;
wxButton *m_ok;
wxChoice *m_project, *m_language;
CrowdinFileList *m_files;
@@ -745,25 +749,33 @@ bool ShouldSyncToCrowdinAutomatically(CatalogPtr cat)
}
-void CrowdinOpenFile(wxWindow *parent, std::function onLoaded)
+void CrowdinOpenFile(wxWindow *parent, std::function onDone)
{
- if (!CrowdinClient::Get().IsSignedIn())
+ wxWindowPtr dlg(new CrowdinOpenDialog(parent));
+
+ if (CrowdinClient::Get().IsSignedIn())
{
- wxWindowPtr login(new CrowdinLoginDialog(parent));
- login->ShowWindowModalThenDo([login,parent,onLoaded](int retval){
- if (retval == wxID_OK)
- CrowdinOpenFile(parent, onLoaded);
+ dlg->LoadFromCrowdin();
+ }
+ else
+ {
+ // We need to show this window-modall after the ShowModal() call below is
+ // executed. Use CallAfter() to delay:
+ dlg->CallAfter([=]
+ {
+ wxWindowPtr login(new CrowdinLoginDialog(dlg.get()));
+ login->ShowWindowModalThenDo([dlg,login](int retval)
+ {
+ if (retval == wxID_OK)
+ dlg->LoadFromCrowdin();
+ else
+ dlg->EndModal(wxID_CANCEL);
+ });
});
- return;
}
- wxWindowPtr dlg(new CrowdinOpenDialog(parent));
-
- dlg->ShowWindowModalThenDo([dlg,onLoaded](int retval) {
- dlg->Hide();
- if (retval == wxID_OK)
- onLoaded(dlg->OutLocalFilename);
- });
+ auto retval = dlg->ShowModal(); // FIXME: Use global modal-less dialog
+ onDone(retval, dlg->OutLocalFilename);
}
diff --git a/src/crowdin_gui.h b/src/crowdin_gui.h
index 0094241e02..323b1103fa 100644
--- a/src/crowdin_gui.h
+++ b/src/crowdin_gui.h
@@ -109,9 +109,9 @@ bool ShouldSyncToCrowdinAutomatically(CatalogPtr cat);
Let the user choose a Crowdin file, download it and open in Poedit.
@param parent PoeditFrame the UI should be shown under.
- @param onLoaded Called with the name of loaded PO file.
+ @param onDone Called with the dialog return value (wxID_OK/CANCEL) and name of loaded PO file.
*/
-void CrowdinOpenFile(wxWindow *parent, std::function onLoaded);
+void CrowdinOpenFile(wxWindow *parent, std::function onDone);
/**
Synces the catalog with Crowdin, uploading and downloading translations.
diff --git a/src/custom_buttons.cpp b/src/custom_buttons.cpp
index 3c62503a69..33695c3e11 100644
--- a/src/custom_buttons.cpp
+++ b/src/custom_buttons.cpp
@@ -30,10 +30,13 @@
#include "str_helpers.h"
#include "utility.h"
+#include
+
#ifdef __WXMSW__
#include
#include
#include
+#include
#include
#include
#endif
@@ -49,6 +52,105 @@
#include "StyleKit.h"
+// ---------------------------------------------------------------------
+// ActionButton
+// ---------------------------------------------------------------------
+
+@interface POActionButton : NSButton
+
+@property wxWindow *parent;
+@property NSString *heading;
+@property BOOL mouseHover;
+
+@end
+
+@implementation POActionButton
+
+- (id)initWithLabel:(NSString*)label heading:(NSString*)heading
+{
+ self = [super init];
+ if (self)
+ {
+ self.title = label;
+ self.heading = heading;
+ self.buttonType = NSMomentaryPushInButton;
+ self.bezelStyle = NSTexturedRoundedBezelStyle;
+ self.showsBorderOnlyWhileMouseInside = YES;
+ self.mouseHover = NO;
+ }
+ return self;
+}
+
+- (void)sizeToFit
+{
+ [super sizeToFit];
+ NSSize size = self.frame.size;
+ size.height = 48;
+ if (self.image)
+ size.width += 32;
+ [self setFrameSize:size];
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+ #pragma unused(dirtyRect)
+ NSColor *bg = self.window.backgroundColor;
+ if (self.mouseHover)
+ {
+ NSColor *highlight;
+ if (@available(macOS 10.14, *)) {
+ highlight = [bg colorWithSystemEffect:NSColorSystemEffectRollover];
+ } else {
+ highlight = [NSColor colorWithWhite:0.72 alpha:1.0];
+ }
+ // use only lighter version of the highlight by blending with the background
+ bg = [bg blendedColorWithFraction:0.2 ofColor:highlight];
+ }
+ [StyleKit drawActionButtonWithFrame:self.bounds
+ buttonColor:bg
+ hasIcon:self.image != nil
+ label:self.heading
+ description:self.title];
+
+ // unlike normal drawing methods, NSButtonCell's drawImage supports template images
+ if (self.image)
+ [self.cell drawImage:self.image withFrame:NSMakeRect(NSMinX(self.bounds) + 18, NSMinY(self.bounds) + 8, 32, 32) inView:self];
+}
+
+- (void)mouseEntered:(NSEvent *)event
+{
+ [super mouseEntered:event];
+ self.mouseHover = YES;
+ [self setNeedsDisplay:YES];
+}
+
+- (void)mouseExited:(NSEvent *)event
+{
+ [super mouseExited:event];
+ self.mouseHover = NO;
+ [self setNeedsDisplay:YES];
+}
+
+- (void)controlAction:(id)sender
+{
+ #pragma unused(sender)
+ wxCommandEvent event(wxEVT_MENU, _parent->GetId());
+ event.SetEventObject(_parent);
+ _parent->ProcessWindowEvent(event);
+}
+
+@end
+
+ActionButton::ActionButton(wxWindow *parent, wxWindowID winid, const wxString& symbolicName, const wxString& label, const wxString& note)
+{
+ POActionButton *view = [[POActionButton alloc] initWithLabel:str::to_NS(note) heading:str::to_NS(label)];
+ if (!symbolicName.empty())
+ view.image = [NSImage imageNamed:str::to_NS("AB_" + symbolicName + "Template")];
+ view.parent = this;
+ wxNativeWindow::Create(parent, winid, view);
+}
+
+
// ---------------------------------------------------------------------
// SwitchButton
// ---------------------------------------------------------------------
@@ -215,6 +317,92 @@ void SwitchButton::SendToggleEvent()
#else // !__WXOSX__
+#ifdef __WXGTK__
+ActionButton::ActionButton(wxWindow *parent, wxWindowID winid, const wxString& /*symbolicName*/, const wxString& label, const wxString& note)
+ : wxButton(parent, winid, label, wxDefaultPosition, wxSize(-1, 50), wxBU_LEFT)
+{
+ SetLabelMarkup(wxString::Format("%s\n%s", label, note));
+ Bind(wxEVT_BUTTON, &ActionButton::OnPressed, this);
+}
+#endif // __WXGTK__
+
+#ifdef __WXMSW__
+ActionButton::ActionButton(wxWindow *parent, wxWindowID winid, const wxString& symbolicName, const wxString& label, const wxString& note)
+ : wxCommandLinkButton(parent, winid, label, note, wxDefaultPosition, wxSize(-1, PX(48)))
+{
+ m_title = label;
+ m_note = note;
+ m_titleFont = GetFont().MakeLarger();
+
+ MakeOwnerDrawn();
+
+ if (!symbolicName.empty())
+ {
+ auto bmp = wxArtProvider::GetBitmap("AB_" + symbolicName + "Template@opaque");
+ SetBitmap(bmp);
+ }
+
+ Bind(wxEVT_BUTTON, &ActionButton::OnPressed, this);
+}
+
+bool ActionButton::MSWOnDraw(WXDRAWITEMSTRUCT* wxdis)
+{
+ LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
+ HDC hdc = lpDIS->hDC;
+
+ UINT state = lpDIS->itemState;
+ const bool highlighted = IsMouseInWindow();
+ const bool isRtl = ::GetLayout(hdc)& LAYOUT_RTL;
+
+ wxRect rect(lpDIS->rcItem.left, lpDIS->rcItem.top, lpDIS->rcItem.right - lpDIS->rcItem.left, lpDIS->rcItem.bottom - lpDIS->rcItem.top);
+ wxRect textRect(rect);
+ textRect.SetLeft(rect.GetLeft() + PX(8));
+
+ wxPaintDCEx dc(this, hdc);
+
+ if (highlighted)
+ {
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ dc.SetBrush(GetBackgroundColour().ChangeLightness(95));
+ dc.DrawRectangle(rect);
+ }
+
+ auto bmp = GetBitmap();
+ if (bmp.IsOk())
+ {
+ dc.DrawBitmap(bmp, PX(16), PX(8));
+ textRect.SetLeft(textRect.GetLeft() + PX(48));
+ textRect.SetRight(rect.GetRight());
+ };
+
+ dc.SetFont(m_titleFont);
+ dc.SetTextForeground(ColorScheme::Get(::Color::Label, this));
+ int theight;
+ dc.GetTextExtent(m_title, nullptr, &theight);
+ dc.DrawText(m_title, textRect.x, PX(24) - theight);
+ dc.SetFont(GetFont());
+ dc.SetTextForeground(ColorScheme::Get(::Color::SecondaryLabel, this));
+ dc.DrawText(m_note, textRect.x, PX(24));
+
+ // draw the focus rectangle if we need it
+ if ((state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT))
+ {
+ RECT r = { rect.x, rect.y, rect.width, rect.height };
+ DrawFocusRect(hdc, &r);
+ }
+
+ return true;
+}
+
+#endif // __WXMSW__
+
+void ActionButton::OnPressed(wxCommandEvent&)
+{
+ wxCommandEvent event(wxEVT_MENU, GetId());
+ event.SetEventObject(this);
+ ProcessWindowEvent(event);
+}
+
SwitchButton::SwitchButton(wxWindow *parent, wxWindowID winid, const wxString& label)
{
diff --git a/src/custom_buttons.h b/src/custom_buttons.h
index 09b12afc8d..33c4e587ca 100644
--- a/src/custom_buttons.h
+++ b/src/custom_buttons.h
@@ -31,13 +31,41 @@
#ifdef __WXOSX__
#include
- #if !wxCHECK_VERSION(3,1,0)
- #include "wx_backports/nativewin.h"
- #endif
+#endif
+
+#ifdef __WXMSW__
+ #include
#endif
#include
+#if defined(__WXOSX__)
+typedef wxNativeWindow ActionButtonBase;
+#elif defined(__WXGTK__)
+typedef wxButton ActionButtonBase;
+#elif defined(__WXMSW__)
+typedef wxCommandLinkButton ActionButtonBase;
+#endif
+
+/// Larger button generating wxEVT_MENU, e.g. for welcome screen
+class ActionButton : public ActionButtonBase
+{
+public:
+ ActionButton(wxWindow *parent, wxWindowID winid, const wxString& symbolicName, const wxString& label, const wxString& note);
+
+private:
+#ifndef __WXOSX__
+ void OnPressed(wxCommandEvent& e);
+#endif
+
+#ifdef __WXMSW__
+ bool MSWOnDraw(WXDRAWITEMSTRUCT* wxdis) override;
+
+ wxString m_title, m_note;
+ wxFont m_titleFont;
+#endif
+};
+
#ifdef __WXOSX__
diff --git a/src/customcontrols.cpp b/src/customcontrols.cpp
index 063263754c..74f426da7b 100644
--- a/src/customcontrols.cpp
+++ b/src/customcontrols.cpp
@@ -362,6 +362,7 @@ ActivityIndicator::ActivityIndicator(wxWindow *parent, int flags)
SetSizer(sizer);
m_spinner = new wxActivityIndicator(this, wxID_ANY);
+ m_spinner->Hide();
m_spinner->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
m_label = new wxStaticText(this, wxID_ANY, "");
#ifdef __WXOSX__
diff --git a/src/edapp.cpp b/src/edapp.cpp
index 09878e6358..d25402eea3 100644
--- a/src/edapp.cpp
+++ b/src/edapp.cpp
@@ -26,6 +26,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -71,6 +73,7 @@
#include "colorscheme.h"
#include "concurrency.h"
#include "configuration.h"
+#include "crowdin_gui.h"
#include "edapp.h"
#include "edframe.h"
#include "extractors/extractor_legacy.h"
@@ -91,13 +94,11 @@
#include "errors.h"
#include "language.h"
#include "crowdin_client.h"
+#include "welcomescreen.h"
#ifdef __WXOSX__
struct PoeditApp::NativeMacAppData
{
- NSMenu *windowMenu = nullptr;
- NSMenuItem *windowMenuItem = nullptr;
- wxMenuBar *menuBar = nullptr;
#ifdef USE_SPARKLE
NSObject *sparkleDelegate = nullptr;
#endif
@@ -273,7 +274,6 @@ PoeditApp::PoeditApp()
{
#ifdef __WXOSX__
m_nativeMacAppData.reset(new NativeMacAppData);
- wxMenuBar::SetAutoWindowMenu(false);
#endif
}
@@ -456,10 +456,7 @@ bool PoeditApp::OnInit()
SetupLanguage();
#ifdef __WXOSX__
- wxMenuBar *bar = wxXmlResource::Get()->LoadMenuBar("mainmenu_mac_global");
- RecentFiles::Get().UseMenu(bar->FindItem(XRCID("open_recent")));
- TweakOSXMenuBar(bar);
- wxMenuBar::MacSetCommonMenuBar(bar);
+ CreateMenu(Menu::Global);
// so that help menu is correctly merged with system-provided menu
// (see http://sourceforge.net/tracker/index.php?func=detail&aid=1600747&group_id=9863&atid=309863)
s_macHelpMenuTitleName = _("&Help");
@@ -512,7 +509,7 @@ bool PoeditApp::OnInit()
// attempted to open MO files), shut the app down. Don't do this on macOS
// where a) the initialization is finished after OnInit() and b) apps
// without windows are OK.
- if (!PoeditFrame::HasAnyWindow())
+ if (!PoeditFrame::HasAnyWindow() && !WelcomeWindow::GetIfActive())
return false;
#endif
@@ -633,17 +630,11 @@ wxLayoutDirection PoeditApp::GetLayoutDirection() const
void PoeditApp::OpenNewFile()
{
- PoeditFrame *unused = PoeditFrame::UnusedWindow(/*active=*/false);
- if (unused)
- unused->Raise();
- else
- PoeditFrame::CreateWelcome();
+ WelcomeWindow::GetAndActivate();
}
void PoeditApp::OpenFiles(const wxArrayString& names, int lineno)
{
- PoeditFrame *active = PoeditFrame::UnusedActiveWindow();
-
for ( auto name: names )
{
// MO files cannot be opened directly in Poedit (yet), but they are
@@ -661,16 +652,8 @@ void PoeditApp::OpenFiles(const wxArrayString& names, int lineno)
continue;
}
- if (active)
- {
- active->OpenFile(name, lineno);
- active->Raise();
- active = nullptr;
- }
- else
- {
- PoeditFrame::Create(name, lineno);
- }
+ WelcomeWindow::HideActive();
+ PoeditFrame::Create(name, lineno);
}
}
@@ -852,18 +835,15 @@ bool PoeditApp::OnExceptionInMainLoop()
// ---------------------------------------------------------------------------
BEGIN_EVENT_TABLE(PoeditApp, wxApp)
-#ifndef __WXMSW__
- EVT_MENU (wxID_NEW, PoeditApp::OnNew)
- EVT_MENU (XRCID("menu_new_from_pot"),PoeditApp::OnNew)
+ EVT_MENU (wxID_NEW, PoeditApp::OnNewFromScratch)
+ EVT_MENU (XRCID("menu_new_from_pot"),PoeditApp::OnNewFromPOT)
EVT_MENU (wxID_OPEN, PoeditApp::OnOpen)
#ifdef HAVE_HTTP_CLIENT
EVT_MENU (XRCID("menu_open_crowdin"),PoeditApp::OnOpenFromCrowdin)
#endif
- #ifndef __WXOSX__
EVT_COMMAND (wxID_ANY, EVT_OPEN_RECENT_FILE, PoeditApp::OnOpenHist)
- #endif
-#endif // !__WXMSW__
EVT_MENU (wxID_ABOUT, PoeditApp::OnAbout)
+ EVT_MENU (XRCID("menu_welcome"), PoeditApp::OnWelcomeWindow)
EVT_MENU (XRCID("menu_manager"), PoeditApp::OnManager)
EVT_MENU (wxID_EXIT, PoeditApp::OnQuit)
EVT_MENU (wxID_PREFERENCES, PoeditApp::OnPreferences)
@@ -878,83 +858,204 @@ BEGIN_EVENT_TABLE(PoeditApp, wxApp)
#endif
END_EVENT_TABLE()
+namespace
+{
+
+/// Information about the window invoking an event. Handles the difference between
+/// Windows (where opening a new file is done in the same file as the existing one,
+/// and so the action must not destroy data) and elsewhere (where new window is created).
+struct InvokingWindowProxy
+{
+ InvokingWindowProxy(const wxCommandEvent& e) : m_actionTarget(nullptr)
+ {
+ auto obj = e.GetEventObject();
+ wxWindow* win = nullptr;
+ auto menu = dynamic_cast(obj);
+ if (menu)
+ win = menu->GetWindow();
+ else
+ win = dynamic_cast(obj);
-// macOS and GNOME apps should open new documents in a new window. On Windows,
-// however, the usual thing to do is to open the new document in the already
-// open window and replace the current document.
-#ifndef __WXMSW__
+ if (win)
+ win = wxGetTopLevelParent(win);
-#define TRY_FORWARD_TO_ACTIVE_WINDOW(funcCall) \
- { \
- PoeditFrame *active = PoeditFrame::UnusedActiveWindow(); \
- if ( active ) \
- { \
- active->funcCall; \
- return; \
- } \
+ m_shouldReactivateWelcomeWindow = false;
+ m_isFromWelcomeWindow = dynamic_cast(win) != nullptr;
+#ifdef __WXOSX__
+ // we can't detect the window from global menu, so always assume welcome window must be hidden:
+ if (!win && menu)
+ m_isFromWelcomeWindow = true;
+#endif
+
+ m_invokingWindow = win;
+#ifdef __WXMSW__
+ m_actionTarget = dynamic_cast(win);
+#endif
+ }
+
+ bool IsPerformingActionAllowed()
+ {
+#ifdef __WXMSW__
+ if (m_actionTarget)
+ return m_actionTarget->AskIfCanDiscardCurrentDoc();
+ else
+#endif
+ return true;
+ }
+
+ void NotifyIsStarting()
+ {
+ if (m_isFromWelcomeWindow)
+ m_shouldReactivateWelcomeWindow = WelcomeWindow::HideActive();
}
-void PoeditApp::OnNew(wxCommandEvent& event)
+ void NotifyWasAborted() const
+ {
+ // restore welcome window if that's where the aborted action came from
+ if (m_shouldReactivateWelcomeWindow)
+ WelcomeWindow::GetAndActivate();
+ }
+
+ /// Window to perform actions (e.g. open files) in, or nullptr for new one
+ PoeditFrame *GetActionTarget() const
+ {
+ return m_actionTarget ? m_actionTarget : PoeditFrame::CreateEmpty();
+ }
+
+ /// Gets PoeditFrame that the action was invoken from; useful for e.g. file open window's parent
+ PoeditFrame *GetParentWindowIfAny() const { return m_actionTarget; }
+
+ /// Gets any invoking window; useful e.g. for parent of Upgrade-to-Pro prompt
+ wxWindow *GetInvokingWindow() const { return m_invokingWindow; }
+
+private:
+ bool m_shouldReactivateWelcomeWindow;
+ bool m_isFromWelcomeWindow;
+ wxWindow *m_invokingWindow;
+ PoeditFrame *m_actionTarget;
+};
+
+} // anonymous namespace
+
+
+void PoeditApp::OnNewFromScratch(wxCommandEvent& event)
{
- TRY_FORWARD_TO_ACTIVE_WINDOW( OnNew(event) );
+ InvokingWindowProxy win(event);
+
+ if (!win.IsPerformingActionAllowed())
+ return;
- PoeditFrame *f = PoeditFrame::CreateEmpty();
- f->OnNew(event);
+ win.NotifyIsStarting();
+ win.GetActionTarget()->NewFromScratch();
}
-void PoeditApp::OnOpen(wxCommandEvent&)
+void PoeditApp::OnNewFromPOT(wxCommandEvent& event)
{
- PoeditFrame *active = PoeditFrame::UnusedActiveWindow();
+ InvokingWindowProxy win(event);
+ if (!win.IsPerformingActionAllowed())
+ return;
+
+ win.NotifyIsStarting();
wxString path = wxConfig::Get()->Read("last_file_path", wxEmptyString);
- wxFileDialog dlg(nullptr,
- MACOS_OR_OTHER("", _("Open catalog")),
+ wxFileDialog dlg(win.GetParentWindowIfAny(),
+ MACOS_OR_OTHER("", _("Select translation template")),
+ path,
+ wxEmptyString,
+ Catalog::GetTypesFileMask({ Catalog::Type::POT, Catalog::Type::PO }),
+ wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+
+ if (dlg.ShowModal() != wxID_OK)
+ {
+ win.NotifyWasAborted();
+ return;
+ }
+
+ wxConfig::Get()->Write("last_file_path", dlg.GetDirectory());
+
+ auto pot = std::make_shared(dlg.GetPath(), Catalog::CreationFlag_IgnoreTranslations);
+ if (!pot->IsOk())
+ {
+ win.NotifyWasAborted();
+ wxLogError(_(L"“%s” is not a valid POT file."), dlg.GetPath());
+ return;
+ }
+
+ // Silently fix duplicates because they are common in WP world:
+ if (pot->HasDuplicateItems())
+ pot->FixDuplicateItems();
+
+ win.GetActionTarget()->NewFromPOT(pot);
+}
+
+
+void PoeditApp::OnOpen(wxCommandEvent& event)
+{
+ InvokingWindowProxy win(event);
+
+ if (!win.IsPerformingActionAllowed())
+ return;
+
+ win.NotifyIsStarting();
+
+ wxString path = wxConfig::Get()->Read("last_file_path", wxEmptyString);
+ wxFileDialog dlg(win.GetParentWindowIfAny(),
+ MACOS_OR_OTHER("", _("Select translation file")),
path,
wxEmptyString,
Catalog::GetAllTypesFileMask(),
wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
- if (dlg.ShowModal() == wxID_OK)
+ if (dlg.ShowModal() != wxID_OK)
{
- wxConfig::Get()->Write("last_file_path", dlg.GetDirectory());
- wxArrayString paths;
- dlg.GetPaths(paths);
+ win.NotifyWasAborted();
+ return;
+ }
- if (paths.size() == 1 && active)
- {
- active->OpenFile(paths[0]);
- return;
- }
+ wxConfig::Get()->Write("last_file_path", dlg.GetDirectory());
+ wxArrayString paths;
+ dlg.GetPaths(paths);
+
+ win.GetActionTarget()->DoOpenFile(paths.front());
+ paths.erase(paths.begin());
+ if (!paths.empty())
OpenFiles(paths);
- }
}
#ifdef HAVE_HTTP_CLIENT
void PoeditApp::OnOpenFromCrowdin(wxCommandEvent& event)
{
- TRY_FORWARD_TO_ACTIVE_WINDOW( OnOpenFromCrowdin(event) );
+ InvokingWindowProxy win(event);
+
+ if (!win.IsPerformingActionAllowed())
+ return;
- PoeditFrame *f = PoeditFrame::CreateEmpty();
- f->OnOpenFromCrowdin(event);
+ win.NotifyIsStarting();
+ CrowdinOpenFile(win.GetParentWindowIfAny(), [=](int retval, wxString filename)
+ {
+ if (retval == wxID_OK)
+ win.GetActionTarget()->NewFromCrowdin(filename);
+ else
+ win.NotifyWasAborted();
+ });
}
#endif
-#ifndef __WXOSX__
void PoeditApp::OnOpenHist(wxCommandEvent& event)
{
- TRY_FORWARD_TO_ACTIVE_WINDOW( OnOpenHist(event) );
+ InvokingWindowProxy win(event);
- wxString f = event.GetString();
- OpenFiles(wxArrayString(1, &f));
-}
-#endif // !__WXOSX__
+ if (!win.IsPerformingActionAllowed())
+ return;
-#endif // !__WXMSW__
+ win.NotifyIsStarting();
+ win.GetActionTarget()->DoOpenFile(event.GetString());
+}
void PoeditApp::OnAbout(wxCommandEvent&)
@@ -975,6 +1076,12 @@ void PoeditApp::OnAbout(wxCommandEvent&)
}
+void PoeditApp::OnWelcomeWindow(wxCommandEvent&)
+{
+ WelcomeWindow::GetAndActivate();
+}
+
+
void PoeditApp::OnManager(wxCommandEvent&)
{
wxFrame *f = ManagerFrame::Create();
@@ -1066,167 +1173,10 @@ void PoeditApp::MacOpenFiles(const wxArrayString& names)
gs_lineToOpen = 0;
}
-
-static NSMenuItem *AddNativeItem(NSMenu *menu, int pos, const wxString& text, SEL ac, NSString *key)
-{
- NSString *str = str::to_NS(text);
- if (pos == -1)
- return [menu addItemWithTitle:str action:ac keyEquivalent:key];
- else
- return [menu insertItemWithTitle:str action:ac keyEquivalent:key atIndex:pos];
-}
-
-void PoeditApp::TweakOSXMenuBar(wxMenuBar *bar)
-{
- wxMenu *apple = bar->OSXGetAppleMenu();
- if (!apple)
- return; // huh
-
- apple->Insert(3, XRCID("menu_manager"), _("Catalogs Manager"));
- apple->InsertSeparator(3);
-
-#if USE_SPARKLE
- Sparkle_AddMenuItem(apple->GetHMenu(), _(L"Check for Updates…").utf8_str());
-#endif
-
- wxMenu *fileMenu = nullptr;
- wxMenuItem *fileCloseItem = bar->FindItem(wxID_CLOSE, &fileMenu);
- if (fileMenu && fileCloseItem)
- {
- NSMenuItem *nativeCloseItem = [fileMenu->GetHMenu() itemWithTitle:str::to_NS(fileCloseItem->GetItemLabelText())];
- if (nativeCloseItem)
- {
- nativeCloseItem.target = nil;
- nativeCloseItem.action = @selector(performClose:);
- }
- }
-
- int editMenuPos = bar->FindMenu(_("&Edit"));
- if (editMenuPos == wxNOT_FOUND)
- editMenuPos = 1;
- wxMenu *edit = bar->GetMenu(editMenuPos);
- int pasteItem = -1;
- int findItem = -1;
- int pos = 0;
- for (auto& i : edit->GetMenuItems())
- {
- if (i->GetId() == wxID_PASTE)
- pasteItem = pos;
- else if (i->GetId() == XRCID("menu_sub_find"))
- findItem = pos;
- pos++;
- }
-
- NSMenu *editNS = edit->GetHMenu();
-
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wundeclared-selector"
- AddNativeItem(editNS, 0, _("Undo"), @selector(undo:), @"z");
- AddNativeItem(editNS, 1, _("Redo"), @selector(redo:), @"Z");
- #pragma clang diagnostic pop
- [editNS insertItem:[NSMenuItem separatorItem] atIndex:2];
- if (pasteItem != -1) pasteItem += 3;
- if (findItem != -1) findItem += 3;
-
- NSMenuItem *item;
- if (pasteItem != -1)
- {
- item = AddNativeItem(editNS, pasteItem+1, _("Paste and Match Style"),
- @selector(pasteAsPlainText:), @"V");
- [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
- item = AddNativeItem(editNS, pasteItem+2, _("Delete"),
- @selector(delete:), @"");
- [item setKeyEquivalentModifierMask:NSCommandKeyMask];
- if (findItem != -1) findItem += 2;
- }
-
- #define FIND_PLUS(ofset) ((findItem != -1) ? (findItem+ofset) : -1)
- if (findItem == -1)
- [editNS addItem:[NSMenuItem separatorItem]];
- item = AddNativeItem(editNS, FIND_PLUS(1), _("Spelling and Grammar"), NULL, @"");
- NSMenu *spelling = [[NSMenu alloc] initWithTitle:@"Spelling and Grammar"];
- AddNativeItem(spelling, -1, _("Show Spelling and Grammar"), @selector(showGuessPanel:), @":");
- AddNativeItem(spelling, -1, _("Check Document Now"), @selector(checkSpelling:), @";");
- [spelling addItem:[NSMenuItem separatorItem]];
- AddNativeItem(spelling, -1, _("Check Spelling While Typing"), @selector(toggleContinuousSpellChecking:), @"");
- AddNativeItem(spelling, -1, _("Check Grammar With Spelling"), @selector(toggleGrammarChecking:), @"");
- AddNativeItem(spelling, -1, _("Correct Spelling Automatically"), @selector(toggleAutomaticSpellingCorrection:), @"");
- [editNS setSubmenu:spelling forItem:item];
-
- item = AddNativeItem(editNS, FIND_PLUS(2), _("Substitutions"), NULL, @"");
- NSMenu *subst = [[NSMenu alloc] initWithTitle:@"Substitutions"];
- AddNativeItem(subst, -1, _("Show Substitutions"), @selector(orderFrontSubstitutionsPanel:), @"");
- [subst addItem:[NSMenuItem separatorItem]];
- AddNativeItem(subst, -1, _("Smart Copy/Paste"), @selector(toggleSmartInsertDelete:), @"");
- AddNativeItem(subst, -1, _("Smart Quotes"), @selector(toggleAutomaticQuoteSubstitution:), @"");
- AddNativeItem(subst, -1, _("Smart Dashes"), @selector(toggleAutomaticDashSubstitution:), @"");
- AddNativeItem(subst, -1, _("Smart Links"), @selector(toggleAutomaticLinkDetection:), @"");
- AddNativeItem(subst, -1, _("Text Replacement"), @selector(toggleAutomaticTextReplacement:), @"");
- [editNS setSubmenu:subst forItem:item];
-
- item = AddNativeItem(editNS, FIND_PLUS(3), _("Transformations"), NULL, @"");
- NSMenu *trans = [[NSMenu alloc] initWithTitle:@"Transformations"];
- AddNativeItem(trans, -1, _("Make Upper Case"), @selector(uppercaseWord:), @"");
- AddNativeItem(trans, -1, _("Make Lower Case"), @selector(lowercaseWord:), @"");
- AddNativeItem(trans, -1, _("Capitalize"), @selector(capitalizeWord:), @"");
- [editNS setSubmenu:trans forItem:item];
-
- item = AddNativeItem(editNS, FIND_PLUS(4), _("Speech"), NULL, @"");
- NSMenu *speech = [[NSMenu alloc] initWithTitle:@"Speech"];
- AddNativeItem(speech, -1, _("Start Speaking"), @selector(startSpeaking:), @"");
- AddNativeItem(speech, -1, _("Stop Speaking"), @selector(stopSpeaking:), @"");
- [editNS setSubmenu:speech forItem:item];
-
- int viewMenuPos = bar->FindMenu(_("&View"));
- if (viewMenuPos != wxNOT_FOUND)
- {
- NSMenu *viewNS = bar->GetMenu(viewMenuPos)->GetHMenu();
- [viewNS addItem:[NSMenuItem separatorItem]];
- item = AddNativeItem(viewNS, -1, _("Enter Full Screen"), @selector(toggleFullScreen:), @"f");
- [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSControlKeyMask];
-
- }
-
- if (!m_nativeMacAppData->windowMenu)
- {
- NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:str::to_NS(_("Window"))];
- AddNativeItem(windowMenu, -1, _("Minimize"), @selector(performMiniaturize:), @"m");
- AddNativeItem(windowMenu, -1, _("Zoom"), @selector(performZoom:), @"");
- [windowMenu addItem:[NSMenuItem separatorItem]];
- AddNativeItem(windowMenu, -1, _("Bring All to Front"), @selector(arrangeInFront:), @"");
- [NSApp setWindowsMenu:windowMenu];
- m_nativeMacAppData->windowMenu = windowMenu;
- }
-}
-
-void PoeditApp::FixupMenusForMac(wxMenuBar *bar)
-{
- m_nativeMacAppData->menuBar = nullptr;
-
- RecentFiles::Get().MacTransferMenuTo(bar);
-
- if (m_nativeMacAppData->windowMenuItem)
- [m_nativeMacAppData->windowMenuItem setSubmenu:nil];
-
- if (!bar)
- return;
-
- NSMenuItem *windowItem = [[NSApp mainMenu] itemWithTitle:str::to_NS(_("Window"))];
- if (windowItem)
- {
- [windowItem setSubmenu:m_nativeMacAppData->windowMenu];
- m_nativeMacAppData->windowMenuItem = windowItem;
- }
-
- m_nativeMacAppData->menuBar = bar;
-}
-
void PoeditApp::OnIdleFixupMenusForMac(wxIdleEvent& event)
{
event.Skip();
- auto installed = wxMenuBar::MacGetInstalledMenuBar();
- if (m_nativeMacAppData->menuBar != installed)
- FixupMenusForMac(installed);
+ FixupMenusForMacIfNeeded();
}
void PoeditApp::OSXOnWillFinishLaunching()
diff --git a/src/edapp.h b/src/edapp.h
index 9e16b995a1..707bf3f847 100644
--- a/src/edapp.h
+++ b/src/edapp.h
@@ -24,8 +24,10 @@
*/
-#ifndef _EDAPP_H_
-#define _EDAPP_H_
+#ifndef Poedit_edapp_h
+#define Poedit_edapp_h
+
+#include "menus.h"
#include
#include
@@ -36,11 +38,10 @@
class WXDLLIMPEXP_FWD_BASE wxConfigBase;
class WXDLLIMPEXP_FWD_BASE wxSingleInstanceChecker;
-class WXDLLIMPEXP_FWD_CORE wxMenuBar;
-/// wxApp for use with
-class PoeditApp : public wxApp
+/// wxApp for use with
+class PoeditApp : public wxApp, public MenusManager
{
public:
PoeditApp();
@@ -79,10 +80,6 @@ class PoeditApp : public wxApp
void OpenPoeditWeb(const wxString& path);
#ifdef __WXOSX__
- // Make OSX-specific modifications to the menus, e.g. adding items into
- // the apple menu etc. Call on every newly created menubar
- void TweakOSXMenuBar(wxMenuBar *bar);
- void FixupMenusForMac(wxMenuBar *bar);
void OnIdleFixupMenusForMac(wxIdleEvent& event);
virtual void OSXOnWillFinishLaunching();
void OnCloseWindowCommand(wxCommandEvent& event);
@@ -104,13 +101,13 @@ class PoeditApp : public wxApp
void SetupLanguage();
// App-global menu commands:
- void OnNew(wxCommandEvent& event);
+ void OnNewFromScratch(wxCommandEvent& event);
+ void OnNewFromPOT(wxCommandEvent& event);
void OnOpen(wxCommandEvent& event);
void OnOpenFromCrowdin(wxCommandEvent& event);
-#ifndef __WXOSX__
void OnOpenHist(wxCommandEvent& event);
-#endif
void OnAbout(wxCommandEvent& event);
+ void OnWelcomeWindow(wxCommandEvent& event);
void OnManager(wxCommandEvent& event);
void OnQuit(wxCommandEvent& event);
void OnPreferences(wxCommandEvent& event);
@@ -146,4 +143,4 @@ class PoeditApp : public wxApp
DECLARE_APP(PoeditApp);
-#endif // _EDAPP_H_
+#endif // Poedit_edapp_h
diff --git a/src/edframe.cpp b/src/edframe.cpp
index 6edc1284e0..c18e26d0f1 100644
--- a/src/edframe.cpp
+++ b/src/edframe.cpp
@@ -260,16 +260,6 @@ bool g_focusToText = false;
return NULL;
}
-/*static*/ PoeditFrame *PoeditFrame::UnusedWindow(bool active)
-{
- for (auto win: ms_instances)
- {
- if ((!active || win->IsActive()) && win->m_catalog == nullptr)
- return win;
- }
- return nullptr;
-}
-
/*static*/ bool PoeditFrame::AnyWindowIsModified()
{
for (PoeditFramesList::const_iterator n = ms_instances.begin();
@@ -333,32 +323,9 @@ bool g_focusToText = false;
return f;
}
-/*static*/ PoeditFrame *PoeditFrame::CreateWelcome()
-{
- PoeditFrame *f = new PoeditFrame;
- f->EnsureContentView(Content::Welcome);
- f->Show(true);
-
- return f;
-}
-
BEGIN_EVENT_TABLE(PoeditFrame, wxFrame)
-// macOS and GNOME apps should open new documents in a new window. On Windows,
-// however, the usual thing to do is to open the new document in the already
-// open window and replace the current document.
- EVT_BUTTON (XRCID("button_new_from_this_pot"),PoeditFrame::OnNew)
-#ifdef __WXMSW__
- EVT_MENU (wxID_NEW, PoeditFrame::OnNew)
- EVT_MENU (XRCID("menu_new_from_pot"),PoeditFrame::OnNew)
- EVT_MENU (wxID_OPEN, PoeditFrame::OnOpen)
- #ifdef HAVE_HTTP_CLIENT
- EVT_MENU (XRCID("menu_open_crowdin"),PoeditFrame::OnOpenFromCrowdin)
- #endif
- #ifndef __WXOSX__
- EVT_COMMAND (wxID_ANY, EVT_OPEN_RECENT_FILE, PoeditFrame::OnOpenHist)
- #endif
-#endif // __WXMSW__
+ EVT_MENU (XRCID("button_new_from_this_pot"),PoeditFrame::OnTranslationFromThisPot)
#ifndef __WXOSX__
EVT_MENU (wxID_CLOSE, PoeditFrame::OnCloseCmd)
#endif
@@ -550,22 +517,10 @@ PoeditFrame::PoeditFrame() :
SetIcons(wxIconBundle(wxStandardPaths::Get().GetResourcesDir() + "\\Resources\\Poedit.ico"));
#endif
- wxMenuBar *MenuBar = wxXmlResource::Get()->LoadMenuBar("mainmenu");
+ wxMenuBar *MenuBar = wxGetApp().CreateMenu(Menu::Editor);
if (MenuBar)
{
- RecentFiles::Get().UseMenu(MenuBar->FindItem(XRCID("open_recent")));
AddBookmarksMenu(MenuBar->GetMenu(MenuBar->FindMenu(_("&Go"))));
-#ifdef __WXOSX__
- wxGetApp().TweakOSXMenuBar(MenuBar);
-#endif
-#ifndef HAVE_HTTP_CLIENT
- wxMenu *menu;
- wxMenuItem *item;
- item = MenuBar->FindItem(XRCID("menu_update_from_crowdin"), &menu);
- menu->Destroy(item);
- item = MenuBar->FindItem(XRCID("menu_open_crowdin"), &menu);
- menu->Destroy(item);
-#endif
SetMenuBar(MenuBar);
}
else
@@ -637,10 +592,6 @@ void PoeditFrame::EnsureContentView(Content type)
m_contentType = Content::Invalid;
return; // nothing to do
- case Content::Welcome:
- m_contentView = CreateContentViewWelcome();
- break;
-
case Content::Empty_PO:
m_contentView = CreateContentViewEmptyPO();
break;
@@ -794,12 +745,6 @@ wxWindow* PoeditFrame::CreateContentViewPO(Content type)
}
-wxWindow* PoeditFrame::CreateContentViewWelcome()
-{
- return new WelcomeScreenPanel(this);
-}
-
-
wxWindow* PoeditFrame::CreateContentViewEmptyPO()
{
bool isGettext = m_catalog->GetFileType() == Catalog::Type::PO || m_catalog->GetFileType() == Catalog::Type::POT;
@@ -1024,8 +969,9 @@ bool PoeditFrame::NeedsToAskIfCanDiscardCurrentDoc() const
return m_catalog && m_modified;
}
-template
-void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
+template
+void PoeditFrame::DoIfCanDiscardCurrentDoc(const TFunctor1& completionHandler, const TFunctor2
+& failureHandler)
{
if ( !NeedsToAskIfCanDiscardCurrentDoc() )
{
@@ -1035,7 +981,7 @@ void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
wxWindowPtr dlg = CreateAskAboutSavingDialog();
- dlg->ShowWindowModalThenDo([this,dlg,completionHandler](int retval) {
+ dlg->ShowWindowModalThenDo([this,dlg,completionHandler,failureHandler](int retval) {
// hide the dialog asap, WriteCatalog() may show another modal sheet
dlg->Hide();
#ifdef __WXOSX__
@@ -1044,7 +990,7 @@ void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
// from event loop (and not this functions' caller) at an unspecified
// time anyway, we can just as well defer it into the next idle time
// iteration.
- CallAfter([this,retval,completionHandler]() {
+ CallAfter([this,retval,completionHandler,failureHandler]() {
#endif
if (retval == wxID_YES)
@@ -1053,6 +999,8 @@ void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
WriteCatalog(fn, [=](bool saved){
if (saved)
completionHandler();
+ else
+ failureHandler();
});
};
if (!m_fileExistsOnDisk || GetFileName().empty())
@@ -1068,6 +1016,7 @@ void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
else if (retval == wxID_CANCEL)
{
// do not call -- not OK
+ failureHandler();
}
#ifdef __WXOSX__
@@ -1076,6 +1025,18 @@ void PoeditFrame::DoIfCanDiscardCurrentDoc(TFunctor completionHandler)
});
}
+#ifndef __WXOSX__
+bool PoeditFrame::AskIfCanDiscardCurrentDoc()
+{
+ // On non-Mac platforms, we can check synchronously, because all UI is modal, not window-modal
+ int status = -1;
+ DoIfCanDiscardCurrentDoc([&status]{ status = 1; }, [&status]{ status = 0; });
+ wxASSERT( status != -1 ); // i.e. was executed synchronously
+ return status != 0;
+}
+#endif
+
+
wxWindowPtr PoeditFrame::CreateAskAboutSavingDialog()
{
wxWindowPtr dlg(new wxMessageDialog
@@ -1131,49 +1092,14 @@ void PoeditFrame::OnCloseWindow(wxCloseEvent& event)
}
-void PoeditFrame::OnOpen(wxCommandEvent&)
-{
- DoIfCanDiscardCurrentDoc([=]{
-
- wxString path = wxPathOnly(GetFileName());
- if (path.empty())
- path = wxConfig::Get()->Read("last_file_path", wxEmptyString);
-
- wxString name = wxFileSelector(MACOS_OR_OTHER("", _("Open catalog")),
- path, wxEmptyString, wxEmptyString,
- Catalog::GetAllTypesFileMask(),
- wxFD_OPEN | wxFD_FILE_MUST_EXIST, this);
-
- if (!name.empty())
- {
- wxConfig::Get()->Write("last_file_path", wxPathOnly(name));
-
- DoOpenFile(name);
- }
- });
-}
-
-
#ifdef HAVE_HTTP_CLIENT
-void PoeditFrame::OnOpenFromCrowdin(wxCommandEvent&)
+void PoeditFrame::NewFromCrowdin(const wxString& filename)
{
- DoIfCanDiscardCurrentDoc([=]{
- CrowdinOpenFile(this, [=](wxString name){
- DoOpenFile(name);
- });
- });
+ DoOpenFile(filename);
}
#endif
-#ifndef __WXOSX__
-void PoeditFrame::OnOpenHist(wxCommandEvent& event)
-{
- OpenFile(event.GetString());
-}
-#endif // !__WXOSX__
-
-
void PoeditFrame::OnSave(wxCommandEvent& event)
{
try
@@ -1349,62 +1275,21 @@ bool PoeditFrame::ExportCatalog(const wxString& filename)
}
-
-void PoeditFrame::OnNew(wxCommandEvent& event)
+void PoeditFrame::OnTranslationFromThisPot(wxCommandEvent&)
{
DoIfCanDiscardCurrentDoc([=]{
- if (event.GetId() == XRCID("menu_new_from_pot"))
- {
- NewFromPOT();
- }
- else if (event.GetId() == XRCID("button_new_from_this_pot"))
- {
- wxWindowPtr dlg(new LanguageDialog(this));
- dlg->ShowWindowModalThenDo([=](int retcode){
- if (retcode != wxID_OK)
- return;
- auto cat = std::dynamic_pointer_cast(m_catalog);
- wxASSERT_MSG(cat, "unexpected file type / catalog class for POT");
- NewFromPOT(cat, dlg->GetLang());
- });
- }
- else
- {
- NewFromScratch();
- }
+ wxWindowPtr dlg(new LanguageDialog(this));
+ dlg->ShowWindowModalThenDo([=](int retcode){
+ if (retcode != wxID_OK)
+ return;
+ auto cat = std::dynamic_pointer_cast(m_catalog);
+ wxASSERT_MSG(cat, "unexpected file type / catalog class for POT");
+ NewFromPOT(cat, dlg->GetLang());
+ });
});
}
-void PoeditFrame::NewFromPOT()
-{
- wxString path = wxPathOnly(GetFileName());
- if (path.empty())
- path = wxConfig::Get()->Read("last_file_path", wxEmptyString);
- wxString pot_file =
- wxFileSelector(_("Open catalog template"),
- path, wxEmptyString, wxEmptyString,
- Catalog::GetTypesFileMask({Catalog::Type::POT, Catalog::Type::PO}),
- wxFD_OPEN | wxFD_FILE_MUST_EXIST, this);
- if (!pot_file.empty())
- {
- wxConfig::Get()->Write("last_file_path", wxPathOnly(pot_file));
-
- auto pot = std::make_shared(pot_file, Catalog::CreationFlag_IgnoreTranslations);
- if (!pot->IsOk())
- {
- wxLogError(_(L"“%s” is not a valid POT file."), pot_file.c_str());
- return;
- }
-
- // Silently fix duplicates because they are common in WP world:
- if (pot->HasDuplicateItems())
- pot->FixDuplicateItems();
-
- NewFromPOT(pot);
- }
-}
-
void PoeditFrame::NewFromPOT(POCatalogPtr pot, Language language)
{
auto catalog = POCatalog::CreateFromPOT(pot);
diff --git a/src/edframe.h b/src/edframe.h
index db970555f5..e501789313 100644
--- a/src/edframe.h
+++ b/src/edframe.h
@@ -45,7 +45,7 @@ class WXDLLIMPEXP_FWD_CORE wxSplitterEvent;
#ifdef __WXMSW__
#include "windows/win10_menubar.h"
- typedef wxFrameWithWindows10Menubar PoeditFrameBase;
+ typedef WithWindows10Menubar PoeditFrameBase;
#else
typedef wxFrame PoeditFrameBase;
#define FindFocusNoMenu() wxWindow::FindFocus()
@@ -78,26 +78,18 @@ class PoeditFrame : public PoeditFrameBase
*/
static PoeditFrame *CreateEmpty();
- /** Public constructor functions. Creates and shows frame
- without catalog, just a welcome screen.
- */
- static PoeditFrame *CreateWelcome();
-
/// Opens given file in this frame. Asks user for permission first
/// if there's unsaved document.
void OpenFile(const wxString& filename, int lineno = 0);
+ // Opens given file in this frame, without asking user
+ void DoOpenFile(const wxString& filename, int lineno = 0);
+
/** Returns pointer to existing instance of PoeditFrame that currently
exists and edits \a catalog. If no such frame exists, returns NULL.
*/
static PoeditFrame *Find(const wxString& catalog);
- /// Returns active PoeditFrame, if it is unused (i.e. not showing
- /// content, not having catalog loaded); NULL otherwise.
- static PoeditFrame *UnusedActiveWindow() { return UnusedWindow(true); }
- /// Ditto, but not required to be active
- static PoeditFrame *UnusedWindow(bool active);
-
/// Returns true if at least one one window has unsaved changes
static bool AnyWindowIsModified();
@@ -151,6 +143,17 @@ class PoeditFrame : public PoeditFrameBase
wxString GetFileNamePartOfTitle() const
{ return m_fileNamePartOfTitle; }
+#ifndef __WXOSX__
+ // synchronous version of DoIfCanDiscardCurrentDoc for public use:
+ bool AskIfCanDiscardCurrentDoc();
+#endif
+
+ void NewFromScratch();
+ void NewFromPOT(POCatalogPtr pot, Language language = Language());
+#ifdef HAVE_HTTP_CLIENT
+ void NewFromCrowdin(const wxString& filename);
+#endif
+
protected:
// Don't show help in status bar, it's not common to do these days:
void DoGiveHelp(const wxString& /*help*/, bool /*show*/) override {}
@@ -166,7 +169,6 @@ class PoeditFrame : public PoeditFrameBase
enum class Content
{
Invalid, // no content whatsoever
- Welcome,
PO,
POT,
Empty_PO
@@ -181,7 +183,6 @@ class PoeditFrame : public PoeditFrameBase
void EnsureContentView(Content type);
void EnsureAppropriateContentView();
wxWindow* CreateContentViewPO(Content type);
- wxWindow* CreateContentViewWelcome();
wxWindow* CreateContentViewEmptyPO();
void DestroyContentView();
@@ -202,14 +203,14 @@ class PoeditFrame : public PoeditFrameBase
// if there's modified catalog, ask user to save it; return true
// if it's save to discard m_catalog and load new data
- template
- void DoIfCanDiscardCurrentDoc(TFunctor completionHandler);
+ template
+ void DoIfCanDiscardCurrentDoc(const TFunctor1& completionHandler, const TFunctor2& failureHandler);
+ template
+ void DoIfCanDiscardCurrentDoc(const TFunctor1& completionHandler)
+ { DoIfCanDiscardCurrentDoc(completionHandler, []{}); }
bool NeedsToAskIfCanDiscardCurrentDoc() const;
wxWindowPtr CreateAskAboutSavingDialog();
- // implements opening of files, without asking user
- void DoOpenFile(const wxString& filename, int lineno = 0);
-
/// Updates statistics in statusbar.
void UpdateStatusBar();
/// Updates frame title.
@@ -241,16 +242,8 @@ class PoeditFrame : public PoeditFrameBase
void OnNextPluralForm(wxCommandEvent&);
// Message handlers:
-public: // for PoeditApp
- void OnNew(wxCommandEvent& event);
- void NewFromScratch();
- void NewFromPOT();
- void NewFromPOT(POCatalogPtr pot, Language language = Language());
-
- void OnOpen(wxCommandEvent& event);
- void OnOpenFromCrowdin(wxCommandEvent& event);
+ void OnTranslationFromThisPot(wxCommandEvent& event);
#ifndef __WXOSX__
- void OnOpenHist(wxCommandEvent& event);
void OnCloseCmd(wxCommandEvent& event);
#endif
private:
diff --git a/src/editing_area.cpp b/src/editing_area.cpp
index f02b6a719d..7ae813a593 100644
--- a/src/editing_area.cpp
+++ b/src/editing_area.cpp
@@ -532,19 +532,20 @@ void EditingArea::CreateEditControls(wxBoxSizer *sizer)
void EditingArea::CreateTemplateControls(wxBoxSizer *panelSizer)
{
auto win = new wxPanel(this, wxID_ANY);
- auto sizer = new wxBoxSizer(wxVERTICAL);
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
- auto explain = new wxStaticText(win, wxID_ANY, _(L"POT files are only templates and don’t contain any translations themselves.\nTo make a translation, create a new PO file based on the template."), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL);
+ auto explain = new wxStaticText(win, wxID_ANY, _(L"POT files are only templates and don’t contain any translations themselves.\nTo make a translation, create a new PO file based on the template."));
#ifdef __WXOSX__
explain->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
#endif
- auto button = new wxButton(win, XRCID("button_new_from_this_pot"), MSW_OR_OTHER(_("Create new translation"), _("Create New Translation")));
+ auto button = new ActionButton(
+ win, XRCID("button_new_from_this_pot"), "CreateTranslation",
+ _("Create new translation"),
+ _("Make a new translation from this POT file."));
- sizer->AddStretchSpacer();
- sizer->Add(explain, wxSizerFlags().Center().Border(wxLEFT|wxRIGHT, PX(100)));
- sizer->Add(button, wxSizerFlags().Center().Border(wxTOP|wxBOTTOM, PX(10)));
- sizer->AddStretchSpacer();
+ sizer->Add(button, wxSizerFlags().CenterVertical().Border(wxLEFT, PX(30)));
+ sizer->Add(explain, wxSizerFlags(1).CenterVertical().Border(wxLEFT|wxRIGHT, PX(20)));
win->SetSizerAndFit(sizer);
diff --git a/src/icons.cpp b/src/icons.cpp
index 31be92b21a..06a8671aa2 100644
--- a/src/icons.cpp
+++ b/src/icons.cpp
@@ -77,14 +77,9 @@ bool ShouldBeMirorredInRTL(const wxArtID& id, const wxArtClient& client)
(void)client;
static std::set s_directional = {
- "ContributeOn",
"poedit-status-comment",
"follow-link",
- "sidebar",
- "Welcome_EditTranslation",
- "Welcome_CreateTranslation",
- "Welcome_WordPress",
- "Welcome_Collaborate"
+ "sidebar"
};
bool mirror = s_directional.find(id) != s_directional.end();
diff --git a/src/macos/PoeditStyleKit.pcvd b/src/macos/PoeditStyleKit.pcvd
index 3afb7378ec..22d5be0cec 100644
Binary files a/src/macos/PoeditStyleKit.pcvd and b/src/macos/PoeditStyleKit.pcvd differ
diff --git a/src/macos/StyleKit.h b/src/macos/StyleKit.h
index f26b6665b4..4edf1241a8 100644
--- a/src/macos/StyleKit.h
+++ b/src/macos/StyleKit.h
@@ -34,6 +34,6 @@
// Drawing Methods
+ (void)drawSwitchButtonWithFrame: (NSRect)frame onColor: (NSColor*)onColor labelOffColor: (NSColor*)labelOffColor label: (NSString*)label togglePosition: (CGFloat)togglePosition isDarkMode: (BOOL)isDarkMode;
+ (void)drawTranslucentButtonWithFrame: (NSRect)frame label: (NSString*)label pressed: (BOOL)pressed;
-+ (void)drawWelcomeButtonWithFrame: (NSRect)frame icon: (NSImage*)icon label: (NSString*)label description: (NSString*)description isDarkMode: (BOOL)isDarkMode pressed: (BOOL)pressed;
++ (void)drawActionButtonWithFrame: (NSRect)frame buttonColor: (NSColor*)buttonColor hasIcon: (BOOL)hasIcon label: (NSString*)label description: (NSString*)description;
@end
diff --git a/src/macos/StyleKit.m b/src/macos/StyleKit.m
index 7279fb0dce..283c4e022a 100644
--- a/src/macos/StyleKit.m
+++ b/src/macos/StyleKit.m
@@ -217,47 +217,23 @@ + (void)drawTranslucentButtonWithFrame: (NSRect)frame label: (NSString*)label pr
[NSGraphicsContext restoreGraphicsState];
}
-+ (void)drawWelcomeButtonWithFrame: (NSRect)frame icon: (NSImage*)icon label: (NSString*)label description: (NSString*)description isDarkMode: (BOOL)isDarkMode pressed: (BOOL)pressed
++ (void)drawActionButtonWithFrame: (NSRect)frame buttonColor: (NSColor*)buttonColor hasIcon: (BOOL)hasIcon label: (NSString*)label description: (NSString*)description
{
//// Color Declarations
- NSColor* welcomeButtonBackground = [NSColor colorWithCalibratedRed: 1 green: 1 blue: 1 alpha: 1];
- NSColor* welcomeButtonPressed = [welcomeButtonBackground shadowWithLevel: 0.1];
- NSColor* welcomeButtonDarkBackground = [NSColor colorWithCalibratedRed: 0.188 green: 0.188 blue: 0.188 alpha: 1];
- NSColor* welcomeButtonDarkPressed = [welcomeButtonDarkBackground highlightWithLevel: 0.1];
- NSColor* fancySecondaryOutlineStroke = [NSColor colorWithCalibratedRed: 0.7 green: 0.7 blue: 0.7 alpha: 0.4];
NSColor* osSecondaryLabelColor = [NSColor secondaryLabelColor]; // manually modified
NSColor* osLabelColor = [NSColor labelColor]; // manually modified
- //// Shadow Declarations
- NSShadow* fancyButtonShadow = [[NSShadow alloc] init];
- [fancyButtonShadow setShadowColor: [NSColor.blackColor colorWithAlphaComponent: 0.15]];
- [fancyButtonShadow setShadowOffset: NSMakeSize(0.1, -1.1)];
- [fancyButtonShadow setShadowBlurRadius: 3];
-
//// Variable Declarations
- BOOL hasIcon = icon != nil; // manually modified
CGFloat textPosition = hasIcon ? 58 : 18;
- NSColor* welcomeButtonColor = isDarkMode ? (pressed ? welcomeButtonDarkPressed : welcomeButtonDarkBackground) : (pressed ? welcomeButtonPressed : welcomeButtonBackground);
//// Rectangle Drawing
- NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRoundedRect: NSMakeRect(NSMinX(frame) + 2, NSMinY(frame) + 2, NSWidth(frame) - 4, NSHeight(frame) - 5) xRadius: 2.5 yRadius: 2.5];
- [NSGraphicsContext saveGraphicsState];
- [fancyButtonShadow set];
- [welcomeButtonColor setFill];
+ NSBezierPath* rectanglePath = [NSBezierPath bezierPathWithRoundedRect: NSMakeRect(NSMinX(frame) + 2, NSMinY(frame) + 2, NSWidth(frame) - 4, NSHeight(frame) - 4) xRadius: 5 yRadius: 5];
+ [buttonColor setFill];
[rectanglePath fill];
- [NSGraphicsContext restoreGraphicsState];
-
-
-
- //// SecondaryOutline Drawing
- NSBezierPath* secondaryOutlinePath = [NSBezierPath bezierPathWithRoundedRect: NSMakeRect(NSMinX(frame) + 1.75, NSMinY(frame) + 1.75, NSWidth(frame) - 3.5, NSHeight(frame) - 4.5) xRadius: 2.5 yRadius: 2.5];
- [fancySecondaryOutlineStroke setStroke];
- [secondaryOutlinePath setLineWidth: 0.5];
- [secondaryOutlinePath stroke];
//// Text Drawing
- NSRect textRect = NSMakeRect(textPosition, 33, 468, 14);
+ NSRect textRect = NSMakeRect(textPosition, 25, 468, 14);
NSMutableParagraphStyle* textStyle = [NSMutableParagraphStyle new];
textStyle.alignment = NSLeftTextAlignment;
@@ -274,17 +250,11 @@ + (void)drawWelcomeButtonWithFrame: (NSRect)frame icon: (NSImage*)icon label: (N
if (hasIcon)
{
//// IconFrame Drawing
- NSRect iconFrameRect = NSMakeRect(NSMinX(frame) + 16, NSMinY(frame) + 16, 32, NSHeight(frame) - 32);
- NSBezierPath* iconFramePath = [NSBezierPath bezierPathWithRect: iconFrameRect];
- [NSGraphicsContext saveGraphicsState];
- [iconFramePath addClip];
- [icon drawInRect: NSMakeRect(floor(NSMinX(iconFrameRect) + 0.5), floor(NSMinY(iconFrameRect) + 0.5), icon.size.width, icon.size.height) fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1 respectFlipped: YES hints: nil];
- [NSGraphicsContext restoreGraphicsState];
}
//// Text 2 Drawing
- NSRect text2Rect = NSMakeRect(textPosition, 16, 468, 18);
+ NSRect text2Rect = NSMakeRect(textPosition, 8, 468, 18);
NSMutableParagraphStyle* text2Style = [NSMutableParagraphStyle new];
text2Style.alignment = NSLeftTextAlignment;
diff --git a/src/menus.cpp b/src/menus.cpp
new file mode 100644
index 0000000000..d031e4e2ee
--- /dev/null
+++ b/src/menus.cpp
@@ -0,0 +1,294 @@
+/*
+ * This file is part of Poedit (https://poedit.net)
+ *
+ * Copyright (C) 1999-2020 Vaclav Slavik
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include "menus.h"
+
+#include "macos_helpers.h"
+#include "recent_files.h"
+#include "str_helpers.h"
+
+#include
+
+
+#ifdef __WXOSX__
+
+// TODO: move this to app delegate, once we have it (proxying to wx's one)
+@interface POMenuActions : NSObject
+@end
+
+@implementation POMenuActions
+
+- (void)showWelcomeWindow: (id)sender
+{
+ #pragma unused(sender)
+ wxCommandEvent event(wxEVT_MENU, XRCID("menu_welcome"));
+ wxTheApp->ProcessEvent(event);
+}
+
+@end
+
+struct MenusManager::NativeMacData
+{
+ NSMenu *windowMenu = nullptr;
+ NSMenuItem *windowMenuItem = nullptr;
+ wxMenuBar *menuBar = nullptr;
+ POMenuActions *actions = nullptr;
+};
+#endif
+
+MenusManager::MenusManager()
+{
+#ifdef __WXOSX__
+ m_nativeMacData.reset(new NativeMacData);
+ m_nativeMacData->actions = [POMenuActions new];
+ wxMenuBar::SetAutoWindowMenu(false);
+#endif
+}
+
+MenusManager::~MenusManager()
+{
+}
+
+
+wxMenuBar *MenusManager::CreateMenu(Menu purpose)
+{
+ wxMenuBar *bar = nullptr;
+
+ switch (purpose)
+ {
+ case Menu::Global:
+#ifdef __WXOSX__
+ bar = wxXmlResource::Get()->LoadMenuBar("mainmenu_global");
+#endif
+ break;
+ case Menu::WelcomeWindow:
+#ifndef __WXOSX__
+ bar = wxXmlResource::Get()->LoadMenuBar("mainmenu_global");
+#endif
+ break;
+ case Menu::Editor:
+ bar = wxXmlResource::Get()->LoadMenuBar("mainmenu");
+ break;
+ }
+
+ wxASSERT( bar );
+
+ RecentFiles::Get().UseMenu(bar->FindItem(XRCID("open_recent")));
+
+#ifdef __WXOSX__
+ TweakOSXMenuBar(bar);
+
+ if (purpose == Menu::Global)
+ wxMenuBar::MacSetCommonMenuBar(bar);
+#endif
+
+#ifndef HAVE_HTTP_CLIENT
+ wxMenu *menu;
+ wxMenuItem *item;
+ item = bar->FindItem(XRCID("menu_update_from_crowdin"), &menu);
+ if (item)
+ menu->Destroy(item);
+ item = bar->FindItem(XRCID("menu_open_crowdin"), &menu);
+ if (item)
+ menu->Destroy(item);
+#endif
+
+ return bar;
+}
+
+
+#ifdef __WXOSX__
+
+static NSMenuItem *AddNativeItem(NSMenu *menu, int pos, const wxString& text, SEL ac, NSString *key)
+{
+ NSString *str = str::to_NS(text);
+ if (pos == -1)
+ return [menu addItemWithTitle:str action:ac keyEquivalent:key];
+ else
+ return [menu insertItemWithTitle:str action:ac keyEquivalent:key atIndex:pos];
+}
+
+void MenusManager::TweakOSXMenuBar(wxMenuBar *bar)
+{
+ wxMenu *apple = bar->OSXGetAppleMenu();
+ if (apple)
+ {
+ apple->Insert(3, XRCID("menu_manager"), _("Catalogs Manager"));
+ apple->InsertSeparator(3);
+ }
+
+#if USE_SPARKLE
+ Sparkle_AddMenuItem(apple->GetHMenu(), _(L"Check for Updates…").utf8_str());
+#endif
+
+ wxMenu *fileMenu = nullptr;
+ wxMenuItem *fileCloseItem = bar->FindItem(wxID_CLOSE, &fileMenu);
+ if (fileMenu && fileCloseItem)
+ {
+ NSMenuItem *nativeCloseItem = [fileMenu->GetHMenu() itemWithTitle:str::to_NS(fileCloseItem->GetItemLabelText())];
+ if (nativeCloseItem)
+ {
+ nativeCloseItem.target = nil;
+ nativeCloseItem.action = @selector(performClose:);
+ }
+ }
+
+ int editMenuPos = bar->FindMenu(_("&Edit"));
+ if (editMenuPos == wxNOT_FOUND)
+ editMenuPos = 1;
+ wxMenu *edit = bar->GetMenu(editMenuPos);
+ int pasteItem = -1;
+ int findItem = -1;
+ int pos = 0;
+ for (auto& i : edit->GetMenuItems())
+ {
+ if (i->GetId() == wxID_PASTE)
+ pasteItem = pos;
+ else if (i->GetId() == XRCID("menu_sub_find"))
+ findItem = pos;
+ pos++;
+ }
+
+ NSMenu *editNS = edit->GetHMenu();
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wundeclared-selector"
+ AddNativeItem(editNS, 0, _("Undo"), @selector(undo:), @"z");
+ AddNativeItem(editNS, 1, _("Redo"), @selector(redo:), @"Z");
+ #pragma clang diagnostic pop
+ [editNS insertItem:[NSMenuItem separatorItem] atIndex:2];
+ if (pasteItem != -1) pasteItem += 3;
+ if (findItem != -1) findItem += 3;
+
+ NSMenuItem *item;
+ if (pasteItem != -1)
+ {
+ item = AddNativeItem(editNS, pasteItem+1, _("Paste and Match Style"),
+ @selector(pasteAsPlainText:), @"V");
+ [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
+ item = AddNativeItem(editNS, pasteItem+2, _("Delete"),
+ @selector(delete:), @"");
+ [item setKeyEquivalentModifierMask:NSCommandKeyMask];
+ if (findItem != -1) findItem += 2;
+ }
+
+ #define FIND_PLUS(ofset) ((findItem != -1) ? (findItem+ofset) : -1)
+ if (findItem == -1)
+ [editNS addItem:[NSMenuItem separatorItem]];
+ item = AddNativeItem(editNS, FIND_PLUS(1), _("Spelling and Grammar"), NULL, @"");
+ NSMenu *spelling = [[NSMenu alloc] initWithTitle:@"Spelling and Grammar"];
+ AddNativeItem(spelling, -1, _("Show Spelling and Grammar"), @selector(showGuessPanel:), @":");
+ AddNativeItem(spelling, -1, _("Check Document Now"), @selector(checkSpelling:), @";");
+ [spelling addItem:[NSMenuItem separatorItem]];
+ AddNativeItem(spelling, -1, _("Check Spelling While Typing"), @selector(toggleContinuousSpellChecking:), @"");
+ AddNativeItem(spelling, -1, _("Check Grammar With Spelling"), @selector(toggleGrammarChecking:), @"");
+ AddNativeItem(spelling, -1, _("Correct Spelling Automatically"), @selector(toggleAutomaticSpellingCorrection:), @"");
+ [editNS setSubmenu:spelling forItem:item];
+
+ item = AddNativeItem(editNS, FIND_PLUS(2), _("Substitutions"), NULL, @"");
+ NSMenu *subst = [[NSMenu alloc] initWithTitle:@"Substitutions"];
+ AddNativeItem(subst, -1, _("Show Substitutions"), @selector(orderFrontSubstitutionsPanel:), @"");
+ [subst addItem:[NSMenuItem separatorItem]];
+ AddNativeItem(subst, -1, _("Smart Copy/Paste"), @selector(toggleSmartInsertDelete:), @"");
+ AddNativeItem(subst, -1, _("Smart Quotes"), @selector(toggleAutomaticQuoteSubstitution:), @"");
+ AddNativeItem(subst, -1, _("Smart Dashes"), @selector(toggleAutomaticDashSubstitution:), @"");
+ AddNativeItem(subst, -1, _("Smart Links"), @selector(toggleAutomaticLinkDetection:), @"");
+ AddNativeItem(subst, -1, _("Text Replacement"), @selector(toggleAutomaticTextReplacement:), @"");
+ [editNS setSubmenu:subst forItem:item];
+
+ item = AddNativeItem(editNS, FIND_PLUS(3), _("Transformations"), NULL, @"");
+ NSMenu *trans = [[NSMenu alloc] initWithTitle:@"Transformations"];
+ AddNativeItem(trans, -1, _("Make Upper Case"), @selector(uppercaseWord:), @"");
+ AddNativeItem(trans, -1, _("Make Lower Case"), @selector(lowercaseWord:), @"");
+ AddNativeItem(trans, -1, _("Capitalize"), @selector(capitalizeWord:), @"");
+ [editNS setSubmenu:trans forItem:item];
+
+ item = AddNativeItem(editNS, FIND_PLUS(4), _("Speech"), NULL, @"");
+ NSMenu *speech = [[NSMenu alloc] initWithTitle:@"Speech"];
+ AddNativeItem(speech, -1, _("Start Speaking"), @selector(startSpeaking:), @"");
+ AddNativeItem(speech, -1, _("Stop Speaking"), @selector(stopSpeaking:), @"");
+ [editNS setSubmenu:speech forItem:item];
+
+ int viewMenuPos = bar->FindMenu(_("&View"));
+ if (viewMenuPos != wxNOT_FOUND)
+ {
+ NSMenu *viewNS = bar->GetMenu(viewMenuPos)->GetHMenu();
+ [viewNS addItem:[NSMenuItem separatorItem]];
+ // TRANSLATORS: This must be the same as OS X's translation of this View menu item
+ item = AddNativeItem(viewNS, -1, _("Show Toolbar"), @selector(toggleToolbarShown:), @"t");
+ [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSAlternateKeyMask];
+ // TRANSLATORS: This must be the same as OS X's translation of this View menu item
+ AddNativeItem(viewNS, -1, _(L"Customize Toolbar…"), @selector(runToolbarCustomizationPalette:), @"");
+ [viewNS addItem:[NSMenuItem separatorItem]];
+ // TRANSLATORS: This must be the same as OS X's translation of this View menu item
+ item = AddNativeItem(viewNS, -1, _("Enter Full Screen"), @selector(toggleFullScreen:), @"f");
+ [item setKeyEquivalentModifierMask:NSCommandKeyMask | NSControlKeyMask];
+
+ }
+
+ if (!m_nativeMacData->windowMenu)
+ {
+ NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:str::to_NS(_("Window"))];
+ AddNativeItem(windowMenu, -1, _("Minimize"), @selector(performMiniaturize:), @"m");
+ AddNativeItem(windowMenu, -1, _("Zoom"), @selector(performZoom:), @"");
+ [windowMenu addItem:[NSMenuItem separatorItem]];
+ item = AddNativeItem(windowMenu, -1, _("Welcome to Poedit"), @selector(showWelcomeWindow:), @"1");
+ item.target = m_nativeMacData->actions;
+ [item setKeyEquivalentModifierMask: NSShiftKeyMask | NSCommandKeyMask];
+
+ [windowMenu addItem:[NSMenuItem separatorItem]];
+ AddNativeItem(windowMenu, -1, _("Bring All to Front"), @selector(arrangeInFront:), @"");
+ [NSApp setWindowsMenu:windowMenu];
+ m_nativeMacData->windowMenu = windowMenu;
+ }
+}
+
+void MenusManager::FixupMenusForMacIfNeeded()
+{
+ auto installed = wxMenuBar::MacGetInstalledMenuBar();
+ if (m_nativeMacData->menuBar == installed)
+ return; // nothing to do
+
+ m_nativeMacData->menuBar = nullptr;
+
+ RecentFiles::Get().MacTransferMenuTo(installed);
+
+ if (m_nativeMacData->windowMenuItem)
+ [m_nativeMacData->windowMenuItem setSubmenu:nil];
+
+ if (!installed)
+ return;
+
+ NSMenuItem *windowItem = [[NSApp mainMenu] itemWithTitle:str::to_NS(_("Window"))];
+ if (windowItem)
+ {
+ [windowItem setSubmenu:m_nativeMacData->windowMenu];
+ m_nativeMacData->windowMenuItem = windowItem;
+ }
+
+ m_nativeMacData->menuBar = installed;
+}
+
+#endif // __WXOSX__
diff --git a/src/menus.h b/src/menus.h
new file mode 100644
index 0000000000..0ea29d3fa0
--- /dev/null
+++ b/src/menus.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of Poedit (https://poedit.net)
+ *
+ * Copyright (C) 1999-2020 Vaclav Slavik
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef Poedit_menus_h
+#define Poedit_menus_h
+
+#include "catalog.h"
+
+#include
+
+
+/// Kind of menu to load
+enum class Menu
+{
+ Global, // app-global menu used on macOS
+ Editor, // for PoeditFrame, editor window
+ WelcomeWindow
+};
+
+
+/**
+ Management of various menus in Poedit.
+
+ Centralizes platform-specific hacks and deals with menu variants in
+ different windows.
+ */
+class MenusManager
+{
+public:
+ MenusManager();
+ ~MenusManager();
+
+ wxMenuBar *CreateMenu(Menu purpose);
+
+#ifdef __WXOSX__
+protected:
+ void FixupMenusForMacIfNeeded();
+
+private:
+ // Make OSX-specific modifications to the menus, e.g. adding items into
+ // the apple menu etc. Call on every newly created menubar
+ void TweakOSXMenuBar(wxMenuBar *bar);
+
+ class NativeMacData;
+ std::unique_ptr m_nativeMacData;
+#endif // __WXOSX__
+};
+
+#endif // Poedit_menus_h
diff --git a/src/recent_files.cpp b/src/recent_files.cpp
index afc2081b3c..7008ce8378 100644
--- a/src/recent_files.cpp
+++ b/src/recent_files.cpp
@@ -25,6 +25,7 @@
#include "recent_files.h"
+#include "colorscheme.h"
#include "hidpi.h"
#include "str_helpers.h"
#include "unicode_helpers.h"
@@ -40,10 +41,15 @@
#include
#include
#include
+#include
#include
#include
#include
+#ifdef __WXMSW__
+#include
+#endif
+
#include
#include
#include