/** Get value of the DOM element. Returns value for elements recognized by get_ctl_type() function. * \param[in] el \b const dom::element&, The element. * \return \b value_t, value of the element. **/ inline value_t get_value(dom::element& el ) { switch(get_ctl_type(el)) { case CTL_EDIT: case CTL_DECIMAL: case CTL_CURRENCY: case CTL_PASSWORD: case CTL_NUMERIC: case CTL_PROGRESS: case CTL_SLIDER: case CTL_SELECT_SINGLE: case CTL_SELECT_MULTIPLE: case CTL_DD_SELECT: case CTL_TEXTAREA: case CTL_DATE: case CTL_CALENDAR: default: return el.get_value(); // special cases: case CTL_UNKNOWN: if( !aux::wcseq(el.get_attribute("type"),L"hidden")) break; //else fall below if it is hidden case CTL_BUTTON: return value_t(el.get_attribute("value")); case CTL_CHECKBOX: return get_checkbox_bits(el); case CTL_RADIO: return get_radio_index(el); case CTL_HTMLAREA: return value_t(el.get_html(false/*inner*/)); } return value_t(); }
void setup_node( dom::element node ) { dom::element on = node.find_first("option[check='on'],options[check='on']"); dom::element off = node.find_first("option[check='off'],options[check='off']"); dom::element mixed = node.find_first("options[check='mixed']"); const wchar_t* _prev = node.get_attribute(CHECK_ATTR); std::wstring prev = _prev?_prev:L""; if( mixed.is_valid() || (on.is_valid() && off.is_valid())) { node.set_attribute(CHECK_ATTR,L"mixed"); if(prev != L"mixed") node.update(); } else if( on.is_valid() ) { node.set_attribute(CHECK_ATTR,L"on"); if(prev != L"on") node.update(); } else { node.set_attribute(CHECK_ATTR,L"off"); if(prev != L"off") node.update(); } }
NODE_STATE init_options(dom::element n) { //NODE_STATE n_state = NODE_MIXED; int n_off = 0; int n_on = 0; int n_total = 0; for(int i = 0; i < int(n.children_count()); ++i) { dom::element t = n.child(i); NODE_STATE t_state; if( streq(t.get_element_type(),"options") ) t_state = init_options(t); else if( streq(t.get_element_type(),"option") ) t_state = get_state(t); else continue; set_state(t, t_state); switch( t_state ) { case NODE_OFF: ++n_off; break; case NODE_ON: ++n_on; break; } ++n_total; } if( n_off == n_total ) return NODE_OFF; if( n_on == n_total ) return NODE_ON; return NODE_MIXED; }
void checkall (dom::element& table, bool onOff ) { if( !is_multiple(table) ) return; struct unchecker_cb: dom::callback { bool on_element(HELEMENT he) { htmlayout::dom::element el = he; if( el.get_state(STATE_CHECKED)) el.set_state(0,STATE_CHECKED,false ); return false; /*continue enumeration*/ } }; struct checker_cb: dom::callback { bool on_element(HELEMENT he) { htmlayout::dom::element el = he; if( !el.get_state(STATE_CHECKED)) el.set_state(STATE_CHECKED,0,false ); return false; /*continue enumeration*/ } }; if(onOff) { checker_cb checker; table.find_all(&checker,"tr"); } else { unchecker_cb unchecker; table.find_all(&unchecker,"tr:checked"); } }
void set_anchor (dom::element& table,const int idx) { dom::element row = table.find_first("tr:anchor"); if( row.is_valid() ) row.set_state( 0,STATE_ANCHOR,false); row = table.child(idx); if( row.is_valid() ) row.set_state( STATE_ANCHOR | STATE_CHECKED,0,false); }
bool belongs_to( dom::element parent, dom::element child ) { if( !child.is_valid()) return false; if( parent == child ) return true; return belongs_to( parent, child.parent() ); }
void sort_rows( dom::element& table, int column_no ) { row_sorter rs( column_no ); int fr = fixed_rows( table ); table.sort(rs,fr); table.update(true); }
NODE_STATE get_state(dom::element item) { if(wcseq(item.get_attribute("check"),L"on")) return NODE_ON; else if(wcseq(item.get_attribute("check"),L"mixed")) return NODE_MIXED; else return NODE_OFF; }
void set_state(dom::element item, NODE_STATE st) { switch( st ) { case NODE_ON: item.set_attribute("check",L"on"); break; case NODE_MIXED: item.set_attribute("check",L"mixed"); break; case NODE_OFF: item.set_attribute("check",L"off"); break; } }
// selects all options in multiselect. inline void select_all_options(dom::element& select_el ) { selected_cb all_options; select_el.find_all(&all_options, "option"); // select all currently selected <option>s for( int n = int(all_options.elements.size()) - 1; n >= 0 ; --n ) all_options.elements[n].set_state(STATE_CHECKED,0, false); // set state select_el.update(); }
// returns bit mask - checkboxes set inline value_t get_checkbox_bits(dom::element& el ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all checkboxes in the group belong to the same parent! r.find_all(&selected, "[type='checkbox'][name='%S']", el.get_attribute("name")); int m = 1, v = 0; for( unsigned int n = 0; n < selected.elements.size(); ++n, m <<= 1 ) if ( selected.elements[n].get_state(STATE_CHECKED) ) v |= m; return selected.elements.size()==1?value_t(v==1):value_t(v); // for alone checkbox we return true/false }
inline value_t get_radio_index( dom::element& el ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); for( unsigned int n = 0; n < selected.elements.size(); ++n ) if ( selected.elements[n].get_state(STATE_CHECKED) ) return value_t(int(n)); return value_t(); }
// clear checked states in multiselect <select>. // this simply resets :checked state for all checked <option>'s inline void clear_all_options(dom::element& select_el ) { selected_cb selected; select_el.find_all(&selected, "option:checked,[role='option']:checked"); // select all currently selected <option>s for( int n = int(selected.elements.size()) - 1; n >= 0 ; --n ) selected.elements[n].set_state(0, STATE_CHECKED, false); // reset state select_el.update(); }
bool is_fullpath(dom::element &el) { BOOL bFullPath = (el.get_attribute("fullpath") != NULL); if ( !bFullPath ) { dom::element ep = el.parent(); bFullPath = (ep.is_valid())?(ep.get_attribute("fullpath") != NULL):FALSE; } return bFullPath; }
/** returns current row (if any) **/ dom::element get_current_row( dom::element& table ) { for( int i = table.children_count() - 1; i >= 0 ; --i) { dom::element t = table.child((unsigned int)i); if( t.get_state(STATE_CURRENT)) return t; } return dom::element(); // empty }
void set_checked_row( dom::element& table, dom::element& row, bool toggle = false ) { if(toggle) { if( row.get_state( STATE_CHECKED) ) row.set_state( 0,STATE_CHECKED,false); else row.set_state( STATE_CHECKED,0,false); } else row.set_state( STATE_CHECKED,0,false); }
/** set current row **/ void set_current_row( dom::element& table, dom::element& row, UINT keyboardStates, bool dblClick = false ) { // get previously selected row: dom::element prev = get_current_row( table ); if( prev.is_valid() ) { if( prev != row ) prev.set_state(0,STATE_CURRENT, false); // drop state flags } row.set_state(STATE_CURRENT); // set state flags row.scroll_to_view(); table.post_event( dblClick? TABLE_ROW_DBL_CLICK:TABLE_ROW_CLICK, row.index(), row); }
inline void set_radio_index( dom::element& el, const value_t& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); unsigned int idx = (unsigned int)t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n ) { dom::element& e = selected.elements[n]; if ( n == idx) e.set_state(STATE_CHECKED, 0); else e.set_state(0, STATE_CHECKED); } }
// sets checkboxes by bit mask inline void set_checkbox_bits(dom::element& el, const value_t& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all checkboxes in the group belong to the same parent! r.find_all(&selected, "[type='checkbox'][name='%S']", el.get_attribute("name")); int m = 1, v = selected.elements.size()==1?(t.get(false)?1:0):t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n, m <<= 1 ) { dom::element& e = selected.elements[n]; if( (v & m) != 0) e.set_state( STATE_CHECKED, 0 ) ; else e.set_state( 0, STATE_CHECKED ) ; } }
void notify(dom::element& el, NMHL_HYPERLINK::type code) { // send notification NMHL_HYPERLINK nm; memset(&nm,0,sizeof(nm)); HWND hwnd = el.get_element_hwnd(true); nm.hdr.code = HLN_HYPERLINK; nm.hdr.hwndFrom = hwnd; nm.hdr.idFrom = GetDlgCtrlID(hwnd); nm.action = code; nm.he = el; dom::element root = el.root(); const wchar_t *pHREF = el.get_attribute("href"); if(pHREF) { if(code == NMHL_HYPERLINK::CLICK && pHREF[0] == '#') // anchor name, this is a local hyperlink { if( pHREF+1 == 0 ) // href='#' case return; dom::element anchor_el = root.find_first("[id='%S'],[name='%S']",pHREF+1,pHREF+1); //find_element_by_name(el.root_element(hwnd), pHREF + 1); if(anchor_el.is_valid()) // found { anchor_el.scroll_to_view(true /* scroll it to top of the view */); return; // shall host be notified about this? } } wcsncpy(nm.szHREF,pHREF,MAX_URL_LENGTH); el.combine_url(nm.szHREF,MAX_URL_LENGTH); } const wchar_t *pszTarget = el.get_attribute("target"); if(pszTarget) { if(code == NMHL_HYPERLINK::CLICK && try_to_load( root, nm.szHREF, pszTarget )) return; wcsncpy(nm.szTarget,pszTarget,MAX_URL_LENGTH); } ::SendMessage(hwnd,WM_BEHAVIOR_NOTIFY,HLN_HYPERLINK,LPARAM(&nm)); }
static BOOL process_key( dom::element& container, const char* keyname ) { // find all callback struct:public htmlayout::dom::callback { htmlayout::dom::element hot_key_element; virtual bool on_element(HELEMENT he) { htmlayout::dom::element t = he; if( t.enabled() && t.visible()) { hot_key_element = t; return true; // found, stop; } return false; } } cb; //Original version was: // container.find_all(&cb, "[accesskey=='%s']", keyname); //By request of Christopher Brown, the Great, from Symantec this became as: container.find_all(&cb, "[accesskey=='%s'],[accesskey-alt=='%s']", keyname, keyname); if( cb.hot_key_element.is_valid()) { METHOD_PARAMS prm; prm.methodID = DO_CLICK; if(cb.hot_key_element.call_behavior_method(&prm)) return true; } return false; }
/** Get values of all "controls" contained inside the DOM element. * Function will gather values of elements having name attribute defined * and recognized by get_ctl_type() function. * \param[in] el \b dom::element&, The element. * \param[out] all \b named_values&, Collection. * \return \b bool, \c true if there are any value was harvested. **/ inline bool get_values(const dom::element& el, named_values& all ) { selected_cb selected; el.find_all(&selected, "[name]" ); // select all elements having name attribute for( unsigned int n = 0; n < selected.elements.size(); ++n ) { const dom::element& t = selected.elements[n]; //if( !t.get_style_attribute("behavior") ) // continue; - commented out to support input type="hidden" that does not have behavior assigned const wchar_t* pn = t.get_attribute("name"); if( !pn ) { assert(false); // how come? continue; } std::wstring name = pn; if( all.find(name) != all.end()) continue; // element with this name is already there, // checkboxes and radios are groups in fact, // we are returning here only cumulative group value int ctl_type = get_ctl_type(t); if( ctl_type == CTL_NO/*|| ctl_type == CTL_BUTTON*/) continue; all[name] = get_value(selected.elements[n]); } return all.size() != 0; }
// set current item virtual void set_current_item( const dom::element& ctl, dom::element& item ) { // get previously selected item: dom::element prev_current = ctl.find_first(":current"); dom::element prev = ctl.find_first(":expanded"); if(prev_current != item && prev_current.is_valid()) prev_current.set_state(0, STATE_CURRENT); if( prev.is_valid() ) { if( prev == item ) return; // already here, nothing to do. prev.set_state(0,STATE_CURRENT | STATE_EXPANDED); // drop state flags } item.set_state(STATE_CURRENT | STATE_EXPANDED); // set state flags }
inline void set_radio_index( dom::element& el, const json::value& t ) { selected_cb selected; dom::element r = el.parent(); // ATTN: I assume here that all radios in the group belong to the same parent! r.find_all(&selected, "[type='radio'][name='%S']", el.get_attribute("name")); unsigned int idx = (unsigned int)t.get(0); for( unsigned int n = 0; n < selected.elements.size(); ++n ) { dom::element& e = selected.elements[n]; if ( n == idx) { e.set_value(json::value(true)); break; } } }
dom::element target_item(const dom::element& ctl, dom::element target) { if( target == ctl ) return dom::element(); if( !target.is_valid() ) return target; dom::element target_parent = target.parent(); if( !target_parent.is_valid() ) return target; if( target.test("li > .caption") ) return target_parent; // only if click on "caption" element of <li>. Returns that <li> element. return target_item( ctl, target.parent() ); }
// select next/prev/first/last tab bool select_tab( dom::element& tabs_el, dom::element& tab_el, int direction ) { // find new tab dom::element new_tab_el(0); switch( direction ) { case -2: new_tab_el = tabs_el.first_sibling(); while( new_tab_el.is_valid() ) { if( !new_tab_el.get_state(STATE_DISABLED) ) break; new_tab_el = new_tab_el.next_sibling(); } break; case -1: new_tab_el = tab_el.prev_sibling(); while( new_tab_el.is_valid() ) { if( !new_tab_el.get_state(STATE_DISABLED) ) break; new_tab_el = new_tab_el.prev_sibling(); } break; case +1: new_tab_el = tab_el.next_sibling(); while( new_tab_el.is_valid() ) { if( !new_tab_el.get_state(STATE_DISABLED) ) break; new_tab_el = new_tab_el.next_sibling(); } break; case +2: new_tab_el = tab_el.last_sibling(); while( new_tab_el.is_valid() ) { if( !new_tab_el.get_state(STATE_DISABLED) ) break; new_tab_el = new_tab_el.prev_sibling(); } break; default: assert(false); return false; } if( !new_tab_el.is_valid() || new_tab_el.get_attribute("panel") == 0 ) //is not a tab element return FALSE; return select_tab( tabs_el, new_tab_el ); }
// select bool select_tab( dom::element& tabs_el, dom::element& tab_el ) { if(tab_el.get_state(STATE_CURRENT)) // already selected, nothing to do... return true; // but we've handled it. //find currently selected element (tab and panel) and remove "selected" from them dom::element prev_panel_el = tabs_el.find_first(":root>[name]:expanded"); dom::element prev_tab_el = tabs_el.find_first(":root>.strip>[panel]:current"); // find new tab and panel const wchar_t* pname = tab_el.get_attribute("panel"); dom::element panel_el = tabs_el.find_first(":root>[name=\"%S\"]", pname); if( !panel_el.is_valid() || !tab_el.is_valid() ) { assert(false); // panel="somename" without matching name="somename" return true; } if( prev_panel_el.is_valid() ) { prev_panel_el.set_attribute("selected", 0); // remove selected attribute - just in case somone is using attribute selectors prev_panel_el.set_state(STATE_COLLAPSED,0); // set collapsed in case of someone use it for styling } if( prev_tab_el.is_valid() ) { prev_tab_el.set_attribute("selected", 0); // remove selected attribute prev_tab_el.set_state(0,STATE_CURRENT); // reset also state flag, :current } panel_el.set_attribute("selected", L""); // set selected attribute (empty) panel_el.set_state(STATE_EXPANDED,0); // expand it tab_el.set_attribute("selected", L""); // set selected attribute (empty) tab_el.set_state(STATE_CURRENT,0); // set also state flag, :current // notify all parties involved if (prev_tab_el.is_valid()) { prev_tab_el.post_event(ELEMENT_COLLAPSED,0, prev_tab_el); // source here is old collapsed tab itself } tab_el.post_event(ELEMENT_EXPANDED,0, tab_el); // source here is new expanded tab itself // NOTE #1: these event will bubble from panel elements up to the root so panel itself, tabs ctl, its parent, etc. // will receive these notifications. Handle them if you need to change UI dependent from current tab. // NOTE #2: while handling this event in: // virtual BOOL on_event (HELEMENT he, HELEMENT target, BEHAVIOR_EVENTS type, UINT reason ), // HELEMENT target is the panel element being collapsed/expanded return true; }
/** set current row **/ void set_current_row( dom::element& table, dom::element& row, UINT keyboardStates, bool dblClick = false ) { if(is_multiple(table)) { if (keyboardStates & SHIFT_KEY_PRESSED) { checkall(table, false); check_range(table,row.index(),TRUE); // from current to new } else { if (keyboardStates & CONTROL_KEY_PRESSED) set_checked_row (table,row, true); // toggle else checkall(table, false); set_anchor(table,row.index ()); } } // get previously selected row: dom::element prev = get_current_row( table ); if( prev.is_valid() ) { if( prev != row ) prev.set_state(0,STATE_CURRENT, false); // drop state flags } row.set_state(STATE_CURRENT); // set state flags row.scroll_to_view(); ::UpdateWindow(row.get_element_hwnd(false)); table.post_event( dblClick? TABLE_ROW_DBL_CLICK:TABLE_ROW_CLICK, row.index(), row); }
virtual void on_column_click( dom::element& table, dom::element& header_cell ) { super::on_column_click( table, header_cell ); dom::element current = table.find_first("th:checked"); if( current == header_cell ) return; // already here, nothing to do. if( current.is_valid() ) current.set_state(0, STATE_CHECKED); header_cell.set_state(STATE_CHECKED); dom::element ctr = get_current_row( table ); sort_rows( table, header_cell.index() ); if( ctr.is_valid() ) ctr.scroll_to_view(); }
bool do_horizontal(UINT event_type, POINT pt, dom::element &splitter_el, dom::element &first, dom::element &second, dom::element &parent_el) { // which element width we will change? RECT rc_parent = parent_el.get_location(); RECT rc = first.get_location(); // if width of first element is less than half of parent we // will change its width. bool change_first = (rc.right - rc.left) < (rc_parent.right - rc_parent.left)/2; if(!change_first) rc = second.get_location(); if(event_type == MOUSE_DOWN) { pressed_offset = pt.x; splitter_el.set_capture(); return false; // don't need updates } // mouse move handling if(pt.x == pressed_offset) return false; // don't need updates int width = rc.right - rc.left; wchar_t buf[32]; if(change_first) { width += (pt.x - pressed_offset); if(width >= 0) { swprintf(buf,L"%dpx", width); first.set_style_attribute("width",buf); second.set_style_attribute("width",L"100%%"); } } else { width -= (pt.x - pressed_offset); if(width >= 0) { swprintf(buf,L"%dpx", width); first.set_style_attribute("width",L"100%%"); second.set_style_attribute("width",buf); } } return true; // need update }