/* * An Implementation of Place for Layout * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2018 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 * @contributors: Ariel Vina-Rodriguez * dankan1890(PR#156) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //numeric_limits #include //std::abs #include //std::memset #include //std::isalpha/std::isalnum #include "place_parts.hpp" namespace nana { namespace place_parts { //check the name void check_field_name(const char* name) { if (*name && (*name != '_' && !(('a' <= *name && *name <= 'z') || ('A' <= *name && *name <= 'Z')))) throw std::invalid_argument("nana.place: bad field name"); } }//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, dock, fit, hfit, vfit, vert, grid, number, array, reparray, weight, width, height, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, undisplayed, invisible, switchable, collapse, parameters, equal, eof, error }; tokenizer(const char* p) noexcept : divstr_(p), sp_(p) {} const std::string& idstr() const noexcept { return idstr_; } const number_t& number() const { return number_; } std::vector& array() noexcept { return array_; } repeated_array&& reparray() noexcept { return std::move(reparray_); } std::vector& parameters() noexcept { return parameters_; } std::size_t pos() const noexcept { return (sp_ - divstr_); } 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; } else { //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("invalid character '" + std::string(1, *sp_) + "'"); break; default: if ('0' <= *sp_ && *sp_ <= '9') { readbytes = _m_number(sp_, false); if (readbytes) { sp_ += readbytes; return token::number; } } break; } if ('_' == *sp_ || std::isalpha(*sp_)) { const char * idstart = sp_++; while ('_' == *sp_ || std::isalpha(*sp_) || std::isalnum(*sp_)) ++sp_; idstr_.assign(idstart, sp_); if ( "weight" == idstr_ || "min" == idstr_ || "max" == idstr_ || "width" == idstr_ || "height" == idstr_ ) { auto c3 = idstr_[2], c1 =idstr_[0]; _m_attr_number_value(); switch (c3) { case 'i': return c1=='w'? token::weight : token::height; case 'n': return token::min_px; case 'x': return token::max_px; case 'd': return token::width; } } else if ("arrange" == idstr_ || "hfit" == idstr_ || "vfit" == idstr_ || "gap" == idstr_) { auto ch = idstr_[0]; _m_attr_reparray(); switch (ch) { case 'a': return token::arrange; case 'h': return token::hfit; case 'v': return token::vfit; case 'g': return token::gap; default: break; } } 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; } else if (!idstr_.empty()) { switch (idstr_.front()) { case 'b': if ("bottom" == idstr_) return token::bottom; break; case 'd': if ("dock" == idstr_) return token::dock; break; case 'f': if ("fit" == idstr_) return token::fit; break; case 'i': if ("invisible" == idstr_) return token::invisible; break; case 'l': if ("left" == idstr_) return token::left; break; case 'r': if ("repeated" == idstr_) return token::repeated; else if ("right" == idstr_) return token::right; break; case 's': if ("switchable" == idstr_) return token::switchable; break; case 't': if ("top" == idstr_) return token::top; break; case 'u': if ("undisplayed" == idstr_) return token::undisplayed; break; case 'v': if ("vertical" == idstr_ || "vert" == idstr_) return token::vert; else if ("variable" == idstr_) return token::variable; break; } } 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(const std::string& err) { throw std::runtime_error("nana::place: " + err + " at " + std::to_string(static_cast(sp_ - divstr_))); } void _m_attr_number_value() { if (token::equal != read()) _m_throw_error("an equal sign is required after '" + idstr_ + "'"); auto p = _m_eat_whitespace(sp_); 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 + "'"); sp_ = _m_eat_whitespace(sp_); 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 + "'"); } } static const char* _m_eat_whitespace(const char* sp) noexcept { while (*sp && !std::isgraph(*sp)) ++sp; return sp; } std::size_t _m_number(const char* sp, bool negative) noexcept { 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) { sp = _m_eat_whitespace(sp); if ('%' != *sp) return sp - allstart; switch (number_.kind_of()) { case number_t::kind::integer: number_.assign_percent(number_.integer()); break; case number_t::kind::real: number_.assign_percent(number_.real()); break; default: break; } return sp - allstart + 1; } 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 } static bool is_vert_dir(::nana::direction dir) { return (dir == ::nana::direction::north || dir == ::nana::direction::south); } static int horz_point(bool vert, const point& pos) { return (vert ? pos.y : pos.x); } static bool is_idchar(int ch) noexcept { return ('_' == ch || std::isalnum(ch)); } static std::size_t find_idstr(const std::string& text, const char* idstr, std::size_t off = 0) { const auto len = std::strlen(idstr); size_t pos; while ((pos = text.find(idstr, off)) != text.npos) { if (!is_idchar(text[pos + len])) { if (pos == 0 || !is_idchar(text[pos - 1])) return pos; } off = pos + len; // occurrence not found, advancing the offset and try again } return text.npos; } //Find the text boundary of a field. The parameter start_pos is one of bound characters of the field whose bound will be returned. //The boundary doesn't include the tag characters. static std::pair get_field_boundary(const std::string& div, std::size_t start_pos) { int depth = 0; if ('<' == div[start_pos]) { auto off = start_pos + 1; while (off < div.length()) { auto pos = div.find_first_of("<>", off); if (div.npos == pos) break; if ('<' == div[pos]) { ++depth; off = pos + 1; continue; } if (0 == depth) return{ start_pos + 1, pos }; --depth; off = pos + 1; } } else if (('>' == div[start_pos]) && (start_pos > 0)) { auto off = start_pos - 1; while (true) { auto pos = div.find_last_of("<>", off); if (div.npos == pos) break; if ('>' == div[pos]) { ++depth; if (0 == pos) break; off = pos - 1; } if (0 == depth) return{ pos + 1, start_pos}; if (0 == pos) break; off = pos - 1; } } return{}; } // Returns the bound of a specified field excluding tag characters. static std::pair get_field_boundary(const std::string& div, const char* idstr, int depth) { auto start_pos = find_idstr(div, idstr); if (depth < 0 || start_pos >= div.length()) return{}; while (depth >= 0) { auto pos = div.find_last_of("<>", start_pos); if (div.npos == pos) return {0, div.length()}; start_pos = pos - 1; if (div[pos] == '>') { ++depth; continue; } --depth; } //The second parameter is the index of a tag character return get_field_boundary(div, start_pos + 1); } //struct implement struct place::implement { class field_gather; class field_dock; class division; class div_arrange; class div_grid; class div_splitter; class div_dock; class div_dockpane; class div_switchable; window window_handle{nullptr}; event_handle event_size_handle{nullptr}; std::string div_text; std::unique_ptr root_division; std::map fields; std::map docks; std::map dock_factoris; std::function split_renderer; std::set splitters; //A temporary pointer used to refer to a specified div object which //will be deleted in modification process. std::unique_ptr tmp_replaced; //The following functions are defined behind the definition of class division. //because the class division here is an incomplete type. ~implement(); void collocate(); static division * search_div_name(division* start, const std::string&) noexcept; std::unique_ptr scan_div(place_parts::tokenizer&); void check_unique(const division*) const; //connect the field/dock with div object void connect(division* start); void disconnect() noexcept; }; //end struct implement class place::implement::field_gather : public place::field_interface { public: struct element_t { window handle; event_handle evt_destroy; element_t(window h, event_handle event_destroy) noexcept :handle(h), evt_destroy(event_destroy) {} }; field_gather(place * p) noexcept : place_ptr_(p) {} ~field_gather() noexcept { for (auto & e : elements) API::umake_event(e.evt_destroy); for (auto & e : fastened) API::umake_event(e.evt_destroy); } void visible(bool vsb, bool sync_fastened = true) { for (auto & e : elements) API::show_window(e.handle, vsb); if (sync_fastened) { for (auto & e : fastened) API::show_window(e.handle, vsb); } } static event_handle erase_element(std::vector& elements, window handle) noexcept { for (auto i = elements.begin(); i != elements.end(); ++i) { if (i->handle == handle) { auto evt_destroy = i->evt_destroy; elements.erase(i); return evt_destroy; } } return nullptr; } private: void _m_insert_widget(window wd, bool to_fasten) { if (API::empty_window(wd)) throw std::invalid_argument("Place: An invalid window handle."); if (API::get_parent_window(wd) != place_ptr_->window_handle()) throw std::invalid_argument("Place: the window is not a child of place bind window"); //Listen to destroy of a window //It will delete the element and recollocate when the window destroyed. auto evt = API::events(wd).destroy.connect([this, to_fasten](const arg_destroy& arg) { if (!to_fasten) { if (erase_element(elements, arg.window_handle)) { if (!API::is_destroying(API::get_parent_window(arg.window_handle))) place_ptr_->collocate(); } } else erase_element(fastened, arg.window_handle); }); (to_fasten ? &fastened : &elements)->emplace_back(wd, evt); } field_interface& operator<<(const char* label_text) override { return static_cast(this)->operator<<(agent