Index: Config.cpp =================================================================== --- Config.cpp (revision 15381) +++ Config.cpp (working copy) @@ -317,8 +317,9 @@ s.Magic(); if(version >= 17) s % hlstyle_is_default; - if(version >= 19) + if(version >= 19) { s % gui_font % gui_font_override; + } } Time Ide::ConfigTime() @@ -335,6 +336,7 @@ } SaveChangedFile(ConfigFile("ide.key"), StoreKeys()); SaveChangedFile(ConfigFile("ide.colors"), editor.StoreHlStyles()); + SaveChangedFile(ConfigFile("ide.search"), StoreAsJson(search_engines, true)); config_time = ConfigTime(); } @@ -359,6 +361,7 @@ editor.DefaultHlStyles(); else editor.LoadHlStyles(LoadFile(ConfigFile("ide.colors"))); + LoadFromJsonFile(search_engines, ConfigFile("ide.search")); config_time = FileGetTime(ConfigFile()); UpdateFormat(); if(filelist.IsCursor()) { Index: OnlineSearch.cpp =================================================================== --- OnlineSearch.cpp (revision 15381) +++ OnlineSearch.cpp (working copy) @@ -1,17 +1,72 @@ #include "ide.h" -static String GetSearchPhrase(AssistEditor& editor) +void OnlineSearchProviders::FetchDefaultProvider(String& name, String& uri, Image& img) const { - return UrlEncode(Nvl(editor.GetSelection(), editor.GetWord())); + constexpr const char *google_name = "Google"; + constexpr const char *google_uri = "https://www.google.com/search?q=\\{@}"; + + int i = -1; + if(IsNull(default_provider) || (i = list.Find(default_provider)) < 0) { + name = google_name; + uri = google_uri; + img = IdeImg::Google(); + } + else { + name = list.GetKey(i); + uri = list[i]; + img = CtrlImg::Network(); + } } -void Ide::OnlineSearch() +void OnlineSearchProviders::Jsonize(JsonIO& jio) { - LaunchWebBrowser("https://www.google.com/search?q=" + GetSearchPhrase(editor)); + jio("DefaultProvider", default_provider); + + if(jio.IsLoading()) { + list.Clear(); + const Value& va = jio.Get("Providers"); + list.Reserve(va.GetCount()); + for(int i = 0; i < va.GetCount(); i++) { + String key, val; + LoadFromJsonValue(key, va[i]["Name"]); + LoadFromJsonValue(val, va[i]["URI"]); + if(!key.IsEmpty() && !val.IsEmpty()) + list.GetAdd(key) = val; + } + } + else { + Vector va; + va.SetCount(list.GetCount()); + for(int i = 0; i < list.GetCount(); i++) + if(!list.IsUnlinked(i)) { + ValueMap provider; + provider.Add("Name", StoreAsJsonValue(list.GetKey(i))); + provider.Add("URI", StoreAsJsonValue(list[i])); + va[i] = provider; + } + jio.Set("Providers", ValueArray(pick(va))); + } } +static String GetSearchPhrase(AssistEditor& editor) +{ + return UrlEncode(Nvl(editor.GetSelection(), editor.GetWord())); +} + void Ide::OnlineSearchOnTheOfficialSite() { LaunchWebBrowser("https://www.google.com/search?q=" + GetSearchPhrase(editor) + "&domains=www.ultimatepp.org&sitesearch=www.ultimatepp.org"); } + +void Ide::OnlineSearch(String uri) +{ + uri.Replace("\\{@}", GetSearchPhrase(editor)); + LaunchWebBrowser(uri); +} + +bool Ide::IsSearchProvidersMenuVisible() const +{ + int n = search_engines.list.GetCount(); + return !(n < 1 || (n == 1 && search_engines.list.GetKey(0) == search_engines.default_provider)); +} Index: Setup.cpp =================================================================== --- Setup.cpp (revision 15381) +++ Setup.cpp (working copy) @@ -325,6 +325,163 @@ TabSpaceConversionMode = false; } +class OnlineSearchManager : public WithSetupWebSearchLayout { + struct Provider + { + String name; + String uri; + bool is_default; + operator Value() const { return RawToValue(*this); } + Provider() : is_default(false) {} + Provider(const char *n, const char *u, bool b) + : name(n) + , uri(u) + , is_default(b) {} + }; + + struct ProviderNameDisplay : Display + { + void Paint(Draw& w, const Rect& r, const Value& q, Color ink, Color paper, dword style) const override + { + const auto& p = q.To(); + const Image& img = CtrlImg::Network(); + StdDisplay().Paint(w, r, AttrText(p.name).Bold(p.is_default).SetImage(img), ink, paper, style); + }; + }; + +public: + OnlineSearchManager(); + void LoadProviders(const OnlineSearchProviders& wsp); + void SaveProviders(OnlineSearchProviders& wsp) const; + +private: + bool SetProvider(Provider& p); + void Add(); + void Edit(); + void Remove(); + int FindDefault() const; + void ClearDefault(); + void Clear(); + void ContextMenu(Bar& menu); + +private: + ToolBar toolbar; +}; + +OnlineSearchManager::OnlineSearchManager() +{ + CtrlLayout(*this); + AddFrame(toolbar); + list.AddColumn("Search Providers").SetDisplay(Single()); + list.WhenBar = [=](Bar& menu) { ContextMenu(menu); }; + list.WhenAction = [=] { toolbar.Set([=](Bar& bar) { ContextMenu(bar); }); }; + list.WhenLeftDouble = [=] { list.IsCursor() ? Edit() : Add(); }; + list.WhenAction(); +} + +void OnlineSearchManager::LoadProviders(const OnlineSearchProviders& osp) +{ + list.Clear(); + for(int i = 0; i < osp.list.GetCount(); i++) { + const String& name = osp.list.GetKey(i); + const String& uri = osp.list[i]; + list.Add(Provider(name, uri, name == osp.default_provider)); + } +} + +void OnlineSearchManager::SaveProviders(OnlineSearchProviders& osp) const +{ + osp.list.Clear(); + if(FindDefault() < 0) + osp.default_provider.Clear(); + for(int i = 0; i < list.GetCount(); i++) { + auto p = list.Get(i, 0).To(); + osp.list.Add(p.name, p.uri); + if(p.is_default) + osp.default_provider = p.name; + } +} + +bool OnlineSearchManager::SetProvider(Provider& p) +{ + WithWebSearchProviderLayout pdlg; + CtrlLayoutOKCancel(pdlg, "Set web search engine"); + CtrlRetriever cr; + cr(pdlg.name, p.name)(pdlg.uri, p.uri)(pdlg.active, p.is_default).Set(); + if(pdlg.ExecuteOK()) { + cr.Retrieve(); + return true; + } + return false; +} + +void OnlineSearchManager::Add() +{ + Provider p; + if(SetProvider(p)) { + if(p.is_default) ClearDefault(); + for(int i = 0; i < list.GetCount(); i++) { + auto q = list.Get(i, 0).To(); + if(q.name == p.name) { + list.Set(i, 0, p); + return; + } + } + list.Add(p); + } +} + +void OnlineSearchManager::Edit() +{ + int i = list.GetCursor(); + if(i < 0) return; + auto p = list.Get(i, 0).To(); + if(SetProvider(p)) { + if(p.is_default) ClearDefault(); + list.Set(i, 0, p); + } +} + +void OnlineSearchManager::Remove() +{ + constexpr const char *msg = "You are about to delete the search provider '%s'\nAre you sure?"; + + int i = list.GetCursor(); + if(i >= 0 && PromptYesNo(DeQtf(Format(msg, list.Get(i, 0).To().name)))) { + list.Remove(i); + list.WhenAction(); + } +} + +int OnlineSearchManager::FindDefault() const +{ + for(int i = 0; i < list.GetCount(); i++) + if(list.Get(i, 0).To().is_default) + return i; + return -1; +} + +void OnlineSearchManager::ClearDefault() +{ + for(int i = 0; i < list.GetCount(); i++) { + auto p = list.Get(i, 0).To(); + p.is_default = false; + list.Set(i, 0, p); + } +} + +void OnlineSearchManager::ContextMenu(Bar& bar) +{ + int i = list.GetCursor(); + bar.Add("Add search engine", IdeImg::add(), [=] { Add(); }).Key(K_INSERT); + bar.Add(i >= 0, "Edit search engine", IdeImg::pencil(), [=] { Edit(); }).Key(K_ENTER); + bar.Add(i >= 0, "Remove search engine",IdeImg::remove(),[=] { Remove(); }).Key(K_DELETE); + if(bar.IsMenuBar()) { + bar.Separator(); + list.StdBar(bar); + } +} + void SetConsole(EditString *e, const char *text) { *e <<= text; @@ -390,6 +547,7 @@ WithSetupEditorLayout edt; WithSetupIdeLayout ide; WithSetupAssistLayout assist; + OnlineSearchManager osm; AStyleSetupDialog ast(this); #ifdef PLATFORM_WIN32 ide.console_txt.Hide(); @@ -430,6 +588,7 @@ dlg.Add(assist, "Assist"); dlg.Add(ide, "IDE"); dlg.Add(ast, "Code formatting"); + dlg.Add(osm, "Web search"); dlg.WhenClose = dlg.Acceptor(IDEXIT); FontSelectManager ed, vf, con, f1, f2, tf, gui; @@ -459,7 +618,9 @@ int hs = hilite_scope; DlSpellerLangs(edt.spellcheck_comments); - + + osm.LoadProviders(search_engines); + rtvr (hlt.hilite_scope, hs) (hlt.hilite_bracket, hilite_bracket) @@ -611,6 +772,9 @@ editor.SetHlStyle(i, hlt.hlstyle.Get(i, 1), hlt.hlstyle.Get(i, 2), hlt.hlstyle.Get(i, 3), hlt.hlstyle.Get(i, 4)); UpdateFormat(); + + osm.SaveProviders(search_engines); + if(c == IDEXIT) break; if(c == 222) Index: ide.h =================================================================== --- ide.h (revision 15381) +++ ide.h (working copy) @@ -323,6 +323,13 @@ FindInFilesDlg(); }; +struct OnlineSearchProviders final { + VectorMap list; + String default_provider; + void FetchDefaultProvider(String& name, String& uri, Image& img) const; + void Jsonize(JsonIO& jio); +}; + struct Ide : public TopWindow, public WorkspaceWork, public IdeContext, public MakeBuild { public: virtual void Paint(Draw& w); @@ -678,6 +685,8 @@ bool hlstyle_is_default = true; // default style reacts to dark / light theme settings + OnlineSearchProviders search_engines; + // ------------------------------------ Time config_time; @@ -836,9 +845,10 @@ void GotoPosition(); void OnlineSearchMenu(Bar& menu); - void OnlineSearch(); + void OnlineSearch(String uri); void OnlineSearchOnTheOfficialSite(); - + bool IsSearchProvidersMenuVisible() const; + void SearchMenu(Bar& bar); void EditFind() { editor.FindReplace(find_pick_sel, find_pick_text, false); } void EditReplace() { editor.FindReplace(find_pick_sel, find_pick_text, true); } Index: ide.lay =================================================================== --- ide.lay (revision 15381) +++ ide.lay (working copy) @@ -880,3 +880,17 @@ ITEM(Upp::Label, dv___3, SetLabel(t_("Data length will be put into the clipboard")).LeftPosZ(8, 260).TopPosZ(108, 19)) END_LAYOUT +LAYOUT(SetupWebSearchLayout, 572, 292) + ITEM(Upp::ArrayCtrl, list, SetLineCy(24).AskRemove(false).Moving(true).Track(true).VertGrid(false).HorzGrid(false).AutoHideSb(true).HSizePosZ(4, 4).VSizePosZ(4, 4)) +END_LAYOUT + +LAYOUT(WebSearchProviderLayout, 564, 84) + ITEM(Upp::Label, dv___0, SetLabel(t_("Name")).LeftPosZ(8, 72).TopPosZ(8, 19)) + ITEM(Upp::Label, dv___1, SetLabel(t_("URI")).LeftPosZ(8, 72).TopPosZ(32, 19)) + ITEM(Upp::EditString, name, NotNull(true).HSizePosZ(80, 4).TopPosZ(8, 19)) + ITEM(Upp::EditString, uri, NotNull(true).HSizePosZ(80, 4).VSizePosZ(32, 33)) + ITEM(Upp::Button, ok, SetLabel(t_("OK")).RightPosZ(72, 64).BottomPosZ(4, 24)) + ITEM(Upp::Button, cancel, SetLabel(t_("Cancel")).RightPosZ(4, 64).BottomPosZ(4, 24)) + ITEM(Upp::Option, active, SetLabel(t_("Set as the default search provider")).HSizePosZ(80, 140).BottomPosZ(8, 16)) +END_LAYOUT + Index: idebar.cpp =================================================================== --- idebar.cpp (revision 15381) +++ idebar.cpp (working copy) @@ -114,9 +114,25 @@ void Ide::OnlineSearchMenu(Bar& menu) { bool b = editor.IsSelection() || IsAlNum(editor.GetChar()) || editor.GetChar() == '_'; - menu.Add(b, AK_GOOGLE, IdeImg::Google(), - THISBACK(OnlineSearch)); - menu.Add(b, AK_GOOGLEUPP, IdeImg::GoogleUpp(), THISBACK(OnlineSearchOnTheOfficialSite)); + + Image img; + String name, uri; + search_engines.FetchDefaultProvider(name, uri, img); + + menu.Add(b, "Search on " + name, img, [=] { OnlineSearch(uri); }).Key(AK_GOOGLE); + menu.Add(b, AK_GOOGLEUPP, IdeImg::GoogleUpp(), [=] { OnlineSearchOnTheOfficialSite(); }); + + if(!menu.IsMenuBar() || !IsSearchProvidersMenuVisible()) + return; + + menu.Sub(b, "Search on...", [=](Bar& menu) { + for(int i = 0; i < search_engines.list.GetCount(); i++) { + const String& name = search_engines.list.GetKey(i); + const String& uri = search_engines.list[i]; + if(name != search_engines.default_provider) + menu.Add(b, name, CtrlImg::Network(), [=] { OnlineSearch(uri); }); + } + }); } void Ide::AssistEdit(Bar& menu)