reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 6c57007c8d4ff1159d89aae432c03ed38f84adae
parent 4df98b3f5fdf10d1312f35695a42b8a17cca1dd2
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Sun, 26 Jun 2016 14:54:34 -0400

extract integer array serializer

Diffstat:
Msrc/listview.cpp | 76+++++++++++++++++-----------------------------------------------------------
Msrc/listview.hpp | 5+++--
Asrc/serializer.cpp | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/serializer.hpp | 41+++++++++++++++++++++++++++++++++++++++++
Atest/serializer.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 230 insertions(+), 61 deletions(-)

diff --git a/src/listview.cpp b/src/listview.cpp @@ -20,7 +20,6 @@ #include "menu.hpp" #include <boost/algorithm/string.hpp> -#include <sstream> #ifdef _WIN32 #include <commctrl.h> @@ -28,12 +27,8 @@ using namespace std; -static const unsigned short VERSION = 1; -static const char FIELD_END = '\x20'; -static const char RECORD_END = ','; - ListView::ListView(const Columns &columns, HWND handle) - : Control(handle), m_userVersion(0), m_sort(), m_defaultSort() + : Control(handle), m_sort(), m_defaultSort() { for(const Column &col : columns) addColumn(col); @@ -298,7 +293,7 @@ bool ListView::onContextMenu(HWND dialog, int x, int y) #endif if(point.y < headerHeight) { - if(m_userVersion) // show menu only if header is customizable + if(m_serializer) // show menu only if header is customizable headerMenu(x, y); return true; } @@ -449,53 +444,34 @@ void ListView::resetColumns() } } -bool ListView::restore(const string &data, const int userVersion) +void ListView::restore(const string &input, const int userVersion) { - m_userVersion = userVersion; // for save(). also enables advanced customization setExStyle(LVS_EX_HEADERDRAGDROP, true); // enable column reordering + const Serializer::Vector &data = m_serializer.read(input, userVersion); if(data.empty()) - return false; + return; - int col = -2; + int col = -1; vector<int> order(columnCount()); - istringstream stream(data); - - while(true) { - string line, first, second; - getline(stream, line, RECORD_END); - istringstream lineStream(line); - getline(lineStream, first, FIELD_END); - getline(lineStream, second, FIELD_END); - - int left, right; - - try { - left = stoi(first.c_str()); - right = stoi(second.c_str()); - } - catch(logic_error &) { - return false; // data is invalid! aborting. - } + for(const auto &rec : data) { + const int left = rec[0]; + const int right = rec[1]; switch(col) { - case -2: // version - if(left != userVersion || right != VERSION) - return false; - break; case -1: // sort if(left < columnCount()) sortByColumn(left, right == 0 ? AscendingOrder : DescendingOrder, true); break; - default: + default: // column order[col] = left; // raw size should not go through adjustSize (via resizeColumn) ListView_SetColumnWidth(handle(), col, right); break; } - if(stream.eof() || ++col >= columnCount()) + if(++col >= columnCount()) break; } @@ -504,37 +480,19 @@ bool ListView::restore(const string &data, const int userVersion) order[col] = col; ListView_SetColumnOrderArray(handle(), columnCount(), &order[0]); - - return true; } string ListView::save() const { + const Sort sort = m_sort.value_or(Sort()); vector<int> order(columnCount()); ListView_GetColumnOrderArray(handle(), columnCount(), &order[0]); - ostringstream stream; - - stream - << m_userVersion << FIELD_END - << VERSION << RECORD_END; + Serializer::Vector data; + data.push_back({sort.column, sort.order}); - Sort sort = m_sort.value_or(Sort()); - stream - << sort.column << FIELD_END - << sort.order << RECORD_END; - - int i = 0; - while(true) { - stream - << order[i] << FIELD_END - << columnWidth(i); - - if(++i < columnCount()) - stream << RECORD_END; - else - break; - } + for(int i = 0; i < columnCount(); i++) + data.push_back({order[i], columnWidth(i)}); - return stream.str(); + return m_serializer.write(data); } diff --git a/src/listview.hpp b/src/listview.hpp @@ -26,6 +26,7 @@ #include <vector> #include "encoding.hpp" +#include "serializer.hpp" class Menu; @@ -83,7 +84,7 @@ public: void sortByColumn(int index, SortOrder order = AscendingOrder, bool user = false); void setSortCallback(int i, const SortCallback &cb) { m_sortFuncs[i] = cb; } - bool restore(const std::string &, int userVersion); + void restore(const std::string &, int userVersion); std::string save() const; void resetColumns(); @@ -113,7 +114,7 @@ private: int translateBack(int internalIndex) const; void headerMenu(int x, int y); - int m_userVersion; + Serializer m_serializer; std::vector<Column> m_cols; std::vector<Row> m_rows; boost::optional<Sort> m_sort; diff --git a/src/serializer.cpp b/src/serializer.cpp @@ -0,0 +1,108 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "serializer.hpp" + +#include <sstream> + +static const unsigned short VERSION = 1; +static const char FIELD_END = '\x20'; +static const char RECORD_END = ','; + +using namespace std; + +auto Serializer::read(const string &input, const int userVersion) -> Vector +{ + m_userVersion = userVersion; + + bool first = true; + istringstream stream(input); + + Vector out; + + while(!stream.eof()) { + string line; + getline(stream, line, RECORD_END); + + istringstream lineStream(line); + + Record rec; + for(size_t i = 0; i < rec.size(); i++) { + if(lineStream.eof()) + return out; + + string field; + getline(lineStream, field, FIELD_END); + + int value; + + try { + value = stoi(field.c_str()); + } + catch(logic_error &) { + return out; // data is invalid! aborting. + } + + rec[i] = value; + } + + if(first) { + if(rec[0] != m_userVersion || rec[1] != VERSION) + return {}; + + first = false; + } + else + out.push_back(rec); + } + + return out; +} + +string Serializer::write(const Vector &data) const +{ + if(!m_userVersion) + return {}; + + ostringstream stream; + + stream + << m_userVersion << FIELD_END + << VERSION << RECORD_END; + + size_t i = 0; + while(true) { + const Record &rec = data[i]; + + size_t j = 0; + while(true) { + stream << rec[j]; + + if(++j < rec.size()) + stream << FIELD_END; + else + break; + } + + if(++i < data.size()) + stream << RECORD_END; + else + break; + } + + return stream.str(); +} diff --git a/src/serializer.hpp b/src/serializer.hpp @@ -0,0 +1,41 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef REAPACK_SERIALIZER_HPP +#define REAPACK_SERIALIZER_HPP + +#include <array> +#include <string> +#include <vector> + +class Serializer { +public: + typedef std::array<int, 2> Record; + typedef std::vector<Record> Vector; + + Serializer() : m_userVersion(0) {} + int userVersion() const { return m_userVersion; } + operator bool() const { return m_userVersion > 0; } + + Vector read(const std::string &, int userVersion); + std::string write(const Vector &in) const; + +private: + int m_userVersion; +}; + +#endif diff --git a/test/serializer.cpp b/test/serializer.cpp @@ -0,0 +1,61 @@ +#include <catch.hpp> + +#include <serializer.hpp> + +static const char *M = "[serializer]"; + +TEST_CASE("read from serialized data", M) { + Serializer s; + REQUIRE(s.userVersion() == 0); + REQUIRE_FALSE(s); + + SECTION("valid") { + const auto &out = s.read("1 1,1 2,3 4,5 6", 1); + REQUIRE(out.size() == 3); + REQUIRE(out[0] == (Serializer::Record{1,2})); + REQUIRE(out[1] == (Serializer::Record{3,4})); + REQUIRE(out[2] == (Serializer::Record{5,6})); + + REQUIRE(s.userVersion() == 1); + REQUIRE(s); + } + + SECTION("wrong user version") { + const auto &out = s.read("1 1,1 2,3 4,5 6", 2); + REQUIRE(out.empty()); + REQUIRE(s.userVersion() == 2); + REQUIRE(s); + } + + SECTION("wrong data version") { + const auto &out = s.read("1 42,1 2,3 4,5 6", 1); + REQUIRE(out.empty()); + REQUIRE(s.userVersion() == 1); + REQUIRE(s); + } + + SECTION("not an integer") { + const auto &out = s.read("1 1,1 2,hello world,3 4", 1); + REQUIRE(out.size() == 1); + REQUIRE(out[0] == (Serializer::Record{1,2})); + } + + SECTION("single field") { + const auto &out = s.read("1 1,1 2,3,4 5", 1); + REQUIRE(out.size() == 1); + REQUIRE(out[0] == (Serializer::Record{1,2})); + } + + SECTION("empty string") { + const auto &out = s.read("", 1); + REQUIRE(out.empty()); + } +} + +TEST_CASE("write to string", M) { + Serializer s; + REQUIRE(s.write({{1, 2}}).empty()); // no user version set + s.read({}, 42); + + REQUIRE(s.write({{1, 2}, {3, 4}}) == "42 1,1 2,3 4"); +}