/** 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();
  }
示例#14
0
 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);
	  }
示例#17
0
 /** 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;
  }
示例#23
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;
     }
   }
 }
示例#25
0
    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() );
    }
示例#26
0
    // 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 );
    }
示例#27
0
    // 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
    }