/* * An Implementation of Place for Layout * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2003-2016 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include //numeric_limits #include //std::abs #include //std::memset #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, vert, grid, number, array, reparray, weight, gap, margin, arrange, variable, repeated, min_px, max_px, left, right, top, bottom, 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_; } std::size_t pos() const { return (sp_ - divstr_); } std::string pos_str() const { return std::to_string(pos()); } 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_ || 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 ("dock" == idstr_) return token::dock; 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; } else if ("left" == idstr_ || "right" == idstr_ || "top" == idstr_ || "bottom" == idstr_) { switch (idstr_.front()) { case 'l': return token::left; case 'r': return token::right; case 't': return token::top; case 'b': return token::bottom; } } 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::string str = "place: invalid character '"; str += err_char; str += '\''; _m_throw_error(str); } 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()); } 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) { 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) { sp = _m_eat_whitespace(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_gather; class field_dock; class division; class div_arrange; class div_grid; class div_splitter; class div_dock; class div_dockpane; window window_handle{nullptr}; event_handle event_size_handle{nullptr}; std::unique_ptr root_division; std::map fields; std::map docks; std::map dock_factoris; //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&); 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(); }; //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) :handle(h), evt_destroy(event_destroy) {} }; field_gather(place * p) : place_ptr_(p) {} ~field_gather() { 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); } static event_handle erase_element(std::vector& elements, window handle) { for (auto i = elements.begin(), end = elements.end(); i != end; ++i) { if (i->handle == handle) { auto evt_destroy = i->evt_destroy; elements.erase(i); return evt_destroy; } } return nullptr; } 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, wd](const arg_destroy& arg) { for (auto i = elements.begin(), end = elements.end(); i != end; ++i) { if (!API::is_destroying(API::get_parent_window(wd))) place_ptr_->collocate(); } }); } field_interface& operator<<(const char* label_text) override { return static_cast(this)->operator<<(agent