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 @@ -106,15 +112,32 @@ class menus_tracker #ifndef __WXOSX__ + class file_icons { public: - file_icons() {} + file_icons() + { + m_iconSize[icon_small] = wxSystemSettings::GetMetric(wxSYS_SMALLICON_X); + m_iconSize[icon_large] = wxSystemSettings::GetMetric(wxSYS_ICON_X); + } + + wxBitmap get_small(const wxString& ext) { return do_get(ext, icon_small); } + wxBitmap get_large(const wxString& ext) { return do_get(ext, icon_large); } - wxBitmap get(const wxString& ext) +private: + enum icon_size + { + icon_small = 0, + icon_large = 1, + icon_max + }; + + wxBitmap do_get(const wxString& ext, icon_size size) { - auto i = m_cache.find(ext); - if (i != m_cache.end()) + auto& cache = m_cache[size]; + auto i = cache.find(ext); + if (i != cache.end()) return i->second; std::unique_ptr ft(wxTheMimeTypesManager->GetFileTypeFromExtension(ext)); @@ -122,17 +145,15 @@ class file_icons { wxIconLocation icon; if (ft->GetIcon(&icon)) - return m_cache.emplace(ext, create_bitmap(icon)).first->second; + return cache.emplace(ext, create_bitmap(icon, size)).first->second; } - m_cache.emplace(ext, wxNullBitmap); + cache.emplace(ext, wxNullBitmap); return wxNullBitmap; } -private: - wxBitmap create_bitmap(const wxIconLocation& loc) + wxBitmap create_bitmap(const wxIconLocation& loc, icon_size size) { - const int size = wxSystemSettings::GetMetric(wxSYS_SMALLICON_X); wxString fullname = loc.GetFileName(); #ifdef __WXMSW__ if (loc.GetIndex()) @@ -141,15 +162,19 @@ class file_icons fullname << ';' << loc.GetIndex(); } #endif - wxIcon icon(fullname, wxBITMAP_TYPE_ICO, size, size); + wxIcon icon(fullname, wxBITMAP_TYPE_ICO, m_iconSize[size], m_iconSize[size]); if (!icon.IsOk()) icon.LoadFile(fullname, wxBITMAP_TYPE_ICO); return icon; } - std::map m_cache; + int m_iconSize[icon_max]; + std::map m_cache[icon_max]; }; + +typedef std::shared_ptr file_icons_ptr; + #endif // !__WXOSX__ } // anonymous namespace @@ -181,6 +206,15 @@ class RecentFiles::impl [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL:url]; } + std::vector GetRecentFiles() + { + std::vector f; + NSArray *urls = [[NSDocumentController sharedDocumentController] recentDocumentURLs]; + for (NSURL *url in urls) + f.emplace_back(str::to_wx(url.path)); + return f; + } + void MacCreateFakeOpenRecentMenu() { // Populate the menu with a hack that will be replaced. @@ -237,6 +271,8 @@ class RecentFiles::impl { public: impl() + : m_icons_cache(new file_icons), + m_history(m_icons_cache) { wxConfigBase *cfg = wxConfig::Get(); cfg->SetPath("/"); @@ -252,7 +288,7 @@ class RecentFiles::impl menu->Bind(wxEVT_MENU, [=](wxCommandEvent& e) { - wxString f(m_history.GetHistoryFile(e.GetId() - wxID_FILE1)); + auto f = GetRecentFiles()[e.GetId() - wxID_FILE1].GetFullPath(); if (!wxFileExists(f)) { wxLogError(_(L"File “%s” doesn’t exist."), f.c_str()); @@ -281,6 +317,11 @@ class RecentFiles::impl UpdateAfterChange(); } + std::vector GetRecentFiles() + { + return m_history.GetRecentFiles(); + } + void ClearHistory() { while (m_history.GetCount()) @@ -289,6 +330,8 @@ class RecentFiles::impl UpdateAfterChange(); } + file_icons_ptr GetIconsCache() const { return m_icons_cache; } + protected: void RebuildMenu(wxMenu *menu) { @@ -323,12 +366,23 @@ class RecentFiles::impl class MyHistory : public wxFileHistory { public: - void AddFilesToMenu(wxMenu *menu) override + MyHistory(file_icons_ptr icons_cache) : m_icons_cache(icons_cache) {} + + std::vector GetRecentFiles() { std::vector files; files.reserve(m_fileHistory.size()); for (auto& f : m_fileHistory) - files.emplace_back(f); + { + if (wxFileName::FileExists(f)) + files.emplace_back(f); + } + return files; + } + + void AddFilesToMenu(wxMenu *menu) override + { + auto files = GetRecentFiles(); std::map nameUses; for (auto& f : files) @@ -354,14 +408,16 @@ class RecentFiles::impl auto item = menu->Append(wxID_FILE1 + n, wxString::Format("&%d %s", n + 1, menuEntry)); item->SetHelp(fn.GetFullPath()); - item->SetBitmap(m_icons.get(fn.GetExt())); + item->SetBitmap(m_icons_cache->get_small(fn.GetExt())); } - file_icons m_icons; + file_icons_ptr m_icons_cache; }; private: const wxWindowID m_idClear = wxNewId(); + + file_icons_ptr m_icons_cache; MyHistory m_history; menus_tracker m_menus; }; @@ -409,6 +465,12 @@ void RecentFiles::NoteRecentFile(const wxFileName& fn) m_impl->NoteRecentFile(fn); } +std::vector RecentFiles::GetRecentFiles() +{ + return m_impl->GetRecentFiles(); +} + + #ifdef __WXOSX__ void RecentFiles::MacCreateFakeOpenRecentMenu() { @@ -420,3 +482,148 @@ void RecentFiles::MacTransferMenuTo(wxMenuBar *bar) m_impl->MacTransferMenuTo(bar); } #endif // __WXOSX__ + + + +class RecentFilesCtrl::MultilineTextRenderer : public wxDataViewTextRenderer +{ +public: + MultilineTextRenderer() : wxDataViewTextRenderer() + { +#if wxCHECK_VERSION(3,1,1) + EnableMarkup(); +#endif + } + +#ifdef __WXMSW__ + bool Render(wxRect rect, wxDC *dc, int state) + { + int flags = 0; + if ( state & wxDATAVIEW_CELL_SELECTED ) + flags |= wxCONTROL_SELECTED; + + for (auto& line: wxSplit(m_text, '\n')) + { + wxItemMarkupText markup(line); + markup.Render(GetView(), *dc, rect, flags, GetEllipsizeMode()); + rect.y += rect.height / 2; + } + + return true; + } + + wxSize GetSize() const + { + if (m_text.empty()) + return wxSize(wxDVC_DEFAULT_RENDERER_SIZE,wxDVC_DEFAULT_RENDERER_SIZE); + + auto size = wxDataViewTextRenderer::GetSize(); + size.y *= 2; // approximation enough for our needs + return size; + } +#endif // __WXMSW__ +}; + +struct RecentFilesCtrl::data +{ + std::vector files; +#ifndef __WXOSX__ + file_icons_ptr icons_cache; +#endif +}; + + +// TODO: merge with CrowdinFileList which is very similar and has lot of duplicated code +RecentFilesCtrl::RecentFilesCtrl(wxWindow *parent) + : wxDataViewListCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_NO_HEADER | wxBORDER_NONE), + m_data(new data) +{ +#ifdef __WXOSX__ + NSScrollView *scrollView = (NSScrollView*)GetHandle(); + scrollView.automaticallyAdjustsContentInsets = NO; + + NSTableView *tableView = (NSTableView*)[scrollView documentView]; + tableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleSourceList; + [tableView setIntercellSpacing:NSMakeSize(0.0, 0.0)]; + const int icon_column_width = PX(32 + 12); +#else // !__WXOSX__ + ColorScheme::SetupWindowColors(this, [=] + { + SetBackgroundColour(ColorScheme::Get(Color::SidebarBackground)); + }); + + m_data->icons_cache = RecentFiles::Get().m_impl->GetIconsCache(); + const int icon_column_width = wxSystemSettings::GetMetric(wxSYS_ICON_X) + PX(12); +#endif + +#if wxCHECK_VERSION(3,1,1) + SetRowHeight(PX(46)); +#endif + + AppendBitmapColumn("", 0, wxDATAVIEW_CELL_INERT, icon_column_width); + auto renderer = new MultilineTextRenderer(); + auto column = new wxDataViewColumn(_("File"), renderer, 1, -1, wxALIGN_NOT, wxDATAVIEW_COL_RESIZABLE); + AppendColumn(column, "string"); + + ColorScheme::SetupWindowColors(this, [=]{ RefreshContent(); }); + + Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &RecentFilesCtrl::OnActivate, this); + + wxGetTopLevelParent(parent)->Bind(wxEVT_SHOW, [=](wxShowEvent& e){ + e.Skip(); + RefreshContent(); + }); +} + +void RecentFilesCtrl::RefreshContent() +{ +#ifdef __WXGTK__ + auto secondaryFormatting = "alpha='50%'"; +#else + auto secondaryFormatting = wxString::Format("foreground='%s'", ColorScheme::Get(Color::SecondaryLabel).GetAsString(wxC2S_HTML_SYNTAX)); +#endif + + DeleteAllItems(); + + m_data->files = RecentFiles::Get().GetRecentFiles(); + for (auto f : m_data->files) + { +#ifndef __WXMSW__ + f.ReplaceHomeDir(); +#endif + +#if wxCHECK_VERSION(3,1,1) + wxString text = wxString::Format + ( + "%s\n%s", + EscapeMarkup(f.GetFullName()), + secondaryFormatting, + EscapeMarkup(f.GetPath()) + ); +#else + wxString text = f.GetFullPath(); +#endif + +#ifdef __WXOSX__ + wxBitmap icon([[NSWorkspace sharedWorkspace] iconForFileType:str::to_NS(f.GetExt())]); +#else + wxBitmap icon(m_data->icons_cache->get_large(f.GetExt())); +#endif + wxVector data{wxVariant(icon), text}; + AppendItem(data); + } +} + +void RecentFilesCtrl::OnActivate(wxDataViewEvent& event) +{ + auto index = ItemToRow(event.GetItem()); + if (index == wxNOT_FOUND || index >= (int)m_data->files.size()) + return; + + auto fn = m_data->files[index].GetFullPath(); + + wxCommandEvent cevent(EVT_OPEN_RECENT_FILE); + cevent.SetEventObject(this); + cevent.SetString(fn); + ProcessWindowEvent(cevent); +} diff --git a/src/recent_files.h b/src/recent_files.h index 9e784109bc..bd557fa070 100644 --- a/src/recent_files.h +++ b/src/recent_files.h @@ -26,9 +26,11 @@ #ifndef Poedit_recent_files_h #define Poedit_recent_files_h +#include #include #include +#include class WXDLLIMPEXP_FWD_BASE wxFileName; class WXDLLIMPEXP_FWD_CORE wxMenuBar; @@ -55,6 +57,8 @@ class RecentFiles /// Record a file as being recently edited. void NoteRecentFile(const wxFileName& fn); + std::vector GetRecentFiles(); + #ifdef __WXOSX__ /// Hack to make macOS' hack for Open Recent work correctly; must be called from applicationWillFinishLaunching: void MacCreateFakeOpenRecentMenu(); @@ -67,6 +71,27 @@ class RecentFiles class impl; std::unique_ptr m_impl; + + friend class RecentFilesCtrl; }; + +/// Control with a list of recently opened files +class RecentFilesCtrl : public wxDataViewListCtrl +{ +public: + RecentFilesCtrl(wxWindow *parent); + +private: + void RefreshContent(); + void OnActivate(wxDataViewEvent& event); + + class MultilineTextRenderer; + + struct data; + std::unique_ptr m_data; +}; + + + #endif // Poedit_recent_files_h diff --git a/src/resources/menus.xrc b/src/resources/menus.xrc index 272861149e..ec5b505944 100644 --- a/src/resources/menus.xrc +++ b/src/resources/menus.xrc @@ -25,6 +25,16 @@ + + + + + Ctrl+Shift+1 + + + + + @@ -55,11 +65,6 @@ Ctrl+, - - - - - @@ -322,7 +327,7 @@ - + @@ -347,16 +352,34 @@ + + + + + Ctrl+Shift+1 + 0 + + + + + Ctrl+W + + + Ctrl+, + + + + @@ -370,13 +393,13 @@ Ctrl+A - + - + - + diff --git a/src/titleless_window.cpp b/src/titleless_window.cpp new file mode 100644 index 0000000000..2df26ab2b9 --- /dev/null +++ b/src/titleless_window.cpp @@ -0,0 +1,285 @@ +/* + * This file is part of Poedit (https://poedit.net) + * + * Copyright (C) 2013-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 "titleless_window.h" + +#include "hidpi.h" +#include "utility.h" + +#include + +#ifdef __WXMSW__ + #include + #include + #include + #pragma comment(lib, "Dwmapi.lib") +#endif + + +#ifdef __WXMSW__ + +namespace +{ + +// See https://docs.microsoft.com/en-us/windows/uwp/design/style/segoe-ui-symbol-font + +wxFont CreateButtonFont() +{ + return wxFont(wxSize(PX(10), PX(10)), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, "Segoe MDL2 Assets"); +} + +const wchar_t* Symbol_ChromClose = L"\U0000E8BB"; + +wxBitmap RenderButton(const wxSize& size, const wxFont& font, const wxColour& bg, const wxColour& fg) +{ + wxBitmap bmp(size); + { + wxMemoryDC dc(bmp); + dc.SetBackground(bg); + dc.SetFont(font); + dc.SetTextForeground(fg); + + dc.Clear(); + + wxString text(Symbol_ChromClose); + auto extent = dc.GetTextExtent(text); + dc.DrawText(text, (size.x - extent.x) / 2, (size.y - extent.y) / 2); + } + return bmp; +} + +} // anonymous namespace + + +class TitlelessWindow::CloseButton : public wxBitmapButton +{ +public: + CloseButton(wxWindow* parent, wxWindowID id) + { + wxBitmapButton::Create(parent, id, wxNullBitmap, wxDefaultPosition, GetButtonSize(), wxBORDER_NONE); + SetToolTip(_("Close")); + CreateBitmaps(); + + wxGetTopLevelParent(parent)->Bind(wxEVT_ACTIVATE, [=](wxActivateEvent& e) { + e.Skip(); + if (!IsBeingDeleted()) + CreateBitmaps(/*onlyBackgroundRelated:*/true, /*isActive:*/e.GetActive()); + }); + } + +private: + wxSize GetButtonSize() const { return wxSize(PX(46), PX(28)); } + + void CreateBitmaps(bool onlyBackgroundRelated = false, bool isActive = true) + { + // Minic Windows 10's caption buttons + auto size = GetButtonSize(); + auto font = CreateButtonFont(); + m_normal = RenderButton(size, font, GetBackgroundColour(), *wxBLACK); + if (!onlyBackgroundRelated) + m_hover = RenderButton(size, font, wxColour(232, 17, 35), *wxWHITE); + m_inactive = RenderButton(size, font, GetBackgroundColour(), wxColour(153,153,153)); + + SetBitmap(isActive ? m_normal : m_inactive); + SetBitmapHover(m_hover); + } + + wxBitmap m_normal, m_hover, m_inactive; +}; + +#endif // __WXMSW__ + +#ifdef __WXOSX__ + +class TitlelessWindow::CloseButton : public wxBitmapButton +{ +public: + CloseButton(wxWindow* parent, wxWindowID id) + { + wxBitmap normal([NSImage imageNamed:@"CloseButtonTemplate"]); + wxBitmap hover([NSImage imageNamed:@"CloseButtonHoverTemplate"]); + wxBitmapButton::Create(parent, id, normal, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + SetBitmapHover(hover); + } +}; + +#endif // __WXOSX__ + + +TitlelessWindow::TitlelessWindow(wxWindow* parent, + wxWindowID id, + const wxString& title, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) + : wxFrame(parent, id, title, pos, size, style, name) +{ +#ifdef __WXOSX__ + // Pretify the window: + NSWindow* wnd = (NSWindow*)GetWXWindow(); + wnd.styleMask |= NSFullSizeContentViewWindowMask; + wnd.titleVisibility = NSWindowTitleHidden; + wnd.titlebarAppearsTransparent = YES; + wnd.movableByWindowBackground = YES; + [wnd standardWindowButton:NSWindowMiniaturizeButton].hidden = YES; + [wnd standardWindowButton:NSWindowZoomButton].hidden = YES; + [wnd standardWindowButton:NSWindowCloseButton].hidden = YES; + + if (style & wxCLOSE_BOX) + m_closeButton = new CloseButton(this, wxID_CLOSE); +#endif + +#ifdef __WXMSW__ + m_isTitleless = ShouldRemoveChrome(); + if (m_isTitleless) + { + auto handle = GetHwnd(); + + static MARGINS margins = { PX(1), PX(1), PX(1), PX(1) }; + DwmExtendFrameIntoClientArea(handle, &margins); + + SetBackgroundStyle(wxBG_STYLE_PAINT); + Bind(wxEVT_PAINT, &TitlelessWindow::OnPaintBackground, this); + + if (style & wxCLOSE_BOX) + m_closeButton = new CloseButton(this, wxID_CLOSE); + } +#endif // __WXMSW__ + +#ifdef __WXGTK3__ + // TODO: Under WXGTK, use GtkButton with icon "window-close-symbolic" with style class "titlebutton" + // https://stackoverflow.com/questions/22069839/how-to-theme-the-header-bar-close-button-in-gtk3 +#endif + + if (m_closeButton) + m_closeButton->Bind(wxEVT_BUTTON, [=](wxCommandEvent&) { Close(); }); +} + +#ifdef __WXMSW__ +bool TitlelessWindow::ShouldRemoveChrome() +{ + if (!IsWindows10OrGreater()) + return false; + + if (!wxUxThemeIsActive()) + return false; + + // Detect screen readers and use normal titlebars to not confuse them + BOOL running; + BOOL ret = SystemParametersInfo(SPI_GETSCREENREADER, 0, &running, 0); + if (ret && running) + return false; + + return true; +} + +wxPoint TitlelessWindow::GetClientAreaOrigin() const +{ + if (m_isTitleless) + return wxPoint(PX(1), PX(1)); + else + return wxFrame::GetClientAreaOrigin(); +} + +void TitlelessWindow::DoGetClientSize(int *width, int *height) const +{ + if (m_isTitleless) + { + auto size = GetSize(); + if (width) + *width = size.x - 2 * PX(1); + if (height) + *height = size.y - 2 * PX(1); + } + else + { + return wxFrame::DoGetClientSize(width, height); + } +} + +WXLRESULT TitlelessWindow::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (m_isTitleless) + { + switch (nMsg) + { + case WM_NCCALCSIZE: + if (wParam == TRUE) + { + // ((LPNCCALCSIZE_PARAMS)lParam)->rgrc[0] is window size on input and + // client size on output; by doing nothing here, we set NC area to zero. + return 0; + } + break; + + case WM_NCHITTEST: + // When we have no border or title bar, we need to perform our + // own hit testing to allow moving etc. + // See https://docs.microsoft.com/en-us/windows/win32/dwm/customframe + return HTCAPTION; + } + } + + return wxFrame::MSWWindowProc(nMsg, wParam, lParam); +} + +void TitlelessWindow::OnPaintBackground(wxPaintEvent&) +{ + wxPaintDC dc(this); + + // 1pt margins around the window must be black for DwmExtendFrameIntoClientArea() to work. + // It would have been better to instead set 1pt non-client area in WM_NCCALCSIZE, but that + // doesn't work for the top side, unfortunately, so here we are. + dc.SetPen(*wxTRANSPARENT_PEN); + + wxRect rect(wxPoint(0, 0), GetSize()); + dc.SetBrush(*wxBLACK); + dc.DrawRectangle(rect); + + rect.Deflate(PX(1)); + dc.SetBrush(GetBackgroundColour()); + dc.DrawRectangle(rect); +} +#endif // __WXMSW__ + + +bool TitlelessWindow::Layout() +{ + if (!wxFrame::Layout()) + return false; + + if (m_closeButton) + { +#ifdef __WXOSX__ + m_closeButton->Move(2, 4); +#else + auto size = GetClientSize(); + m_closeButton->Move(size.x - m_closeButton->GetSize().x, 0); +#endif + } + + return true; +} diff --git a/src/titleless_window.h b/src/titleless_window.h new file mode 100644 index 0000000000..7a10d2d5e2 --- /dev/null +++ b/src/titleless_window.h @@ -0,0 +1,72 @@ +/* + * This file is part of Poedit (https://poedit.net) + * + * Copyright (C) 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_titleless_window_h +#define Poedit_titleless_window_h + +#include + +class WXDLLIMPEXP_CORE wxButton; + + +/** + Window without a titlebar. + + Used for windows with redundant titlebar (welcome, progress etc.). + On Windows, only shown on modern versions (Windows 10) and when no + accessibility reader is present, to avoid degrading usability. + */ +class TitlelessWindow : public wxFrame +{ +public: + TitlelessWindow(wxWindow* parent, + wxWindowID id, + const wxString& title, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxDEFAULT_FRAME_STYLE, + const wxString& name = wxFrameNameStr); + +protected: + bool Layout() override; + +#ifdef __WXMSW__ + wxPoint GetClientAreaOrigin() const override; + void DoGetClientSize(int *width, int *height) const override; + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; + void OnPaintBackground(wxPaintEvent& event); + +private: + static bool ShouldRemoveChrome(); + bool m_isTitleless = false; +#endif // __WXMSW__ + +private: + class CloseButton; + wxButton* m_closeButton = nullptr; +}; + + +#endif // Poedit_titleless_window_h diff --git a/src/welcomescreen.cpp b/src/welcomescreen.cpp index 427a13e4d5..96996cee44 100644 --- a/src/welcomescreen.cpp +++ b/src/welcomescreen.cpp @@ -26,14 +26,18 @@ #include "welcomescreen.h" #include "colorscheme.h" +#include "custom_buttons.h" #include "customcontrols.h" #include "crowdin_gui.h" #include "edapp.h" #include "edframe.h" #include "hidpi.h" +#include "menus.h" +#include "recent_files.h" #include "str_helpers.h" #include "utility.h" +#include #include #include #include @@ -41,112 +45,14 @@ #include #include #include +#include #include #include #include -#ifdef __WXOSX__ - #include "StyleKit.h" - #include - #if !wxCHECK_VERSION(3,1,0) - #include "wx_backports/nativewin.h" - #endif -#else - #include -#endif - -#ifdef __WXOSX__ - -@interface POWelcomeButton : NSButton - -@property wxWindow *parent; -@property NSString *heading; - -@end - -@implementation POWelcomeButton - -- (id)initWithLabel:(NSString*)label heading:(NSString*)heading -{ - self = [super init]; - if (self) - { - self.title = label; - self.heading = heading; - } - return self; -} - -- (void)sizeToFit -{ - [super sizeToFit]; - NSSize size = self.frame.size; - size.height = 64; - [self setFrameSize:size]; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - #pragma unused(dirtyRect) - [StyleKit drawWelcomeButtonWithFrame:self.bounds - icon:self.image - label:self.heading - description:self.title - isDarkMode:(ColorScheme::GetWindowMode(self.parent) == ColorScheme::Dark) - pressed:[self isHighlighted]]; -} - -- (void)controlAction:(id)sender -{ - #pragma unused(sender) - wxCommandEvent event(wxEVT_BUTTON, _parent->GetId()); - event.SetEventObject(_parent); - _parent->ProcessWindowEvent(event); -} - -@end - -#endif // __WXOSX__ - namespace { -#ifdef __WXOSX__ - -class ActionButton : public wxNativeWindow -{ -public: - ActionButton(wxWindow *parent, wxWindowID winid, const wxString& label, const wxString& note, const wxString& image = wxString()) - { - SetMinSize(wxSize(510, -1)); - - POWelcomeButton *view = [[POWelcomeButton alloc] initWithLabel:str::to_NS(note) heading:str::to_NS(label)]; - if (!image.empty()) - view.image = [NSImage imageNamed:str::to_NS(image)]; - view.parent = this; - wxNativeWindow::Create(parent, winid, view); - } -}; - -#elif defined(__WXGTK__) - -class ActionButton : public wxButton -{ -public: - ActionButton(wxWindow *parent, wxWindowID winid, const wxString& label, const wxString& note) - : wxButton(parent, winid, label, wxDefaultPosition, wxSize(500, 50), wxBU_LEFT) - { - SetLabelMarkup(wxString::Format("%s\n%s", label, note)); - } -}; - -#else - -typedef wxCommandLinkButton ActionButton; - -#endif - - class HeaderStaticText : public wxStaticText { public: @@ -201,71 +107,6 @@ WelcomeScreenBase::WelcomeScreenBase(wxWindow *parent) : wxPanel(parent, wxID_AN } -WelcomeScreenPanel::WelcomeScreenPanel(wxWindow *parent) - : WelcomeScreenBase(parent) -{ - auto sizer = new wxBoxSizer(wxVERTICAL); - auto uberSizer = new wxBoxSizer(wxHORIZONTAL); - uberSizer->AddStretchSpacer(); - uberSizer->Add(sizer, wxSizerFlags().Center().Border(wxALL, PX(50))); - uberSizer->AddStretchSpacer(); - SetSizer(uberSizer); - - auto headerSizer = new wxBoxSizer(wxVERTICAL); - - auto hdr = new wxStaticBitmap(this, wxID_ANY, wxArtProvider::GetBitmap("PoeditWelcome")); - headerSizer->Add(hdr, wxSizerFlags().Center()); - - auto header = new HeaderStaticText(this, wxID_ANY, _("Welcome to Poedit")); - headerSizer->Add(header, wxSizerFlags().Center().Border(wxTOP, PX(10))); - - auto version = new wxStaticText(this, wxID_ANY, wxString::Format(_("Version %s"), wxGetApp().GetAppVersion())); - headerSizer->Add(version, wxSizerFlags().Center()); - - headerSizer->AddSpacer(PX(20)); - - sizer->Add(headerSizer, wxSizerFlags().Expand()); - - sizer->Add(new ActionButton( - this, wxID_OPEN, - _("Edit a translation"), - _("Open an existing PO file and edit the translation.")), - wxSizerFlags().PXBorderAll().Expand()); - - sizer->Add(new ActionButton( - this, XRCID("menu_new_from_pot"), - _("Create new translation"), - _("Take an existing PO file or POT template and create a new translation from it.")), - wxSizerFlags().PXBorderAll().Expand()); - -#ifdef HAVE_HTTP_CLIENT - sizer->Add(new ActionButton( - this, XRCID("menu_open_crowdin"), - _("Collaborate on a translation with others"), - _("Download a file from Crowdin project, translate and sync your changes back.")), - wxSizerFlags().PXBorderAll().Expand()); - sizer->Add(new LearnAboutCrowdinLink(this, _("What is Crowdin?")), wxSizerFlags().Right().Border(wxRIGHT, PX(8))); -#endif // HAVE_HTTP_CLIENT - - sizer->AddSpacer(PX(50)); - - ColorScheme::SetupWindowColors(this, [=] - { - header->SetForegroundColour(ColorScheme::Get(Color::Label)); - version->SetForegroundColour(ColorScheme::Get(Color::SecondaryLabel)); - }); - - // Hide the cosmetic logo part if the screen is too small: - auto minFullSize = sizer->GetMinSize().y + PX(50); - Bind(wxEVT_SIZE, [=](wxSizeEvent& e){ - sizer->Show((size_t)0, e.GetSize().y >= minFullSize); - e.Skip(); - }); -} - - - - EmptyPOScreenPanel::EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext) : WelcomeScreenBase(parent) { @@ -295,7 +136,7 @@ EmptyPOScreenPanel::EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext) sizer->Add(explain2, wxSizerFlags().Expand().Border(wxTOP|wxBOTTOM, PX(10))); sizer->Add(new ActionButton( - this, XRCID("menu_update_from_pot"), + this, XRCID("menu_update_from_pot"), "UpdateFromPOT", _("Update from POT"), _("Take translatable strings from an existing POT template.")), wxSizerFlags().Expand()); @@ -305,7 +146,7 @@ EmptyPOScreenPanel::EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext) sizer->Add(explain3, wxSizerFlags().Expand().Border(wxTOP|wxBOTTOM, PX(10))); auto btnSources = new ActionButton( - this, wxID_ANY, + this, wxID_ANY, "ExtractFromSources", _("Extract from sources"), _("Configure source code extraction in Properties.")); sizer->Add(btnSources, wxSizerFlags().Expand()); @@ -318,10 +159,154 @@ EmptyPOScreenPanel::EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext) explain3->SetForegroundColour(ColorScheme::Get(Color::SecondaryLabel)); }); - btnSources->Bind(wxEVT_BUTTON, [=](wxCommandEvent&){ + btnSources->Bind(wxEVT_MENU, [=](wxCommandEvent&){ parent->EditCatalogPropertiesAndUpdateFromSources(); }); } Layout(); } + + + +WelcomeWindow *WelcomeWindow::ms_instance = nullptr; + +WelcomeWindow *WelcomeWindow::GetAndActivate() +{ + if (!ms_instance) + ms_instance = new WelcomeWindow(); + ms_instance->Show(); + if (ms_instance->IsIconized()) + ms_instance->Iconize(false); + ms_instance->Raise(); + return ms_instance; +} + +bool WelcomeWindow::HideActive() +{ + bool retval = ms_instance && ms_instance->IsShown(); + if (ms_instance) + ms_instance->Hide(); + return retval; +} + + +WelcomeWindow::~WelcomeWindow() +{ + ms_instance = nullptr; +} + +WelcomeWindow::WelcomeWindow() + : WelcomeWindowBase(nullptr, wxID_ANY, _("Welcome to Poedit"), + wxDefaultPosition, wxDefaultSize, + wxSYSTEM_MENU | wxCLOSE_BOX | wxCAPTION | wxCLIP_CHILDREN) +{ + ColorScheme::SetupWindowColors(this, [=] + { + if (ColorScheme::GetWindowMode(this) == ColorScheme::Light) + SetBackgroundColour(*wxWHITE); + else + SetBackgroundColour(GetDefaultAttributes().colBg); + }); + +#ifdef __WXOSX__ + NSWindow *wnd = (NSWindow*)GetWXWindow(); + wnd.excludedFromWindowsMenu = YES; +#endif + +#ifdef __WXMSW__ + SetIcons(wxIconBundle(wxStandardPaths::Get().GetResourcesDir() + "\\Resources\\Poedit.ico")); +#endif + +#ifndef __WXOSX__ + SetMenuBar(wxGetApp().CreateMenu(Menu::WelcomeWindow)); +#endif + + auto topsizer = new wxBoxSizer(wxHORIZONTAL); + auto leftoutersizer = new wxBoxSizer(wxVERTICAL); + auto leftsizer = new wxBoxSizer(wxVERTICAL); + +#ifdef __WXMSW__ + if (GetMenuWindow()) + { + leftoutersizer->Add(GetMenuWindow(), wxSizerFlags().Left()); + } +#endif + +#if defined(__WXMSW__) + wxIcon logo; + if (HiDPIScalingFactor() == 1.0) + { + logo.LoadFile("appicon", wxBITMAP_TYPE_ICO_RESOURCE, 128, 128); + } + else + { + logo.LoadFile("appicon", wxBITMAP_TYPE_ICO_RESOURCE, 256, 256); + if (HiDPIScalingFactor() != 2.0) + { + wxBitmap bmp; + bmp.CopyFromIcon(logo); + logo.CopyFromBitmap(wxBitmap(bmp.ConvertToImage().Scale(PX(128), PX(128), wxIMAGE_QUALITY_BICUBIC))); + } + } +#elif defined(__WXGTK__) + auto logo = wxArtProvider::GetIcon("net.poedit.Poedit", wxART_FRAME_ICON, wxSize(128, 128)); +#else + auto logo = wxArtProvider::GetBitmap("Poedit"); +#endif + auto logoWindow = new wxStaticBitmap(this, wxID_ANY, logo, wxDefaultPosition, wxSize(PX(128),PX(128))); + leftsizer->Add(logoWindow, wxSizerFlags().Center().Border(wxALL, PX(5))); + + auto header = new HeaderStaticText(this, wxID_ANY, _("Welcome to Poedit")); + leftsizer->Add(header, wxSizerFlags().Center()); + + auto version = new wxStaticText(this, wxID_ANY, wxString::Format(_("Version %s"), wxGetApp().GetAppVersion())); + leftsizer->Add(version, wxSizerFlags().Center().Border(wxTOP, PX(5))); + + leftsizer->AddSpacer(PX(30)); + + leftsizer->Add(new ActionButton( + this, XRCID("menu_new_from_pot"), "CreateTranslation", + _(L"Create new…"), + _("Create new translation from POT template.")), + wxSizerFlags().Border(wxTOP, PX(2)).Expand()); + + leftsizer->Add(new ActionButton( + this, wxID_OPEN, "EditTranslation", + _("Browse files"), + _("Open and edit translation files.")), + wxSizerFlags().Border(wxTOP, PX(2)).Expand()); + +#ifdef HAVE_HTTP_CLIENT + leftsizer->Add(new ActionButton( + this, XRCID("menu_open_crowdin"), "Collaborate", + _("Translate Crowdin project"), + _("Collaborate with others in a Crowdin project.")), + wxSizerFlags().Border(wxTOP|wxBOTTOM, PX(2)).Expand()); +#endif // HAVE_HTTP_CLIENT + + leftoutersizer->Add(leftsizer, wxSizerFlags().Center().Border(wxALL, PX(50))); + topsizer->Add(leftoutersizer, wxSizerFlags(1).Expand()); + +#ifndef __WXGTK__ + auto recentFiles = new RecentFilesCtrl(this); + recentFiles->SetMinSize(wxSize(PX(320), -1)); + topsizer->Add(recentFiles, wxSizerFlags().Expand()); +#endif + + SetSizerAndFit(topsizer); + + ColorScheme::SetupWindowColors(this, [=] + { + header->SetForegroundColour(ColorScheme::Get(Color::Label)); + version->SetForegroundColour(ColorScheme::Get(Color::SecondaryLabel)); + +#ifdef __WXMSW__ + for (auto& w : GetChildren()) + { + if (dynamic_cast(w)) + w->SetBackgroundColour(GetBackgroundColour()); + } +#endif + }); +} diff --git a/src/welcomescreen.h b/src/welcomescreen.h index 48e54843a1..b0090dba99 100644 --- a/src/welcomescreen.h +++ b/src/welcomescreen.h @@ -26,7 +26,18 @@ #ifndef Poedit_welcomescreen_h #define Poedit_welcomescreen_h +#include "titleless_window.h" + #include +#include + +#ifdef __WXMSW__ +#include "windows/win10_menubar.h" +typedef WithWindows10Menubar WelcomeWindowBase; +#else +typedef TitlelessWindow WelcomeWindowBase; +#endif + class PoeditFrame; @@ -36,19 +47,40 @@ class WelcomeScreenBase : public wxPanel WelcomeScreenBase(wxWindow *parent); }; -/// Content view for initially opened Poedit, without a file -class WelcomeScreenPanel : public WelcomeScreenBase +/// Content view for an empty file (File->New) +class EmptyPOScreenPanel : public WelcomeScreenBase { public: - WelcomeScreenPanel(wxWindow *parent); + EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext); }; -/// Content view for an empty file (File->New) -class EmptyPOScreenPanel : public WelcomeScreenBase +/// Window for initially opened Poedit, without a file +class WelcomeWindow : public WelcomeWindowBase { public: - EmptyPOScreenPanel(PoeditFrame *parent, bool isGettext); + static WelcomeWindow *GetAndActivate(); + static WelcomeWindow *GetIfActive() + { return ms_instance && ms_instance->IsShown() ? ms_instance : nullptr; } + + /// Hides the welcome window if it is active, and returns true iff it was previously visible + static bool HideActive(); + + bool ShouldPreventAppExit() const override + { + return IsShownOnScreen(); + } + +protected: + WelcomeWindow(); + ~WelcomeWindow(); + +#ifdef __WXMSW__ + bool ShouldPlaceMenuInNCArea() const override { return false; } +#endif + +private: + static WelcomeWindow *ms_instance; }; diff --git a/src/windows/win10_menubar.cpp b/src/windows/win10_menubar.cpp index 28127909b6..837e2b0e09 100644 --- a/src/windows/win10_menubar.cpp +++ b/src/windows/win10_menubar.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include @@ -46,147 +45,135 @@ const int MENUBAR_OFFSET = -2; } // anonymous namespace -class wxFrameWithWindows10Menubar::MenuBarWindow : public wxWindow +class Windows10MenubarMixin::MenuWindow::mCtrlWrapper : public wxNativeWindow { public: - MenuBarWindow(wxWindow *parent) - : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE), - m_mctrlWin(nullptr), m_mctrlHandle(0) - { - if (g_mctrlInitialized++ == 0) - mcMenubar_Initialize(); - - m_mctrlHandle = CreateWindowEx - ( - WS_EX_COMPOSITED, - MC_WC_MENUBAR, - _T(""), - WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CCS_NORESIZE | CCS_NOPARENTALIGN, - 0, 0, 1000, 2 * PX(23), - (HWND)this->GetHWND(), - (HMENU) -1, - wxGetInstance(), - NULL - ); - SendMessage(m_mctrlHandle, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS); - SendMessage(m_mctrlHandle, CCM_SETNOTIFYWINDOW, (WPARAM)parent->GetHWND(), 0); - - m_mctrlWin = new mCtrlWrapper(this, m_mctrlHandle); + mCtrlWrapper(wxWindow* parent, WXHWND wnd) : wxNativeWindow(parent, wxID_ANY, wnd), m_flagReenterMctrl(0) {} + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override + { + switch (nMsg) { - wxUxThemeHandle hTheme(this, L"ExplorerMenu::Toolbar"); - SetBackgroundColour(wxRGBToColour(::GetThemeSysColor(hTheme, COLOR_WINDOW))); + case WM_COMMAND: + case WM_NOTIFY: + { + wxRecursionGuard guard(m_flagReenterMctrl); + + if (!guard.IsInside()) + { + return MSWDefWindowProc(nMsg, wParam, lParam); + } + else + { + return ::DefWindowProc(GetHwnd(), nMsg, wParam, lParam); + } + } } - // mCtrl menus get focus, which is not compatible with PoeditFrame::OnTextEditingCommandUpdate(). - // Remember previous focus for it. - m_mctrlWin->Bind(wxEVT_SET_FOCUS, [=](wxFocusEvent& e) { e.Skip(); m_previousFocus = e.GetWindow(); }); - m_mctrlWin->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent& e) { e.Skip(); m_previousFocus = nullptr; }); + return wxNativeWindow::MSWWindowProc(nMsg, wParam, lParam); } - ~MenuBarWindow() - { - m_mctrlWin->Destroy(); - ::DestroyWindow(m_mctrlHandle); +private: + wxRecursionGuardFlag m_flagReenterMctrl; +}; - if (--g_mctrlInitialized == 0) - mcMenubar_Terminate(); - } +Windows10MenubarMixin::MenuWindow::MenuWindow() : m_mctrlWin(nullptr), m_mctrlHandle(0) +{ +} - void SetHMENU(WXHMENU menu) - { - SendMessage(m_mctrlHandle, MC_MBM_SETMENU, 0, (LPARAM) menu); - SendMessage(m_mctrlHandle, MC_MBM_REFRESH, 0, 0); - } +void Windows10MenubarMixin::MenuWindow::Create(wxWindow *parent) +{ + wxWindow::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE); + + if (g_mctrlInitialized++ == 0) + mcMenubar_Initialize(); + + m_mctrlHandle = CreateWindowEx + ( + WS_EX_COMPOSITED, + MC_WC_MENUBAR, + _T(""), + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | CCS_NORESIZE | CCS_NOPARENTALIGN, + 0, 0, 1000, 2 * PX(23), + (HWND)this->GetHWND(), + (HMENU) -1, + wxGetInstance(), + NULL + ); + SendMessage(m_mctrlHandle, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS); + SendMessage(m_mctrlHandle, CCM_SETNOTIFYWINDOW, (WPARAM)parent->GetHWND(), 0); + + m_mctrlWin = new mCtrlWrapper(this, m_mctrlHandle); - bool TranslateMenubarMessage(WXMSG *pMsg) { - MSG *msg = (MSG *) pMsg; - if (mcIsMenubarMessage(m_mctrlHandle, msg)) - return true; - - return false; + wxUxThemeHandle hTheme(this, L"ExplorerMenu::Toolbar"); + SetBackgroundColour(wxRGBToColour(::GetThemeSysColor(hTheme, COLOR_WINDOW))); } - void DoSetSize(int x, int y, int width, int height, int sizeFlags) override - { - wxWindow::DoSetSize(x, y, width, height, sizeFlags); - SendMessage(m_mctrlHandle, MC_MBM_REFRESH, 0, 0); - } + // mCtrl menus get focus, which is not compatible with PoeditFrame::OnTextEditingCommandUpdate(). + // Remember previous focus for it. + m_mctrlWin->Bind(wxEVT_SET_FOCUS, [=](wxFocusEvent& e) { e.Skip(); m_previousFocus = e.GetWindow(); }); + m_mctrlWin->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent& e) { e.Skip(); m_previousFocus = nullptr; }); +} - wxSize DoGetBestSize() const override - { - wxSize sizeBest = wxDefaultSize; - SIZE size; - if (::SendMessage(m_mctrlHandle, TB_GETMAXSIZE, 0, (LPARAM) &size)) - { - sizeBest.x = size.cx; - sizeBest.y = size.cy + 1; - CacheBestSize(sizeBest); - } - return sizeBest; - } +Windows10MenubarMixin::MenuWindow::~MenuWindow() +{ + m_mctrlWin->Destroy(); + ::DestroyWindow(m_mctrlHandle); - wxWindow* AdjustEffectiveFocus(wxWindow* focus) const - { - return (focus == m_mctrlWin) ? m_previousFocus.get() : focus; - } + if (--g_mctrlInitialized == 0) + mcMenubar_Terminate(); +} -private: - class mCtrlWrapper : public wxNativeWindow - { - public: - mCtrlWrapper(wxWindow *parent, WXHWND wnd) : wxNativeWindow(parent, wxID_ANY, wnd), m_flagReenterMctrl(0) {} +void Windows10MenubarMixin::MenuWindow::SetHMENU(WXHMENU menu) +{ + SendMessage(m_mctrlHandle, MC_MBM_SETMENU, 0, (LPARAM) menu); + SendMessage(m_mctrlHandle, MC_MBM_REFRESH, 0, 0); +} - WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override - { - switch (nMsg) - { - case WM_COMMAND: - case WM_NOTIFY: - { - wxRecursionGuard guard(m_flagReenterMctrl); - - if (!guard.IsInside()) - { - return MSWDefWindowProc(nMsg, wParam, lParam); - } - else - { - return ::DefWindowProc(GetHwnd(), nMsg, wParam, lParam); - } - } - } +bool Windows10MenubarMixin::MenuWindow::TranslateMenubarMessage(WXMSG *pMsg) +{ + MSG *msg = (MSG *) pMsg; + if (mcIsMenubarMessage(m_mctrlHandle, msg)) + return true; - return wxNativeWindow::MSWWindowProc(nMsg, wParam, lParam); - } + return false; +} - private: - wxRecursionGuardFlag m_flagReenterMctrl; - }; +wxWindow* Windows10MenubarMixin::MenuWindow::AdjustEffectiveFocus(wxWindow* focus) const +{ + return (focus == m_mctrlWin) ? m_previousFocus.get() : focus; +} - mCtrlWrapper *m_mctrlWin; - WXHWND m_mctrlHandle; - wxWeakRef m_previousFocus; -}; +WXDWORD Windows10MenubarMixin::MenuWindow::MSWGetStyle(long flags, WXDWORD* exstyle) const +{ + // The toolbar control used by mCtrl doesn't fully paint its area, so we need to do it + // in this wrapper window. Because wx clips childen unconditionally these days, it is + // necessary to remove WS_CLIPCHILDREN here. + return wxWindow::MSWGetStyle(flags, exstyle) & ~WS_CLIPCHILDREN; +} +void Windows10MenubarMixin::MenuWindow::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + SendMessage(m_mctrlHandle, MC_MBM_REFRESH, 0, 0); +} -wxFrameWithWindows10Menubar::wxFrameWithWindows10Menubar(wxWindow *parent, - wxWindowID id, - const wxString& title, - const wxPoint& pos, - const wxSize& size, - long style, - const wxString& name) - : wxFrame(parent, id, title, pos, size, style, name), m_menuBar(nullptr) +wxSize Windows10MenubarMixin::MenuWindow::DoGetBestSize() const { - if (ShouldUse()) + wxSize sizeBest = wxDefaultSize; + SIZE size; + if (::SendMessage(m_mctrlHandle, TB_GETMAXSIZE, 0, (LPARAM) &size)) { - m_menuBar = new MenuBarWindow(this); + sizeBest.x = size.cx; + sizeBest.y = size.cy + 1; + CacheBestSize(sizeBest); } + return sizeBest; } -bool wxFrameWithWindows10Menubar::ShouldUse() const + +bool Windows10MenubarMixin::ShouldUseCustomMenu() const { if (!IsWindows10OrGreater()) return false; @@ -207,30 +194,53 @@ bool wxFrameWithWindows10Menubar::ShouldUse() const return true; } -wxPoint wxFrameWithWindows10Menubar::GetClientAreaOrigin() const +void Windows10MenubarMixin::CreateCustomMenu(wxWindow* parent) { - wxPoint pt = wxFrame::GetClientAreaOrigin(); - if (IsUsed()) + m_menuBar = new MenuWindow; + m_menuBar->Create(parent); +} + +template +WithWindows10Menubar::WithWindows10Menubar(wxWindow* parent, + wxWindowID id, + const wxString& title, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) + : BaseClass(parent, id, title, pos, size, style, name) +{ + if (ShouldUseCustomMenu()) + CreateCustomMenu(this); +} + +template +wxPoint WithWindows10Menubar::GetClientAreaOrigin() const +{ + wxPoint pt = BaseClass::GetClientAreaOrigin(); + if (IsCustomMenuUsed() && ShouldPlaceMenuInNCArea()) { - pt.y += m_menuBar->GetBestSize().y + MENUBAR_OFFSET; + pt.y += GetMenuWindow()->GetBestSize().y + MENUBAR_OFFSET; } return pt; } -wxWindow* wxFrameWithWindows10Menubar::FindFocusNoMenu() +template +wxWindow* WithWindows10Menubar::FindFocusNoMenu() { auto focus = wxWindow::FindFocus(); - if (focus && IsUsed()) - focus = m_menuBar->AdjustEffectiveFocus(focus); + if (focus && IsCustomMenuUsed()) + focus = GetMenuWindow()->AdjustEffectiveFocus(focus); return focus; } -void wxFrameWithWindows10Menubar::PositionToolBar() +template +void WithWindows10Menubar::PositionToolBar() { // Position both the toolbar and our menu bar (which is really another toolbar) here. - if (!IsUsed()) + if (!IsCustomMenuUsed() || !ShouldPlaceMenuInNCArea()) { - wxFrame::PositionToolBar(); + BaseClass::PositionToolBar(); return; } @@ -243,8 +253,8 @@ void wxFrameWithWindows10Menubar::PositionToolBar() // use the 'real' MSW position here, don't offset relatively to the // client area origin - int mch = m_menuBar->GetBestSize().y; - m_menuBar->SetSize(0, y, width, mch, wxSIZE_NO_ADJUSTMENTS); + int mch = GetMenuWindow()->GetBestSize().y; + GetMenuWindow()->SetSize(0, y, width, mch, wxSIZE_NO_ADJUSTMENTS); y += mch; wxToolBar *toolbar = GetToolBar(); @@ -255,20 +265,22 @@ void wxFrameWithWindows10Menubar::PositionToolBar() } } -bool wxFrameWithWindows10Menubar::MSWTranslateMessage(WXMSG *msg) +template +bool WithWindows10Menubar::MSWTranslateMessage(WXMSG *msg) { - if (wxFrame::MSWTranslateMessage(msg)) + if (BaseClass::MSWTranslateMessage(msg)) return true; - if (IsUsed() && m_menuBar->TranslateMenubarMessage(msg)) + if (IsCustomMenuUsed() && GetMenuWindow()->TranslateMenubarMessage(msg)) return true; return false; } -WXLRESULT wxFrameWithWindows10Menubar::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) +template +WXLRESULT WithWindows10Menubar::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) { - if (IsUsed()) + if (IsCustomMenuUsed()) { // mCtrl doesn't play nice with the wxMSW's menus interaction where accelerators are updated // when a menu is opened (which works because TranslateAccelerators() normally sends a fake @@ -278,17 +290,25 @@ WXLRESULT wxFrameWithWindows10Menubar::MSWWindowProc(WXUINT message, WXWPARAM wP GetMenuBar()->UpdateMenus(); } - return wxFrame::MSWWindowProc(message, wParam, lParam); + return BaseClass::MSWWindowProc(message, wParam, lParam); } -void wxFrameWithWindows10Menubar::InternalSetMenuBar() +template +void WithWindows10Menubar::InternalSetMenuBar() { - if (IsUsed()) + if (IsCustomMenuUsed()) { - m_menuBar->SetHMENU(m_hMenu); + GetMenuWindow()->SetHMENU(m_hMenu); } else { - wxFrame::InternalSetMenuBar(); + BaseClass::InternalSetMenuBar(); } } + + +// We need to explicitly instantiate the template for all uses within Poedit because of all the code in .cpp file: +template class WithWindows10Menubar; + +#include "titleless_window.h" +template class WithWindows10Menubar; diff --git a/src/windows/win10_menubar.h b/src/windows/win10_menubar.h index 41f0e4d010..1068221e9d 100644 --- a/src/windows/win10_menubar.h +++ b/src/windows/win10_menubar.h @@ -27,19 +27,65 @@ #define Poedit_win10_menubar_h #include +#include + + +class Windows10MenubarMixin +{ +protected: + /// Implementation of the non-standard menu, wraps mCtrl + class MenuWindow : public wxWindow + { + public: + MenuWindow(); + ~MenuWindow(); + + void Create(wxWindow *parent); + + void SetHMENU(WXHMENU menu); + bool TranslateMenubarMessage(WXMSG* pMsg); + wxWindow* AdjustEffectiveFocus(wxWindow* focus) const; + + WXDWORD MSWGetStyle(long flags, WXDWORD* exstyle) const override; + void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; + wxSize DoGetBestSize() const override; + + private: + class mCtrlWrapper; + + mCtrlWrapper* m_mctrlWin; + WXHWND m_mctrlHandle; + wxWeakRef m_previousFocus; + }; + + // Whether the new menubar should be used. Only do this on Windows 10 and + // only when a screen reader isn't present, because the implementation + // isn't accessibility enabled. + bool ShouldUseCustomMenu() const; + bool IsCustomMenuUsed() const { return m_menuBar != nullptr; } + void CreateCustomMenu(wxWindow* parent); + + MenuWindow *GetMenuWindow() const { return m_menuBar; } + +private: + MenuWindow* m_menuBar = nullptr; +}; + // Implementation of nicer menubar on Windows 10 where it doesn't have extra // horizontal line between the menubar and the toolbar. -class wxFrameWithWindows10Menubar : public wxFrame +template +class WithWindows10Menubar : public T, + protected Windows10MenubarMixin { public: - wxFrameWithWindows10Menubar(wxWindow *parent, - wxWindowID id, - const wxString& title, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxDEFAULT_FRAME_STYLE, - const wxString& name = wxFrameNameStr); + WithWindows10Menubar(wxWindow *parent, + wxWindowID id, + const wxString& title, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxDEFAULT_FRAME_STYLE, + const wxString& name = wxFrameNameStr); wxPoint GetClientAreaOrigin() const override; @@ -49,19 +95,14 @@ class wxFrameWithWindows10Menubar : public wxFrame WXLRESULT MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) override; protected: + /// Can be overriden to return false if derived class wants to place GetMenuWindow() itself + virtual bool ShouldPlaceMenuInNCArea() const { return true; } + void PositionToolBar() override; void InternalSetMenuBar() override; private: - // Whether the new menubar should be used. Only do this on Windows 10 and - // only when a screen reader isn't present, because the implementation - // isn't accessibility enabled. - bool ShouldUse() const; - - bool IsUsed() const { return m_menuBar != nullptr; } - - class MenuBarWindow; - MenuBarWindow *m_menuBar; + typedef T BaseClass; }; #endif // Poedit_win10_menubar_h