/* * An Implementation of Place for Layout * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2014 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/place.cpp */ #include #include #include #include #include #include #include #include #include #include #include namespace nana { namespace place_parts { void check_field_name(const char* name) { //check the name if (*name && (*name != '_' && !(('a' <= *name && *name <= 'z') || ('A' <= *name && *name <= 'Z')))) throw std::invalid_argument("place.field: bad field name"); } class splitter_interface { public: virtual ~splitter_interface(){} }; class splitter_dtrigger : public drawer_trigger { }; template class splitter : public widget_object ::type, splitter_dtrigger>, public splitter_interface { }; //number_t is used for storing a number type variable //such as integer, real and percent. Essentially, percent is a typo of real. class number_t { public: enum class kind{ none, integer, real, percent }; number_t() : kind_(kind::none) { value_.integer = 0; } void reset() { kind_ = kind::none; value_.integer = 0; } bool is_negative() const { switch (kind_) { case kind::integer: return (value_.integer < 0); case kind::real: case kind::percent: return (value_.real < 0); default: break; } return false; } bool is_none() const { return (kind::none == kind_); } bool is_not_none() const { return (kind::none != kind_); } kind kind_of() const { return kind_; } double get_value(int ref_percent) const { switch (kind_) { case kind::integer: return value_.integer; case kind::real: return value_.real; case kind::percent: return value_.real * ref_percent; default: break; } return 0; } int integer() const { if (kind::integer == kind_) return value_.integer; return static_cast(value_.real); } double real() const { if (kind::integer == kind_) return value_.integer; return value_.real; } void assign(int i) { kind_ = kind::integer; value_.integer = i; } void assign(double d) { kind_ = kind::real; value_.real = d; } void assign_percent(double d) { kind_ = kind::percent; value_.real = d / 100; } private: kind kind_; union valueset { int integer; double real; }value_; };//end class number_t class margin { public: margin& operator=(margin&& rhs) { if (this != &rhs) { all_edges_ = rhs.all_edges_; margins_ = std::move(rhs.margins_); } return *this; } void clear() { all_edges_ = true; margins_.clear(); } void push(const number_t& v) { margins_.emplace_back(v); } void set_value(const number_t& v) { clear(); margins_.emplace_back(v); } void set_array(const std::vector& v) { all_edges_ = false; margins_ = v; } nana::rectangle area(const ::nana::rectangle& field_area) const { if (margins_.empty()) return field_area; auto r = field_area; if (all_edges_) { auto px = static_cast(margins_.back().get_value(static_cast(r.width))); const auto dbl_px = static_cast(px << 1); r.x += px; r.width = (r.width < dbl_px ? 0 : r.width - dbl_px); r.y += px; r.height = (r.height < dbl_px ? 0 : r.height - dbl_px); } else { int il{ -1 }, ir{ -1 }, it{ -1 }, ib{ -1 }; //index of four corners in margin switch (margins_.size()) { case 0: break; case 1: //top it = 0; break; case 2://top,bottom and left,right it = ib = 0; il = ir = 1; break; default: il = 3; //left case 3: //top, right, bottom it = 0; ir = 1; ib = 2; } typedef decltype(r.height) px_type; auto calc = [](px_type a, px_type b) { return (a > b ? a - b : 0); }; if (0 == it) //top { auto px = static_cast(margins_[it].get_value(static_cast(field_area.height))); r.y += px; r.height = calc(r.height, static_cast(px)); } if (-1 != ib) //bottom { auto px = static_cast(margins_[ib].get_value(static_cast(field_area.height))); r.height = calc(r.height, static_cast(px)); } if (-1 != il) //left { auto px = static_cast(margins_[il].get_value(static_cast(field_area.width))); r.x += px; r.width = calc(r.width, static_cast(px)); } if (-1 != ir) //right { auto px = static_cast(margins_[ir].get_value(static_cast(field_area.width))); r.width = calc(r.width, static_cast(px)); } } return r; } private: bool all_edges_ = true; std::vector margins_; };//end class margin class repeated_array { public: //A workaround for VC2013, becuase it does not generated an implicit declared move-constructor as defaulted. repeated_array() = default; repeated_array(repeated_array && other) : repeated_{other.repeated_}, values_(std::move(other.values_)) { } repeated_array& operator=(repeated_array&& other) { if(this != &other) { repeated_ = other.repeated_; other.repeated_ = false; values_ = std::move(other.values_); } return *this; } void assign(std::vector&& c) { values_ = std::move(c); } bool empty() const { return values_.empty(); } void reset() { repeated_ = false; values_.clear(); } void repeated() { repeated_ = true; } void push(const number_t& n) { values_.emplace_back(n); } number_t at(std::size_t pos) const { if (values_.empty()) return{}; if (repeated_) pos %= values_.size(); else if (pos >= values_.size()) return{}; return values_[pos]; } private: bool repeated_ = false; std::vector values_; }; }//end namespace place_parts typedef place_parts::number_t number_t; typedef place_parts::repeated_array repeated_array; namespace place_parts { class tokenizer { public: enum class token { div_start, div_end, splitter, identifier, vert, grid, number, array, reparray, weight, gap, margin, arrange, variable, repeated, min_px, max_px, collapse, parameters, equal, eof, error }; tokenizer(const char* p) : divstr_(p), sp_(p) {} const std::string& idstr() const { return idstr_; } number_t number() const { return number_; } std::vector& array() { return array_; } repeated_array&& reparray() { return std::move(reparray_); } std::vector& parameters() { return parameters_; } token read() { sp_ = _m_eat_whitespace(sp_); std::size_t readbytes = 0; switch (*sp_) { case '\0': return token::eof; case '|': ++sp_; readbytes = _m_number(sp_, false); sp_ += readbytes; return token::splitter; case '=': ++sp_; return token::equal; case '<': ++sp_; return token::div_start; case '>': ++sp_; return token::div_end; case '[': array_.clear(); sp_ = _m_eat_whitespace(sp_ + 1); if (*sp_ == ']') { ++sp_; return token::array; } { //When search the repeated. bool repeated = false; while (true) { sp_ = _m_eat_whitespace(sp_); auto tk = read(); if (token::number != tk && token::variable != tk && token::repeated != tk) _m_throw_error("invalid array element"); if (!repeated) { switch (tk) { case token::number: array_.push_back(number_); break; case token::variable: array_.push_back({}); break; default: repeated = true; reparray_.repeated(); reparray_.assign(std::move(array_)); } } sp_ = _m_eat_whitespace(sp_); char ch = *sp_++; if (ch == ']') return (repeated ? token::reparray : token::array); if (ch != ',') _m_throw_error("invalid array"); } } break; case '(': parameters_.clear(); sp_ = _m_eat_whitespace(sp_ + 1); if (*sp_ == ')') { ++sp_; return token::parameters; } while (true) { if (token::number == read()) parameters_.push_back(number_); else _m_throw_error("invalid parameter."); sp_ = _m_eat_whitespace(sp_); char ch = *sp_++; if (ch == ')') return token::parameters; if (ch != ',') _m_throw_error("invalid parameter."); } break; case '.': case '-': if (*sp_ == '-') { readbytes = _m_number(sp_ + 1, true); if (readbytes) ++readbytes; } else readbytes = _m_number(sp_, false); if (readbytes) { sp_ += readbytes; return token::number; } else _m_throw_error(*sp_); break; default: if ('0' <= *sp_ && *sp_ <= '9') { readbytes = _m_number(sp_, false); if (readbytes) { sp_ += readbytes; return token::number; } } break; } if ('_' == *sp_ || isalpha(*sp_)) { const char * idstart = sp_++; while ('_' == *sp_ || isalpha(*sp_) || isalnum(*sp_)) ++sp_; idstr_.assign(idstart, sp_); if ("weight" == idstr_ || "min" == idstr_ || "max" == idstr_) { auto ch = idstr_[1]; _m_attr_number_value(); switch (ch) { case 'e': return token::weight; case 'i': return token::min_px; case 'a': return token::max_px; } } else if ("vertical" == idstr_ || "vert" == idstr_) return token::vert; else if ("variable" == idstr_ || "repeated" == idstr_) return ('v' == idstr_[0] ? token::variable : token::repeated); else if ("arrange" == idstr_ || "gap" == idstr_) { auto ch = idstr_[0]; _m_attr_reparray(); return ('a' == ch ? token::arrange : token::gap); } else if ("grid" == idstr_ || "margin" == idstr_) { auto idstr = idstr_; if (token::equal != read()) _m_throw_error("an equal sign is required after '" + idstr + "'"); return ('g' == idstr[0] ? token::grid : token::margin); } else if ("collapse" == idstr_) { if (token::parameters != read()) _m_throw_error("a parameter list is required after 'collapse'"); return token::collapse; } return token::identifier; } std::string err = "an invalid character '"; err += *sp_; err += "'"; _m_throw_error(err); return token::error; //Useless, just for syntax correction. } private: void _m_throw_error(char err_char) { std::stringstream ss; ss << "place: invalid character '" << err_char << "' at " << static_cast(sp_ - divstr_); throw std::runtime_error(ss.str()); } void _m_attr_number_value() { if (token::equal != read()) _m_throw_error("an equal sign is required after '" + idstr_ + "'"); const char* p = sp_; for (; *p == ' '; ++p); auto neg_ptr = p; if ('-' == *p) ++p; auto len = _m_number(p, neg_ptr != p); if (0 == len) _m_throw_error("the '" + idstr_ + "' requires a number(integer or real or percent)"); sp_ += len + (p - sp_); } void _m_attr_reparray() { auto idstr = idstr_; if (token::equal != read()) _m_throw_error("an equal sign is required after '" + idstr + "'"); const char* p = sp_; for (; *p == ' ' || *p == '\t'; ++p); reparray_.reset(); auto tk = read(); switch (tk) { case token::number: reparray_.push(number()); reparray_.repeated(); break; case token::array: reparray_.assign(std::move(array_)); break; case token::reparray: break; default: _m_throw_error("a (repeated) array is required after '" + idstr + "'"); } } void _m_throw_error(const std::string& err) { std::stringstream ss; ss << "place: " << err << " at " << static_cast(sp_ - divstr_); throw std::runtime_error(ss.str()); } const char* _m_eat_whitespace(const char* sp) { while (*sp && !isgraph(*sp)) ++sp; return sp; } std::size_t _m_number(const char* sp, bool negative) { const char* allstart = sp; sp = _m_eat_whitespace(sp); number_.assign(0); bool gotcha = false; int integer = 0; double real = 0; //read the integral part. const char* istart = sp; while ('0' <= *sp && *sp <= '9') { integer = integer * 10 + (*sp - '0'); ++sp; } const char* iend = sp; if ('.' == *sp) { double div = 1; const char* rstart = ++sp; while ('0' <= *sp && *sp <= '9') { real += (*sp - '0') / (div *= 10); ++sp; } if (rstart != sp) { real += integer; number_.assign(negative ? -real : real); gotcha = true; } } else if (istart != iend) { number_.assign(negative ? -integer : integer); gotcha = true; } if (gotcha) { for (; *sp == ' ' || *sp == '\t'; ++sp); if ('%' == *sp) { if (number_t::kind::integer == number_.kind_of()) number_.assign_percent(number_.integer()); return sp - allstart + 1; } return sp - allstart; } number_.reset(); return 0; } private: const char* divstr_; const char* sp_; std::string idstr_; number_t number_; std::vector array_; repeated_array reparray_; std::vector parameters_; }; //end class tokenizer } //struct implement struct place::implement { class field_impl; class division; class div_arrange; class div_grid; class div_splitter; window window_handle{nullptr}; event_handle event_size_handle{nullptr}; std::unique_ptr root_division; std::map fields; //The following functions are defined behind the definition of class division. //because the class division here is an incomplete type. ~implement(); static division * search_div_name(division* start, const std::string&); std::unique_ptr scan_div(place_parts::tokenizer&); }; //end struct implement class place::implement::field_impl : public place::field_interface { public: struct element_t { window handle; event_handle evt_destroy; element_t(window h, event_handle event_destroy) :handle(h), evt_destroy(event_destroy) {} }; field_impl(place * p) : place_ptr_(p) {} ~field_impl() { for (auto & e : elements) API::umake_event(e.evt_destroy); for (auto & e : fastened) API::umake_event(e.evt_destroy); } void visible(bool vsb) { for (auto & e : elements) API::show_window(e.handle, vsb); for (auto & e : fastened) API::show_window(e.handle, vsb); } private: //The defintion is moved after the definition of class division template void _m_for_each(division*, Function); //Listen to destroy of a window //It will delete the element and recollocate when the window destroyed. event_handle _m_make_destroy(window wd) { return API::events(wd).destroy.connect([this](const arg_destroy& arg) { for (auto i = elements.begin(), end = elements.end(); i != end; ++i) { if (arg.window_handle == i->handle) { elements.erase(i); break; } } place_ptr_->collocate(); }); } field_interface& operator<<(const nana::char_t* label_text) override { return static_cast(this)->operator<<(agent