commit b98a8652ae8bd66501f76b0e35066f5e9a1cbfc4
parent 133831039a4cc180cfa375d403105bdbd094c643
Author: cfillion <cfillion@users.noreply.github.com>
Date: Tue, 2 Feb 2016 17:17:50 -0500
add description and links to the repository indexes
Diffstat:
14 files changed, 251 insertions(+), 32 deletions(-)
diff --git a/src/about.cpp b/src/about.cpp
@@ -20,10 +20,13 @@
#include "encoding.hpp"
#include "index.hpp"
#include "listview.hpp"
+#include "menu.hpp"
#include "resource.hpp"
#include "richedit.hpp"
#include "tabbar.hpp"
+#include <boost/algorithm/string/replace.hpp>
+
using namespace std;
About::About(const RemoteIndex *index)
@@ -61,6 +64,9 @@ void About::onInit()
{AUTO_STR("Installed Files"), {}},
});
+ m_website = getControl(IDC_WEBSITE);
+ m_donate = getControl(IDC_DONATE);
+
populate();
#ifdef LVSCW_AUTOSIZE_USEHEADER
@@ -72,10 +78,21 @@ void About::onInit()
void About::onCommand(const int id)
{
switch(id) {
+ case IDC_WEBSITE:
+ selectLink(id, m_websiteLinks);
+ break;
+ case IDC_DONATE:
+ selectLink(id, m_donationLinks);
+ break;
case IDOK:
case IDCANCEL:
close();
break;
+ default:
+ if(id >> 8 == IDC_WEBSITE)
+ openLink(m_websiteLinks[id & 0xff]);
+ else if(id >> 8 == IDC_DONATE)
+ openLink(m_donationLinks[id & 0xff]);
}
}
@@ -86,17 +103,15 @@ void About::populate()
auto_snprintf(title, sizeof(title), AUTO_STR("About %s"), name.c_str());
SetWindowText(handle(), title);
- const char *tmpRtf = \
- "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1348\\cocoasubrtf170\n"
- "{\\fonttbl\\f0\\fnil\\fcharset134 STHeitiSC-Light;}\n"
- "{\\colortbl;\\red255\\green255\\blue255;}\n"
- "\\margl1440\\margr1440\\vieww10800\\viewh8400\\viewkind0\n"
- "\\pard\\tx566\\tx1133\\tx1700\\tx2267\\tx2834\\tx3401\\tx3968\\tx4535\\tx5102\\tx5669\\tx6236\\tx6803\\pardirnatural\n"
- "\\f0\\fs24 \\cf0 http://perdu.com test"
- "{\\field{\\*\\fldinst{HYPERLINK \"https://msdn.microsoft.com/en-us/library/windows/desktop/bb787974%28v=vs.85%29.aspx\"}}{\\fldrslt \\f0\\fs24 \\cf0 \\'d0\\'c2\\'ca\\'c0\\'bd\\'e7\\'a4\\'e8\\'a4\\'ea}}}\n"
- ;
-
- if(!m_about->setRichText(tmpRtf)) {
+ m_websiteLinks = m_index->links(RemoteIndex::WebsiteLink);
+ if(m_websiteLinks.empty())
+ hide(m_website);
+
+ m_donationLinks = m_index->links(RemoteIndex::DonationLink);
+ if(m_donationLinks.empty())
+ hide(m_donate);
+
+ if(!m_about->setRichText(m_index->aboutText())) {
// if description is invalid or empty, don't display it
m_tabs->removeTab(0);
m_tabs->setCurrentIndex(0);
@@ -144,3 +159,29 @@ void About::updatePackages()
m_currentCat = catIndex;
}
+
+void About::selectLink(const int ctrl, const std::vector<const Link *> &links)
+{
+ const int count = (int)links.size();
+
+ if(count > 1) {
+ Menu menu;
+
+ for(int i = 0; i < count; i++) {
+ const string &name = boost::replace_all_copy(links[i]->name, "&", "&&");
+ menu.addAction(make_autostring(name).c_str(), i | (ctrl << 8));
+ }
+
+ RECT rect;
+ GetWindowRect(getControl(ctrl), &rect);
+ menu.show(rect.left, rect.bottom - 1, handle());
+ }
+ else if(count == 1)
+ openLink(links.front());
+}
+
+void About::openLink(const Link *link)
+{
+ const auto_string &url = make_autostring(link->url);
+ ShellExecute(nullptr, AUTO_STR("open"), url.c_str(), nullptr, nullptr, SW_SHOW);
+}
diff --git a/src/about.hpp b/src/about.hpp
@@ -20,6 +20,9 @@
#include "dialog.hpp"
+#include <vector>
+
+struct Link;
class ListView;
class RemoteIndex;
class RichEdit;
@@ -36,6 +39,8 @@ protected:
private:
void populate();
void updatePackages();
+ void selectLink(const int control, const std::vector<const Link *> &);
+ void openLink(const Link *);
const RemoteIndex *m_index;
int m_currentCat;
@@ -44,6 +49,11 @@ private:
RichEdit *m_about;
ListView *m_cats;
ListView *m_packages;
+ HWND m_website;
+ HWND m_donate;
+
+ std::vector<const Link *> m_websiteLinks;
+ std::vector<const Link *> m_donationLinks;
};
#endif
diff --git a/src/dialog.cpp b/src/dialog.cpp
@@ -130,14 +130,14 @@ void Dialog::Destroy(Dialog *dlg)
delete dlg;
}
-void Dialog::show()
+void Dialog::show(HWND handle)
{
- ShowWindow(m_handle, SW_SHOW);
+ ShowWindow(handle, SW_SHOW);
}
-void Dialog::hide()
+void Dialog::hide(HWND handle)
{
- ShowWindow(m_handle, SW_HIDE);
+ ShowWindow(handle, SW_HIDE);
}
void Dialog::close(const INT_PTR result)
diff --git a/src/dialog.hpp b/src/dialog.hpp
@@ -72,8 +72,10 @@ public:
void setEnabled(const bool enable) { setEnabled(enable, m_handle); }
void setEnabled(const bool, HWND);
- void show();
- void hide();
+ void show(HWND);
+ void show() { show(m_handle); }
+ void hide(HWND);
+ void hide() { hide(m_handle); }
void close(const INT_PTR = 0);
void center();
void setFocus();
diff --git a/src/index.cpp b/src/index.cpp
@@ -28,13 +28,13 @@ using namespace std;
static FILE *OpenFile(const char *path)
{
- #ifdef _WIN32
+#ifdef _WIN32
FILE *file = nullptr;
_wfopen_s(&file, make_autostring(path).c_str(), L"rb");
return file;
- #else
+#else
return fopen(path, "rb");
- #endif
+#endif
}
Path RemoteIndex::pathFor(const string &name)
@@ -42,6 +42,14 @@ Path RemoteIndex::pathFor(const string &name)
return Path::prefixCache(name + ".xml");
}
+auto RemoteIndex::linkTypeFor(const char *rel) -> LinkType
+{
+ if(!strcmp(rel, "donation"))
+ return DonationLink;
+
+ return WebsiteLink;
+}
+
RemoteIndex *RemoteIndex::load(const string &name)
{
TiXmlDocument doc;
@@ -104,6 +112,25 @@ void RemoteIndex::addCategory(Category *cat)
cat->packages().begin(), cat->packages().end());
}
+void RemoteIndex::addLink(const LinkType type, const Link &link)
+{
+ if(link.url.find("http") == 0)
+ m_links.insert({type, link});
+}
+
+auto RemoteIndex::links(const LinkType type) const -> LinkList
+{
+ const auto begin = m_links.lower_bound(type);
+ const auto end = m_links.upper_bound(type);
+
+ LinkList list(m_links.count(type));
+
+ for(auto it = begin; it != end; it++)
+ list[distance(begin, it)] = &it->second;
+
+ return list;
+}
+
Category::Category(const string &name, RemoteIndex *ri)
: m_index(ri), m_name(name)
{
diff --git a/src/index.hpp b/src/index.hpp
@@ -18,7 +18,7 @@
#ifndef REAPACK_INDEX_HPP
#define REAPACK_INDEX_HPP
-#include <memory>
+#include <map>
#include <string>
#include <vector>
@@ -30,9 +30,16 @@ typedef std::vector<Category *> CategoryList;
class Path;
class TiXmlElement;
+struct Link { std::string name; std::string url; };
+
class RemoteIndex {
public:
+ enum LinkType { WebsiteLink, DonationLink };
+ typedef std::multimap<LinkType, Link> LinkMap;
+ typedef std::vector<const Link *> LinkList;
+
static Path pathFor(const std::string &name);
+ static LinkType linkTypeFor(const char *rel);
static RemoteIndex *load(const std::string &name);
RemoteIndex(const std::string &name);
@@ -40,6 +47,11 @@ public:
const std::string &name() const { return m_name; }
+ void setAboutText(const std::string &rtf) { m_about = rtf; }
+ const std::string &aboutText() const { return m_about; }
+ void addLink(const LinkType, const Link &);
+ LinkList links(const LinkType type) const;
+
void addCategory(Category *cat);
const CategoryList &categories() const { return m_categories; }
Category *category(const size_t i) const { return m_categories[i]; }
@@ -50,6 +62,8 @@ private:
static RemoteIndex *loadV1(TiXmlElement *, const std::string &);
std::string m_name;
+ std::string m_about;
+ LinkMap m_links;
CategoryList m_categories;
PackageList m_packages;
};
diff --git a/src/index_v1.cpp b/src/index_v1.cpp
@@ -25,6 +25,7 @@
using namespace std;
+static void LoadMetadataV1(TiXmlElement *, RemoteIndex *ri);
static void LoadCategoryV1(TiXmlElement *, RemoteIndex *ri);
static void LoadPackageV1(TiXmlElement *, Category *cat);
static void LoadVersionV1(TiXmlElement *, Package *pkg);
@@ -37,18 +38,54 @@ RemoteIndex *RemoteIndex::loadV1(TiXmlElement *root, const string &name)
// thrown during the loading process
unique_ptr<RemoteIndex> ptr(ri);
- TiXmlElement *catNode = root->FirstChildElement("category");
+ TiXmlElement *node = root->FirstChildElement("category");
- while(catNode) {
- LoadCategoryV1(catNode, ri);
+ while(node) {
+ LoadCategoryV1(node, ri);
- catNode = catNode->NextSiblingElement("category");
+ node = node->NextSiblingElement("category");
}
+ node = root->FirstChildElement("metadata");
+
+ if(node)
+ LoadMetadataV1(node, ri);
+
ptr.release();
return ri;
}
+void LoadMetadataV1(TiXmlElement *meta, RemoteIndex *ri)
+{
+ TiXmlElement *node = meta->FirstChildElement("description");
+
+ if(node) {
+ const char *rtf = node->GetText();
+
+ if(rtf)
+ ri->setAboutText(rtf);
+ }
+
+ node = meta->FirstChildElement("link");
+
+ while(node) {
+ const char *rel = node->Attribute("rel");
+ const char *url = node->Attribute("href");
+ const char *name = node->GetText();
+
+ if(!rel) rel = "";
+ if(!name) {
+ if(!url) url = "";
+ name = url;
+ }
+ else if(!url) url = name;
+
+ ri->addLink(RemoteIndex::linkTypeFor(rel), {name, url});
+
+ node = node->NextSiblingElement("link");
+ }
+}
+
void LoadCategoryV1(TiXmlElement *catNode, RemoteIndex *ri)
{
const char *name = catNode->Attribute("name");
@@ -78,7 +115,7 @@ void LoadPackageV1(TiXmlElement *packNode, Category *cat)
const char *name = packNode->Attribute("name");
if(!name) name = "";
- Package *pack = new Package(Package::ConvertType(type), name, cat);
+ Package *pack = new Package(Package::typeFor(type), name, cat);
unique_ptr<Package> ptr(pack);
TiXmlElement *verNode = packNode->FirstChildElement("version");
diff --git a/src/package.cpp b/src/package.cpp
@@ -24,7 +24,7 @@
using namespace std;
-Package::Type Package::ConvertType(const char *type)
+Package::Type Package::typeFor(const char *type)
{
if(!strcmp(type, "script"))
return ScriptType;
diff --git a/src/package.hpp b/src/package.hpp
@@ -33,7 +33,7 @@ public:
ScriptType,
};
- static Type ConvertType(const char *);
+ static Type typeFor(const char *);
Package(const Type, const std::string &name, Category * = nullptr);
~Package();
diff --git a/src/resource.hpp b/src/resource.hpp
@@ -43,8 +43,8 @@
#define IDC_IMPORT 204
#define IDC_TABS 205
#define IDC_ENABLE 206
-#define IDC_DONATE 207
-#define IDC_WEBSITE 208
+#define IDC_WEBSITE 207
+#define IDC_DONATE 208
#define IDC_ABOUT 209
#define IDC_CATEGORIES 210
#define IDC_PACKAGES 211
diff --git a/test/index.cpp b/test/index.cpp
@@ -201,3 +201,46 @@ TEST_CASE("category full name", M) {
Category cat2("Category Name", &ri);
REQUIRE(cat2.fullName() == "Remote Name/Category Name");
}
+
+TEST_CASE("repository description", M) {
+ RemoteIndex ri("Remote Name");
+ CHECK(ri.aboutText().empty());
+
+ ri.setAboutText("Hello World");
+ REQUIRE(ri.aboutText() == "Hello World");
+}
+
+TEST_CASE("repository links", M) {
+ RemoteIndex ri("Remote name");
+ CHECK(ri.links(RemoteIndex::WebsiteLink).empty());
+ CHECK(ri.links(RemoteIndex::DonationLink).empty());
+
+ SECTION("website links") {
+ ri.addLink(RemoteIndex::WebsiteLink, {"First", "http://example.com"});
+ REQUIRE(ri.links(RemoteIndex::WebsiteLink).size() == 1);
+ ri.addLink(RemoteIndex::WebsiteLink, {"Second", "http://example.com"});
+
+ const auto &links = ri.links(RemoteIndex::WebsiteLink);
+ REQUIRE(links.size() == 2);
+ REQUIRE(links[0]->name == "First");
+ REQUIRE(links[1]->name == "Second");
+
+ REQUIRE(ri.links(RemoteIndex::DonationLink).empty());
+ }
+
+ SECTION("donation links") {
+ ri.addLink(RemoteIndex::DonationLink, {"First", "http://example.com"});
+ REQUIRE(ri.links(RemoteIndex::DonationLink).size() == 1);
+ }
+
+ SECTION("drop invalid links") {
+ ri.addLink(RemoteIndex::WebsiteLink, {"name", "not http(s)"});
+ REQUIRE(ri.links(RemoteIndex::WebsiteLink).empty());
+ }
+}
+
+TEST_CASE("link type from string", M) {
+ REQUIRE(RemoteIndex::linkTypeFor("website") == RemoteIndex::WebsiteLink);
+ REQUIRE(RemoteIndex::linkTypeFor("donation") == RemoteIndex::DonationLink);
+ REQUIRE(RemoteIndex::linkTypeFor("bacon") == RemoteIndex::WebsiteLink);
+}
diff --git a/test/index_v1.cpp b/test/index_v1.cpp
@@ -177,3 +177,34 @@ TEST_CASE("full index", M) {
REQUIRE(source->file() == "test.lua");
REQUIRE(source->url() == "https://google.com/");
}
+
+TEST_CASE("read index metadata", M) {
+ UseRootPath root(RIPATH);
+
+ RemoteIndex *ri = RemoteIndex::load("metadata");
+ RIPTR(ri);
+
+ SECTION("description") {
+ REQUIRE(ri->aboutText() == "Chunky\nBacon");
+ }
+
+ SECTION("website links") {
+ const auto &links = ri->links(RemoteIndex::WebsiteLink);
+ REQUIRE(links.size() == 4);
+ REQUIRE(links[0]->name == "http://cfillion.tk");
+ REQUIRE(links[0]->url == "http://cfillion.tk");
+ REQUIRE(links[1]->name == "https://github.com/cfillion");
+ REQUIRE(links[1]->url == "https://github.com/cfillion");
+ REQUIRE(links[2]->name == "http://twitter.com/cfi30");
+ REQUIRE(links[2]->url == "http://twitter.com/cfi30");
+ REQUIRE(links[3]->name == "http://google.com");
+ REQUIRE(links[3]->url == "http://google.com");
+ }
+
+ SECTION("donation links") {
+ const auto &links = ri->links(RemoteIndex::DonationLink);
+ REQUIRE(links.size() == 1);
+ REQUIRE(links[0]->name == "Donate");
+ REQUIRE(links[0]->url == "http://paypal.com");
+ }
+}
diff --git a/test/indexes/v1/ReaPack/metadata.xml b/test/indexes/v1/ReaPack/metadata.xml
@@ -0,0 +1,14 @@
+<index version="1">
+ <metadata>
+ <link rel="website" href="http://cfillion.tk" />
+ <link rel="website" href="https://github.com/cfillion"></link>
+ <link rel="website">http://twitter.com/cfi30</link>
+ <link>http://google.com</link>
+ <link />
+ <description><![CDATA[Chunky
+Bacon]]>
+ </description>
+ <link rel="donation" href="http://paypal.com">Donate</link>
+ <link rel="website" href="/"></link>
+ </metadata>
+</index>
diff --git a/test/package.cpp b/test/package.cpp
@@ -14,11 +14,11 @@ static const char *M = "[package]";
TEST_CASE("package type from string", M) {
SECTION("unknown") {
- REQUIRE(Package::ConvertType("yoyo") == Package::UnknownType);
+ REQUIRE(Package::typeFor("yoyo") == Package::UnknownType);
}
SECTION("script") {
- REQUIRE(Package::ConvertType("script") == Package::ScriptType);
+ REQUIRE(Package::typeFor("script") == Package::ScriptType);
}
}