///////////////////////////////////////////////////////////////////////////// // Name: propgrid.cpp // Purpose: wxPropertyGrid // Author: Jaakko Salli // Modified by: // Created: Sep-25-2004 // RCS-ID: $Id: // Copyright: (c) Jaakko Salli // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx/wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include "wx/defs.h" #include "wx/object.h" #include "wx/hash.h" #include "wx/string.h" #include "wx/log.h" #include "wx/event.h" #include "wx/window.h" #include "wx/panel.h" #include "wx/dc.h" #include "wx/dcmemory.h" #include "wx/button.h" #include "wx/pen.h" #include "wx/brush.h" #include "wx/cursor.h" #include "wx/dialog.h" #include "wx/settings.h" #include "wx/msgdlg.h" #include "wx/choice.h" #include "wx/stattext.h" #include "wx/scrolwin.h" #include "wx/dirdlg.h" #include "wx/layout.h" #include "wx/sizer.h" #include "wx/textdlg.h" #include "wx/filedlg.h" #include "wx/statusbr.h" #include "wx/intl.h" #include "wx/frame.h" #endif // This define is necessary to prevent macro clearing #define __wxPG_SOURCE_FILE__ #include #include #include #ifdef __WXPYTHON__ #include #include #endif #if wxPG_USE_RENDERER_NATIVE #include #endif #if wxPG_USING_WXOWNERDRAWNCOMBOBOX #include #else #include #endif #include "wx/timer.h" #include "wx/dcbuffer.h" #include #include #ifdef __WXMSW__ #include #endif // Two pics for the expand / collapse buttons. // Files are not supplied with this project (since it is // recommended to use either custom or native rendering). // If you want them, get wxTreeMultiCtrl by Jorgen Bodde, // and copy xpm files from archive to wxPropertyGrid src directory // (and also comment/undef wxPG_ICON_WIDTH in propGrid.h // and set wxPG_USE_RENDERER_NATIVE to 0). #ifndef wxPG_ICON_WIDTH #if defined(__WXMAC__) #include "mac_collapse.xpm" #include "mac_expand.xpm" #elif defined(__WXGTK__) #include "linux_collapse.xpm" #include "linux_expand.xpm" #else #include "default_collapse.xpm" #include "default_expand.xpm" #endif #endif //#define wxPG_TEXT_INDENT 4 // For the wxComboControl #define wxPG_ALLOW_CLIPPING 1 // If 1, GetUpdateRegion() in OnPaint event handler is not ignored #define wxPG_GUTTER_DIV 3 // gutter is max(iconwidth/gutter_div,gutter_min) #define wxPG_GUTTER_MIN 3 // gutter before and after image of [+] or [-] #define wxPG_YSPACING_MIN 1 #define wxPG_DEFAULT_VSPACING 2 // This matches .NET propertygrid's value, // but causes normal combobox to spill out under MSW #define wxPG_OPTIMAL_WIDTH 200 // Arbitrary #define wxPG_CAPRECTXMARGIN 2 // space between caption and selection rectangle, #define wxPG_CAPRECTYMARGIN 1 // horizontally and vertically #define PWC_CHILD_SUMMARY_LIMIT 16 // Maximum number of children summarized in a parent property's // value field. #define PWC_CHILD_SUMMARY_CHAR_LIMIT 64 // Character limit of summary field when not editing #define wxPG_MIN_SCROLLBAR_WIDTH 10 // Smallest scrollbar width on any platform // Must be larger than largest control border // width * 2. #define wxPG_DEFAULT_CURSOR wxNullCursor // // Here are some extra platform dependent defines. // #if defined(__WXMSW__) // tested #define wxPG_DEFAULT_SPLITTERX 110 // default splitter position #define wxPG_NO_CHILD_EVT_MOTION 0 // 1 if splitter drag detect margin and control cannot overlap #define wxPG_CUSTOM_IMAGE_WIDTH 20 // for wxColourProperty etc. #define wxPG_ALLOW_EMPTY_TOOLTIPS 1 // If 1, then setting empty tooltip actually hides it #define wxPG_NAT_BUTTON_BORDER_ANY 1 #define wxPG_NAT_BUTTON_BORDER_X 1 #define wxPG_NAT_BUTTON_BORDER_Y 1 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 0 // If 1 then controls are refreshed after selected was drawn. #elif defined(__WXGTK__) // tested #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_NO_CHILD_EVT_MOTION 1 // 1 if splitter drag detect margin and control cannot overlap #define wxPG_CUSTOM_IMAGE_WIDTH 20 // for wxColourProperty etc. #define wxPG_ALLOW_EMPTY_TOOLTIPS 0 // If 1, then setting empty tooltip actually hides it #define wxPG_NAT_BUTTON_BORDER_ANY 1 #define wxPG_NAT_BUTTON_BORDER_X 1 #define wxPG_NAT_BUTTON_BORDER_Y 1 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 1 // If 1 then controls are refreshed after selected was drawn. #elif defined(__WXMAC__) // *not* tested #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_NO_CHILD_EVT_MOTION 0 // 1 if splitter drag detect margin and control cannot overlap #define wxPG_CUSTOM_IMAGE_WIDTH 20 // for wxColourProperty etc. #define wxPG_ALLOW_EMPTY_TOOLTIPS 1 // If 1, then setting empty tooltip actually hides it #define wxPG_NAT_BUTTON_BORDER_ANY 0 #define wxPG_NAT_BUTTON_BORDER_X 0 #define wxPG_NAT_BUTTON_BORDER_Y 0 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 0 // If 1 then controls are refreshed after selected was drawn. #else // defaults #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_NO_CHILD_EVT_MOTION 1 // 1 if splitter drag detect margin and control cannot overlap #define wxPG_CUSTOM_IMAGE_WIDTH 20 // for wxColourProperty etc. #define wxPG_ALLOW_EMPTY_TOOLTIPS 0 // If 1, then setting empty tooltip actually hides it #define wxPG_NAT_BUTTON_BORDER_ANY 0 #define wxPG_NAT_BUTTON_BORDER_X 0 #define wxPG_NAT_BUTTON_BORDER_Y 0 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 1 // If 1 then controls are refreshed after selected was drawn. #endif #if wxPG_NO_CHILD_EVT_MOTION #define wxPG_SPLITTERX_DETECTMARGIN1 3 // this much on left #define wxPG_SPLITTERX_DETECTMARGIN2 2 // this much on right #else #define wxPG_SPLITTERX_DETECTMARGIN1 3 // this much on left #define wxPG_SPLITTERX_DETECTMARGIN2 2 // this much on right #endif //#define wxPG_NAT_CHOICE_BORDER_ANY 0 #define wxPG_DRAG_MARGIN 30 #define wxPG_CUSTOM_IMAGE_SPACINGY 1 // space between vertical sides of a custom image #define wxPG_HIDER_BUTTON_HEIGHT 25 #define wxPG_PIXELS_PER_UNIT m_lineHeight #define DEFAULT_IMAGE_OFFSET_INCREMENT \ (wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2) #ifdef wxPG_ICON_WIDTH #define m_iconHeight m_iconWidth #endif #define wxPG_TOOLTIP_DELAY 1000 // This is the number of pixels the expander button inside // property cells (i.e. not in the grey margin area are // adjusted. #define IN_CELL_EXPANDER_BUTTON_X_ADJUST 2 #if wxMINOR_VERSION < 7 || ( wxMINOR_VERSION == 7 && wxRELEASE_NUMBER < 1 ) // NOTE: Since wxWidgets at this point doesn't have wxArrayDouble, we have // to create it ourself, using wxObjArray model. #include WX_DEFINE_OBJARRAY(wxArrayDouble); #endif // ----------------------------------------------------------------------- #if wxUSE_INTL void wxPropertyGrid::AutoGetTranslation ( bool enable ) { wxPGGlobalVars->m_autoGetTranslation = enable; } #else void wxPropertyGrid::AutoGetTranslation ( bool ) { } #endif // ----------------------------------------------------------------------- #if !wxCHECK_VERSION(2, 7, 1) #if defined(__WXMSW__) #ifndef WS_EX_COMPOSITED #define WS_EX_COMPOSITED 0x02000000L #endif static bool wxPGIsWindowBuffered( const wxWindow* wnd ) { while ( wnd ) { if ( GetWindowLong((HWND)wnd->GetHWND(), GWL_EXSTYLE) & WS_EX_COMPOSITED ) return true; if ( wnd->IsTopLevel() ) break; wnd = wnd->GetParent(); } return false; } #elif defined(__WXGTK20__) #include static bool wxPGIsWindowBuffered( const wxWindow* wnd ) { return GTK_WIDGET_DOUBLE_BUFFERED(wnd->GetHandle()); } #elif defined(__WXMAC_OSX__) || defined(__WXCOCOA__) || defined(__WXDFB__) static bool wxPGIsWindowBuffered( const wxWindow* WXUNUSED(wnd) ) { return true; } #else static bool wxPGIsWindowBuffered( const wxWindow* WXUNUSED(wnd) ) { return false; } #endif #else static bool wxPGIsWindowBuffered( const wxWindow* wnd ) { return wnd->IsDoubleBuffered(); } #endif // ----------------------------------------------------------------------- const wxChar *wxPropertyGridNameStr = wxT("wxPropertyGrid"); const wxChar *wxPGTypeName_long = wxT("long"); const wxChar *wxPGTypeName_bool = wxT("bool"); const wxChar *wxPGTypeName_double = wxT("double"); const wxChar *wxPGTypeName_wxString = wxT("string"); const wxChar *wxPGTypeName_void = wxT("void*"); const wxChar *wxPGTypeName_wxArrayString = wxT("arrstring"); #ifdef __WXPYTHON__ const wxChar *wxPGTypeName_PyObject = wxT("PyObject"); #endif static const wxChar* gs_noCellText = wxT("@!"); // ----------------------------------------------------------------------- static void wxPGDrawFocusRect( wxDC& dc, const wxRect& rect ) { #if defined(__WXMSW__) && !defined(__WXWINCE__) // FIXME: Use DrawFocusRect code above (currently it draws solid line // for caption focus but works ok for other stuff). // Also, it seems that this code may not work in future wx versions. dc.SetLogicalFunction(wxINVERT); wxPen pen(*wxBLACK,1,wxDOT); pen.SetCap(wxCAP_BUTT); dc.SetPen(pen); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(rect); dc.SetLogicalFunction(wxCOPY); #else dc.SetLogicalFunction(wxINVERT); dc.SetPen(wxPen(*wxBLACK,1,wxDOT)); dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(rect); dc.SetLogicalFunction(wxCOPY); #endif } // ----------------------------------------------------------------------- #if !wxCHECK_VERSION(2,8,0) // There is no wxString::EndsWith() in wxWidgets 2.6. // check that the string ends with suffix and return the rest of it in the // provided pointer if it is not NULL, otherwise return false bool wxPG_String_EndsWith(const wxString& str, const wxChar *suffix, wxString *rest) { wxASSERT_MSG( suffix, wxT("invalid parameter in wxString::EndssWith") ); int start = str.length() - wxStrlen(suffix); if ( start < 0 || wxStrcmp(str.c_str() + start, suffix) != 0 ) return false; if ( rest ) { // put the rest of the string into provided pointer rest->assign(str, 0, start); } return true; } #endif #if !wxCHECK_VERSION(2,9,0) // // wxSplit (and maybe wxJoin) is needed for editable state code // #include "wx/tokenzr.h" static wxArrayString wxSplit(const wxString& str, const wxChar sep, const wxChar escape) { if ( escape == wxT('\0') ) { // simple case: we don't need to honour the escape character return wxStringTokenize(str, sep, wxTOKEN_RET_EMPTY_ALL); } wxArrayString ret; wxString curr; wxChar prev = wxT('\0'); for ( wxString::const_iterator i = str.begin(), end = str.end(); i != end; ++i ) { const wxChar ch = *i; if ( ch == sep ) { if ( prev == escape ) { // remove the escape character and don't consider this // occurrence of 'sep' as a real separator *curr.rbegin() = sep; } else // real separator { ret.push_back(curr); curr.clear(); } } else // normal character { curr += ch; } prev = ch; } // add the last token if ( !curr.empty() || prev == sep ) ret.Add(curr); return ret; } #endif // !wxCHECK_VERSION(2,9,0) // ----------------------------------------------------------------------- // Choice related methods from various classes // ----------------------------------------------------------------------- void wxPropertyGridInterface::AddPropertyChoice( wxPGPropArg id, const wxString& label, int value ) { wxPG_PROP_ARG_CALL_PROLOG() p->InsertChoice(label,-1,value); } void wxPropertyGridInterface::InsertPropertyChoice( wxPGPropArg id, const wxString& label, int index, int value ) { wxPG_PROP_ARG_CALL_PROLOG() p->InsertChoice(label,index,value); } void wxPropertyGridInterface::DeletePropertyChoice( wxPGPropArg id, int index ) { wxPG_PROP_ARG_CALL_PROLOG() p->DeleteChoice(index); } // ----------------------------------------------------------------------- // Statics in one class for easy destruction. // NB: We prefer to use wxModule, as it offers more consistent behavior // across platforms. However, for those rare problem situations, we // also need to offer option to use simpler approach. // ----------------------------------------------------------------------- #ifndef wxPG_USE_WXMODULE #define wxPG_USE_WXMODULE 1 #endif #if wxPG_USE_WXMODULE #include class wxPGGlobalVarsClassManager : public wxModule { DECLARE_DYNAMIC_CLASS(wxPGGlobalVarsClassManager) public: wxPGGlobalVarsClassManager() {} virtual bool OnInit() { wxPGGlobalVars = new wxPGGlobalVarsClass(); return true; } virtual void OnExit() { delete wxPGGlobalVars; wxPGGlobalVars = NULL; } }; IMPLEMENT_DYNAMIC_CLASS(wxPGGlobalVarsClassManager, wxModule) // When wxPG is loaded dynamically after the application is already running // then the built-in module system won't pick this one up. Add it manually. void wxPGInitResourceModule() { wxModule* module = new wxPGGlobalVarsClassManager; module->Init(); wxModule::RegisterModule(module); } #else // !wxPG_USE_WXMODULE class wxPGGlobalVarsClassManager { public: wxPGGlobalVarsClassManager() {} ~wxPGGlobalVarsClassManager() { delete wxPGGlobalVars; } }; static wxPGGlobalVarsClassManager gs_pgGlobalVarsClassManager; #endif wxPGGlobalVarsClass* wxPGGlobalVars = (wxPGGlobalVarsClass*) NULL; wxPGGlobalVarsClass::wxPGGlobalVarsClass() { m_boolChoices.Add(_("False")); m_boolChoices.Add(_("True")); m_fontFamilyChoices = (wxPGChoices*) NULL; m_defaultRenderer = new wxPGDefaultRenderer(); m_autoGetTranslation = false; m_offline = 0; m_extraStyle = 0; wxVariant v; v = (long)0; wxVariantClassInfo_long = wxPGVariantDataGetClassInfo(v.GetData()); v = wxString(); wxVariantClassInfo_string = wxPGVariantDataGetClassInfo(v.GetData()); v = (double)0.0; wxVariantClassInfo_double = wxPGVariantDataGetClassInfo(v.GetData()); v = (bool)false; wxVariantClassInfo_bool = wxPGVariantDataGetClassInfo(v.GetData()); v = wxArrayString(); wxVariantClassInfo_arrstring = wxPGVariantDataGetClassInfo(v.GetData()); wxColour col; wxVariant v2((wxObject*)&col); wxVariantClassInfo_wxobject = wxPGVariantDataGetClassInfo(v2.GetData()); wxVariantList list; v = wxVariant(list); wxVariantClassInfo_list = wxPGVariantDataGetClassInfo(v.GetData()); #if wxCHECK_VERSION(2,8,0) v << *wxRED; wxVariantClassInfo_wxColour = wxPGVariantDataGetClassInfo(v.GetData()); #endif #if wxUSE_DATETIME v = wxVariant(wxDateTime::Now()); wxVariantClassInfo_datetime = wxPGVariantDataGetClassInfo(v.GetData()); #endif // Prepare some shared variants m_vEmptyString = wxString(); m_vZero = (long) 0; m_vMinusOne = (long) -1; m_vTrue = true; m_vFalse = false; // Prepare cached string constants m_strDefaultValue = wxT("DefaultValue"); m_strMin = wxT("Min"); m_strMax = wxT("Max"); m_strUnits = wxT("Units"); m_strInlineHelp = wxT("InlineHelp"); #ifdef __WXDEBUG__ m_warnings = 0; #endif } wxPGGlobalVarsClass::~wxPGGlobalVarsClass() { size_t i; delete m_defaultRenderer; // This will always have one ref delete m_fontFamilyChoices; #if wxUSE_VALIDATORS for ( i=0; isecond); } } // ----------------------------------------------------------------------- // wxPGProperty // ----------------------------------------------------------------------- IMPLEMENT_ABSTRACT_CLASS(wxPGProperty, wxObject) void wxPGProperty::Init() { #ifdef __WXPYTHON__ m_scriptObject = NULL; m_OORClientData = NULL; #endif m_commonValue = -1; m_arrIndex = 0xFFFF; m_parent = NULL; m_parentState = (wxPropertyGridState*) NULL; m_clientData = NULL; m_customEditor = (wxPGEditor*) NULL; #if wxUSE_VALIDATORS m_validator = (wxValidator*) NULL; #endif m_valueBitmap = (wxBitmap*) NULL; m_maxLen = 0; // infinite maximum length m_flags = wxPG_PROP_PROPERTY; m_depth = 1; m_bgColIndex = 0; m_fgColIndex = 0; SetExpanded(true); } void wxPGProperty::Init( const wxString& label, const wxString& name ) { if ( &label != ((wxString*)NULL) ) m_label = label; #ifndef __WXPYTHON__ if ( &name != ((wxString*)NULL) ) #else if ( (&name != ((wxString*)NULL)) && name != wxT("_LABEL_AS_NAME") ) #endif DoSetName( name ); else DoSetName( m_label ); Init(); } wxPGProperty::wxPGProperty() : wxObject() { Init(); } wxPGProperty::wxPGProperty( const wxString& label, const wxString& name ) : wxObject() { Init( label, name ); } wxPGProperty::~wxPGProperty() { #ifdef __WXPYTHON__ delete m_OORClientData; if ( m_clientData ) Py_DECREF( m_clientData ); #endif DoEmpty(); // this deletes items delete m_valueBitmap; #if wxUSE_VALIDATORS delete m_validator; #endif unsigned int i; for ( i=0; im_parent; } while ( parent ); return false; } void wxPGProperty::SetName( const wxString& newName ) { wxPropertyGrid* pg = GetGrid(); if ( pg ) pg->SetPropertyName(this, newName); else DoSetName(newName); } wxString wxPGProperty::GetName() const { wxPGProperty* parent = GetParent(); if ( !m_name.length() || !parent || parent->IsCategory() || parent->IsRoot() ) return m_name; return m_parent->GetName() + wxT(".") + m_name; } wxPropertyGrid* wxPGProperty::GetGrid() const { if ( !m_parentState ) return NULL; return m_parentState->GetGrid(); } bool wxPGProperty::ValidateValue( wxVariant& WXUNUSED(value), wxPGValidationInfo& WXUNUSED(validationInfo) ) const { return true; } void wxPGProperty::OnSetValue() { } void wxPGProperty::RefreshChildren () { } void wxPGProperty::OnValidationFailure( wxVariant& WXUNUSED(pendingValue) ) { } wxString wxPGProperty::GetColumnText( unsigned int col ) const { wxPGCell* cell = GetCell(col); if ( cell ) { return cell->GetText(); } else { if ( col == 0 ) return GetLabel(); else if ( col == 1 ) return GetDisplayedString(); else if ( col == 2 ) return GetAttribute(wxPGGlobalVars->m_strUnits, wxEmptyString); } return wxEmptyString; } void wxPGProperty::GenerateComposedValue( wxString& text, int argFlags ) const { int i; int iMax = m_children.GetCount(); text.clear(); if ( iMax == 0 ) return; if ( iMax > PWC_CHILD_SUMMARY_LIMIT && !(argFlags & wxPG_FULL_VALUE) ) iMax = PWC_CHILD_SUMMARY_LIMIT; int iMaxMinusOne = iMax-1; if ( !IsTextEditable() ) argFlags |= wxPG_UNEDITABLE_COMPOSITE_FRAGMENT; wxPGProperty* curChild = (wxPGProperty*) m_children.Item(0); for ( i = 0; i < iMax; i++ ) { wxString s; if ( !curChild->IsValueUnspecified() ) s = curChild->GetValueString(argFlags|wxPG_COMPOSITE_FRAGMENT); bool skip = false; if ( (argFlags & wxPG_UNEDITABLE_COMPOSITE_FRAGMENT) && !s.length() ) skip = true; if ( !curChild->GetChildCount() || skip ) text += s; else text += wxT("[") + s + wxT("]"); if ( i < iMaxMinusOne ) { if ( text.length() > PWC_CHILD_SUMMARY_CHAR_LIMIT && !(argFlags & wxPG_EDITABLE_VALUE) && !(argFlags & wxPG_FULL_VALUE) ) break; if ( !skip ) { if ( !curChild->GetChildCount() ) text += wxT("; "); else text += wxT(" "); } curChild = (wxPGProperty*) m_children.Item(i+1); } } if ( (unsigned int)i < m_children.GetCount() ) { if ( !wxPG_String_EndsWith(text, wxT("; ")) ) text += wxT("; ..."); else text += wxT("..."); } } wxString wxPGProperty::GetValueAsString( int argFlags ) const { wxCHECK_MSG( GetCount() > 0, wxString(), wxT("If user property does not have any children, it must override GetValueAsString") ); wxString text; GenerateComposedValue(text, argFlags); return text; } wxString wxPGProperty::GetValueString( int argFlags ) const { wxPropertyGrid* pg = GetGrid(); if ( IsValueUnspecified() ) return pg->GetUnspecifiedValueText(argFlags); if ( m_commonValue == -1 ) return GetValueAsString(argFlags); // // Return common value's string representation const wxPGCommonValue* cv = pg->GetCommonValue(m_commonValue); if ( argFlags & wxPG_FULL_VALUE ) { return cv->GetLabel(); } else if ( argFlags & wxPG_EDITABLE_VALUE ) { return cv->GetEditableText(); } else { return cv->GetLabel(); } } bool wxPGProperty::IntToValue( wxVariant& variant, int number, int WXUNUSED(argFlags) ) const { variant = (long)number; return true; } // Convert semicolon delimited tokens into child values. bool wxPGProperty::StringToValue( wxVariant& variant, const wxString& text, int argFlags ) const { if ( !GetCount() ) return false; unsigned int curChild = 0; unsigned int iMax = m_children.GetCount(); if ( iMax > PWC_CHILD_SUMMARY_LIMIT && !(argFlags & wxPG_FULL_VALUE) ) iMax = PWC_CHILD_SUMMARY_LIMIT; bool changed = false; wxString token; size_t pos = 0; // Its best only to add non-empty group items bool addOnlyIfNotEmpty = false; const wxChar delimeter = wxT(';'); size_t tokenStart = 0xFFFFFF; wxVariantList temp_list; wxVariant list(temp_list); int propagatedFlags = argFlags & (wxPG_REPORT_ERROR|wxPG_PROGRAMMATIC_VALUE); #ifdef __WXDEBUG__ bool debug_print = false; #endif #ifdef __WXDEBUG__ if ( debug_print ) wxLogDebug(wxT(">> %s.StringToValue('%s')"),GetLabel().c_str(),text.c_str()); #endif wxString::const_iterator it = text.begin(); wxUniChar a; if ( it != text.end() ) a = *it; else a = 0; for ( ;; ) { // How many units we iterate string forward at the end of loop? // We need to keep track of this or risk going to negative // with it-- operation. unsigned int strPosIncrement = 1; if ( tokenStart != 0xFFFFFF ) { // Token is running if ( a == delimeter || a == 0 ) { token = text.substr(tokenStart,pos-tokenStart); token.Trim(true); size_t len = token.length(); if ( !addOnlyIfNotEmpty || len > 0 ) { const wxPGProperty* child = Item(curChild); wxVariant variant(child->GetValue()); wxString childName(child->GetBaseName()); #ifdef __WXDEBUG__ if ( debug_print ) wxLogDebug(wxT("token = '%s', child = %s"),token.c_str(),childName.c_str()); #endif // Add only if editable or setting programmatically if ( (argFlags & wxPG_PROGRAMMATIC_VALUE) || !child->HasFlag(wxPG_PROP_DISABLED|wxPG_PROP_READONLY) ) { if ( len > 0 ) { if ( child->ActualStringToValue(variant, token, propagatedFlags|wxPG_COMPOSITE_FRAGMENT) ) { // We really need to set the variant's name // *after* child->StringToValue() has been // called, since variant's value may be set by // assigning another variant into it, which // then usually causes name to be copied (ie. // usually cleared) as well. wxBoolProperty // being case in point with its use of // wxPGVariant_Bool macro as an optimization. variant.SetName(childName); list.Append(variant); changed = true; } } else { // Empty, becomes unspecified variant.MakeNull(); variant.SetName(childName); list.Append(variant); changed = true; } } curChild++; if ( curChild >= iMax ) break; } tokenStart = 0xFFFFFF; } } else { // Token is not running if ( a != wxT(' ') ) { addOnlyIfNotEmpty = false; // Is this a group of tokens? if ( a == wxT('[') ) { int depth = 1; if ( it != text.end() ) it++; pos++; size_t startPos = pos; // Group item - find end while ( it != text.end() && depth > 0 ) { a = *it; it++; pos++; if ( a == wxT(']') ) depth--; else if ( a == wxT('[') ) depth++; } token = text.substr(startPos,pos-startPos-1); if ( !token.length() ) break; const wxPGProperty* child = Item(curChild); // Add only if editable or setting programmatically if ( (argFlags & wxPG_PROGRAMMATIC_VALUE) || !child->HasFlag(wxPG_PROP_DISABLED|wxPG_PROP_READONLY) ) { wxVariant variant(child->GetValue()); if ( child->ActualStringToValue( variant, token, propagatedFlags ) ) { variant.SetName(child->GetBaseName()); list.Append(variant); changed = true; } else { // Failed, becomes unspecified variant.MakeNull(); list.Append(variant); changed = true; } } curChild++; if ( curChild >= iMax ) break; addOnlyIfNotEmpty = true; tokenStart = 0xFFFFFF; } else { tokenStart = pos; if ( a == delimeter ) strPosIncrement--; } } } if ( a == 0 ) break; it += strPosIncrement; if ( it != text.end() ) { a = *it; } else { a = 0; } pos += strPosIncrement; } if ( changed ) variant = list; return changed; } #ifdef __WXPYTHON__ wxPGVariantAndBool wxPGProperty::PyValidateValue( const wxVariant& value, wxPGValidationInfo& validationInfo ) const { wxPGVariantAndBool vab; vab.m_value = value; vab.m_valueValid = true; vab.m_result = ValidateValue(vab.m_value, validationInfo); return vab; } wxPGVariantAndBool wxPGProperty::PyStringToValue( const wxString& text, int argFlags ) const { wxPGVariantAndBool vab; vab.m_result = StringToValue(vab.m_value, text, argFlags); if ( vab.m_result ) vab.m_valueValid = true; return vab; } wxPGVariantAndBool wxPGProperty::PyIntToValue( int number, int argFlags ) const { wxPGVariantAndBool vab; vab.m_result = IntToValue(vab.m_value, number, argFlags); if ( vab.m_result ) vab.m_valueValid = true; return vab; } wxVariant wxPGProperty::PyChildChanged( wxVariant& thisValue, int WXUNUSED(childIndex), wxVariant& WXUNUSED(childValue) ) const { return thisValue; } #endif bool wxPGProperty::SetValueFromString( const wxString& text, int argFlags ) { #if wxPG_COMPATIBILITY_1_2_0 if ( argFlags == 0xFFFF ) { // Do not override! (for backwards compliancy) m_commonValue = -1234; return true; } #endif wxVariant variant(m_value); bool res = ActualStringToValue(variant, text, argFlags); if ( res ) SetValue(variant); return res; } bool wxPGProperty::SetValueFromInt( long number, int argFlags ) { #if wxPG_COMPATIBILITY_1_2_0 if ( argFlags == 0xFFFF ) { // Do not override! (for backwards compliancy) m_commonValue = -1234; return true; } #endif wxVariant variant(m_value); bool res = ActualIntToValue(variant, number, argFlags); if ( res ) SetValue(variant); return res; } wxSize wxPGProperty::OnMeasureImage( int WXUNUSED(item) ) const { if ( m_valueBitmap ) return wxSize(m_valueBitmap->GetWidth(),-1); return wxSize(0,0); } #if wxPG_COMPATIBILITY_1_2_0 wxSize wxPGProperty::GetImageSize() const { return wxSize(-1234,-1234); } #endif int wxPGProperty::GetImageOffset( int imageWidth ) const { int imageOffset = 0; if ( imageWidth ) { // Do not increment offset too much for wide images if ( imageWidth <= (wxPG_CUSTOM_IMAGE_WIDTH+5) ) imageOffset = imageWidth + DEFAULT_IMAGE_OFFSET_INCREMENT; else imageOffset = imageWidth + 1; } return imageOffset; } wxPGCellRenderer* wxPGProperty::GetCellRenderer( int WXUNUSED(column) ) const { return wxPGGlobalVars->m_defaultRenderer; } void wxPGProperty::OnCustomPaint( wxDC& dc, const wxRect& rect, wxPGPaintData& ) { wxBitmap* bmp = m_valueBitmap; wxCHECK_RET( bmp && bmp->Ok(), wxT("invalid bitmap") ); wxCHECK_RET( rect.x >= 0, wxT("unexpected measure call") ); dc.DrawBitmap(*bmp,rect.x,rect.y); } const wxPGEditor* wxPGProperty::DoGetEditorClass() const { return wxPG_EDITOR(TextCtrl); } #ifdef __WXPYTHON__ wxString wxPGProperty::GetEditor() const { return wxEmptyString; } #endif // Default extra property event handling - that is, none at all. bool wxPGProperty::OnEvent( wxPropertyGrid*, wxWindow*, wxEvent& ) { return false; } void wxPGProperty::SetValue( wxVariant value, wxVariant* pList, int flags ) { #ifdef __WXPYTHON__ // Translate Py_None to Null wxVariant PyObject* obj = PyObjectFromVariant(value); if ( obj ) { if ( obj == Py_None ) value.MakeNull(); Py_DECREF(obj); } #endif // If auto unspecified values are not wanted (via window or property style), // then get default value instead of wxNullVariant. if ( value.IsNull() && (flags & wxPG_SETVAL_BY_USER) && !UsesAutoUnspecified() ) { value = GetDefaultValue(); } if ( !value.IsNull() ) { wxVariant tempListVariant; SetCommonValue(-1); // List variants are reserved a special purpose // as intermediate containers for child values // of properties with children. if ( wxPGIsVariantType(value, list) ) { // // However, situation is different for composed string properties if ( HasFlag(wxPG_PROP_COMPOSED_VALUE) ) { tempListVariant = value; pList = &tempListVariant; } wxVariant newValue; AdaptListToValue(value, &newValue); value = newValue; //wxLogDebug(wxT(">> %s.SetValue() adapted list value to type '%s'"),GetName().c_str(),value.GetType().c_str()); } if ( HasFlag( wxPG_PROP_AGGREGATE) ) flags |= wxPG_SETVAL_AGGREGATED; if ( pList && !pList->IsNull() ) { wxASSERT( wxPGIsVariantType(*pList, list) ); wxASSERT( GetChildCount() ); wxASSERT( !IsCategory() ); wxVariantList& list = pList->GetList(); wxVariantList::iterator node; unsigned int i = 0; //wxLogDebug(wxT(">> %s.SetValue() pList parsing"),GetName().c_str()); // Children in list can be in any order, but we will give hint to // GetPropertyByNameWH(). This optimizes for full list parsing. for ( node = list.begin(); node != list.end(); node++ ) { wxVariant& childValue = *((wxVariant*)*node); wxPGProperty* child = GetPropertyByNameWH(childValue.GetName(), i); if ( child ) { //wxLogDebug(wxT("%i: child = %s, childValue.GetType()=%s, child.GetValue().GetType()=%s"),i,child->GetBaseName().c_str(),childValue.GetType().c_str(),child->GetValue().GetType().c_str()); if ( wxPGIsVariantType(childValue, list) ) { if ( child->HasFlag(wxPG_PROP_AGGREGATE) && !(flags & wxPG_SETVAL_AGGREGATED) ) { wxVariant listRefCopy = childValue; child->SetValue(childValue, &listRefCopy, flags|wxPG_SETVAL_FROM_PARENT); } else { wxVariant oldVal = child->GetValue(); child->SetValue(oldVal, &childValue, flags|wxPG_SETVAL_FROM_PARENT); } } else if ( !wxPG_VARIANT_EQ(child->GetValue(), childValue) ) { // For aggregate properties, we will trust RefreshChildren() // to update child values. if ( !IsFlagSet(wxPG_PROP_AGGREGATE) ) child->SetValue(childValue, NULL, flags|wxPG_SETVAL_FROM_PARENT); if ( flags & wxPG_SETVAL_BY_USER ) child->SetFlag(wxPG_PROP_MODIFIED); } } i++; } } if ( !value.IsNull() ) { wxPGVariantAssign(m_value, value); OnSetValue(); } if ( flags & wxPG_SETVAL_BY_USER ) SetFlag(wxPG_PROP_MODIFIED); if ( IsFlagSet(wxPG_PROP_AGGREGATE) ) RefreshChildren(); } else { if ( m_commonValue != -1 ) { wxPropertyGrid* pg = GetGrid(); if ( !pg || m_commonValue != pg->GetUnspecifiedCommonValue() ) SetCommonValue(-1); } m_value = value; // Set children to unspecified, but only if aggregate or // value is if ( AreChildrenComponents() ) { unsigned int i; for ( i=0; iSetValue(value, NULL, flags|wxPG_SETVAL_FROM_PARENT); } } if ( !(flags & wxPG_SETVAL_FROM_PARENT) ) UpdateParentValues(); // // Update editor control. if ( flags & wxPG_SETVAL_REFRESH_EDITOR ) { wxPropertyGrid* pg = GetGridIfDisplayed(); if ( pg ) { wxPGProperty* selected = pg->GetSelectedProperty(); // Only refresh the control if this was selected, or // this was some parent of selected, or vice versa) if ( selected && (selected == this || selected->IsSomeParent(this) || this->IsSomeParent(selected)) ) RefreshEditor(); pg->DrawItemAndValueRelated(this); } } } void wxPGProperty::SetValueInEvent( wxVariant value ) const { GetGrid()->ValueChangeInEvent(value); } void wxPGProperty::SetFlagRecursively( FlagType flag, bool set ) { ChangeFlag(flag, set); unsigned int i; for ( i = 0; i < GetChildCount(); i++ ) Item(i)->SetFlagRecursively(flag, set); } void wxPGProperty::RefreshEditor() { if ( !m_parent ) return; wxPropertyGrid* pg = GetGrid(); if ( pg && pg->GetSelectedProperty() == this ) pg->RefreshEditor(); } wxVariant wxPGProperty::GetDefaultValue() const { wxVariant defVal = GetAttribute(wxPG_ATTR_DEFAULT_VALUE); if ( !defVal.IsNull() ) return defVal; wxVariant value = GetValue(); if ( !value.IsNull() ) { wxPGVariantDataClassInfo classInfo = wxPGVariantDataGetClassInfo(value.GetData()); if ( wxPGIsVariantClassInfo(classInfo, long) ) return wxPGVariant_Zero; if ( wxPGIsVariantClassInfo(classInfo, string) ) return wxPGVariant_EmptyString; if ( wxPGIsVariantClassInfo(classInfo, bool) ) return wxPGVariant_False; if ( wxPGIsVariantClassInfo(classInfo, double) ) return wxVariant(0.0); wxPGVariantData* pgvdata = wxDynamicCastVariantData(m_value.GetData(), wxPGVariantData); if ( pgvdata ) return pgvdata->GetDefaultValue(); if ( wxPGIsVariantClassInfo(classInfo, arrstring) ) return wxVariant(wxArrayString()); #if wxCHECK_VERSION(2,8,0) if ( wxPGIsVariantClassInfo(classInfo, wxColour) ) return WXVARIANT(*wxRED); #endif #if wxUSE_DATETIME if ( wxPGIsVariantClassInfo(classInfo, datetime) ) return wxVariant(wxDateTime::Now()); #endif wxFAIL_MSG( wxString::Format(wxT("Inorder for value to have default value, it must be added to") wxT("wxPGProperty::GetDefaultValue or it's variantdata must inherit") wxT("from wxPGVariantData (unrecognized type was '%s')"),m_value.GetType().c_str()) ); } return wxVariant(); } wxPGProperty* wxPGProperty::InsertChild( int index, wxPGProperty* childProperty ) { if ( index < 0 ) index = m_children.size(); wxASSERT_MSG( m_parentState, wxT("Add property to a grid or page before ") wxT("adding children.") ); m_parentState->DoInsert(this, index, childProperty); return childProperty; } void wxPGProperty::SetCell( int column, wxPGCell* cellObj ) { if ( column >= (int)m_cells.size() ) m_cells.SetCount(column+1, NULL); delete (wxPGCell*) m_cells[column]; m_cells[column] = cellObj; } wxPGCell* wxPGProperty::GetOrCreateCell( unsigned int column ) { wxPGCell* cell = GetCell(column); if ( cell ) return cell; wxString text; if ( column == 0 ) text = m_label; cell = new wxPGCell(text); SetCell(column, cell); return cell; } void wxPGProperty::SetChoiceSelection( int newValue, const wxPGChoiceInfo& choiceInfo ) { // Changes value of a property with choices, but only // works if the value type is long or string. wxString ts = GetValue().GetType(); wxCHECK_RET( choiceInfo.m_choices, wxT("invalid choiceinfo") ); if ( ts == wxT("long") ) { SetValue( (long) newValue ); } else if ( ts == wxT("string") ) { SetValue( choiceInfo.m_choices->GetLabel(newValue) ); } } wxString wxPGProperty::GetChoiceString( unsigned int index ) { wxPGChoiceInfo ci; GetChoiceInfo(&ci); wxCHECK_MSG( ci.m_choices, wxEmptyString, wxT("This property class does not support choices.") ); return ci.m_choices->GetLabel(index); } int wxPGProperty::InsertChoice( const wxString& label, int index, int value ) { wxPropertyGrid* pg = GetGrid(); wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; int sel = GetChoiceInfo(&ci); wxCHECK_MSG( ci.m_choices, -1, wxT("This property class does not support choices.") ); int newSel = sel; if ( index < 0 ) index = ci.m_choices->GetCount(); if ( index <= sel ) newSel++; ci.m_choices->Insert(label, index, value); if ( sel != newSel ) SetChoiceSelection(newSel, ci); if ( this == pg->GetSelection() ) GetEditorClass()->InsertItem(pg->GetEditorControl(),label,index); return index; } void wxPGProperty::DeleteChoice( int index ) { wxPropertyGrid* pg = GetGrid(); wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; int sel = GetChoiceInfo(&ci); wxCHECK_RET( ci.m_choices, wxT("This property class does not support choices.") ); int newSel = sel; // Adjust current value if ( sel == index ) { SetValueToUnspecified(); newSel = 0; } else if ( index < sel ) { newSel--; } ci.m_choices->RemoveAt(index); if ( sel != newSel ) SetChoiceSelection(newSel, ci); if ( this == pg->GetSelection() ) GetEditorClass()->DeleteItem(pg->GetEditorControl(), index); } int wxPGProperty::GetChoiceInfo( wxPGChoiceInfo* WXUNUSED(info) ) { return -1; } wxPGEditorDialogAdapter* wxPGProperty::GetEditorDialog() const { return NULL; } bool wxPGProperty::DoSetAttribute( const wxString& WXUNUSED(name), wxVariant& WXUNUSED(value) ) { return false; } void wxPGProperty::SetAttribute( const wxString& name, wxVariant value ) { if ( DoSetAttribute( name, value ) ) { // Support working without grid, when possible if ( wxPGGlobalVars->HasExtraStyle( wxPG_EX_WRITEONLY_BUILTIN_ATTRIBUTES ) ) return; } m_attributes.Set( name, value ); } wxVariant wxPGProperty::DoGetAttribute( const wxString& WXUNUSED(name) ) const { return wxVariant(); } wxVariant wxPGProperty::GetAttribute( const wxString& name ) const { return m_attributes.FindValue(name); } wxString wxPGProperty::GetAttribute( const wxString& name, const wxString& defVal ) const { wxVariant variant = m_attributes.FindValue(name); if ( !variant.IsNull() ) return variant.GetString(); return defVal; } long wxPGProperty::GetAttributeAsLong( const wxString& name, long defVal ) const { wxVariant variant = m_attributes.FindValue(name); return wxPGVariantToInt(variant, defVal); } double wxPGProperty::GetAttributeAsDouble( const wxString& name, double defVal ) const { double retVal; wxVariant variant = m_attributes.FindValue(name); if ( wxPGVariantToDouble(variant, &retVal) ) return retVal; return defVal; } wxVariant wxPGProperty::GetAttributesAsList() const { wxVariantList tempList; wxVariant v( tempList, wxString::Format(wxT("@%s@attr"),m_name.c_str()) ); wxPGAttributeStorage::const_iterator it = m_attributes.StartIteration(); wxVariant variant; while ( m_attributes.GetNext(it, variant) ) v.Append(variant); return v; } // Slots of utility flags are NULL const unsigned int gs_propFlagToStringSize = 14; static const wxChar* gs_propFlagToString[gs_propFlagToStringSize] = { NULL, wxT("DISABLED"), wxT("HIDDEN"), NULL, wxT("NOEDITOR"), wxT("COLLAPSED"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; wxString wxPGProperty::GetFlagsAsString( FlagType flagsMask ) const { wxString s; int relevantFlags = m_flags & flagsMask & wxPG_STRING_STORED_FLAGS; FlagType a = 1; unsigned int i = 0; for ( i=0; iGetChoices(); } unsigned int wxPGProperty::GetChoiceCount() const { wxPGChoiceInfo ci; ci.m_choices = NULL; ((wxPGProperty*)this)->GetChoiceInfo(&ci); if ( ci.m_choices && ci.m_choices->IsOk() ) return ci.m_choices->GetCount(); return 0; } const wxPGChoiceEntry* wxPGProperty::GetCurrentChoice() const { wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; int index = ((wxPGProperty*)this)->GetChoiceInfo(&ci); if ( index == -1 || !ci.m_choices || index >= (int)ci.m_choices->GetCount() ) return NULL; return &(*ci.m_choices)[index]; } bool wxPGProperty::SetChoices( wxPGChoices& choices ) { wxPGChoiceInfo ci; ci.m_choices = NULL; GetChoiceInfo(&ci); wxCHECK_MSG( ci.m_choices, false, wxT("This property class does not support choices.") ); // Property must be de-selected first (otherwise choices in // the control would be de-synced with true choices) wxPropertyGrid* pg = GetGrid(); if ( pg && pg->GetSelection() == this ) pg->ClearSelection(); ci.m_choices->Assign(choices); // This may be needed to trigger some initialization // (but don't do it if property is somewhat uninitialized) wxVariant defVal = GetDefaultValue(); if ( defVal.IsNull() ) return false; SetValue(defVal); return true; } const wxPGEditor* wxPGProperty::GetEditorClass() const { const wxPGEditor* editor; if ( !m_customEditor ) { #ifdef __WXPYTHON__ wxString editorName = GetEditor(); if ( editorName.length() ) editor = wxPropertyGridInterface::GetEditorByName(editorName); else #endif editor = DoGetEditorClass(); } else editor = m_customEditor; // // Maybe override editor if common value specified if ( GetDisplayedCommonValueCount() ) { // TextCtrlAndButton -> ComboBoxAndButton if ( editor->IsKindOf(CLASSINFO(wxPGTextCtrlAndButtonEditor)) ) editor = wxPG_EDITOR(ChoiceAndButton); // TextCtrl -> ComboBox else if ( editor->IsKindOf(CLASSINFO(wxPGTextCtrlEditor)) ) editor = wxPG_EDITOR(ComboBox); } return editor; } // Privatizes set of choices void wxPGProperty::SetChoicesExclusive() { wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; GetChoiceInfo(&ci); if ( ci.m_choices ) ci.m_choices->SetExclusive(); } bool wxPGProperty::Hide( bool hide, int flags ) { wxPropertyGrid* pg = GetGrid(); if ( pg ) return pg->HideProperty(this, hide, flags); return DoHide( hide, flags ); } bool wxPGProperty::DoHide( bool hide, int flags ) { if ( !hide ) ClearFlag( wxPG_PROP_HIDDEN ); else SetFlag( wxPG_PROP_HIDDEN ); if ( flags & wxPG_RECURSE ) { unsigned int i; for ( i = 0; i < GetChildCount(); i++ ) Item(i)->DoHide(hide, flags | wxPG_RECURSE_STARTS); } return true; } bool wxPGProperty::HasVisibleChildren() const { unsigned int i; for ( i=0; iHasFlag(wxPG_PROP_HIDDEN) ) return true; } return false; } bool wxPGProperty::PrepareValueForDialogEditing( wxPropertyGrid* propGrid ) { return propGrid->EditorValidate(); } bool wxPGProperty::RecreateEditor() { wxPropertyGrid* pg = GetGrid(); wxASSERT(pg); wxPGProperty* selected = pg->GetSelection(); if ( this == selected ) { pg->DoSelectProperty(this, wxPG_SEL_FORCE); return true; } return false; } void wxPGProperty::SetValueImage( wxBitmap& bmp ) { delete m_valueBitmap; if ( &bmp && bmp.Ok() ) { // Resize the image wxSize maxSz = GetGrid()->GetImageSize(); wxSize imSz(bmp.GetWidth(),bmp.GetHeight()); if ( imSz.y > maxSz.y ) { // Create a memory DC wxBitmap* bmpNew = new wxBitmap(maxSz.x,maxSz.y,bmp.GetDepth()); wxMemoryDC dc; dc.SelectObject(*bmpNew); // Scale // FIXME: This is ugly - use image or wait for scaling patch. double scaleY = (double)maxSz.y / (double)imSz.y; dc.SetUserScale(scaleY, scaleY); dc.DrawBitmap(bmp, 0, 0); m_valueBitmap = bmpNew; } else { m_valueBitmap = new wxBitmap(bmp); } m_flags |= wxPG_PROP_CUSTOMIMAGE; } else { m_valueBitmap = NULL; m_flags &= ~(wxPG_PROP_CUSTOMIMAGE); } } wxPGProperty* wxPGProperty::GetMainParent() const { const wxPGProperty* curChild = this; const wxPGProperty* curParent = m_parent; while ( curParent && !curParent->IsCategory() ) { curChild = curParent; curParent = curParent->m_parent; } return (wxPGProperty*) curChild; } const wxPGProperty* wxPGProperty::GetLastVisibleSubItem() const { // // Returns last visible sub-item, recursively. if ( !IsExpanded() || !GetChildCount() ) return this; return Last()->GetLastVisibleSubItem(); } bool wxPGProperty::IsVisible() const { const wxPGProperty* parent; if ( HasFlag(wxPG_PROP_HIDDEN) ) return false; for ( parent = GetParent(); parent != NULL; parent = parent->GetParent() ) { if ( !parent->IsExpanded() || parent->HasFlag(wxPG_PROP_HIDDEN) ) return false; } return true; } wxPropertyGrid* wxPGProperty::GetGridIfDisplayed() const { wxPropertyGridState* state = GetParentState(); if ( !state ) return NULL; wxPropertyGrid* propGrid = state->GetGrid(); if ( state == propGrid->GetState() ) return propGrid; return NULL; } int wxPGProperty::GetY2( int lh ) const { const wxPGProperty* parent; const wxPGProperty* child = this; int y = 0; for ( parent = GetParent(); parent != NULL; parent = child->GetParent() ) { if ( !parent->IsExpanded() ) return -1; y += parent->GetChildrenHeight(lh, child->GetIndexInParent()); y += lh; child = parent; } y -= lh; // need to reduce one level return y; } int wxPGProperty::GetY() const { return GetY2(GetGrid()->GetRowHeight()); } wxPGProperty* wxPGPropArgCls::GetPtr( wxPropertyGridInterface* methods ) const { if ( !m_isName ) { wxASSERT_MSG( m_ptr.property, wxT("invalid property ptr") ); return m_ptr.property; } else if ( m_isName == 1 ) return methods->GetPropertyByNameI(*m_ptr.name); else if ( m_isName == 2 ) return methods->GetPropertyByNameI(m_ptr.rawname); #ifdef __WXPYTHON__ else if ( m_isName == 3 ) return methods->GetPropertyByNameI(*m_ptr.name); #endif wxASSERT( m_isName <= 3 ); return NULL; } // This is used by Insert etc. void wxPGProperty::AddChild2( wxPGProperty* prop, int index, bool correct_mode ) { if ( index < 0 || (size_t)index >= m_children.GetCount() ) { if ( correct_mode ) prop->m_arrIndex = m_children.GetCount(); m_children.Add( prop ); } else { m_children.Insert( prop, index ); if ( correct_mode ) FixIndexesOfChildren( index ); } prop->m_parent = this; } // This is used by properties that have fixed sub-properties void wxPGProperty::AddChild( wxPGProperty* prop ) { wxASSERT_MSG( prop->GetBaseName().length(), wxT("Property's children must have unique, non-empty names within their scope") ); prop->m_arrIndex = m_children.GetCount(); m_children.Add( prop ); int custImgHeight = prop->OnMeasureImage().y; if ( custImgHeight < 0 /*|| custImgHeight > 1*/ ) prop->m_flags |= wxPG_PROP_CUSTOMIMAGE; prop->m_parent = this; } void wxPGProperty::AdaptListToValue( wxVariant& list, wxVariant* value ) const { wxASSERT( GetChildCount() ); wxASSERT( !IsCategory() ); *value = GetValue(); if ( !list.GetCount() ) return; wxASSERT( GetCount() >= (unsigned int)list.GetCount() ); bool allChildrenSpecified; // Don't fully update aggregate properties unless all children have // specified value if ( HasFlag(wxPG_PROP_AGGREGATE) ) allChildrenSpecified = AreAllChildrenSpecified(&list); else allChildrenSpecified = true; wxVariant childValue = list[0]; unsigned int i; unsigned int n = 0; //wxLogDebug(wxT(">> %s.AdaptListToValue()"),GetBaseName().c_str()); for ( i=0; iGetBaseName() ) { //wxLogDebug(wxT(" %s(n=%i), %s"),childValue.GetName().c_str(),n,childValue.GetType().c_str()); if ( wxPGIsVariantType(childValue, list) ) { wxVariant cv2(child->GetValue()); child->AdaptListToValue(childValue, &cv2); childValue = cv2; } if ( allChildrenSpecified ) ActualChildChanged(*value, i, childValue); n++; if ( n == (unsigned int)list.GetCount() ) break; childValue = list[n]; } } } void wxPGProperty::FixIndexesOfChildren( unsigned int starthere ) { unsigned int i; for ( i=starthere;im_arrIndex = i; } // Returns (direct) child property with given name (or NULL if not found) wxPGProperty* wxPGProperty::GetPropertyByName( const wxString& name ) const { size_t i; for ( i=0; im_name == name ) return p; } // Does it have point, then? int pos = name.Find(wxT('.')); if ( pos <= 0 ) return (wxPGProperty*) NULL; wxPGProperty* p = GetPropertyByName(name. substr(0,pos)); if ( !p || !p->GetChildCount() ) return NULL; return p->GetPropertyByName(name.substr(pos+1,name.length()-pos-1)); } wxPGProperty* wxPGProperty::GetPropertyByNameWH( const wxString& name, unsigned int hintIndex ) const { unsigned int i = hintIndex; if ( i >= GetCount() ) i = 0; unsigned int lastIndex = i - 1; if ( lastIndex >= GetCount() ) lastIndex = GetCount() - 1; for (;;) { wxPGProperty* p = Item(i); if ( p->m_name == name ) return p; if ( i == lastIndex ) break; i++; if ( i == GetCount() ) i = 0; }; return NULL; } int wxPGProperty::GetChildrenHeight( int lh, int iMax_ ) const { // Returns height of children, recursively, and // by taking expanded/collapsed status into account. // // iMax is used when finding property y-positions. // unsigned int i = 0; int h = 0; if ( iMax_ == -1 ) iMax_ = GetChildCount(); unsigned int iMax = iMax_; wxASSERT( iMax <= GetChildCount() ); if ( !IsExpanded() && GetParent() ) return 0; while ( i < iMax ) { wxPGProperty* pwc = (wxPGProperty*) Item(i); if ( !pwc->HasFlag(wxPG_PROP_HIDDEN) ) { if ( !pwc->IsExpanded() || pwc->GetChildCount() == 0 ) h += lh; else h += pwc->GetChildrenHeight(lh) + lh; } i++; } return h; } wxPGProperty* wxPGProperty::GetItemAtY( unsigned int y, unsigned int lh, unsigned int* nextItemY ) const { wxASSERT( nextItemY ); // Linear search at the moment // // nextItemY = y of next visible property, final value will be written back. wxPGProperty* result = NULL; wxPGProperty* current = NULL; unsigned int iy = *nextItemY; unsigned int i = 0; unsigned int iMax = GetCount(); while ( i < iMax ) { wxPGProperty* pwc = Item(i); if ( !pwc->HasFlag(wxPG_PROP_HIDDEN) ) { // Found? if ( y < iy ) { result = current; break; } iy += lh; if ( pwc->IsExpanded() && pwc->GetChildCount() > 0 ) { result = (wxPGProperty*) pwc->GetItemAtY( y, lh, &iy ); if ( result ) break; } current = pwc; } i++; } // Found? if ( !result && y < iy ) result = current; *nextItemY = iy; /* if ( current ) wxLogDebug(wxT("%s::GetItemAtY(%i) -> %s"),this->GetLabel().c_str(),y,current->GetLabel().c_str()); else wxLogDebug(wxT("%s::GetItemAtY(%i) -> NULL"),this->GetLabel().c_str(),y); */ return (wxPGProperty*) result; } void wxPGProperty::DoEmpty() { size_t i; if ( !HasFlag(wxPG_PROP_CHILDREN_ARE_COPIES) ) { for ( i=0; i 0 ) { i--; state->DoDelete(Item(i), true); } } void wxPGProperty::ChildChanged( wxVariant& WXUNUSED(thisValue), int WXUNUSED(childIndex), wxVariant& WXUNUSED(childValue) ) const { } bool wxPGProperty::AreAllChildrenSpecified( wxVariant* pendingList ) const { unsigned int i; const wxVariantList* pList = NULL; wxVariantList::const_iterator node; if ( pendingList ) { pList = &pendingList->GetList(); node = pList->begin(); } for ( i=0; iGetBaseName(); for ( ; node != pList->end(); node++ ) { const wxVariant& item = *((const wxVariant*)*node); if ( item.GetName() == childName ) { listValue = &item; value = item; break; } } } if ( !listValue ) value = child->GetValue(); if ( value.IsNull() ) return false; // Check recursively if ( child->GetChildCount() ) { const wxVariant* childList = NULL; if ( listValue && wxPGIsVariantType(*listValue, list) ) childList = listValue; if ( !child->AreAllChildrenSpecified((wxVariant*)childList) ) return false; } } return true; } wxPGProperty* wxPGProperty::UpdateParentValues() { wxPGProperty* parent = m_parent; if ( parent && parent->HasFlag(wxPG_PROP_COMPOSED_VALUE) && !parent->IsCategory() && !parent->IsRoot() ) { wxString s; parent->GenerateComposedValue(s, 0); parent->m_value = s; return parent->UpdateParentValues(); } return this; } bool wxPGProperty::IsTextEditable() const { if ( HasFlag(wxPG_PROP_READONLY) ) return false; if ( HasFlag(wxPG_PROP_NOEDITOR) && (GetChildCount() || wxPG_String_EndsWith(wxString(GetEditorClass()->GetClassInfo()->GetClassName()), wxT("Button"))) ) return false; return true; } // ----------------------------------------------------------------------- // wxPGRootProperty // ----------------------------------------------------------------------- WX_PG_IMPLEMENT_PROPERTY_CLASS_PLAIN(wxPGRootProperty,none,TextCtrl) IMPLEMENT_DYNAMIC_CLASS(wxPGRootProperty, wxPGProperty) wxPGRootProperty::wxPGRootProperty() : wxPGProperty() { #ifdef __WXDEBUG__ m_name = wxT(""); #endif SetParentalType(0); m_depth = 0; } wxPGRootProperty::~wxPGRootProperty() { } // ----------------------------------------------------------------------- // wxPropertyCategory // ----------------------------------------------------------------------- WX_PG_IMPLEMENT_PROPERTY_CLASS_PLAIN(wxPropertyCategory,none,TextCtrl) IMPLEMENT_DYNAMIC_CLASS(wxPropertyCategory, wxPGProperty) void wxPropertyCategory::Init() { // don't set colour - prepareadditem method should do this SetParentalType(wxPG_PROP_CATEGORY); m_capFgColIndex = 1; m_textExtent = -1; } wxPropertyCategory::wxPropertyCategory() : wxPGProperty() { Init(); } wxPropertyCategory::wxPropertyCategory( const wxString &label, const wxString& name ) : wxPGProperty(label,name) { Init(); } wxPropertyCategory::~wxPropertyCategory() { } wxString wxPropertyCategory::GetValueAsString( int ) const { return wxEmptyString; } int wxPropertyCategory::GetTextExtent( const wxWindow* wnd, const wxFont& font ) const { if ( m_textExtent > 0 ) return m_textExtent; int x = 0, y = 0; ((wxWindow*)wnd)->GetTextExtent( m_label, &x, &y, 0, 0, &font ); return x; } void wxPropertyCategory::CalculateTextExtent( wxWindow* wnd, const wxFont& font ) { int x = 0, y = 0; wnd->GetTextExtent( m_label, &x, &y, 0, 0, &font ); m_textExtent = x; } // ----------------------------------------------------------------------- // wxPGCellRenderer // ----------------------------------------------------------------------- wxSize wxPGCellRenderer::GetImageSize( const wxPGProperty* WXUNUSED(property), int WXUNUSED(column), int WXUNUSED(item) ) const { return wxSize(0, 0); } void wxPGCellRenderer::DrawText( wxDC& dc, const wxRect& rect, int xOffset, const wxString& text ) const { //if ( xOffset ) // xOffset += DEFAULT_IMAGE_OFFSET_INCREMENT; dc.DrawText( text, rect.x+xOffset+wxPG_XBEFORETEXT, rect.y+((rect.height-dc.GetCharHeight())/2) ); } void wxPGCellRenderer::DrawEditorValue( wxDC& dc, const wxRect& rect, int xOffset, const wxString& text, wxPGProperty* property, const wxPGEditor* editor ) const { int yOffset = ((rect.height-dc.GetCharHeight())/2); if ( editor ) { wxRect rect2(rect); rect2.x += xOffset; rect2.y += yOffset; rect2.height -= yOffset; editor->DrawValue( dc, rect2, property, text ); } else { dc.DrawText( text, rect.x+xOffset+wxPG_XBEFORETEXT, rect.y+yOffset ); } } void wxPGCellRenderer::DrawCaptionSelectionRect( wxDC& dc, int x, int y, int w, int h ) const { wxRect focusRect(x,y+((h-dc.GetCharHeight())/2),w,h); wxPGDrawFocusRect(dc,focusRect); } int wxPGCellRenderer::PreDrawCell( wxDC& dc, const wxRect& rect, const wxPGCell& cell, int flags ) const { int imageWidth = 0; if ( !(flags & Selected) ) { // Draw using wxPGCell information, if available wxColour fgCol = cell.GetFgCol(); if ( fgCol.Ok() ) dc.SetTextForeground(fgCol); wxColour bgCol = cell.GetBgCol(); if ( bgCol.Ok() ) { dc.SetPen(bgCol); dc.SetBrush(bgCol); dc.DrawRectangle(rect); } } // Use cell font, if provided const wxFont& font = cell.GetFont(); if ( font.Ok() ) dc.SetFont(font); const wxBitmap& bmp = cell.GetBitmap(); if ( bmp.Ok() && // In control, do not draw oversized bitmap (!(flags & Control) || bmp.GetHeight() < rect.height ) ) { dc.DrawBitmap( bmp, rect.x + wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1, rect.y + wxPG_CUSTOM_IMAGE_SPACINGY, true ); imageWidth = bmp.GetWidth(); } return imageWidth; } void wxPGCellRenderer::PostDrawCell( wxDC& dc, const wxPropertyGrid* propGrid, const wxPGCell& cell, int WXUNUSED(flags) ) const { // Revert font const wxFont& font = cell.GetFont(); if ( font.Ok() ) dc.SetFont(propGrid->GetFont()); } // ----------------------------------------------------------------------- // wxPGDefaultRenderer // ----------------------------------------------------------------------- void wxPGDefaultRenderer::Render( wxDC& dc, const wxRect& rect, const wxPropertyGrid* propertyGrid, wxPGProperty* property, int column, int item, int flags ) const { bool isUnspecified = property->IsValueUnspecified(); if ( column == 1 && item == -1 ) { int cmnVal = property->GetCommonValue(); if ( cmnVal >= 0 ) { // Common Value if ( !isUnspecified ) DrawText( dc, rect, 0, propertyGrid->GetCommonValueLabel(cmnVal) ); return; } } const wxPGEditor* editor = NULL; const wxPGCell* cell = property->GetCell(column); wxString text; int imageWidth = 0; if ( column == 1 ) { if ( !(flags & Control) ) { // Use special unspecified value cell if ( property->IsValueUnspecified() ) cell = &propertyGrid->GetUnspecifiedValueAppearance(); } if ( !cell ) { // Use choice cell? const wxPGCell* ccell = property->GetCurrentChoice(); if ( ccell && ( wxGDI_IS_OK(ccell->GetBitmap()) || wxGDI_IS_OK(ccell->GetFgCol()) || wxGDI_IS_OK(ccell->GetBgCol()) ) ) cell = ccell; } } int preDrawFlags = flags; if ( cell ) { if ( propertyGrid->GetInternalFlags() & wxPG_FL_CELL_OVERRIDES_SEL ) preDrawFlags = preDrawFlags & ~(Selected); // Always use check box editor's DrawValue() function, if present editor = property->GetColumnEditor(column); if ( editor && !editor->IsKindOf(CLASSINFO(wxPGCheckBoxEditor)) ) editor = NULL; imageWidth = PreDrawCell( dc, rect, *cell, preDrawFlags ); text = cell->GetText(); if ( text == gs_noCellText ) { if ( column == 0 ) text = property->GetLabel(); else if ( column == 1 ) text = property->GetValueString(); else text = wxEmptyString; } } else if ( column == 0 ) { // Caption DrawText( dc, rect, 0, property->GetLabel() ); } else if ( column == 1 ) { editor = property->GetColumnEditor(column); if ( !isUnspecified ) { // Regular property value wxSize imageSize = propertyGrid->GetImageSize(property, item); wxPGPaintData paintdata; paintdata.m_parent = propertyGrid; paintdata.m_choiceItem = item; if ( imageSize.x > 0 ) { wxRect imageRect(rect.x + wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1, rect.y+wxPG_CUSTOM_IMAGE_SPACINGY, wxPG_CUSTOM_IMAGE_WIDTH, rect.height-(wxPG_CUSTOM_IMAGE_SPACINGY*2)); /*if ( imageSize.x == wxPG_FULL_CUSTOM_PAINT_WIDTH ) { imageRect.width = m_width - imageRect.x; }*/ dc.SetPen( wxPen(propertyGrid->GetCellTextColour(), 1, wxSOLID) ); paintdata.m_drawnWidth = imageSize.x; paintdata.m_drawnHeight = imageSize.y; if ( !isUnspecified ) { property->OnCustomPaint( dc, imageRect, paintdata ); } else { dc.SetBrush(*wxWHITE_BRUSH); dc.DrawRectangle(imageRect); } imageWidth = paintdata.m_drawnWidth; } text = property->GetValueString(); // Add units string? if ( propertyGrid->GetColumnCount() <= 2 ) { wxString unitsString = property->GetAttribute(wxPGGlobalVars->m_strUnits, wxEmptyString); if ( unitsString.length() ) text = wxString::Format(wxT("%s %s"), text.c_str(), unitsString.c_str() ); } } if ( text.length() == 0 ) { // Try to show inline help if no text wxVariant vInlineHelp = property->GetAttribute(wxPGGlobalVars->m_strInlineHelp); if ( !vInlineHelp.IsNull() ) { text = vInlineHelp.GetString(); dc.SetTextForeground(propertyGrid->GetCellDisabledTextColour()); // Must make the editor NULL to override it's own rendering // code. editor = NULL; } } } else if ( column == 2 ) { // Add units string? if ( !text.length() ) text = property->GetAttribute(wxPGGlobalVars->m_strUnits, wxEmptyString); } int imageOffset = property->GetImageOffset(imageWidth); DrawEditorValue( dc, rect, imageOffset, text, property, editor ); // active caption gets nice dotted rectangle if ( property->IsCategory() /*&& column == 0*/ ) { if ( flags & Selected ) { if ( imageOffset > 0 ) { imageOffset -= DEFAULT_IMAGE_OFFSET_INCREMENT; imageOffset += wxCC_CUSTOM_IMAGE_MARGIN2 + 4; } DrawCaptionSelectionRect( dc, rect.x+wxPG_XBEFORETEXT-wxPG_CAPRECTXMARGIN+imageOffset, rect.y-wxPG_CAPRECTYMARGIN+1, ((wxPropertyCategory*)property)->GetTextExtent(propertyGrid, propertyGrid->GetCaptionFont()) +(wxPG_CAPRECTXMARGIN*2), propertyGrid->GetFontHeight()+(wxPG_CAPRECTYMARGIN*2) ); } } if ( cell ) PostDrawCell(dc, propertyGrid, *cell, preDrawFlags); } wxSize wxPGDefaultRenderer::GetImageSize( const wxPGProperty* property, int column, int item ) const { if ( property && column == 1 ) { if ( item == -1 ) { wxBitmap* bmp = property->GetValueImage(); if ( bmp && bmp->Ok() ) return wxSize(bmp->GetWidth(),bmp->GetHeight()); } } return wxSize(0,0); } // ----------------------------------------------------------------------- // wxPGCell // ----------------------------------------------------------------------- wxPGCell::wxPGCell() { } bool wxPGCell::HasText() const { if ( m_text != gs_noCellText ) return true; return false; } wxPGCell::wxPGCell( const wxString& text, const wxBitmap& bitmap, const wxColour& fgCol, const wxColour& bgCol ) : m_bitmap(bitmap), m_fgCol(fgCol), m_bgCol(bgCol) { #ifndef __WXPYTHON__ if ( &text != ((wxString*)NULL) ) #else if ( (&text != ((wxString*)NULL)) && text != wxT("_LABEL_AS_NAME") ) #endif { wxASSERT_MSG( m_text != gs_noCellText, wxT("\"@!\" is not an allowed as a cell text.") ); m_text = text; } else { m_text = gs_noCellText; } } void wxPGCell::Assign(const wxPGCell& cell) { if ( cell.HasText() ) SetText(cell.GetText()); const wxBitmap& bmp = cell.GetBitmap(); if ( wxGDI_IS_OK(bmp) ) SetBitmap(bmp); const wxColour& fgCol = cell.GetFgCol(); if ( wxGDI_IS_OK(fgCol) ) SetFgCol(fgCol); const wxColour& bgCol = cell.GetBgCol(); if ( wxGDI_IS_OK(bgCol) ) SetBgCol(bgCol); const wxFont& font = cell.GetFont(); if ( wxGDI_IS_OK(font) ) SetFont(font); } // ----------------------------------------------------------------------- // wxPGBrush // ----------------------------------------------------------------------- // // This class is a wxBrush derivative used in the background colour // brush cache. It adds wxPG-type colour-in-long to the class. // JMS: Yes I know wxBrush doesn't actually hold the value (refcounted // object does), but this is simpler implementation and equally // effective. // class wxPGBrush : public wxBrush { public: wxPGBrush( const wxColour& colour ); wxPGBrush(); virtual ~wxPGBrush() { } void SetColour2( const wxColour& colour ); inline long GetColourAsLong() const { return m_colAsLong; } private: long m_colAsLong; }; void wxPGBrush::SetColour2( const wxColour& colour ) { wxBrush::SetColour(colour); m_colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); } wxPGBrush::wxPGBrush() : wxBrush() { m_colAsLong = 0; } wxPGBrush::wxPGBrush( const wxColour& colour ) : wxBrush(colour) { m_colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); } // ----------------------------------------------------------------------- // wxPGColour // ----------------------------------------------------------------------- // // Same as wxPGBrush, but for wxColour instead. // class wxPGColour : public wxColour { public: wxPGColour( const wxColour& colour ); wxPGColour(); virtual ~wxPGColour() { } void SetColour2( const wxColour& colour ); inline long GetColourAsLong() const { return m_colAsLong; } private: long m_colAsLong; }; void wxPGColour::SetColour2( const wxColour& colour ) { *this = colour; m_colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); } wxPGColour::wxPGColour() : wxColour() { m_colAsLong = 0; } wxPGColour::wxPGColour( const wxColour& colour ) : wxColour(colour) { m_colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); } // ----------------------------------------------------------------------- // wxPGCanvas // ----------------------------------------------------------------------- // // wxPGCanvas acts as a graphics sub-window of the // wxScrolledWindow that wxPropertyGrid is. // class wxPGCanvas : public wxPanel { public: wxPGCanvas() : wxPanel() { } virtual ~wxPGCanvas() { } protected: void OnMouseMove( wxMouseEvent &event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnMouseMove( event ); } void OnMouseClick( wxMouseEvent &event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnMouseClick( event ); } void OnMouseUp( wxMouseEvent &event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnMouseUp( event ); } void OnMouseRightClick( wxMouseEvent &event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnMouseRightClick( event ); } void OnMouseDoubleClick( wxMouseEvent &event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnMouseDoubleClick( event ); } void OnKey( wxKeyEvent& event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnKey( event ); } void OnKeyUp( wxKeyEvent& event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnKeyUp( event ); } void OnNavigationKey( wxNavigationKeyEvent& event ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); pg->OnNavigationKey( event ); } void OnPaint( wxPaintEvent& event ); private: DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGCanvas, wxPanel) EVT_MOTION(wxPGCanvas::OnMouseMove) EVT_PAINT(wxPGCanvas::OnPaint) EVT_LEFT_DOWN(wxPGCanvas::OnMouseClick) EVT_LEFT_UP(wxPGCanvas::OnMouseUp) EVT_RIGHT_UP(wxPGCanvas::OnMouseRightClick) EVT_LEFT_DCLICK(wxPGCanvas::OnMouseDoubleClick) EVT_KEY_DOWN(wxPGCanvas::OnKey) EVT_KEY_UP(wxPGCanvas::OnKeyUp) EVT_CHAR(wxPGCanvas::OnKey) EVT_NAVIGATION_KEY(wxPGCanvas::OnNavigationKey) END_EVENT_TABLE() void wxPGCanvas::OnPaint( wxPaintEvent& WXUNUSED(event) ) { wxPropertyGrid* pg = wxStaticCast(GetParent(), wxPropertyGrid); wxASSERT( pg->IsKindOf(CLASSINFO(wxPropertyGrid)) ); wxPaintDC dc(this); // Don't paint after destruction has begun if ( !(pg->GetInternalFlags() & wxPG_FL_INITIALIZED) ) return; // Update everything inside the box wxRect r = GetUpdateRegion().GetBox(); // FIXME: This is just a workaround for a bug that causes splitters not // to paint when other windows are being dragged over the grid. r.x = 0; r.width = GetClientSize().x; // Repaint this rectangle pg->DrawItems( dc, r.y, r.y + r.height, &r ); // We assume that the size set when grid is shown // is what is desired. pg->SetInternalFlag(wxPG_FL_GOOD_SIZE_SET); } // ----------------------------------------------------------------------- // wxPropertyGrid // ----------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxPropertyGrid, wxScrolledWindow) BEGIN_EVENT_TABLE(wxPropertyGrid, wxScrolledWindow) EVT_IDLE(wxPropertyGrid::OnIdle) EVT_MOTION(wxPropertyGrid::OnMouseMoveBottom) EVT_PAINT(wxPropertyGrid::OnPaint) EVT_SIZE(wxPropertyGrid::OnResize) EVT_ENTER_WINDOW(wxPropertyGrid::OnMouseEntry) EVT_LEAVE_WINDOW(wxPropertyGrid::OnMouseEntry) EVT_MOUSE_CAPTURE_CHANGED(wxPropertyGrid::OnCaptureChange) EVT_SCROLLWIN(wxPropertyGrid::OnScrollEvent) EVT_CHILD_FOCUS(wxPropertyGrid::OnChildFocusEvent) EVT_SET_FOCUS(wxPropertyGrid::OnFocusEvent) EVT_KILL_FOCUS(wxPropertyGrid::OnFocusEvent) EVT_TEXT_ENTER(wxPG_SUBID1,wxPropertyGrid::OnCustomEditorEvent) EVT_SYS_COLOUR_CHANGED(wxPropertyGrid::OnSysColourChanged) END_EVENT_TABLE() // ----------------------------------------------------------------------- wxPropertyGrid::wxPropertyGrid() : wxScrolledWindow() { Init1(); } // ----------------------------------------------------------------------- wxPropertyGrid::wxPropertyGrid( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxScrolledWindow() { Init1(); Create(parent,id,pos,size,style,name); } // ----------------------------------------------------------------------- bool wxPropertyGrid::Create( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) { // Use a native border if border not specified if ( !(style&wxBORDER_MASK) || (style & wxBORDER_THEME) ) { #ifdef __WXMSW__ // Themed border looks bad on Windows due to white inner border, so // draw internally in this case. // Unfortunately we can't easily draw our own border, so revert to // themed drawing. #if wxCHECK_VERSION(2,8,5) #ifdef __WXMSW__ style |= GetThemedBorderStyle(); #else style |= wxBORDER_SIMPLE; #endif #else style |= wxBORDER_SIMPLE; #endif #else style |= wxBORDER_SUNKEN; #endif } style |= wxVSCROLL; #ifdef __WXMSW__ // This prevents crash under Win2K, but still // enables keyboard navigation if ( style & wxTAB_TRAVERSAL ) { style &= ~(wxTAB_TRAVERSAL); style |= wxWANTS_CHARS; } #else if ( style & wxTAB_TRAVERSAL ) style |= wxWANTS_CHARS; #endif wxScrolledWindow::Create(parent,id,pos,size,style,name); Init2(); return true; } // ----------------------------------------------------------------------- // // Initialize values to defaults // void wxPropertyGrid::Init1() { #if !wxPG_USE_WXMODULE if ( !wxPGGlobalVars ) wxPGGlobalVars = new wxPGGlobalVarsClass(); #endif // Register editor classes, if necessary. if ( wxPGGlobalVars->m_mapEditorClasses.empty() ) RegisterDefaultEditors(); m_iFlags = 0; m_pState = (wxPropertyGridState*) NULL; m_wndEditor = m_wndEditor2 = (wxWindow*) NULL; m_selColumn = 1; m_colHover = 1; m_propHover = (wxPGProperty*) NULL; m_labelEditor = NULL; m_labelEditorProperty = NULL; m_eventObject = this; m_curFocused = (wxWindow*) NULL; m_processedEvent = NULL; m_sortFunction = NULL; m_inDoPropertyChanged = 0; m_inCommitChangesFromEditor = 0; m_inDoSelectProperty = false; m_inOnValidationFailure = false; m_permanentValidationFailureBehavior = wxPG_VFB_DEFAULT; m_dragStatus = 0; m_mouseSide = 16; m_editorFocused = 0; // Set up default unspecified value 'colour' m_unspecifiedAppearance.SetFgCol(*wxLIGHT_GREY); // Set default keys AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RIGHT ); AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN ); AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_LEFT ); AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP ); AddActionTrigger( wxPG_ACTION_EXPAND_PROPERTY, WXK_RIGHT); AddActionTrigger( wxPG_ACTION_COLLAPSE_PROPERTY, WXK_LEFT); AddActionTrigger( wxPG_ACTION_CANCEL_EDIT, WXK_ESCAPE ); AddActionTrigger( wxPG_ACTION_CUT, 'X', wxMOD_CONTROL ); AddActionTrigger( wxPG_ACTION_CUT, WXK_DELETE, wxMOD_SHIFT ); AddActionTrigger( wxPG_ACTION_COPY, 'C', wxMOD_CONTROL); AddActionTrigger( wxPG_ACTION_COPY, WXK_INSERT, wxMOD_CONTROL ); AddActionTrigger( wxPG_ACTION_PASTE, 'V', wxMOD_CONTROL ); AddActionTrigger( wxPG_ACTION_PASTE, WXK_INSERT, wxMOD_SHIFT ); AddActionTrigger( wxPG_ACTION_SELECT_ALL, 'A', wxMOD_CONTROL ); m_coloursCustomized = 0; m_frozen = 0; m_canvas = NULL; #if wxPG_DOUBLE_BUFFER m_doubleBuffer = (wxBitmap*) NULL; #endif m_windowsToDelete = NULL; #ifndef wxPG_ICON_WIDTH m_expandbmp = NULL; m_collbmp = NULL; m_iconWidth = 11; m_iconHeight = 11; #else m_iconWidth = wxPG_ICON_WIDTH; #endif m_prevVY = -1; m_gutterWidth = wxPG_GUTTER_MIN; m_subgroup_extramargin = 10; m_lineHeight = 0; m_width = m_height = 0; SetButtonShortcut(0); m_keyComboConsumed = 0; m_commonValues.push_back(new wxPGCommonValue(_("Unspecified"), wxPGGlobalVars->m_defaultRenderer) ); m_cvUnspecified = 0; m_chgInfo_changedProperty = NULL; } // ----------------------------------------------------------------------- // // Initialize after parent etc. set // void wxPropertyGrid::Init2() { wxASSERT( !(m_iFlags & wxPG_FL_INITIALIZED ) ); #ifdef __WXMAC__ // Smaller controls on Mac SetWindowVariant(wxWINDOW_VARIANT_SMALL); #endif // Now create state, if one didn't exist already // (wxPropertyGridManager might have created it for us). if ( !m_pState ) { m_pState = CreateState(); m_pState->m_pPropGrid = this; m_iFlags |= wxPG_FL_CREATEDSTATE; } if ( !(m_windowStyle & wxPG_SPLITTER_AUTO_CENTER) ) m_iFlags |= wxPG_FL_DONT_CENTER_SPLITTER; if ( m_windowStyle & wxPG_HIDE_CATEGORIES ) { m_pState->InitNonCatMode(); m_pState->m_properties = m_pState->m_abcArray; } GetClientSize(&m_width,&m_height); #ifndef wxPG_ICON_WIDTH // create two bitmap nodes for drawing m_expandbmp = new wxBitmap(expand_xpm); m_collbmp = new wxBitmap(collapse_xpm); // calculate average font height for bitmap centering m_iconWidth = m_expandbmp->GetWidth(); m_iconHeight = m_expandbmp->GetHeight(); #endif m_curcursor = wxCURSOR_ARROW; m_cursorSizeWE = new wxCursor( wxCURSOR_SIZEWE ); // adjust bitmap icon y position so they are centered m_vspacing = wxPG_DEFAULT_VSPACING; //SetFont(*wxNORMAL_FONT); CalculateFontAndBitmapStuff(m_vspacing); // Add base brush item m_arrBgBrushes.Add((void*)new wxPGBrush()); // Add base colour items m_arrFgCols.Add((void*)new wxPGColour()); m_arrFgCols.Add((void*)new wxPGColour()); RegainColours(); // This helps with flicker SetBackgroundStyle( wxBG_STYLE_CUSTOM ); // Hook the TLW m_tlp = NULL; m_tlpClosed = NULL; m_tlpClosedTime = 0; OnTLPChanging(::wxGetTopLevelParent(this)); // set virtual size to this window size wxSize wndsize = GetSize(); SetVirtualSize(wndsize.GetWidth(), wndsize.GetWidth()); m_timeCreated = ::wxGetLocalTimeMillis(); m_canvas = new wxPGCanvas(); m_canvas->Create(this, 1, wxPoint(0, 0), GetClientSize(), (GetWindowStyle() & wxTAB_TRAVERSAL) | wxWANTS_CHARS | wxCLIP_CHILDREN); m_canvas->SetBackgroundStyle( wxBG_STYLE_CUSTOM ); wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_canvas, 1, wxEXPAND, 0); SetSizer(sizer); SetScrollRate(wxPG_PIXELS_PER_UNIT, wxPG_PIXELS_PER_UNIT); m_iFlags |= wxPG_FL_INITIALIZED; m_ncWidth = wndsize.GetWidth(); // Need to call OnResize handler or size given in constructor/Create // will never work. wxSizeEvent sizeEvent(wndsize,0); OnResize(sizeEvent); } // ----------------------------------------------------------------------- wxPropertyGrid::~wxPropertyGrid() { size_t i; if ( m_processedEvent ) { // All right... we are being deleted while wxPropertyGrid event // is being sent. Make sure that event propagates as little // as possible (although usually this is not enough to prevent // a crash). m_processedEvent->Skip(false); m_processedEvent->StopPropagation(); // Let's use wxMessageBox to make the message appear more // reliably (and *before* the crash can happen). ::wxMessageBox(wxT("wxPropertyGrid was being destroyed in an event ") wxT("generated by it. This usually leads to a crash ") wxT("so it is recommended to destroy the control ") wxT("at idle time instead.")); } DoSelectProperty(NULL, wxPG_SEL_NOVALIDATE|wxPG_SEL_DONT_SEND_EVENT); // This should do prevent things from going too badly wrong m_iFlags &= ~(wxPG_FL_INITIALIZED); if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED ) m_canvas->ReleaseMouse(); // Call with NULL to disconnect event handling if ( !(GetExtraStyle() & wxPG_EX_DISABLE_TLP_TRACKING) ) { OnTLPChanging(NULL); #ifdef __WXDEBUG__ if ( IsEditorsValueModified() ) ::wxMessageBox(wxT("Most recent change in property editor ") wxT("was lost!!!\n\n(if you don't want this ") wxT("to happen, close your frames and ") wxT("dialogs using Close(false).)"), wxT("wxPropertyGrid Debug Warning") ); #endif } #if wxPG_DOUBLE_BUFFER if ( m_doubleBuffer ) delete m_doubleBuffer; #endif delete m_windowsToDelete; if ( m_iFlags & wxPG_FL_CREATEDSTATE ) delete m_pState; delete m_cursorSizeWE; #ifndef wxPG_ICON_WIDTH delete m_expandbmp; delete m_collbmp; #endif // Delete cached text colours. for ( i=0; iReleaseMouse(); return wxScrolledWindow::Destroy(); } // ----------------------------------------------------------------------- wxPropertyGridState* wxPropertyGrid::CreateState() const { return new wxPropertyGridState(); } // ----------------------------------------------------------------------- // wxPropertyGrid overridden wxWindow methods // ----------------------------------------------------------------------- void wxPropertyGrid::SetWindowStyleFlag( long style ) { long old_style = m_windowStyle; if ( m_iFlags & wxPG_FL_INITIALIZED ) { wxASSERT( m_pState ); if ( !(style & wxPG_HIDE_CATEGORIES) && (old_style & wxPG_HIDE_CATEGORIES) ) { // Enable categories EnableCategories( true ); } else if ( (style & wxPG_HIDE_CATEGORIES) && !(old_style & wxPG_HIDE_CATEGORIES) ) { // Disable categories EnableCategories( false ); } if ( !(old_style & wxPG_AUTO_SORT) && (style & wxPG_AUTO_SORT) ) { // // Autosort enabled // if ( !m_frozen ) PrepareAfterItemsAdded(); else m_pState->m_itemsAdded = 1; } #if wxPG_SUPPORT_TOOLTIPS if ( !(old_style & wxPG_TOOLTIPS) && (style & wxPG_TOOLTIPS) ) { // // Tooltips enabled // /* wxToolTip* tooltip = new wxToolTip ( wxEmptyString ); SetToolTip ( tooltip ); tooltip->SetDelay ( wxPG_TOOLTIP_DELAY ); */ } else if ( (old_style & wxPG_TOOLTIPS) && !(style & wxPG_TOOLTIPS) ) { // // Tooltips disabled // m_canvas->SetToolTip( (wxToolTip*) NULL ); } #endif } wxScrolledWindow::SetWindowStyleFlag ( style ); if ( m_iFlags & wxPG_FL_INITIALIZED ) { if ( (old_style & wxPG_HIDE_MARGIN) != (style & wxPG_HIDE_MARGIN) ) { CalculateFontAndBitmapStuff( m_vspacing ); Refresh(); } } } // ----------------------------------------------------------------------- void wxPropertyGrid::Freeze() { m_frozen++; wxScrolledWindow::Freeze(); } // ----------------------------------------------------------------------- void wxPropertyGrid::Thaw() { m_frozen--; wxScrolledWindow::Thaw(); RecalculateVirtualSize(); #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT m_canvas->Refresh(); #endif // Force property re-selection. // NB: We must copy the selection. wxArrayPGProperty selection = m_pState->m_selection; DoSetSelection(selection, wxPG_SEL_FORCE | wxPG_SEL_NONVISIBLE); } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoAddToSelection( wxPGProperty* prop, int selFlags ) { wxCHECK( prop, false ); if ( !(GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION) ) return DoSelectProperty(prop, selFlags); wxArrayPGProperty& selection = m_pState->m_selection; if ( !selection.size() ) { return DoSelectProperty(prop, selFlags); } else { // For categories, only one can be selected at a time if ( prop->IsCategory() || selection[0]->IsCategory() ) return true; selection.push_back(prop); // TODO: Enable following line in wxWidgets 2.9 //if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) ) { SendEvent( wxEVT_PG_SELECTED, prop, NULL ); } DrawItem(prop); } return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoRemoveFromSelection( wxPGProperty* prop, int selFlags ) { wxCHECK( prop, false ); bool res; wxArrayPGProperty& selection = m_pState->m_selection; if ( selection.size() <= 1 ) { res = DoSelectProperty(NULL, selFlags); } else { m_pState->DoRemoveFromSelection(prop); DrawItem(prop); res = true; } return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoSelectAndEdit( wxPGProperty* prop, unsigned int colIndex, unsigned int selFlags ) { // // NB: Enable following if label editor background colour is // ever changed to any other than m_colSelBack. // // We use this workaround to prevent visible flicker when editing // a cell. Atleast on wxMSW, there is a difficult to find // (and perhaps prevent) redraw somewhere between making property // selected and enabling label editing. // //wxColour prevColSelBack = m_colSelBack; //m_colSelBack = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ); bool res; if ( colIndex == 1 ) { res = DoSelectProperty(prop, selFlags); } else { // send event DoClearSelection(false, wxPG_SEL_NO_REFRESH); if ( m_pState->m_editableColumns.Index(colIndex) == wxNOT_FOUND ) { res = DoAddToSelection(prop, selFlags); } else { res = DoAddToSelection(prop, selFlags|wxPG_SEL_NO_REFRESH); DoBeginLabelEdit(colIndex, selFlags); } } //m_colSelBack = prevColSelBack; return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::AddToSelectionFromInputEvent( wxPGProperty* prop, unsigned int colIndex, wxMouseEvent* mouseEvent, int selFlags ) { const wxArrayPGProperty& selection = GetSelectedProperties(); bool alreadySelected = m_pState->DoIsPropertySelected(prop); bool res = true; // Set to 2 if also add all items in between int addToExistingSelection = 0; if ( GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION ) { if ( mouseEvent ) { if ( mouseEvent->GetEventType() == wxEVT_RIGHT_DOWN || mouseEvent->GetEventType() == wxEVT_RIGHT_UP ) { // Allow right-click for context menu without // disturbing the selection. if ( GetSelectedProperties().size() <= 1 || !alreadySelected ) return DoSelectAndEdit(prop, colIndex, selFlags); return true; } else { if ( mouseEvent->ControlDown() ) { addToExistingSelection = 1; } else if ( mouseEvent->ShiftDown() ) { if ( selection.size() > 0 && !prop->IsCategory() ) addToExistingSelection = 2; else addToExistingSelection = 1; } } } } if ( addToExistingSelection == 1 ) { // Add/remove one if ( !alreadySelected ) { res = DoAddToSelection(prop, selFlags); } else if ( GetSelectedProperties().size() > 1 ) { res = DoRemoveFromSelection(prop, selFlags); } } else if ( addToExistingSelection == 2 ) { // Add this, and all in between // Find top selected property wxPGProperty* topSelProp = selection[0]; int topSelPropY = topSelProp->GetY(); for ( unsigned int i=1; iGetY(); if ( y < topSelPropY ) { topSelProp = p; topSelPropY = y; } } wxPGProperty* startFrom; wxPGProperty* stopAt; if ( prop->GetY() <= topSelPropY ) { // Property is above selection (or same) startFrom = prop; stopAt = topSelProp; } else { // Property is below selection startFrom = topSelProp; stopAt = prop; } // Iterate through properties in-between, and select them wxPropertyGridIterator it; for ( it = GetIterator(wxPG_ITERATE_VISIBLE, startFrom); !it.AtEnd(); it++ ) { wxPGProperty* p = *it; if ( !p->IsCategory() && !m_pState->DoIsPropertySelected(p) ) { DoAddToSelection(p, selFlags); } if ( p == stopAt ) break; } } else { res = DoSelectAndEdit(prop, colIndex, selFlags); } return res; } // ----------------------------------------------------------------------- void wxPropertyGrid::DoSetSelection( const wxArrayPGProperty& newSelection, int selFlags ) { if ( newSelection.size() > 0 ) { if ( !DoSelectProperty(newSelection[0], selFlags) ) return; } else { DoClearSelection(false, selFlags); } for ( unsigned int i = 1; i < newSelection.size(); i++ ) { DoAddToSelection(newSelection[i], selFlags); } Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::MakeColumnEditable( unsigned int column, bool editable ) { wxASSERT( column != 1 ); wxArrayInt& cols = m_pState->m_editableColumns; if ( editable ) { cols.push_back(column); } else { for ( int i = cols.size() - 1; i > 0; i-- ) { if ( cols[i] == (int)column ) cols.erase( cols.begin() + i ); } } } // ----------------------------------------------------------------------- void wxPropertyGrid::DoBeginLabelEdit( unsigned int colIndex, int selFlags ) { wxPGProperty* selected = GetSelection(); wxCHECK_RET(selected, wxT("No property selected")); wxCHECK_RET(colIndex != 1, wxT("Do not use this for column 1")); if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) ) { if ( SendEvent( wxEVT_PG_LABEL_EDIT_BEGIN, selected, NULL, 0, colIndex ) ) return; } wxString text; wxPGCell* cell = selected->GetCell(colIndex); if ( !cell ) { if ( colIndex == 0 ) text = selected->GetLabel(); else cell = selected->GetOrCreateCell(colIndex); } if ( cell ) text = cell->GetText(); // send event DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE); m_selColumn = colIndex; wxRect r = GetEditorWidgetRect(selected, m_selColumn); wxWindow* ed = GenerateEditorTextCtrl(r.GetPosition(), r.GetSize(), text, NULL, wxTE_PROCESS_ENTER, 0, colIndex); m_labelEditor = ed; wxTextCtrl* tc = GetLabelEditor(); wxWindowID id = tc->GetId(); tc->Connect(id, wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler(wxPropertyGrid::OnLabelEditorEnterPress), NULL, this); tc->Connect(id, wxEVT_KEY_DOWN, wxKeyEventHandler(wxPropertyGrid::OnLabelEditorKeyPress), NULL, this); tc->SetFocus(); m_labelEditorProperty = selected; DrawItem(selected); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnLabelEditorEnterPress( wxCommandEvent& WXUNUSED(event) ) { DoEndLabelEdit(true); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnLabelEditorKeyPress( wxKeyEvent& event ) { int keycode = event.GetKeyCode(); if ( keycode == WXK_ESCAPE ) { DoEndLabelEdit(false); } else { // Do not block key presses, except left and right (to allow // normal wxTextCtrl usage) int secondAction; if ( KeyEventToActions(event, &secondAction) != wxPG_ACTION_INVALID && keycode != WXK_LEFT && keycode != WXK_RIGHT) HandleKeyEvent(event); else event.Skip(); } } // ----------------------------------------------------------------------- void wxPropertyGrid::DoEndLabelEdit( bool commit, int selFlags ) { if ( !m_labelEditor ) return; wxPGProperty* prop = m_labelEditorProperty; wxASSERT(prop); if ( commit ) { if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) ) { // wxPG_SEL_NOVALIDATE is passed correctly in selFlags if ( SendEvent( wxEVT_PG_LABEL_EDIT_ENDING, prop, NULL, selFlags, m_selColumn ) ) return; } wxString text = GetLabelEditor()->GetValue(); wxPGCell* cell = prop->GetCell(m_selColumn); if ( !cell ) { if ( m_selColumn == 0 ) prop->SetLabel(text); else cell = prop->GetOrCreateCell(m_selColumn); } if ( cell ) cell->SetText(text); } m_selColumn = 1; int wasFocused = m_iFlags & wxPG_FL_FOCUSED; DestroyEditorWnd(m_labelEditor); m_labelEditor = NULL; m_labelEditorProperty = NULL; // Fix focus (needed at least on wxGTK) if ( wasFocused ) SetFocusOnCanvas(); DrawItem(prop); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetExtraStyle( long exStyle ) { if ( exStyle & wxPG_EX_DISABLE_TLP_TRACKING ) OnTLPChanging(NULL); else OnTLPChanging(::wxGetTopLevelParent(this)); if ( exStyle & wxPG_EX_NATIVE_DOUBLE_BUFFERING ) { #if defined(__WXMSW__) /* // Don't use WS_EX_COMPOSITED just now. HWND hWnd; if ( m_iFlags & wxPG_FL_IN_MANAGER ) hWnd = (HWND)GetParent()->GetHWND(); else hWnd = (HWND)GetHWND(); ::SetWindowLong( hWnd, GWL_EXSTYLE, ::GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_COMPOSITED ); */ //#elif defined(__WXGTK20__) #endif // Only apply wxPG_EX_NATIVE_DOUBLE_BUFFERING if the window // truly was double-buffered. if ( !wxPGIsWindowBuffered(this) ) { exStyle &= ~(wxPG_EX_NATIVE_DOUBLE_BUFFERING); } else { #if wxPG_DOUBLE_BUFFER delete m_doubleBuffer; m_doubleBuffer = NULL; #endif } } wxScrolledWindow::SetExtraStyle( exStyle ); if ( exStyle & wxPG_EX_INIT_NOCAT ) m_pState->InitNonCatMode(); if ( exStyle & wxPG_EX_HELP_AS_TOOLTIPS ) m_windowStyle |= wxPG_TOOLTIPS; // Set global style wxPGGlobalVars->m_extraStyle = exStyle; } // ----------------------------------------------------------------------- // returns the best acceptable minimal size wxSize wxPropertyGrid::DoGetBestSize() const { int hei = 15; if ( m_lineHeight > hei ) hei = m_lineHeight; wxSize sz = wxSize( 60, hei+40 ); CacheBestSize(sz); return sz; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnTLPChanging( wxWindow* newTLP ) { if ( newTLP == m_tlp ) return; wxLongLong currentTime = ::wxGetLocalTimeMillis(); // // Parent changed so let's redetermine and re-hook the // correct top-level window. if ( m_tlp ) { m_tlp->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxPropertyGrid::OnTLPClose), NULL, this ); m_tlpClosed = m_tlp; m_tlpClosedTime = currentTime; } if ( newTLP ) { // Only accept new tlp if same one was not just dismissed. if ( newTLP != m_tlpClosed || m_tlpClosedTime+250 < currentTime ) { newTLP->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxPropertyGrid::OnTLPClose), NULL, this ); m_tlpClosed = NULL; } else { newTLP = NULL; } } m_tlp = newTLP; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnTLPClose( wxCloseEvent& event ) { // ClearSelection forces value validation/commit. if ( event.CanVeto() && !ClearSelection() ) { event.Veto(); return; } // Ok, it can close, set tlp pointer to NULL. Some other event // handler can of course veto the close, but our OnIdle() should // then be able to regain the tlp pointer. OnTLPChanging(NULL); event.Skip(); } // ----------------------------------------------------------------------- bool wxPropertyGrid::Reparent( wxWindowBase *newParent ) { OnTLPChanging((wxWindow*)newParent); bool res = wxScrolledWindow::Reparent(newParent); return res; } // ----------------------------------------------------------------------- // wxPropertyGrid Font and Colour Methods // ----------------------------------------------------------------------- void wxPropertyGrid::CalculateFontAndBitmapStuff( int vspacing ) { int x = 0, y = 0; m_captionFont = GetFont(); GetTextExtent(wxT("jG"), &x, &y, 0, 0, &m_captionFont); m_subgroup_extramargin = x + (x/2); m_fontHeight = y; #if wxPG_USE_RENDERER_NATIVE m_iconWidth = wxPG_ICON_WIDTH; #elif wxPG_ICON_WIDTH // scale icon m_iconWidth = (m_fontHeight * wxPG_ICON_WIDTH) / 13; if ( m_iconWidth < 5 ) m_iconWidth = 5; else if ( !(m_iconWidth & 0x01) ) m_iconWidth++; // must be odd #endif m_gutterWidth = m_iconWidth / wxPG_GUTTER_DIV; if ( m_gutterWidth < wxPG_GUTTER_MIN ) m_gutterWidth = wxPG_GUTTER_MIN; int vdiv = 6; if ( vspacing <= 1 ) vdiv = 12; else if ( vspacing >= 3 ) vdiv = 3; m_spacingy = m_fontHeight / vdiv; if ( m_spacingy < wxPG_YSPACING_MIN ) m_spacingy = wxPG_YSPACING_MIN; m_marginWidth = 0; if ( !(m_windowStyle & wxPG_HIDE_MARGIN) ) m_marginWidth = m_gutterWidth*2 + m_iconWidth; m_captionFont.SetWeight(wxBOLD); GetTextExtent(wxT("jG"), &x, &y, 0, 0, &m_captionFont); m_lineHeight = m_fontHeight+(2*m_spacingy)+1; m_visPropArray.SetCount((m_height/m_lineHeight)+10); // button spacing m_buttonSpacingY = (m_lineHeight - m_iconHeight) / 2; if ( m_buttonSpacingY < 0 ) m_buttonSpacingY = 0; if ( m_pState ) m_pState->CalculateFontAndBitmapStuff(vspacing); if ( m_iFlags & wxPG_FL_INITIALIZED ) RecalculateVirtualSize(); InvalidateBestSize(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnSysColourChanged( wxSysColourChangedEvent &WXUNUSED(event) ) { RegainColours(); Refresh(); } // ----------------------------------------------------------------------- static wxColour wxPGAdjustColour(const wxColour& src, int ra, int ga = 1000, int ba = 1000, bool forceDifferent = false) { if ( ga >= 1000 ) ga = ra; if ( ba >= 1000 ) ba = ra; // Recursion guard (allow 2 max) static int isinside = 0; isinside++; wxCHECK_MSG( isinside < 3, *wxBLACK, wxT("wxPGAdjustColour should not be recursively called more than once") ); wxColour dst; int r = src.Red(); int g = src.Green(); int b = src.Blue(); int r2 = r + ra; if ( r2>255 ) r2 = 255; else if ( r2<0) r2 = 0; int g2 = g + ga; if ( g2>255 ) g2 = 255; else if ( g2<0) g2 = 0; int b2 = b + ba; if ( b2>255 ) b2 = 255; else if ( b2<0) b2 = 0; // Make sure they are somewhat different if ( forceDifferent && (abs((r+g+b)-(r2+g2+b2)) < abs(ra/2)) ) dst = wxPGAdjustColour(src,-(ra*2)); else dst = wxColour(r2,g2,b2); // Recursion guard (allow 2 max) isinside--; return dst; } static int wxPGGetColAvg( const wxColour& col ) { return (col.Red() + col.Green() + col.Blue()) / 3; } void wxPropertyGrid::RegainColours() { wxColour def_bgcol = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ); if ( !(m_coloursCustomized & 0x0002) ) { wxColour col = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ); // Make sure colour is dark enough #ifdef __WXGTK__ int colDec = wxPGGetColAvg(col) - 230; #else int colDec = wxPGGetColAvg(col) - 200; #endif if ( colDec > 0 ) m_colCapBack = wxPGAdjustColour(col,-colDec); else m_colCapBack = col; } if ( !(m_coloursCustomized & 0x0001) ) m_colMargin = m_colCapBack; if ( !(m_coloursCustomized & 0x0004) ) { #ifdef __WXGTK__ int colDec = -90; #else int colDec = -72; #endif wxColour capForeCol = wxPGAdjustColour(m_colCapBack,colDec,5000,5000,true); m_colCapFore = capForeCol; // Set the cached colour as well. ((wxPGColour*)m_arrFgCols.Item(1))->SetColour2(capForeCol); } if ( !(m_coloursCustomized & 0x0008) ) { wxColour bgCol = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ); m_colPropBack = bgCol; // Set the cached brush as well. ((wxPGBrush*)m_arrBgBrushes.Item(0))->SetColour2(bgCol); } if ( !(m_coloursCustomized & 0x0010) ) { wxColour fgCol = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT ); m_colPropFore = fgCol; // Set the cached colour as well. ((wxPGColour*)m_arrFgCols.Item(0))->SetColour2(fgCol); } if ( !(m_coloursCustomized & 0x0020) ) m_colSelBack = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ); if ( !(m_coloursCustomized & 0x0040) ) m_colSelFore = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ); if ( !(m_coloursCustomized & 0x0080) ) m_colLine = m_colCapBack; if ( !(m_coloursCustomized & 0x0100) ) m_colDisPropFore = m_colCapFore; m_colEmptySpace = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ); } // ----------------------------------------------------------------------- void wxPropertyGrid::ResetColours() { m_coloursCustomized = 0; RegainColours(); Refresh(); } // ----------------------------------------------------------------------- bool wxPropertyGrid::SetFont( const wxFont& font ) { // Must disable active editor. ClearSelection(false); // TODO: Following code is disabled with wxMac because // it is reported to fail. I (JMS) cannot debug it // personally right now. bool res = wxScrolledWindow::SetFont( font ); // If this object has not been initialised properly (Create not called // yet), don't try to use wxClientDC. This can happen when SetVariant // is called from within the manager ctor, which triggers SetFont (and // subsequently the grid's SetFont) before the grid has been created. if ( res && GetParent() ) // may not have been Create()ed yet { CalculateFontAndBitmapStuff( m_vspacing ); if ( m_pState ) m_pState->CalculateFontAndBitmapStuff(m_vspacing); Refresh(); } return res; } // ----------------------------------------------------------------------- void wxPropertyGrid::SetLineColour( const wxColour& col ) { m_colLine = col; m_coloursCustomized |= 0x80; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetMarginColour( const wxColour& col ) { m_colMargin = col; m_coloursCustomized |= 0x01; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCellBackgroundColour( const wxColour& col ) { m_colPropBack = col; m_coloursCustomized |= 0x08; // Set the cached brush as well. ((wxPGBrush*)m_arrBgBrushes.Item(0))->SetColour2(col); Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCellTextColour( const wxColour& col ) { m_colPropFore = col; m_coloursCustomized |= 0x10; // Set the cached colour as well. ((wxPGColour*)m_arrFgCols.Item(0))->SetColour2(col); Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetEmptySpaceColour( const wxColour& col ) { m_colEmptySpace = col; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCellDisabledTextColour( const wxColour& col ) { m_colDisPropFore = col; m_coloursCustomized |= 0x100; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetSelectionBackground( const wxColour& col ) { m_colSelBack = col; m_coloursCustomized |= 0x20; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetSelectionForeground( const wxColour& col ) { m_colSelFore = col; m_coloursCustomized |= 0x40; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCaptionBackgroundColour( const wxColour& col ) { m_colCapBack = col; m_coloursCustomized |= 0x02; Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCaptionForegroundColour( const wxColour& col ) { m_colCapFore = col; m_coloursCustomized |= 0x04; // Set the cached colour as well. ((wxPGColour*)m_arrFgCols.Item(1))->SetColour2(col); Refresh(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetBackgroundColourIndex( wxPGProperty* p, int index, int flags ) { unsigned char ind = index; p->m_bgColIndex = ind; if ( flags & wxPG_RECURSE ) { unsigned int i; for ( i=0; iGetChildCount(); i++ ) SetBackgroundColourIndex(p->Item(i),index); } } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyBackgroundColour( wxPGPropArg id, const wxColour& colour, int flags ) { wxPG_PROP_ARG_CALL_PROLOG() size_t i; int colInd = -1; long colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); // As it is most likely that the previous colour is used, start comparison // from the end. for ( i=(m_arrBgBrushes.GetCount()-1); i>0; i-- ) { if ( ((wxPGBrush*)m_arrBgBrushes.Item(i))->GetColourAsLong() == colAsLong ) { colInd = i; break; } } if ( colInd < 0 ) { colInd = m_arrBgBrushes.GetCount(); wxCHECK_RET( colInd < 256, wxT("wxPropertyGrid: Warning - Only 255 different ") wxT("property background colours allowed.") ); m_arrBgBrushes.Add( (void*)new wxPGBrush(colour) ); } // Set indexes SetBackgroundColourIndex(p, colInd, flags); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- wxColour wxPropertyGrid::GetPropertyBackgroundColour( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxColour()) if ( p->IsCategory() && p->m_bgColIndex == 0 ) return GetCaptionBackgroundColour(); return ((wxPGBrush*)m_arrBgBrushes.Item(p->m_bgColIndex))->GetColour(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetTextColourIndex( wxPGProperty* p, int index, int flags ) { unsigned char ind = index; p->m_fgColIndex = ind; if ( flags & wxPG_RECURSE ) { unsigned int i; for ( i=0; iGetChildCount(); i++ ) SetTextColourIndex( p->Item(i), index, flags ); } } // ----------------------------------------------------------------------- int wxPropertyGrid::CacheColour( const wxColour& colour ) { unsigned int i; int colInd = -1; long colAsLong = wxPG_COLOUR(colour.Red(),colour.Green(),colour.Blue()); // As it is most likely that the previous colour is used, start comparison // from the end. for ( i=(m_arrFgCols.GetCount()-1); i>0; i-- ) { if ( ((wxPGColour*)m_arrFgCols.Item(i))->GetColourAsLong() == colAsLong ) { colInd = i; break; } } if ( colInd < 0 ) { colInd = m_arrFgCols.GetCount(); wxCHECK_MSG( colInd < 256, 0, wxT("wxPropertyGrid: Warning - Only 255 ") wxT("different property foreground colours allowed.") ); m_arrFgCols.Add( (void*)new wxPGColour(colour) ); } return colInd; } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyTextColour( wxPGPropArg id, const wxColour& colour, int flags ) { wxPG_PROP_ARG_CALL_PROLOG() // Set indexes SetTextColourIndex(p, CacheColour(colour), flags); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCaptionTextColour( wxPGPropArg id, const wxColour& colour ) { wxPG_PROP_ARG_CALL_PROLOG() wxCHECK_RET( p->IsCategory(), wxT("Only call SetCaptionTextColour for caption properties") ); // Set indexes wxPropertyCategory* cat = (wxPropertyCategory*) p; cat->SetTextColIndex(CacheColour(colour)); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- wxColour wxPropertyGrid::GetPropertyTextColour( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxColour()) return wxColour(*((wxPGColour*)m_arrFgCols.Item(p->m_fgColIndex))); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyColourToDefault( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG() SetBackgroundColourIndex( p, 0 ); SetTextColourIndex( p, 0, wxPG_RECURSE ); if ( p->IsCategory() ) { wxPropertyCategory* cat = (wxPropertyCategory*) p; cat->SetTextColIndex(1); } } // ----------------------------------------------------------------------- // wxPropertyGrid property adding and removal // ----------------------------------------------------------------------- void wxPropertyGridInterface::RefreshGrid( wxPropertyGridState* state ) { if ( !state ) state = m_pState; wxPropertyGrid* grid = state->GetGrid(); if ( grid->GetState() == state && !grid->IsFrozen() ) { grid->Refresh(); } } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::Append( wxPGProperty* property ) { return m_pState->DoAppend(property); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::AppendIn( wxPGPropArg id, wxPGProperty* newproperty ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxNullProperty) wxPGProperty* pwc = (wxPGProperty*) p; wxPGProperty* retp = m_pState->DoInsert(pwc, pwc->GetChildCount(), newproperty); return retp; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::Insert( wxPGPropArg id, wxPGProperty* property ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxNullProperty) wxPGProperty* retp = m_pState->DoInsert(p->GetParent(), p->GetArrIndex(), property); RefreshGrid(); return retp; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::Insert( wxPGPropArg id, int index, wxPGProperty* newproperty ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxNullProperty) wxPGProperty* retp = m_pState->DoInsert((wxPGProperty*)p,index,newproperty); RefreshGrid(); return retp; } // ----------------------------------------------------------------------- void wxPropertyGridInterface::DeleteProperty( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG() wxPropertyGridState* state = p->GetParentState(); state->DoDelete( p, true ); RefreshGrid(state); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::RemoveProperty( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxNullProperty) wxCHECK( !p->GetChildCount() || p->HasFlag(wxPG_PROP_AGGREGATE), wxNullProperty); wxPropertyGridState* state = p->GetParentState(); state->DoDelete( p, false ); RefreshGrid(state); return p; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::ReplaceProperty( wxPGPropArg id, wxPGProperty* property ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxNullProperty) wxPGProperty* replaced = p; wxCHECK_MSG( replaced && property, wxNullProperty, wxT("NULL property") ); wxCHECK_MSG( !replaced->IsCategory(), wxNullProperty, wxT("cannot replace this type of property") ); wxCHECK_MSG( !m_pState->IsInNonCatMode(), wxNullProperty, wxT("cannot replace properties in alphabetic mode") ); // Get address to the slot wxPGProperty* parent = replaced->GetParent(); int ind = replaced->GetIndexInParent(); wxPropertyGridState* state = replaced->GetParentState(); DeleteProperty(replaced); // Must use generic Delete state->DoInsert(parent,ind,property); return property; } // ----------------------------------------------------------------------- void wxPropertyGrid::PrepareAfterItemsAdded() { if ( m_pState && m_pState->m_itemsAdded ) { m_pState->m_itemsAdded = 0; if ( m_windowStyle & wxPG_AUTO_SORT ) Sort(); } RecalculateVirtualSize(); } // ----------------------------------------------------------------------- // wxPropertyGridInterface property operations // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::GetSelection() const { return m_pState->GetSelection(); } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::ClearSelection( bool validation ) { bool res = DoClearSelection(validation, wxPG_SEL_DONT_SEND_EVENT); wxPropertyGrid* pg = GetPropertyGrid(); if ( pg ) pg->Refresh(); return res; } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::DoClearSelection( bool validation, int selFlags ) { if ( !validation ) selFlags |= wxPG_SEL_NOVALIDATE; wxPropertyGridState* state = m_pState; if ( state ) { wxPropertyGrid* pg = state->GetGrid(); if ( pg->GetState() == state ) return pg->DoSelectProperty(NULL, selFlags); else state->DoSetSelection(NULL); } return true; } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::IsPropertySelected( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) return m_pState->DoIsPropertySelected(p); } // ----------------------------------------------------------------------- void wxPropertyGridInterface::LimitPropertyEditing( wxPGPropArg id, bool limit ) { wxPG_PROP_ARG_CALL_PROLOG() m_pState->DoLimitPropertyEditing(p, limit); RefreshProperty(p); } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::EnableProperty( wxPGPropArg id, bool enable ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxPropertyGridState* state = p->GetParentState(); wxPropertyGrid* grid = state->GetGrid(); if ( enable ) { if ( !(p->m_flags & wxPG_PROP_DISABLED) ) return false; // If active, Set active Editor. if ( grid && grid->GetState() == state && p == grid->GetSelection() ) grid->DoSelectProperty( p, wxPG_SEL_FORCE ); } else { if ( p->m_flags & wxPG_PROP_DISABLED ) return false; // If active, Disable as active Editor. if ( grid && grid->GetState() == state && p == grid->GetSelection() ) grid->DoSelectProperty( p, wxPG_SEL_FORCE ); } state->DoEnableProperty(p, enable); RefreshProperty( p ); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::ExpandAll( bool doExpand ) { wxPropertyGridState* state = m_pState; if ( !state->DoGetRoot()->GetChildCount() ) return true; wxPropertyGrid* pg = state->GetGrid(); if ( GetSelection() && GetSelection() != state->DoGetRoot() && !doExpand ) { pg->ClearSelection(false); } wxPGVIterator it; for ( it = GetVIterator( wxPG_ITERATE_ALL ); !it.AtEnd(); it.Next() ) { wxPGProperty* p = (wxPGProperty*) it.GetProperty(); if ( p->GetChildCount() ) { if ( doExpand ) { if ( !p->IsExpanded() ) { state->DoExpand(p); } } else { if ( p->IsExpanded() ) { state->DoCollapse(p); } } } } pg->RecalculateVirtualSize(); RefreshGrid(); return true; } // ----------------------------------------------------------------------- // wxPropertyGrid property value setting and getting // ----------------------------------------------------------------------- void wxPGGetFailed( const wxPGProperty* p, const wxChar* typestr ) { wxPGTypeOperationFailed(p,typestr,wxT("Get")); } // ----------------------------------------------------------------------- void wxPGTypeOperationFailed( const wxPGProperty* p, const wxChar* typestr, const wxChar* op ) { wxASSERT( p != NULL ); wxLogError( _("Type operation \"%s\" failed: Property labeled \"%s\" is of type \"%s\", NOT \"%s\"."), op,p->GetLabel().c_str(),p->GetValue().GetType().c_str(),typestr ); } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetPropVal( wxPGPropArg id, wxVariant& value ) { wxPG_PROP_ARG_CALL_PROLOG() if ( p ) { p->SetValue(value); wxPropertyGrid* propGrid = p->GetGridIfDisplayed(); if ( propGrid ) propGrid->DrawItemAndValueRelated( p ); } } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetPropertyValueString( wxPGPropArg id, const wxString& value ) { wxPG_PROP_ARG_CALL_PROLOG() m_pState->DoSetPropertyValueString(p, value); } // ----------------------------------------------------------------------- // wxPropertyGrid property operations // ----------------------------------------------------------------------- void wxPropertyGrid::DoSetPropertyName( wxPGProperty* p, const wxString& newname ) { wxCHECK_RET( p, wxT("invalid property id") ); wxPGProperty* parent = p->GetParent(); if ( parent->IsCategory() || parent->IsRoot() ) { if ( p->GetBaseName().length() ) m_pState->m_dictName.erase( wxPGNameConv(p->GetBaseName()) ); if ( newname.length() ) m_pState->m_dictName[newname] = (void*) p; } p->DoSetName(newname); } // ----------------------------------------------------------------------- bool wxPropertyGrid::EnsureVisible( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) Update(); bool changed = false; // Is it inside collapsed section? if ( !p->IsVisible() ) { // expand parents wxPGProperty* parent = p->GetParent(); wxPGProperty* grandparent = parent->GetParent(); if ( grandparent && grandparent != m_pState->m_properties ) Expand( grandparent ); Expand( parent ); changed = true; } // Need to scroll? int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; int y = p->GetY(); if ( y < vy ) { Scroll(vx, y/wxPG_PIXELS_PER_UNIT ); m_iFlags |= wxPG_FL_SCROLLED; changed = true; } else if ( (y+m_lineHeight) > (vy+m_height) ) { Scroll(vx, (y-m_height+(m_lineHeight*2))/wxPG_PIXELS_PER_UNIT ); m_iFlags |= wxPG_FL_SCROLLED; changed = true; } if ( changed ) DrawItems( p, p ); return changed; } // ----------------------------------------------------------------------- // wxPropertyGrid helper methods called by properties // ----------------------------------------------------------------------- // Control font changer helper. void wxPropertyGrid::SetCurControlBoldFont() { wxASSERT( m_wndEditor ); m_wndEditor->SetFont( m_captionFont ); } // ----------------------------------------------------------------------- wxPoint wxPropertyGrid::GetGoodEditorDialogPosition( wxPGProperty* p, const wxSize& sz ) { #if wxPG_SMALL_SCREEN // On small-screen devices, always show dialogs with default position and size. return wxDefaultPosition; #else int splitterX = GetSplitterPosition(); int x = splitterX; int y = p->GetY(); wxCHECK_MSG( y >= 0, wxPoint(-1,-1), wxT("invalid y?") ); ImprovedClientToScreen( &x, &y ); int sw = wxSystemSettings::GetMetric( ::wxSYS_SCREEN_X ); int sh = wxSystemSettings::GetMetric( ::wxSYS_SCREEN_Y ); int new_x; int new_y; if ( x > (sw/2) ) // left new_x = x + (m_width-splitterX) - sz.x; else // right new_x = x; if ( y > (sh/2) ) // above new_y = y - sz.y; else // below new_y = y + m_lineHeight; return wxPoint(new_x,new_y); #endif } // ----------------------------------------------------------------------- wxString& wxPropertyGrid::ExpandEscapeSequences( wxString& dst_str, wxString& src_str ) { if ( src_str.length() == 0 ) { dst_str = src_str; return src_str; } bool prev_is_slash = false; wxString::const_iterator i = src_str.begin(); dst_str.clear(); for ( ; i != src_str.end(); i++ ) { wxUniChar a = wxPGGetIterChar(src_str, i); if ( a != wxT('\\') ) { if ( !prev_is_slash ) { dst_str << a; } else { if ( a == wxT('n') ) { #ifdef __WXMSW__ dst_str << wxT('\n'); //dst_str << wxT('\10'); #else dst_str << wxT('\n'); //dst_str << 10; #endif } else if ( a == wxT('t') ) dst_str << wxT('\t'); else dst_str << a; } prev_is_slash = false; } else { if ( prev_is_slash ) { dst_str << wxT('\\'); prev_is_slash = false; } else { prev_is_slash = true; } } } return dst_str; } // ----------------------------------------------------------------------- wxString& wxPropertyGrid::CreateEscapeSequences( wxString& dst_str, wxString& src_str ) { if ( src_str.length() == 0 ) { dst_str = src_str; return src_str; } wxString::const_iterator i = src_str.begin(); wxUniChar prev_a = wxT('\0'); dst_str.clear(); for ( ; i != src_str.end(); i++ ) { wxChar a = wxPGGetIterChar(src_str, i); if ( a >= wxT(' ') ) { // This surely is not something that requires an escape sequence. dst_str << a; } else { // This might need... if ( a == wxT('\r') ) { // DOS style line end. // Already taken care below //dst_str = wxT("\\n"); //src++; } else if ( a == wxT('\n') ) // UNIX style line end. dst_str << wxT("\\n"); else if ( a == wxT('\t') ) // Tab. dst_str << wxT('\t'); else { //wxLogDebug(wxT("WARNING: Could not create escape sequence for character #%i"),(int)a); dst_str << a; } } prev_a = a; } return dst_str; } // ----------------------------------------------------------------------- // Item iteration macros // NB: Nowadays nly needed for alphabetic/categoric mode switching. // ----------------------------------------------------------------------- #define II_INVALID_I 0x00FFFFFF #define ITEM_ITERATION_VARIABLES \ wxPGProperty* parent; \ unsigned int i; \ unsigned int iMax; #define ITEM_ITERATION_INIT_FROM_THE_TOP \ parent = m_properties; \ i = 0; #define ITEM_ITERATION_INIT(startparent, startindex, state) \ parent = startparent; \ i = (unsigned int)startindex; \ if ( parent == (wxPGProperty*) NULL ) \ { \ parent = state->m_properties; \ i = 0; \ } #define ITEM_ITERATION_LOOP_BEGIN \ do \ { \ iMax = parent->GetCount(); \ while ( i < iMax ) \ { \ wxPGProperty* p = parent->Item(i); #define ITEM_ITERATION_LOOP_END \ if ( p->GetChildCount() ) \ { \ i = 0; \ parent = (wxPGProperty*)p; \ iMax = parent->GetCount(); \ } \ else \ i++; \ } \ i = parent->m_arrIndex + 1; \ parent = parent->m_parent; \ } \ while ( parent != NULL ); // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGrid::DoGetItemAtY( int y ) const { // Outside? if ( y < 0 ) return (wxPGProperty*) NULL; unsigned int a = 0; return m_pState->m_properties->GetItemAtY(y, m_lineHeight, &a); } // ----------------------------------------------------------------------- wxPropertyGridHitTestResult wxPropertyGrid::HitTest( const wxPoint& pt ) const { wxPoint pt2; GetViewStart(&pt2.x,&pt2.y); pt2.x *= wxPG_PIXELS_PER_UNIT; pt2.y *= wxPG_PIXELS_PER_UNIT; pt2.x += pt.x; pt2.y += pt.y; return m_pState->HitTest(pt2); } // ----------------------------------------------------------------------- // wxPropertyGrid graphics related methods // ----------------------------------------------------------------------- void wxPropertyGrid::OnPaint( wxPaintEvent& WXUNUSED(event) ) { wxPaintDC dc(this); // Update everything inside the box wxRect r = GetUpdateRegion().GetBox(); // This is workaround for inaccurate GetUpdateRegion() bug if ( r.x > 0 && r.y > 0 ) r.Inflate(1); dc.SetPen(m_colEmptySpace); dc.SetBrush(m_colEmptySpace); dc.DrawRectangle(r); } void wxPropertyGrid::DrawExpanderButton( wxDC& dc, const wxRect& rect, wxPGProperty* property ) const { // Prepare rectangle to be used wxRect r(rect); r.x += m_gutterWidth; r.y += m_buttonSpacingY; r.width = m_iconWidth; r.height = m_iconHeight; #if (wxPG_USE_RENDERER_NATIVE) // #elif wxPG_ICON_WIDTH // Drawing expand/collapse button manually dc.SetPen(m_colPropFore); if ( property->IsCategory() ) dc.SetBrush(*wxTRANSPARENT_BRUSH); else dc.SetBrush(m_colPropBack); dc.DrawRectangle( r ); int _y = r.y+(m_iconWidth/2); dc.DrawLine(r.x+2,_y,r.x+m_iconWidth-2,_y); #else wxBitmap* bmp; #endif if ( property->IsExpanded() ) { // wxRenderer functions are non-mutating in nature, so it // should be safe to cast "const wxPropertyGrid*" to "wxWindow*". // Hopefully this does not cause problems. #if (wxPG_USE_RENDERER_NATIVE) wxRendererNative::Get().DrawTreeItemButton( (wxWindow*)this, dc, r, wxCONTROL_EXPANDED ); #elif wxPG_ICON_WIDTH // #else bmp = m_collbmp; #endif } else { #if (wxPG_USE_RENDERER_NATIVE) wxRendererNative::Get().DrawTreeItemButton( (wxWindow*)this, dc, r, 0 ); #elif wxPG_ICON_WIDTH int _x = r.x+(m_iconWidth/2); dc.DrawLine(_x,r.y+2,_x,r.y+m_iconWidth-2); #else bmp = m_expandbmp; #endif } #if (wxPG_USE_RENDERER_NATIVE) // #elif wxPG_ICON_WIDTH // #else dc.DrawBitmap( *bmp, r.x, r.y, true ); #endif } // ----------------------------------------------------------------------- // // This is the one called by OnPaint event handler and others. // topy and bottomy are already unscrolled (ie. physical) // void wxPropertyGrid::DrawItems( wxDC& dc, unsigned int topy, unsigned int bottomy, const wxRect* clipRect ) { if ( m_frozen || m_height < 1 || bottomy < topy || !m_pState ) return; m_pState->EnsureVirtualHeight(); wxRect tempClipRect; if ( !clipRect ) { tempClipRect = wxRect(0,topy,m_pState->m_width,bottomy); clipRect = &tempClipRect; } // items added check if ( m_pState->m_itemsAdded ) PrepareAfterItemsAdded(); int paintFinishY = 0; if ( m_pState->m_properties->GetCount() > 0 ) { wxDC* dcPtr = &dc; bool isBuffered = false; #if wxPG_DOUBLE_BUFFER wxMemoryDC* bufferDC = NULL; if ( !(GetExtraStyle() & wxPG_EX_NATIVE_DOUBLE_BUFFERING) ) { if ( !m_doubleBuffer ) { paintFinishY = clipRect->y; dcPtr = NULL; } else { bufferDC = new wxMemoryDC(); // If nothing was changed, then just copy from double-buffer bufferDC->SelectObject( *m_doubleBuffer ); dcPtr = bufferDC; isBuffered = true; } } #endif if ( dcPtr ) { paintFinishY = DoDrawItems(*dcPtr, NULL, NULL, clipRect, isBuffered); if ( paintFinishY <= (clipRect->y+clipRect->height) ) { dcPtr->SetPen(m_colEmptySpace); dcPtr->SetBrush(m_colEmptySpace); dcPtr->DrawRectangle(0, paintFinishY, m_width, dcPtr->GetSize().y - paintFinishY); } } #if wxPG_DOUBLE_BUFFER if ( bufferDC ) { dc.Blit(clipRect->x, clipRect->y, clipRect->width, clipRect->height, bufferDC, 0, 0, wxCOPY ); delete bufferDC; } #endif } else { dc.SetPen(m_colEmptySpace); dc.SetBrush(m_colEmptySpace); dc.DrawRectangle(*clipRect); } } // ----------------------------------------------------------------------- int wxPropertyGrid::DoDrawItems( wxDC& dc, const wxPGProperty* firstItem, const wxPGProperty* lastItem, const wxRect* clipRect, bool isBuffered ) const { // TODO: This should somehow be eliminated. wxRect tempClipRect; if ( !clipRect ) { wxASSERT(firstItem); wxASSERT(lastItem); tempClipRect = GetPropertyRect(firstItem, lastItem); clipRect = &tempClipRect; } if ( !firstItem ) firstItem = DoGetItemAtY(clipRect->y); if ( !lastItem ) { lastItem = DoGetItemAtY(clipRect->y+clipRect->height-1); if ( !lastItem ) lastItem = GetLastItem( wxPG_ITERATE_VISIBLE ); } if ( m_frozen || m_height < 1 || firstItem == NULL ) return 0; wxCHECK_MSG( !m_pState->m_itemsAdded, clipRect->y, wxT("no items added") ); wxASSERT( m_pState->m_properties->GetCount() ); int lh = m_lineHeight; int firstItemTopY; int lastItemBottomY; firstItemTopY = clipRect->y; lastItemBottomY = clipRect->y + clipRect->height; // Align y coordinates to item boundaries firstItemTopY -= firstItemTopY % lh; lastItemBottomY += lh - (lastItemBottomY % lh); lastItemBottomY -= 1; // Entire range outside scrolled, visible area? if ( firstItemTopY >= (int)m_pState->GetVirtualHeight() || lastItemBottomY <= 0 ) return 0; wxCHECK_MSG( firstItemTopY < lastItemBottomY, clipRect->y, wxT("invalid y values") ); /* wxLogDebug(wxT(" -> DoDrawItems ( \"%s\" -> \"%s\", height=%i (ch=%i), clipRect = 0x%lX )"), firstItem->GetLabel().c_str(), lastItem->GetLabel().c_str(), (int)(lastItemBottomY - firstItemTopY), (int)m_height, (unsigned long)clipRect ); */ wxRect r; long windowStyle = m_windowStyle; int xRelMod = 0; int yRelMod = 0; // // With wxPG_DOUBLE_BUFFER, do double buffering // - buffer's y = 0, so align cliprect and coordinates to that // #if wxPG_DOUBLE_BUFFER wxRect cr2; if ( isBuffered ) { xRelMod = clipRect->x; yRelMod = clipRect->y; // // clipRect conversion if ( clipRect ) { cr2 = *clipRect; cr2.x -= xRelMod; cr2.y -= yRelMod; clipRect = &cr2; } firstItemTopY -= yRelMod; lastItemBottomY -= yRelMod; } #else wxUnusedVar(isBuffered); #endif int x = m_marginWidth - xRelMod; wxFont normalfont = GetFont(); bool reallyFocused = (m_iFlags & wxPG_FL_FOCUSED) ? true : false; bool isEnabled = IsEnabled(); wxPGProperty* firstSelected = GetSelection(); // // Prepare some pens and brushes that are often changed to. // wxBrush marginBrush(m_colMargin); wxPen marginPen(m_colMargin); wxBrush capbgbrush(m_colCapBack,wxSOLID); wxPen linepen(m_colLine,1,wxSOLID); wxBrush selBackBrush; if ( isEnabled ) selBackBrush = wxBrush(m_colSelBack); else selBackBrush = marginBrush; // pen that has same colour as text wxPen outlinepen(m_colPropFore,1,wxSOLID); // // Clear margin with background colour // dc.SetBrush( marginBrush ); if ( !(windowStyle & wxPG_HIDE_MARGIN) ) { dc.SetPen( *wxTRANSPARENT_PEN ); dc.DrawRectangle(-1-xRelMod,firstItemTopY-1,x+2,lastItemBottomY-firstItemTopY+2); } const wxPropertyGridState* state = m_pState; #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT bool wasSelectedPainted = false; #endif // TODO: Only render columns that are within clipping region. dc.SetFont(normalfont); wxPropertyGridConstIterator it( state, wxPG_ITERATE_VISIBLE, firstItem ); int endScanBottomY = lastItemBottomY + lh; int y = firstItemTopY; unsigned int arrInd = 0; for ( ; !it.AtEnd(); it.Next() ) { const wxPGProperty* p = *it; //wxLogDebug(wxT("%s, %i < %i"),p.GetName().c_str(), y, endScanBottomY); if ( !p->HasFlag(wxPG_PROP_HIDDEN) ) { m_visPropArray[arrInd] = (wxPGProperty*)p; arrInd++; if ( y > endScanBottomY ) break; y += lh; } } m_visPropArray[arrInd] = NULL; int gridWidth = state->m_width; int rowHeight = m_fontHeight+(m_spacingy*2)+1; y = firstItemTopY; for ( arrInd=0; m_visPropArray[arrInd] != NULL && y <= lastItemBottomY; arrInd++ ) { wxPGProperty* p =(wxPGProperty*) m_visPropArray[arrInd]; wxPGProperty* nextP = (wxPGProperty*) m_visPropArray[arrInd+1]; wxPGProperty* parent = p->GetParent(); int textMarginHere = x; int renderFlags = 0; int greyDepth = m_marginWidth; if ( !(windowStyle & wxPG_HIDE_CATEGORIES) ) greyDepth = (((int)p->m_depthBgCol)-1) * m_subgroup_extramargin + m_marginWidth; int greyDepthX = greyDepth - xRelMod; // Use basic depth if in non-categoric mode and parent is base array. if ( !(windowStyle & wxPG_HIDE_CATEGORIES) || parent != m_pState->m_properties ) { textMarginHere += ((unsigned int)((p->m_depth-1)*m_subgroup_extramargin)); } // Paint margin area dc.SetBrush(marginBrush); dc.SetPen(marginPen); dc.DrawRectangle( -xRelMod, y, greyDepth, lh ); dc.SetPen( linepen ); int y2 = y + lh; #ifdef __WXMSW__ // Margin Edge // Let's not draw a margin if wxPG_HIDE_MARGIN is specified, since it // looks better, at least under Windows when we have a themed border // (the themed-window-specific whitespace between the real border and // the propgrid margin exacerbates the double-border look). // Is this or its parent themed? bool suppressMarginEdge = (GetWindowStyle() & wxPG_HIDE_MARGIN) && (((GetWindowStyle() & wxBORDER_MASK) == wxBORDER_THEME) || (((GetWindowStyle() & wxBORDER_MASK) == wxBORDER_NONE) && ((GetParent()->GetWindowStyle() & wxBORDER_MASK) == wxBORDER_THEME))); #else bool suppressMarginEdge = false; #endif if ( !suppressMarginEdge ) { dc.DrawLine( greyDepthX, y, greyDepthX, y2 ); } else { // Blank out the margin edge dc.SetPen(wxPen(GetBackgroundColour())); dc.DrawLine( greyDepthX, y, greyDepthX, y2 ); dc.SetPen( linepen ); } // Splitters unsigned int si; int sx = x; for ( si=0; sim_colWidths.size(); si++ ) { sx += state->m_colWidths[si]; dc.DrawLine( sx, y, sx, y2 ); } // Horizontal Line, below // (not if both this and next is category caption) if ( p->IsCategory() && nextP && nextP->IsCategory() ) dc.SetPen(m_colCapBack); dc.DrawLine( greyDepthX, y2-1, gridWidth-xRelMod, y2-1 ); bool isSelected = state->DoIsPropertySelected(p); if ( p == firstSelected ) { renderFlags |= wxPGCellRenderer::Selected; #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT wasSelectedPainted = true; #endif } wxColour rowBgCol; wxColour rowFgCol; wxBrush rowBgBrush; if ( p->IsCategory() ) { if ( p->m_fgColIndex == 0 ) rowFgCol = m_colCapFore; else rowFgCol = *(wxPGColour*)m_arrFgCols[p->m_fgColIndex]; rowBgBrush = wxBrush(m_colCapBack); } else if ( !isSelected ) { // Disabled may get different colour. if ( !p->IsEnabled() ) rowFgCol = m_colDisPropFore; else rowFgCol = *(wxPGColour*)m_arrFgCols[p->m_fgColIndex]; rowBgBrush = *(wxPGBrush*)m_arrBgBrushes[p->m_bgColIndex]; } else { // Selected gets different colour. if ( reallyFocused && p == firstSelected ) { rowFgCol = m_colSelFore; rowBgBrush = selBackBrush; } else if ( isEnabled ) { rowFgCol = *(wxPGColour*)m_arrFgCols[p->m_fgColIndex]; if ( p == firstSelected ) rowBgBrush = marginBrush; else rowBgBrush = selBackBrush; } else { rowFgCol = m_colDisPropFore; rowBgBrush = selBackBrush; } } bool fontChanged = false; wxRect butRect( ((p->m_depth - 1) * m_subgroup_extramargin) - xRelMod, y, m_marginWidth, lh ); if ( p->IsCategory() ) { // Captions are all cells merged as one dc.SetFont(m_captionFont); fontChanged = true; wxRect cellRect(greyDepthX, y, gridWidth - greyDepth + 2, rowHeight-1 ); dc.SetBrush(rowBgBrush); dc.SetPen(rowBgBrush.GetColour()); dc.SetTextForeground(rowFgCol); dc.DrawRectangle(cellRect); // Foreground wxPGCellRenderer* renderer = p->GetCellRenderer(0); renderer->Render( dc, cellRect, this, p, 0, -1, renderFlags ); // Tree Item Button if ( !HasFlag(wxPG_HIDE_MARGIN) && p->HasVisibleChildren() ) DrawExpanderButton( dc, butRect, p ); } else { // Fine tune button rectangle to actually fit the cell if ( butRect.x > 0 ) butRect.x += IN_CELL_EXPANDER_BUTTON_X_ADJUST; if ( p->m_flags & wxPG_PROP_MODIFIED && (windowStyle & wxPG_BOLD_MODIFIED) ) { dc.SetFont(m_captionFont); fontChanged = true; } unsigned int ci; int cellX = x + 1; int nextCellWidth = state->m_colWidths[0] - (greyDepthX - m_marginWidth); wxRect cellRect(greyDepthX+1, y, 0, rowHeight-1); int textXAdd = textMarginHere - greyDepthX; for ( ci=0; cim_colWidths.size(); ci++ ) { cellRect.width = nextCellWidth - 1; wxWindow* cellEditor = NULL; wxWindow* editorClipperWnd = NULL; // Tree Item Button (must be drawn before clipping is set up // if it is outside the cell proper) bool drawExpanderButton = ( ci == 0 && p->HasVisibleChildren() && !HasFlag(wxPG_HIDE_MARGIN) ); bool drawExpanderButtonPostClip = false; if ( drawExpanderButton ) { drawExpanderButtonPostClip = true; // Coordinate-wise, the expander button rectangle // can be partially left of the cell rectangle. if ( butRect.x < cellRect.x ) DrawExpanderButton(dc, butRect, p); else drawExpanderButtonPostClip = true; } // Background if ( isSelected && (ci == 1 || ci == m_selColumn) ) { if ( p == firstSelected ) { if ( ci == 1 && m_wndEditor ) { editorClipperWnd = m_wndEditor; cellEditor = GetEditorControl(); } else if ( ci == m_selColumn && m_labelEditor ) { editorClipperWnd = m_labelEditor; cellEditor = GetLabelEditor(); } } if ( cellEditor ) { wxColour editorBgCol = cellEditor->GetBackgroundColour(); dc.SetBrush(editorBgCol); dc.SetPen(editorBgCol); dc.SetTextForeground(m_colPropFore); if ( m_dragStatus != 0 || (m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE) ) cellEditor = NULL; } else { dc.SetBrush(m_colPropBack); dc.SetPen(m_colPropBack); if ( p->IsEnabled() ) dc.SetTextForeground(m_colPropFore); else dc.SetTextForeground(m_colDisPropFore); } } else { dc.SetBrush(rowBgBrush); dc.SetPen(rowBgBrush.GetColour()); dc.SetTextForeground(rowFgCol); } if ( cellEditor ) { wxRegion clipRegion(cellRect); clipRegion.Subtract(editorClipperWnd->GetRect()); dc.SetClippingRegion(clipRegion); } else { dc.SetClippingRegion(cellRect); } dc.DrawRectangle(cellRect); if ( drawExpanderButtonPostClip ) DrawExpanderButton(dc, butRect, p); cellRect.x += textXAdd; cellRect.width -= textXAdd; // Foreground if ( !cellEditor ) { wxPGCellRenderer* renderer; int cmnVal = p->GetCommonValue(); if ( cmnVal == -1 || ci != 1 ) { renderer = p->GetCellRenderer(ci); renderer->Render( dc, cellRect, this, p, ci, -1, renderFlags ); } else { renderer = GetCommonValue(cmnVal)->GetRenderer(); renderer->Render( dc, cellRect, this, p, ci, -1, renderFlags ); } } cellX += state->m_colWidths[ci]; if ( ci < (state->m_colWidths.size()-1) ) nextCellWidth = state->m_colWidths[ci+1]; cellRect.x = cellX; dc.DestroyClippingRegion(); // Is this really necessary? textXAdd = 0; } } if ( fontChanged ) dc.SetFont(normalfont); y += rowHeight; } // Refresh editor controls (seems not needed on msw) // NOTE: This code is mandatory for GTK! #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT if ( wasSelectedPainted ) { if ( m_wndEditor ) m_wndEditor->Refresh(); if ( m_wndEditor2 ) m_wndEditor2->Refresh(); } #endif return y; } // ----------------------------------------------------------------------- wxRect wxPropertyGrid::GetPropertyRect( const wxPGProperty* p1, const wxPGProperty* p2 ) const { wxRect r; if ( m_width < 10 || m_height < 10 || !m_pState->m_properties->GetCount() || p1 == (wxPGProperty*) NULL ) return wxRect(0,0,0,0); int vy = 0; // // Return rect which encloses the given property range int visTop = p1->GetY(); int visBottom; if ( p2 ) visBottom = p2->GetY() + m_lineHeight; else visBottom = m_height + visTop; // If seleced property is inside the range, we'll extend the range to include // control's size. wxPGProperty* selected = GetSelection(); if ( selected ) { int selectedY = selected->GetY(); if ( selectedY >= visTop && selectedY < visBottom ) { wxWindow* editor = GetEditorControl(); if ( editor ) { int visBottom2 = selectedY + editor->GetSize().y; if ( visBottom2 > visBottom ) visBottom = visBottom2; } } } return wxRect(0,visTop-vy,m_pState->m_width,visBottom-visTop); } // ----------------------------------------------------------------------- void wxPropertyGrid::DrawItems( const wxPGProperty* p1, const wxPGProperty* p2 ) { if ( m_frozen ) return; if ( m_pState->m_itemsAdded ) PrepareAfterItemsAdded(); wxRect rect = GetPropertyRect(p1, p2); if ( rect.width <= 0 ) return; // // Reduce editor rects wxRegion region(rect); if ( m_wndEditor ) region.Subtract(m_wndEditor->GetRect()); if ( m_wndEditor2 ) region.Subtract(m_wndEditor2->GetRect()); if ( m_labelEditor ) region.Subtract(m_labelEditor->GetRect()); wxRegionIterator rects(region); while ( rects ) { m_canvas->RefreshRect(rects.GetRect(), false); rects++; } } // ----------------------------------------------------------------------- void wxPropertyGrid::RefreshProperty( wxPGProperty* p ) { if ( m_pState->DoIsPropertySelected(p) ) { // NB: We must copy the selection. wxArrayPGProperty selection = m_pState->m_selection; DoSetSelection(selection, wxPG_SEL_FORCE); } DrawItemAndChildren(p); } // ----------------------------------------------------------------------- void wxPropertyGrid::RefreshEditor() { wxPGProperty* p = GetSelection(); if ( !p ) return; wxWindow* wnd = GetEditorControl(); if ( !wnd ) return; // Set editor font boldness - must do this before // calling UpdateControl(). if ( HasFlag(wxPG_BOLD_MODIFIED) ) { if ( p->HasFlag(wxPG_PROP_MODIFIED) ) wnd->SetFont(GetCaptionFont()); else wnd->SetFont(GetFont()); } const wxPGEditor* editorClass = p->GetEditorClass(); editorClass->UpdateControl(p, wnd); if ( p->IsValueUnspecified() ) { editorClass->SetValueToUnspecified(p, wnd); SetEditorAppearance(m_unspecifiedAppearance); } } // ----------------------------------------------------------------------- void wxPropertyGrid::DrawItemAndValueRelated( wxPGProperty* p ) { if ( m_frozen ) return; // Draw item, children, and parent too, if it is not category wxPGProperty* parent = p->GetParent(); while ( parent && !parent->IsCategory() && parent->GetParent() ) { DrawItem(parent); parent = parent->GetParent(); } DrawItemAndChildren(p); } void wxPropertyGrid::DrawItemAndChildren( wxPGProperty* p ) { wxCHECK_RET( p, wxT("invalid property id") ); // Do not draw if in non-visible page if ( p->GetParentState() != m_pState ) return; // do not draw a single item if multiple pending if ( m_pState->m_itemsAdded || m_frozen ) return; // Update child control. wxPGProperty* selected = GetSelection(); if ( selected && selected->GetParent() == p ) RefreshEditor(); const wxPGProperty* lastDrawn = p->GetLastVisibleSubItem(); DrawItems(p, lastDrawn); } // ----------------------------------------------------------------------- void wxPropertyGrid::Refresh( bool WXUNUSED(eraseBackground), const wxRect *rect ) { PrepareAfterItemsAdded(); wxWindow::Refresh(false); if ( m_canvas ) // TODO: Coordinate translation m_canvas->Refresh(false, rect); #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // I think this really helps only GTK+1.2 if ( m_wndEditor ) m_wndEditor->Refresh(); if ( m_wndEditor2 ) m_wndEditor2->Refresh(); #endif } // ----------------------------------------------------------------------- // wxPropertyGrid global operations // ----------------------------------------------------------------------- void wxPropertyGrid::Clear() { m_pState->DoClear(); m_propHover = NULL; m_prevVY = 0; RecalculateVirtualSize(); // Need to clear some area at the end if ( !m_frozen ) RefreshRect(wxRect(0, 0, m_width, m_height)); } // ----------------------------------------------------------------------- bool wxPropertyGrid::EnableCategories( bool enable ) { ClearSelection(false); if ( enable ) { // // Enable categories // m_windowStyle &= ~(wxPG_HIDE_CATEGORIES); } else { // // Disable categories // m_windowStyle |= wxPG_HIDE_CATEGORIES; } if ( !m_pState->EnableCategories(enable) ) return false; if ( !m_frozen ) { if ( m_windowStyle & wxPG_AUTO_SORT ) { m_pState->m_itemsAdded = 1; // force PrepareAfterItemsAdded(); } } else m_pState->m_itemsAdded = 1; // No need for RecalculateVirtualSize() here - it is already called in // wxPropertyGridState method above. Refresh(); return true; } // ----------------------------------------------------------------------- void wxPropertyGrid::SwitchState( wxPropertyGridState* pNewState ) { wxASSERT( pNewState ); wxASSERT( pNewState->GetGrid() ); if ( pNewState == m_pState ) return; wxArrayPGProperty oldSelection = m_pState->m_selection; // Call ClearSelection() instead of DoClearSelection() // so that selection clear events are not sent. ClearSelection(); m_pState->m_selection = oldSelection; bool orig_mode = m_pState->IsInNonCatMode(); bool new_state_mode = pNewState->IsInNonCatMode(); m_pState = pNewState; // Validate width int pgWidth = GetClientSize().x; if ( HasVirtualWidth() ) { int minWidth = pgWidth; if ( pNewState->m_width < minWidth ) { pNewState->m_width = minWidth; pNewState->CheckColumnWidths(); } } else { // // Just in case, fully re-center splitter if ( HasFlag( wxPG_SPLITTER_AUTO_CENTER ) ) pNewState->m_fSplitterX = -1.0; pNewState->OnClientWidthChange( pgWidth, pgWidth - pNewState->m_width ); } m_propHover = (wxPGProperty*) NULL; // If necessary, convert state to correct mode. if ( orig_mode != new_state_mode ) { // This should refresh as well. EnableCategories( orig_mode?false:true ); } else if ( !m_frozen ) { // Refresh, if not frozen. if ( m_pState->m_itemsAdded ) PrepareAfterItemsAdded(); // Reselect (Use SetSelection() instead of Do-variant so that // events won't be sent). SetSelection(m_pState->m_selection); RecalculateVirtualSize(0); Refresh(); } else m_pState->m_itemsAdded = 1; } // ----------------------------------------------------------------------- void wxPropertyGrid::Sort( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG() m_pState->Sort( p ); } // ----------------------------------------------------------------------- void wxPropertyGrid::Sort() { m_pState->Sort(); // Y-position of any open property editor may have changed CorrectEditorWidgetPosY(); } // ----------------------------------------------------------------------- // Call to SetSplitterPosition will always disable splitter auto-centering // if parent window is shown. void wxPropertyGrid::DoSetSplitterPosition_( int newxpos, bool refresh, int splitterIndex, bool allPages ) { if ( ( newxpos < wxPG_DRAG_MARGIN ) ) return; wxPropertyGridState* state = m_pState; state->DoSetSplitterPosition( newxpos, splitterIndex, allPages ); if ( refresh ) { if ( GetSelection() ) CorrectEditorWidgetSizeX(); Refresh(); } } // ----------------------------------------------------------------------- void wxPropertyGrid::ResetColumnSizes( bool enableAutoResizing ) { wxPropertyGridState* state = m_pState; if ( state ) state->ResetColumnSizes(false); if ( enableAutoResizing && ( m_windowStyle & wxPG_SPLITTER_AUTO_CENTER ) ) m_iFlags &= ~(wxPG_FL_DONT_CENTER_SPLITTER); } // ----------------------------------------------------------------------- void wxPropertyGrid::CenterSplitter( bool enableAutoResizing ) { SetSplitterPosition( m_width/2, true ); if ( enableAutoResizing && ( m_windowStyle & wxPG_SPLITTER_AUTO_CENTER ) ) m_iFlags &= ~(wxPG_FL_DONT_CENTER_SPLITTER); } // ----------------------------------------------------------------------- // wxPropertyGrid item iteration (GetNextProperty etc.) methods // ----------------------------------------------------------------------- // Returns nearest paint visible property (such that will be painted unless // window is scrolled or resized). If given property is paint visible, then // it itself will be returned wxPGProperty* wxPropertyGrid::GetNearestPaintVisible( wxPGProperty* p ) const { int vx,vy1;// Top left corner of client GetViewStart(&vx,&vy1); vy1 *= wxPG_PIXELS_PER_UNIT; int vy2 = vy1 + m_height; int propY = p->GetY2(m_lineHeight); if ( (propY + m_lineHeight) < vy1 ) { // Too high return DoGetItemAtY( vy1 ); } else if ( propY > vy2 ) { // Too low return DoGetItemAtY( vy2 ); } // Itself paint visible return p; } // ----------------------------------------------------------------------- void wxPropertyGrid::SetButtonShortcut( int keycode, bool ctrlDown, bool altDown ) { if ( keycode ) { m_pushButKeyCode = keycode; m_pushButKeyCodeNeedsCtrl = ctrlDown ? 1 : 0; m_pushButKeyCodeNeedsAlt = altDown ? 1 : 0; } else { m_pushButKeyCode = WXK_DOWN; m_pushButKeyCodeNeedsCtrl = 0; m_pushButKeyCodeNeedsAlt = 1; } } // ----------------------------------------------------------------------- // Methods related to change in value, value modification and sending events // ----------------------------------------------------------------------- // commits any changes in editor of selected property // return true if validation did not fail // flags are same as with DoSelectProperty bool wxPropertyGrid::CommitChangesFromEditor( wxUint32 flags ) { // Committing already? if ( m_inCommitChangesFromEditor ) return true; // Don't do this if already processing editor event. It might // induce recursive dialogs and crap like that. if ( m_iFlags & wxPG_FL_IN_ONCUSTOMEDITOREVENT ) { if ( m_inDoPropertyChanged ) return true; return false; } wxPGProperty* selected = GetSelection(); if ( m_wndEditor && IsEditorsValueModified() && (m_iFlags & wxPG_FL_INITIALIZED) && selected ) { m_inCommitChangesFromEditor = 1; wxVariant variant(selected->GetValueRef()); bool valueIsPending = false; // JACS - necessary to avoid new focus being found spuriously within OnIdle // due to another window getting focus wxWindow* oldFocus = m_curFocused; bool validationFailure = false; bool forceSuccess = (flags & (wxPG_SEL_NOVALIDATE|wxPG_SEL_FORCE)) ? true : false; m_chgInfo_changedProperty = NULL; // If truly modified, schedule value as pending. if ( selected->GetEditorClass()->ActualGetValueFromControl( variant, selected, GetEditorControl() ) ) { if ( DoEditorValidate() && PerformValidation(selected, variant) ) { valueIsPending = true; } else { validationFailure = true; } } else { EditorsValueWasNotModified(); } bool res = true; m_inCommitChangesFromEditor = 0; if ( validationFailure && !forceSuccess ) { if (oldFocus) { oldFocus->SetFocus(); m_curFocused = oldFocus; } res = OnValidationFailure(selected, variant); // Now prevent further validation failure messages if ( res ) { EditorsValueWasNotModified(); OnValidationFailureReset(selected); } } else if ( valueIsPending ) { DoPropertyChanged( selected, flags ); EditorsValueWasNotModified(); } return res; } return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::PerformValidation( wxPGProperty* p, wxVariant& pendingValue ) { // // Runs all validation functionality. // Returns true if value passes all tests. // //wxLogDebug(wxT("wxPropertyGrid::PerformValidation(p=%s)"),p->GetName().c_str()); m_validationInfo.m_failureBehavior = m_permanentValidationFailureBehavior; m_validationInfo.m_isFailing = true; if ( !wxPGIsVariantType(pendingValue, list) ) { if ( !p->ActualValidateValue(pendingValue, m_validationInfo) ) return false; } // // Adapt list to child values, if necessary wxVariant listValue = pendingValue; wxVariant* pPendingValue = &pendingValue; wxVariant* pList = NULL; // If parent has wxPG_PROP_AGGREGATE flag, or uses composite // string value, then we need treat as it was changed instead // (or, in addition, as is the case with composite string parent). // This includes creating list variant for child values. wxPGProperty* pwc = p->GetParent(); wxPGProperty* changedProperty = p; wxPGProperty* baseChangedProperty = changedProperty; wxVariant bcpPendingList; listValue = pendingValue; listValue.SetName(p->GetBaseName()); while ( pwc && (pwc->HasFlag(wxPG_PROP_AGGREGATE) || pwc->HasFlag(wxPG_PROP_COMPOSED_VALUE)) ) { wxVariantList tempList; wxVariant lv(tempList, pwc->GetBaseName()); lv.Append(listValue); listValue = lv; pPendingValue = &listValue; if ( pwc->HasFlag(wxPG_PROP_AGGREGATE) ) { baseChangedProperty = pwc; bcpPendingList = lv; } changedProperty = pwc; pwc = pwc->GetParent(); } //wxLogDebug(wxT("wxPropertyGrid::PerformValidation(changedProperty=%s)"),changedProperty->GetName().c_str()); //wxLogDebug(wxT("wxPropertyGrid::PerformValidation(baseChangedProperty=%s)"),baseChangedProperty->GetName().c_str()); wxVariant value; wxPGProperty* evtChangingProperty = changedProperty; if ( !wxPGIsVariantType(*pPendingValue, list) ) { value = *pPendingValue; } else { // Convert list to child values pList = pPendingValue; changedProperty->AdaptListToValue( *pPendingValue, &value ); } wxVariant evtChangingValue = value; // FIXME: After proper ValueToString()s added, remove // this. It is just a temporary fix, as evt_changing // will simply not work for wxPG_PROP_COMPOSED_VALUE // (unless it is selected, and textctrl editor is open). if ( changedProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) ) { evtChangingProperty = baseChangedProperty; if ( evtChangingProperty != p ) { evtChangingProperty->AdaptListToValue( bcpPendingList, &evtChangingValue ); } else { evtChangingValue = pendingValue; } } if ( evtChangingProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) ) { if ( changedProperty == GetSelection() ) { wxWindow* editorCtrl = GetEditorControl(); wxASSERT( editorCtrl->IsKindOf(CLASSINFO(wxTextCtrl)) ); evtChangingValue = wxStaticCast(editorCtrl, wxTextCtrl)->GetValue(); } else { wxLogDebug(wxT("WARNING: wxEVT_PG_CHANGING is about to happen with old value.")); } } wxASSERT( m_chgInfo_changedProperty == NULL ); m_chgInfo_changedProperty = changedProperty; m_chgInfo_baseChangedProperty = baseChangedProperty; m_chgInfo_pendingValue = value; if ( pList ) m_chgInfo_valueList = *pList; else m_chgInfo_valueList.MakeNull(); // If changedProperty is not property which value was edited, // then call wxPGProperty::ValidateValue() for that as well. if ( p != changedProperty && !wxPGIsVariantType(value, list) ) { if ( !changedProperty->ActualValidateValue(value, m_validationInfo) ) return false; } // SendEvent returns true if event was vetoed if ( SendEvent( wxEVT_PG_CHANGING, evtChangingProperty, &evtChangingValue ) ) return false; m_validationInfo.m_isFailing = false; return true; } // ----------------------------------------------------------------------- #if wxUSE_STATUSBAR wxStatusBar* wxPropertyGrid::GetStatusBar() { wxWindow* topWnd = ::wxGetTopLevelParent(this); if ( topWnd && topWnd->IsKindOf(CLASSINFO(wxFrame)) ) { wxFrame* pFrame = wxStaticCast(topWnd, wxFrame); if ( pFrame ) return pFrame->GetStatusBar(); } return NULL; } #endif // ----------------------------------------------------------------------- void wxPropertyGrid::DoShowPropertyError( wxPGProperty* WXUNUSED(property), const wxString& msg ) { if ( !msg.length() ) return; #if wxUSE_STATUSBAR if ( !wxPGGlobalVars->m_offline ) { wxStatusBar* pStatusBar = GetStatusBar(); if ( pStatusBar ) { pStatusBar->SetStatusText(msg); return; } } #endif ::wxMessageBox(msg, _("Property Error")); } // ----------------------------------------------------------------------- void wxPropertyGrid::DoHidePropertyError( wxPGProperty* WXUNUSED(property) ) { #if wxUSE_STATUSBAR if ( !wxPGGlobalVars->m_offline ) { wxStatusBar* pStatusBar = GetStatusBar(); if ( pStatusBar ) { pStatusBar->SetStatusText(wxEmptyString); return; } } #endif } // ----------------------------------------------------------------------- bool wxPropertyGrid::OnValidationFailure( wxPGProperty* property, wxVariant& invalidValue ) { if ( m_inOnValidationFailure ) return true; m_inOnValidationFailure = true; wxWindow* editor = GetEditorControl(); int vfb = m_validationInfo.m_failureBehavior; if ( m_inDoSelectProperty ) { // When property selection is being changed, do not display any // messages, if some were already shown for this property. if ( property->HasFlag(wxPG_PROP_INVALID_VALUE) ) { m_validationInfo.m_failureBehavior = vfb & ~(wxPG_VFB_SHOW_MESSAGE | wxPG_VFB_SHOW_MESSAGEBOX | wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR); } } // First call property's handler property->OnValidationFailure(invalidValue); bool res = DoOnValidationFailure(property, invalidValue); // // For non-wxTextCtrl editors, we do need to revert the value if ( !editor->IsKindOf(CLASSINFO(wxTextCtrl)) && property == GetSelection() ) { property->GetEditorClass()->UpdateControl(property, editor); } property->SetFlag(wxPG_PROP_INVALID_VALUE); m_validationInfo.m_failureBehavior = vfb; m_inOnValidationFailure = false; return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoOnValidationFailure( wxPGProperty* property, wxVariant& WXUNUSED(invalidValue) ) { int vfb = m_validationInfo.m_failureBehavior; if ( vfb & wxPG_VFB_BEEP ) ::wxBell(); if ( (vfb & wxPG_VFB_MARK_CELL) && !property->HasFlag(wxPG_PROP_INVALID_VALUE) ) { if ( !property->GetCell(0) && !property->GetCell(1) ) { wxColour vfbFg = *wxWHITE; wxColour vfbBg = *wxRED; property->SetCell(0, new wxPGCell(property->GetLabel(), wxNullBitmap, vfbFg, vfbBg)); property->SetCell(1, new wxPGCell(property->GetDisplayedString(), wxNullBitmap, vfbFg, vfbBg)); DrawItemAndChildren(property); if ( property == GetSelection() ) { SetInternalFlag(wxPG_FL_CELL_OVERRIDES_SEL); wxWindow* editor = GetEditorControl(); if ( editor ) { editor->SetForegroundColour(vfbFg); editor->SetBackgroundColour(vfbBg); } } } } if ( vfb & (wxPG_VFB_SHOW_MESSAGE | wxPG_VFB_SHOW_MESSAGEBOX | wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR) ) { wxString msg = m_validationInfo.m_failureMessage; if ( !msg.length() ) msg = _("You have entered invalid value. Press ESC to cancel editing."); #if wxUSE_STATUSBAR if ( vfb & wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR ) { if ( !wxPGGlobalVars->m_offline ) { wxStatusBar* pStatusBar = GetStatusBar(); if ( pStatusBar ) pStatusBar->SetStatusText(msg); } } #endif if ( vfb & wxPG_VFB_SHOW_MESSAGE ) DoShowPropertyError(property, msg); if ( vfb & wxPG_VFB_SHOW_MESSAGEBOX ) ::wxMessageBox(msg, _("Property Error")); } return (vfb & wxPG_VFB_STAY_IN_PROPERTY) ? false : true; } // ----------------------------------------------------------------------- void wxPropertyGrid::DoOnValidationFailureReset( wxPGProperty* property ) { int vfb = m_validationInfo.m_failureBehavior; if ( vfb & wxPG_VFB_MARK_CELL ) { property->SetCell(0, NULL); property->SetCell(1, NULL); ClearInternalFlag(wxPG_FL_CELL_OVERRIDES_SEL); if ( property == GetSelection() && GetEditorControl() ) { // Calling this will recreate the control, thus resetting its colour RefreshProperty(property); } else { DrawItemAndChildren(property); } } #if wxUSE_STATUSBAR if ( vfb & wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR ) { if ( !wxPGGlobalVars->m_offline ) { wxStatusBar* pStatusBar = GetStatusBar(); if ( pStatusBar ) pStatusBar->SetStatusText(wxEmptyString); } } #endif if ( vfb & wxPG_VFB_SHOW_MESSAGE ) { DoHidePropertyError(property); } m_validationInfo.m_isFailing = false; } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetValidationFailureBehavior( int vfbFlags ) { GetPropertyGrid()->m_permanentValidationFailureBehavior = vfbFlags; } // ----------------------------------------------------------------------- // flags are same as with DoSelectProperty bool wxPropertyGrid::DoPropertyChanged( wxPGProperty* p, unsigned int selFlags ) { if ( m_inDoPropertyChanged ) return true; wxPGProperty* selected = GetSelection(); m_pState->m_anyModified = 1; m_inDoPropertyChanged = 1; // If property's value is being changed, assume it is valid OnValidationFailureReset(selected); // Maybe need to update control wxASSERT( m_chgInfo_changedProperty != NULL ); // These values were calculated in PerformValidation() wxPGProperty* changedProperty = m_chgInfo_changedProperty; wxVariant value = m_chgInfo_pendingValue; wxPGProperty* topPaintedProperty = changedProperty; while ( !topPaintedProperty->IsCategory() && !topPaintedProperty->IsRoot() ) { topPaintedProperty = topPaintedProperty->GetParent(); } changedProperty->SetValue(value, &m_chgInfo_valueList, wxPG_SETVAL_BY_USER); // NB: Call GetEditorControl() as late as possible, because OnSetValue() // and perhaps other user-defined virtual functions may change it. wxWindow* editor = GetEditorControl(); // Set as Modified (not if dragging just began) if ( !(p->m_flags & wxPG_PROP_MODIFIED) ) { p->m_flags |= wxPG_PROP_MODIFIED; if ( p == selected && (m_windowStyle & wxPG_BOLD_MODIFIED) ) { if ( editor ) SetCurControlBoldFont(); } } wxPGProperty* pwc; // Propagate updates to parent(s) pwc = p; wxPGProperty* prevPwc = NULL; while ( prevPwc != topPaintedProperty ) { pwc->m_flags |= wxPG_PROP_MODIFIED; if ( pwc == selected && (m_windowStyle & wxPG_BOLD_MODIFIED) ) { if ( editor ) SetCurControlBoldFont(); } prevPwc = pwc; pwc = pwc->GetParent(); } // Draw the actual property DrawItemAndChildren( topPaintedProperty ); // // If value was set by wxPGProperty::OnEvent, then update the editor // control. if ( selFlags & wxPG_SEL_DIALOGVAL ) { RefreshEditor(); } else { #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT if ( m_wndEditor ) m_wndEditor->Refresh(); if ( m_wndEditor2 ) m_wndEditor2->Refresh(); #endif } // Sanity check wxASSERT( !changedProperty->GetParent()->HasFlag(wxPG_PROP_AGGREGATE) ); // If top parent has composite string value, then send to child parents, // starting from baseChangedProperty. if ( changedProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) ) { pwc = m_chgInfo_baseChangedProperty; while ( pwc != changedProperty ) { SendEvent( wxEVT_PG_CHANGED, pwc, NULL ); pwc = pwc->GetParent(); } } SendEvent( wxEVT_PG_CHANGED, changedProperty, NULL ); m_inDoPropertyChanged = 0; return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::ChangePropertyValue( wxPGPropArg id, wxVariant newValue ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) m_chgInfo_changedProperty = NULL; if ( PerformValidation(p, newValue) ) { DoPropertyChanged(p); return true; } else { OnValidationFailure(p, newValue); } return false; } // ----------------------------------------------------------------------- // Runs wxValidator for the selected property bool wxPropertyGrid::DoEditorValidate() { #if wxUSE_VALIDATORS // With traditional validator style, we dont need to more if ( !(GetExtraStyle() & wxPG_EX_LEGACY_VALIDATORS) ) return true; if ( m_iFlags & wxPG_FL_VALIDATION_FAILED ) { return false; } wxWindow* wnd = GetEditorControl(); wxPGProperty* selected = GetSelection(); wxValidator* validator = NULL; if ( selected ) validator = selected->GetValidator(); if ( validator && wnd ) { // Use TextCtrl of ODComboBox instead if ( wnd->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) { wnd = ((wxPGOwnerDrawnComboBox*)wnd)->GetTextCtrl(); if ( !wnd ) return true; } validator->SetWindow(wnd); // Instead setting the flag after the failure, we set // it before checking and then clear afterwards if things // went fine. This trick is necessary since focus events // may be triggered while in Validate. m_iFlags |= wxPG_FL_VALIDATION_FAILED; if ( !validator->Validate(this) ) { // If you dpm't want to display message multiple times per change, // comment the following line. m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED); return false; } m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED); } #endif return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::ProcessEvent(wxEvent& event) { wxWindow* wnd = (wxWindow*) event.GetEventObject(); if ( wnd && wnd->IsKindOf(CLASSINFO(wxWindow)) ) { wxWindow* parent = wnd->GetParent(); if ( parent && (parent == m_canvas || parent->GetParent() == m_canvas) ) { OnCustomEditorEvent((wxCommandEvent&)event); return true; } } return wxPanel::ProcessEvent(event); } // ----------------------------------------------------------------------- // NB: It may really not be wxCommandEvent - must check if necessary // (usually not). void wxPropertyGrid::OnCustomEditorEvent( wxCommandEvent &event ) { // It is possible that this handler receives event even before // the control has been properly initialized. Let's skip the // handling in that case. if ( !m_pState ) return; // Don't care about the event if it originated from the // 'label editor'. In this function we only care about the // property value editor. if ( m_labelEditor && event.GetId() == m_labelEditor->GetId() ) { event.Skip(); return; } wxPGProperty* selected = GetSelection(); // // Somehow, event is handled after property has been deselected. // Possibly, but very rare. if ( !selected || selected->HasFlag(wxPG_PROP_BEING_DELETED) ) return; if ( m_iFlags & wxPG_FL_IN_ONCUSTOMEDITOREVENT || m_inDoSelectProperty || m_inOnValidationFailure || // Also don't handle editor event if wxEVT_PG_CHANGED or // similar is currently doing something (showing a // message box, for instance). m_processedEvent ) return; wxVariant pendingValue(selected->GetValueRef()); wxWindow* wnd = GetEditorControl(); wxWindow* editorWnd = wxDynamicCast(event.GetEventObject(), wxWindow); int selFlags = 0; bool wasUnspecified = selected->IsValueUnspecified(); int usesAutoUnspecified = selected->UsesAutoUnspecified(); bool valueIsPending = false; m_chgInfo_changedProperty = NULL; m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED|wxPG_FL_VALUE_CHANGE_IN_EVENT); // // Filter out excess wxTextCtrl modified events if ( event.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED && wnd ) { if ( wnd->IsKindOf(CLASSINFO(wxTextCtrl)) ) { wxTextCtrl* tc = (wxTextCtrl*) wnd; wxString newTcValue = tc->GetValue(); if ( m_prevTcValue == newTcValue ) return; m_prevTcValue = newTcValue; } else if ( wnd->IsKindOf(CLASSINFO(wxPGComboCtrl)) ) { wxPGComboCtrl* cc = (wxPGComboCtrl*) wnd; wxString newTcValue = cc->GetTextCtrl()->GetValue(); if ( m_prevTcValue == newTcValue ) return; m_prevTcValue = newTcValue; } } SetInternalFlag(wxPG_FL_IN_ONCUSTOMEDITOREVENT); bool validationFailure = false; bool buttonWasHandled = false; // // Try common button handling if ( m_wndEditor2 && event.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED ) { wxPGEditorDialogAdapter* adapter = selected->GetEditorDialog(); if ( adapter ) { buttonWasHandled = true; // Store as res2, as previously (and still currently alternatively) // dialogs can be shown by handling wxEVT_COMMAND_BUTTON_CLICKED // in wxPGProperty::OnEvent(). adapter->ShowDialog( this, selected ); delete adapter; } } if ( !buttonWasHandled ) { if ( wnd || m_wndEditor2 ) { // First call editor class' event handler. const wxPGEditor* editor = selected->GetEditorClass(); if ( editor->OnEvent( this, selected, editorWnd, event ) ) { // If changes, validate them if ( DoEditorValidate() ) { if ( editor->ActualGetValueFromControl( pendingValue, selected, wnd ) ) valueIsPending = true; // Mark value always as pending if validation is currently // failing and value was not unspecified if ( !valueIsPending && !pendingValue.IsNull() && m_validationInfo.m_isFailing ) valueIsPending = true; } else { validationFailure = true; } } } // Then the property's custom handler (must be always called, unless // validation failed). if ( !validationFailure ) buttonWasHandled = selected->OnEvent( this, editorWnd, event ); } // SetValueInEvent(), as called in one of the functions referred above // overrides editor's value. if ( m_iFlags & wxPG_FL_VALUE_CHANGE_IN_EVENT ) { valueIsPending = true; pendingValue = m_changeInEventValue; selFlags |= wxPG_SEL_DIALOGVAL; } if ( !validationFailure && valueIsPending ) if ( !PerformValidation(selected, pendingValue) ) validationFailure = true; if ( validationFailure ) { OnValidationFailure(selected, pendingValue); } else if ( valueIsPending ) { selFlags |= ( !wasUnspecified && selected->IsValueUnspecified() && usesAutoUnspecified ) ? wxPG_SEL_SETUNSPEC : 0; DoPropertyChanged(selected, selFlags); EditorsValueWasNotModified(); // Unfocus on enter press? if ( GetExtraStyle() & wxPG_EX_UNFOCUS_ON_ENTER && event.GetEventType() == wxEVT_COMMAND_TEXT_ENTER) { UnfocusEditor(); } } else { // No value after all // Let unhandled button click events go to the parent if ( !buttonWasHandled && event.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED ) { wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED,GetId()); GetEventHandler()->AddPendingEvent(evt); } } ClearInternalFlag(wxPG_FL_IN_ONCUSTOMEDITOREVENT); } // ----------------------------------------------------------------------- // wxPropertyGrid editor control helper methods // ----------------------------------------------------------------------- wxRect wxPropertyGrid::GetEditorWidgetRect( wxPGProperty* p, int column ) const { int itemy = p->GetY2(m_lineHeight); int vy = 0; int splitterX = m_pState->DoGetSplitterPosition(column-1); int colEnd = splitterX + m_pState->m_colWidths[column]; int imageOffset = 0; if ( column == 1 ) { // TODO: If custom image detection changes from current, change this. if ( m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE ) { //m_iFlags |= wxPG_FL_CUR_USES_CUSTOM_IMAGE; int iw = p->OnMeasureImage().x; if ( iw < 1 ) iw = wxPG_CUSTOM_IMAGE_WIDTH; imageOffset = p->GetImageOffset(iw); } } else if ( column == 0 ) { splitterX += (p->m_depth-1) * m_subgroup_extramargin; } return wxRect ( splitterX+imageOffset+wxPG_XBEFOREWIDGET+wxPG_CONTROL_MARGIN+1, itemy-vy, colEnd-splitterX-wxPG_XBEFOREWIDGET-wxPG_CONTROL_MARGIN-imageOffset-1, m_lineHeight-1 ); } // ----------------------------------------------------------------------- wxRect wxPropertyGrid::GetImageRect( wxPGProperty* p, int item ) const { wxSize sz = GetImageSize(p, item); return wxRect(wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1, wxPG_CUSTOM_IMAGE_SPACINGY, sz.x, sz.y); } // return size of custom paint image wxSize wxPropertyGrid::GetImageSize( wxPGProperty* p, int item ) const { // If called with NULL property, then return default image // size for properties that use image. if ( !p ) return wxSize(wxPG_CUSTOM_IMAGE_WIDTH,wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight)); wxSize cis = p->OnMeasureImage(item); int choiceCount = p->GetChoiceCount(); int comVals = p->GetDisplayedCommonValueCount(); if ( item >= choiceCount && comVals > 0 ) { unsigned int cvi = item-choiceCount; cis = GetCommonValue(cvi)->GetRenderer()->GetImageSize(NULL, 1, cvi); } else if ( item >= 0 && choiceCount == 0 ) return wxSize(0, 0); if ( cis.x < 0 ) { if ( cis.x <= -1 ) cis.x = wxPG_CUSTOM_IMAGE_WIDTH; } if ( cis.y <= 0 ) { if ( cis.y >= -1 ) cis.y = wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight); else cis.y = -cis.y; } return cis; } // ----------------------------------------------------------------------- // takes scrolling into account void wxPropertyGrid::ImprovedClientToScreen( int* px, int* py ) { int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; vx*=wxPG_PIXELS_PER_UNIT; *px -= vx; *py -= vy; ClientToScreen( px, py ); } // ----------------------------------------------------------------------- // custom set cursor void wxPropertyGrid::CustomSetCursor( int type, bool override ) { if ( type == m_curcursor && !override ) return; wxCursor* cursor = &wxPG_DEFAULT_CURSOR; if ( type == wxCURSOR_SIZEWE ) cursor = m_cursorSizeWE; m_canvas->SetCursor( *cursor ); m_curcursor = type; } // ----------------------------------------------------------------------- wxString wxPropertyGrid::GetUnspecifiedValueText( int argFlags ) const { const wxPGCell& ua = GetUnspecifiedValueAppearance(); if ( ua.HasText() && !(argFlags & wxPG_FULL_VALUE) && !(argFlags & wxPG_EDITABLE_VALUE) ) return ua.GetText(); return wxEmptyString; } // ----------------------------------------------------------------------- // wxPropertyGrid property selection // ----------------------------------------------------------------------- #define CONNECT_CHILD(EVT,FUNCTYPE,FUNC) \ wnd->Connect(id, EVT, \ (wxObjectEventFunction) (wxEventFunction) \ FUNCTYPE (&wxPropertyGrid::FUNC), \ NULL, this ); // Setups event handling for child control void wxPropertyGrid::SetupEventHandling( wxWindow* argWnd, int id ) { wxWindow* wnd = argWnd; if ( argWnd == m_wndEditor ) { CONNECT_CHILD(wxEVT_MOTION,(wxMouseEventFunction),OnMouseMoveChild) CONNECT_CHILD(wxEVT_LEFT_UP,(wxMouseEventFunction),OnMouseUpChild) CONNECT_CHILD(wxEVT_LEFT_DOWN,(wxMouseEventFunction),OnMouseClickChild) CONNECT_CHILD(wxEVT_RIGHT_UP,(wxMouseEventFunction),OnMouseRightClickChild) CONNECT_CHILD(wxEVT_ENTER_WINDOW,(wxMouseEventFunction),OnMouseEntry) CONNECT_CHILD(wxEVT_LEAVE_WINDOW,(wxMouseEventFunction),OnMouseEntry) } else { CONNECT_CHILD(wxEVT_NAVIGATION_KEY,(wxNavigationKeyEventFunction),OnNavigationKey) } CONNECT_CHILD(wxEVT_KEY_DOWN,(wxCharEventFunction),OnChildKeyDown) CONNECT_CHILD(wxEVT_KEY_UP,(wxCharEventFunction),OnChildKeyUp) CONNECT_CHILD(wxEVT_KILL_FOCUS,(wxFocusEventFunction),OnFocusEvent) } void wxPropertyGrid::DestroyEditorWnd( wxWindow* wnd ) { if ( !wnd ) return; #if wxCHECK_VERSION(2,8,0) wxPendingDelete.Append(wnd); #else // Do not free editors immediately (for sake of processing events) if ( !m_windowsToDelete ) m_windowsToDelete = new wxArrayPtrVoid; m_windowsToDelete->push_back(wnd); #endif wnd->Hide(); } void wxPropertyGrid::FreeEditors() { // // Return focus back to canvas from children (this is required at least for // GTK+, which, unlike Windows, clears focus when control is destroyed // instead of moving it to closest parent). wxWindow* focus = wxWindow::FindFocus(); if ( focus ) { wxWindow* parent = focus->GetParent(); while ( parent ) { if ( parent == m_canvas ) { SetFocusOnCanvas(); break; } parent = parent->GetParent(); } } DestroyEditorWnd(m_wndEditor2); m_wndEditor2 = NULL; DestroyEditorWnd(m_wndEditor); m_wndEditor = NULL; } // Call with NULL to de-select property bool wxPropertyGrid::DoSelectProperty( wxPGProperty* p, unsigned int flags ) { /* if (p) wxLogDebug(wxT("SelectProperty( %s (%s[%i]) )"),p->m_label.c_str(), p->m_parent->m_label.c_str(),p->GetIndexInParent()); else wxLogDebug(wxT("SelectProperty( NULL, -1 )")); */ if ( m_inDoSelectProperty ) return true; m_inDoSelectProperty = true; #if !wxCHECK_VERSION(2,8,0) // // Delete windows pending for deletion if ( m_windowsToDelete && !m_inDoPropertyChanged && m_windowsToDelete->size() ) { unsigned int i; for ( i=0; isize(); i++ ) delete ((wxWindow*)((*m_windowsToDelete)[i])); m_windowsToDelete->clear(); } #endif if ( !m_pState ) { m_inDoSelectProperty = false; return false; } wxArrayPGProperty prevSelection = m_pState->m_selection; wxPGProperty* prevFirstSel; if ( prevSelection.size() > 0 ) prevFirstSel = prevSelection[0]; else prevFirstSel = NULL; if ( prevFirstSel && prevFirstSel->HasFlag(wxPG_PROP_BEING_DELETED) ) prevFirstSel = NULL; // Always send event, as this is indirect call DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE); // // If we are frozen, then just set the values. if ( m_frozen ) { m_iFlags &= ~(wxPG_FL_ABNORMAL_EDITOR); m_editorFocused = 0; m_pState->DoSetSelection(p); m_selColumn = 1; // If frozen, always free controls. But don't worry, as Thaw will // recall SelectProperty to recreate them. FreeEditors(); // Prevent any further selection measures in this call p = (wxPGProperty*) NULL; } else { // Is it the same? if ( prevFirstSel == p && prevSelection.size() <= 1 && !(flags & wxPG_SEL_FORCE) ) { // Only set focus if not deselecting if ( p ) { if ( flags & wxPG_SEL_FOCUS ) { if ( m_wndEditor ) { m_wndEditor->SetFocus(); m_editorFocused = 1; } } else { SetFocusOnCanvas(); } } m_inDoSelectProperty = 0; return true; } // // First, deactivate previous if ( prevFirstSel ) { // Must double-check if this is an selected in case of forceswitch if ( p != prevFirstSel ) { if ( !CommitChangesFromEditor(flags) ) { // Validation has failed, so we can't exit the previous editor //::wxMessageBox(_("Please correct the value or press ESC to cancel the edit."), // _("Invalid Value"),wxOK|wxICON_ERROR); m_inDoSelectProperty = 0; return false; } } // This should be called after CommitChangesFromEditor(), so that // OnValidationFailure() still has information on property's // validation state. OnValidationFailureReset(prevFirstSel); FreeEditors(); m_iFlags &= ~(wxPG_FL_ABNORMAL_EDITOR); EditorsValueWasNotModified(); } SetInternalFlag(wxPG_FL_IN_SELECT_PROPERTY); m_pState->DoSetSelection(p); // // Redraw unselected for ( unsigned int i=0; iGetY2(m_lineHeight); int splitterX = GetSplitterPosition(); m_editorFocused = 0; m_iFlags |= wxPG_FL_PRIMARY_FILLS_ENTIRE; if ( p != prevFirstSel ) m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED); wxASSERT( m_wndEditor == (wxWindow*) NULL ); // Do we need OnMeasureCalls? wxSize imsz = p->OnMeasureImage(); // // Only create editor for non-disabled non-caption if ( !p->IsCategory() && !(p->m_flags & wxPG_PROP_DISABLED) ) { // do this for non-caption items m_selColumn = 1; const wxPGEditor* editor = p->GetEditorClass(); wxCHECK_MSG(editor, false, wxT("NULL editor class not allowed")); // Do we need to paint the custom image, if any? m_iFlags &= ~(wxPG_FL_CUR_USES_CUSTOM_IMAGE); if ( (p->m_flags & wxPG_PROP_CUSTOMIMAGE) && !editor->CanContainCustomImage() ) m_iFlags |= wxPG_FL_CUR_USES_CUSTOM_IMAGE; wxRect grect = GetEditorWidgetRect(p, m_selColumn); wxPoint goodPos = grect.GetPosition(); // Reset editor appearance wxPGCell emptyCell; m_editorAppearance.Assign(emptyCell); m_iFlags &= ~wxPG_FL_FIXED_WIDTH_EDITOR; wxPGWindowList wndList = editor->CreateControls(this, p, goodPos, grect.GetSize()); // // Below, bear in mind the difference between primaryCtrl and m_wndEditor: // m_wndEditor is the actual wxWindow on canvas, and primaryCtrl is // the actual editor control. They may different if wxPGClipperWindow is // used for this editor. // m_wndEditor = wndList.m_primary; m_wndEditor2 = wndList.m_secondary; wxWindow* primaryCtrl = GetEditorControl(); // NOTE: It is allowed for m_wndEditor to be NULL - in this case // value is drawn as normal, and m_wndEditor2 is assumed // to be a right-aligned button that triggers a separate editor // window. if ( m_wndEditor ) { wxCHECK_MSG( m_wndEditor->GetParent() == m_canvas, false, wxT("CreateControls must use result of ") wxT("wxPropertyGrid::GetPanel() as parent ") wxT("of controls.") ); // Set validator, if any #if wxUSE_VALIDATORS if ( !(GetExtraStyle() & wxPG_EX_LEGACY_VALIDATORS) ) { wxValidator* validator = p->GetValidator(); if ( validator ) primaryCtrl->SetValidator(*validator); } #endif if ( m_wndEditor->GetSize().y > (m_lineHeight+6) ) m_iFlags |= wxPG_FL_ABNORMAL_EDITOR; // If it has modified status, use bold font // (must be done before capturing m_ctrlXAdjust) if ( (p->m_flags & wxPG_PROP_MODIFIED) && (m_windowStyle & wxPG_BOLD_MODIFIED) ) SetCurControlBoldFont(); // // Fix TextCtrl indentation wxTextCtrl* tc = NULL; if ( primaryCtrl->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) tc = ((wxPGOwnerDrawnComboBox*)primaryCtrl)->GetTextCtrl(); else tc = wxDynamicCast(primaryCtrl, wxTextCtrl); if ( tc ) wxPG_TextCtrl_SetMargins(tc, wxPoint(0, -1)); // Store x relative to splitter (we'll need it). m_ctrlXAdjust = m_wndEditor->GetPosition().x - splitterX; // Check if background clear is not necessary wxPoint pos = m_wndEditor->GetPosition(); if ( pos.x > (splitterX+1) || pos.y > propY ) { m_iFlags &= ~(wxPG_FL_PRIMARY_FILLS_ENTIRE); } m_wndEditor->SetSizeHints(3, 3); if ( primaryCtrl != m_wndEditor ) primaryCtrl->SetSizeHints(3, 3); SetupEventHandling(primaryCtrl, wxPG_SUBID1); if ( p->IsValueUnspecified() ) { editor->SetValueToUnspecified(p, primaryCtrl); SetEditorAppearance(m_unspecifiedAppearance); } // Focus and select all (wxTextCtrl, wxComboBox etc) if ( flags & wxPG_SEL_FOCUS ) { primaryCtrl->SetFocus(); editor->OnFocus(p, primaryCtrl); } } if ( m_wndEditor2 ) { wxCHECK_MSG( m_wndEditor2->GetParent() == m_canvas, false, wxT("CreateControls must use result of ") wxT("wxPropertyGrid::GetPanel() as parent ") wxT("of controls.") ); // Get proper id for wndSecondary m_wndSecId = m_wndEditor2->GetId(); wxWindowList children = m_wndEditor2->GetChildren(); wxWindowList::iterator node = children.begin(); if ( node != children.end() ) m_wndSecId = ((wxWindow*)*node)->GetId(); m_wndEditor2->SetSizeHints(3,3); m_wndEditor2->Show(); SetupEventHandling(m_wndEditor2,wxPG_SUBID2); // If no primary editor, focus to button to allow // it to interprete ENTER etc. // NOTE: Due to problems focusing away from it, this // has been disabled. /* if ( (flags & wxPG_SEL_FOCUS) && !m_wndEditor ) m_wndEditor2->SetFocus(); */ } if ( flags & wxPG_SEL_FOCUS ) m_editorFocused = 1; } else { // wxGTK atleast seems to need this (wxMSW not) SetFocusOnCanvas(); } EditorsValueWasNotModified(); // If it's inside collapsed section, expand parent, scroll, etc. // Also, if it was partially visible, scroll it into view. if ( !(flags & wxPG_SEL_NONVISIBLE) ) EnsureVisible( p ); if ( m_wndEditor ) { m_wndEditor->Show(true); } } if ( !(flags & wxPG_SEL_NO_REFRESH) ) DrawItem(p); ClearInternalFlag(wxPG_FL_IN_SELECT_PROPERTY); } #if wxUSE_STATUSBAR // // Show help text in status bar. // (if found and grid not embedded in manager with help box and // style wxPG_EX_HELP_AS_TOOLTIPS is not used). // if ( !(GetExtraStyle() & wxPG_EX_HELP_AS_TOOLTIPS) ) { wxStatusBar* statusbar = GetStatusBar(); if ( statusbar ) { const wxString* pHelpString = (const wxString*) NULL; if ( p ) { pHelpString = &p->GetHelpString(); if ( pHelpString->length() ) { // Set help box text. statusbar->SetStatusText( *pHelpString ); m_iFlags |= wxPG_FL_STRING_IN_STATUSBAR; } } if ( (!pHelpString || !pHelpString->length()) && (m_iFlags & wxPG_FL_STRING_IN_STATUSBAR) ) { // Clear help box - but only if it was written // by us at previous time. statusbar->SetStatusText( m_emptyString ); m_iFlags &= ~(wxPG_FL_STRING_IN_STATUSBAR); } } } #endif m_inDoSelectProperty = 0; // call wx event handler (here so that it also occurs on deselection) SendEvent( wxEVT_PG_SELECTED, p, NULL ); return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::UnfocusEditor() { wxPGProperty* selected = GetSelection(); if ( !selected || !m_wndEditor || m_frozen ) return true; if ( !CommitChangesFromEditor(0) ) { // Allow UnfocusEditor() to work when called from CommitChangesFromEditor() if ( !(m_iFlags & wxPG_FL_IN_ONCUSTOMEDITOREVENT) ) return false; } SetFocusOnCanvas(); DrawItem(selected); return true; } // ----------------------------------------------------------------------- // wxPropertyGrid expand/collapse state // ----------------------------------------------------------------------- bool wxPropertyGrid::DoCollapse( wxPGProperty* p, bool sendEvents ) { wxPGProperty* pwc = wxStaticCast(p, wxPGProperty); wxPGProperty* selected = GetSelection(); // If active editor was inside collapsed section, then disable it if ( selected && selected->IsSomeParent (p) ) { if ( !ClearSelection() ) return false; } // Store dont-center-splitter flag 'cause we need to temporarily set it wxUint32 old_flag = m_iFlags & wxPG_FL_DONT_CENTER_SPLITTER; m_iFlags |= wxPG_FL_DONT_CENTER_SPLITTER; bool res = m_pState->DoCollapse(pwc); if ( res ) { if ( sendEvents ) SendEvent( wxEVT_PG_ITEM_COLLAPSED, p ); RecalculateVirtualSize(); // Redraw etc. only if collapsed was visible. if (pwc->IsVisible() && !m_frozen && ( !pwc->IsCategory() || !(m_windowStyle & wxPG_HIDE_CATEGORIES) ) ) { // When item is collapsed so that scrollbar would move, // graphics mess is about (unless we redraw everything). Refresh(); } } // Clear dont-center-splitter flag if it wasn't set m_iFlags = (m_iFlags & ~(wxPG_FL_DONT_CENTER_SPLITTER)) | old_flag; return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoExpand( wxPGProperty* p, bool sendEvents ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGProperty* pwc = (wxPGProperty*)p; // Store dont-center-splitter flag 'cause we need to temporarily set it wxUint32 old_flag = m_iFlags & wxPG_FL_DONT_CENTER_SPLITTER; m_iFlags |= wxPG_FL_DONT_CENTER_SPLITTER; bool res = m_pState->DoExpand(pwc); if ( res ) { if ( sendEvents ) SendEvent( wxEVT_PG_ITEM_EXPANDED, p ); RecalculateVirtualSize(); // Redraw etc. only if expanded was visible. if ( pwc->IsVisible() && !m_frozen && ( !pwc->IsCategory() || !(m_windowStyle & wxPG_HIDE_CATEGORIES) ) ) { // Redraw #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT Refresh(); #else DrawItems(pwc, NULL); #endif } } // Clear dont-center-splitter flag if it wasn't set m_iFlags = (m_iFlags & ~(wxPG_FL_DONT_CENTER_SPLITTER)) | old_flag; return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::DoHideProperty( wxPGProperty* p, bool hide, int flags ) { if ( m_frozen ) return m_pState->DoHideProperty(p, hide, flags); wxArrayPGProperty selection = m_pState->m_selection; // Must use a copy int selRemoveCount = 0; for ( unsigned int i=0; iIsSomeParent(p) ) { if ( !DoRemoveFromSelection(p, flags) ) return false; selRemoveCount += 1; } } m_pState->DoHideProperty(p, hide, flags); RecalculateVirtualSize(); Refresh(); return true; } // ----------------------------------------------------------------------- // wxPropertyGrid size related methods // ----------------------------------------------------------------------- void wxPropertyGrid::RecalculateVirtualSize( int WXUNUSED(forceXPos) ) { if ( HasInternalFlag(wxPG_FL_RECALCULATING_VIRTUAL_SIZE) || m_frozen || !m_pState ) return; // // If virtual height was changed, then recalculate editor control position(s) if ( m_pState->m_vhCalcPending ) CorrectEditorWidgetPosY(); m_pState->EnsureVirtualHeight(); #ifdef __WXDEBUG__ int by1 = m_pState->GetVirtualHeight(); int by2 = m_pState->GetActualVirtualHeight(); if ( by1 != by2 ) { wxString s = wxString::Format(wxT("VirtualHeight=%i, ActualVirtualHeight=%i, should match!"), by1, by2); wxASSERT_MSG( false, s.c_str() ); wxLogDebug(s); } #endif m_iFlags |= wxPG_FL_RECALCULATING_VIRTUAL_SIZE; int x = m_pState->m_width; int y = m_pState->m_virtualHeight; int width, height; GetClientSize(&width, &height); if ( !HasVirtualWidth() ) { m_pState->SetVirtualWidth(width); x = width; } m_width = width; m_height = height; // Notify users about height problem wxASSERT_MSG( y <= 32767, wxT("Sorry, wxPanel height limitations exceeded") ); // Set the min size so that the sizer can correctly determine how large // the virtual window should be. // // NB: It is possible that m_canvas is NULL (at least on OS X). if ( m_canvas ) m_canvas->SetMinSize(wxSize(10, y)); FitInside(); m_pState->CheckColumnWidths(); if ( GetSelection() ) CorrectEditorWidgetSizeX(); m_iFlags &= ~wxPG_FL_RECALCULATING_VIRTUAL_SIZE; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnResize( wxSizeEvent& event ) { if ( !(m_iFlags & wxPG_FL_INITIALIZED) ) return; int width, height; GetClientSize(&width,&height); m_width = width; m_height = height; m_visPropArray.SetCount((height/m_lineHeight)+10); #if wxPG_DOUBLE_BUFFER if ( !(GetExtraStyle() & wxPG_EX_NATIVE_DOUBLE_BUFFERING) ) { int dblh = (m_lineHeight*2); if ( !m_doubleBuffer ) { // Create double buffer bitmap to draw on, if none int w = (width>250)?width:250; int h = height + dblh; h = (h>400)?h:400; m_doubleBuffer = new wxBitmap( w, h ); } else { int w = m_doubleBuffer->GetWidth(); int h = m_doubleBuffer->GetHeight(); // Double buffer must be large enough if ( w < width || h < (height+dblh) ) { if ( w < width ) w = width; if ( h < (height+dblh) ) h = height + dblh; delete m_doubleBuffer; m_doubleBuffer = new wxBitmap( w, h ); } } } #endif m_pState->OnClientWidthChange( width, event.GetSize().x - m_ncWidth, true ); m_ncWidth = event.GetSize().x; if ( !m_frozen ) { // This will also call RecalculateVirtualSize() PrepareAfterItemsAdded(); Refresh(); } } // ----------------------------------------------------------------------- void wxPropertyGrid::SetVirtualWidth( int width ) { if ( width == -1 ) { // Disable virtual width width = GetClientSize().x; ClearInternalFlag(wxPG_FL_HAS_VIRTUAL_WIDTH); } else { // Enable virtual width SetInternalFlag(wxPG_FL_HAS_VIRTUAL_WIDTH); } m_pState->SetVirtualWidth( width ); } // ----------------------------------------------------------------------- void wxPropertyGridState::SetVirtualWidth( int width ) { // Sometimes width less than 0 is offered. Let's make things easy for // everybody and deal with it here. if ( width < 0 ) width = 0; wxPropertyGrid* pg = GetGrid(); int gw = pg->GetClientSize().x; if ( width < gw ) width = gw; m_width = width; } // ----------------------------------------------------------------------- void wxPropertyGridState::OnClientWidthChange( int newWidth, int widthChange, bool fromOnResize ) { wxPropertyGrid* pg = GetGrid(); if ( pg->HasVirtualWidth() ) { if ( m_width < newWidth ) SetVirtualWidth( newWidth ); CheckColumnWidths(widthChange); } else { SetVirtualWidth( newWidth ); // This should be done before splitter auto centering // NOTE: Splitter auto-centering is done in this function. if ( !fromOnResize ) widthChange = 0; CheckColumnWidths(widthChange); if ( !(GetGrid()->GetInternalFlags() & wxPG_FL_SPLITTER_PRE_SET) && (GetGrid()->GetInternalFlags() & wxPG_FL_DONT_CENTER_SPLITTER) ) { long timeSinceCreation = (::wxGetLocalTimeMillis() - GetGrid()->m_timeCreated).ToLong(); // If too long, don't set splitter if ( timeSinceCreation < 250 ) { if ( m_properties->GetCount() ) { SetSplitterLeft( false ); } else { DoSetSplitterPosition( newWidth / 2 ); GetGrid()->ClearInternalFlag(wxPG_FL_SPLITTER_PRE_SET); } } } } } // ----------------------------------------------------------------------- // wxPropertyGrid mouse event handling // ----------------------------------------------------------------------- // selFlags uses same values DoSelectProperty's flags // Returns true if event was vetoed. bool wxPropertyGrid::SendEvent( int eventType, wxPGProperty* p, wxVariant* pValue, unsigned int selFlags, unsigned int column ) { // Send property grid event of specific type and with specific property wxPropertyGridEvent evt( eventType, m_eventObject->GetId() ); evt.SetPropertyGrid(this); evt.SetEventObject(m_eventObject); evt.SetProperty(p); evt.SetColumn(column); if ( pValue ) { evt.SetCanVeto(true); evt.SetupValidationInfo(); m_validationInfo.m_pValue = pValue; } else if ( !(selFlags & wxPG_SEL_NOVALIDATE) ) { evt.SetCanVeto(true); } wxEvtHandler* evtHandler = m_eventObject->GetEventHandler(); wxPropertyGridEvent* prevProcessedEvent = m_processedEvent; m_processedEvent = &evt; evtHandler->ProcessEvent(evt); m_processedEvent = prevProcessedEvent; return evt.WasVetoed(); } // ----------------------------------------------------------------------- // Return false if should be skipped bool wxPropertyGrid::HandleMouseClick( int x, unsigned int y, wxMouseEvent &event ) { bool res = true; // Need to set focus? if ( !(m_iFlags & wxPG_FL_FOCUSED) ) { SetFocusOnCanvas(); } wxPropertyGridState* state = m_pState; int splitterHit; int splitterHitOffset; int columnHit = state->HitTestH( x, &splitterHit, &splitterHitOffset ); wxPGProperty* p = DoGetItemAtY(y); if ( p ) { int depth = (int)p->GetDepth() - 1; int marginEnds = m_marginWidth + ( depth * m_subgroup_extramargin ); if ( x >= marginEnds ) { // Outside margin. if ( p->IsCategory() ) { // Click on a category.caption wxPropertyCategory* pwc = (wxPropertyCategory*)p; int textX = m_marginWidth + ((unsigned int)((pwc->m_depth-1)*m_subgroup_extramargin)); // Expand, collapse, activate etc. if click on text or left of splitter. if ( x >= textX && ( x < (textX+pwc->GetTextExtent(this, m_captionFont)+(wxPG_CAPRECTXMARGIN*2)) || columnHit == 0 ) ) { if ( !AddToSelectionFromInputEvent( p, columnHit, &event ) ) return res; // On double-click, expand/collapse. if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) ) { if ( pwc->IsExpanded() ) DoCollapse( p, true ); else DoExpand( p, true ); } } } else if ( splitterHit == -1 ) { // Click on cell unsigned int selFlag = 0; if ( columnHit == 1 ) { m_iFlags |= wxPG_FL_ACTIVATION_BY_CLICK; selFlag = wxPG_SEL_FOCUS; } if ( !AddToSelectionFromInputEvent( p, columnHit, &event, selFlag ) ) return res; m_iFlags &= ~(wxPG_FL_ACTIVATION_BY_CLICK); if ( p->GetChildCount() && !p->IsCategory() ) // On double-click, expand/collapse. if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) ) { wxPGProperty* pwc = (wxPGProperty*)p; if ( pwc->IsExpanded() ) DoCollapse( p, true ); else DoExpand( p, true ); } res = false; } else { // Click on splitter if ( !(m_windowStyle & wxPG_STATIC_SPLITTER) ) { if ( event.GetEventType() == wxEVT_LEFT_DCLICK ) { // Double-clicking the splitter causes auto-centering ResetColumnSizes( true ); } else if ( m_dragStatus == 0 ) { // // Begin draggin the splitter // // send event DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE); if ( m_wndEditor ) { // Changes must be committed here or the // value won't be drawn correctly if ( !CommitChangesFromEditor() ) return res; m_wndEditor->Show ( false ); } if ( !(m_iFlags & wxPG_FL_MOUSE_CAPTURED) ) { m_canvas->CaptureMouse(); m_iFlags |= wxPG_FL_MOUSE_CAPTURED; } m_dragStatus = 1; m_draggedSplitter = splitterHit; m_dragOffset = splitterHitOffset; wxClientDC dc(m_canvas); #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // Fixes button disappearance bug if ( m_wndEditor2 ) m_wndEditor2->Show ( false ); #endif m_startingSplitterX = x - splitterHitOffset; } } } } else { // Click on margin if ( p->GetChildCount() ) { int nx = x + m_marginWidth - marginEnds; // Normalize x. // // Fine tune cell button x if ( !p->IsCategory() ) nx -= IN_CELL_EXPANDER_BUTTON_X_ADJUST; if ( (nx >= m_gutterWidth && nx < (m_gutterWidth+m_iconWidth)) ) { int y2 = y % m_lineHeight; if ( (y2 >= m_buttonSpacingY && y2 < (m_buttonSpacingY+m_iconHeight)) ) { // On click on expander button, expand/collapse if ( ((wxPGProperty*)p)->IsExpanded() ) DoCollapse( p, true ); else DoExpand( p, true ); } } } } } return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::HandleMouseRightClick( int WXUNUSED(x), unsigned int WXUNUSED(y), wxMouseEvent& event ) { if ( m_propHover ) { // Select property here as well wxPGProperty* p = m_propHover; AddToSelectionFromInputEvent(p, m_colHover, &event); // Send right click event. SendEvent( wxEVT_PG_RIGHT_CLICK, p ); return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGrid::HandleMouseDoubleClick( int WXUNUSED(x), unsigned int WXUNUSED(y), wxMouseEvent& event ) { if ( m_propHover ) { // Select property here as well wxPGProperty* p = m_propHover; AddToSelectionFromInputEvent(p, m_colHover, &event); // Send double-click event. SendEvent( wxEVT_PG_DOUBLE_CLICK, m_propHover ); return true; } return false; } // ----------------------------------------------------------------------- #if wxPG_SUPPORT_TOOLTIPS void wxPropertyGrid::SetToolTip( const wxString& tipString ) { if ( tipString.length() ) { m_canvas->SetToolTip(tipString); } else { #if wxPG_ALLOW_EMPTY_TOOLTIPS m_canvas->SetToolTip( m_emptyString ); #else m_canvas->SetToolTip( NULL ); #endif } } #endif // #if wxPG_SUPPORT_TOOLTIPS // ----------------------------------------------------------------------- // Return false if should be skipped bool wxPropertyGrid::HandleMouseMove( int x, unsigned int y, wxMouseEvent &event ) { // Safety check (needed because mouse capturing may // otherwise freeze the control) if ( m_dragStatus > 0 && !event.Dragging() ) { HandleMouseUp(x,y,event); } wxPropertyGridState* state = m_pState; int splitterHit; int splitterHitOffset; int columnHit = state->HitTestH( x, &splitterHit, &splitterHitOffset ); int splitterX = x - splitterHitOffset; m_colHover = columnHit; if ( m_dragStatus > 0 ) { if ( x > (m_marginWidth + wxPG_DRAG_MARGIN) && x < (m_pState->m_width - wxPG_DRAG_MARGIN) ) { int newSplitterX = x - m_dragOffset; int splitterX = x - splitterHitOffset; // Splitter redraw required? if ( newSplitterX != splitterX ) { // Move everything SetInternalFlag(wxPG_FL_DONT_CENTER_SPLITTER); state->DoSetSplitterPosition( newSplitterX, m_draggedSplitter, false ); state->m_fSplitterX = (float) newSplitterX; if ( GetSelection() ) CorrectEditorWidgetSizeX(); Update(); Refresh(); } m_dragStatus = 2; } return false; } else { int ih = m_lineHeight; int sy = y; #if wxPG_SUPPORT_TOOLTIPS wxPGProperty* prevHover = m_propHover; unsigned char prevSide = m_mouseSide; #endif int curPropHoverY = y - (y % ih); // On which item it hovers if ( ( !m_propHover ) || ( m_propHover && ( sy < m_propHoverY || sy >= (m_propHoverY+ih) ) ) ) { // Mouse moves on another property m_propHover = DoGetItemAtY(y); m_propHoverY = curPropHoverY; // Send hover event SendEvent( wxEVT_PG_HIGHLIGHTED, m_propHover ); } #if wxPG_SUPPORT_TOOLTIPS // Store which side we are on m_mouseSide = 0; if ( columnHit == 1 ) m_mouseSide = 2; else if ( columnHit == 0 ) m_mouseSide = 1; // // If tooltips are enabled, show label or value as a tip // in case it doesn't otherwise show in full length. // if ( m_windowStyle & wxPG_TOOLTIPS ) { wxToolTip* tooltip = m_canvas->GetToolTip(); if ( m_propHover != prevHover || prevSide != m_mouseSide ) { if ( m_propHover && !m_propHover->IsCategory() ) { if ( GetExtraStyle() & wxPG_EX_HELP_AS_TOOLTIPS ) { // Show help string as a tooltip wxString tipString = m_propHover->GetHelpString(); SetToolTip(tipString); } else { // Show cropped value string as a tooltip wxString tipString; int space = 0; if ( m_mouseSide == 1 ) { tipString = m_propHover->m_label; space = splitterX-m_marginWidth-3; } else if ( m_mouseSide == 2 ) { tipString = m_propHover->GetDisplayedString(); space = m_width - splitterX; if ( m_propHover->m_flags & wxPG_PROP_CUSTOMIMAGE ) space -= wxPG_CUSTOM_IMAGE_WIDTH + wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2; } if ( space ) { int tw, th; GetTextExtent( tipString, &tw, &th, 0, 0 ); if ( tw > space ) { SetToolTip( tipString ); } } else { if ( tooltip ) { #if wxPG_ALLOW_EMPTY_TOOLTIPS m_canvas->SetToolTip( m_emptyString ); #else m_canvas->SetToolTip( NULL ); #endif } } } } else { if ( tooltip ) { #if wxPG_ALLOW_EMPTY_TOOLTIPS m_canvas->SetToolTip( m_emptyString ); #else m_canvas->SetToolTip( NULL ); #endif } } } } #endif if ( splitterHit == -1 || !m_propHover || HasFlag(wxPG_STATIC_SPLITTER) ) { // hovering on something else if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor( wxCURSOR_ARROW ); } else { // Do not allow splitter cursor on caption items. // (also not if we were dragging and its started // outside the splitter region) if ( m_propHover && !m_propHover->IsCategory() && !event.Dragging() ) { // hovering on splitter // NB: Condition disabled since MouseLeave event (from the editor control) cannot be // reliably detected. //if ( m_curcursor != wxCURSOR_SIZEWE ) CustomSetCursor( wxCURSOR_SIZEWE, true ); return false; } else { // hovering on something else if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor( wxCURSOR_ARROW ); } } // // Multi select by dragging // if ( (GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION) && event.LeftIsDown() && m_propHover && GetSelection() && columnHit != 1 && !state->DoIsPropertySelected(m_propHover) ) { // Additional requirement is that the hovered property // is adjacent to edges of selection. const wxArrayPGProperty& selection = GetSelectedProperties(); // Since categories cannot be selected along with 'other' // properties, exclude them from iterator flags. int iterFlags = wxPG_ITERATE_VISIBLE & (~wxPG_PROP_CATEGORY); for ( int i=(selection.size()-1); i>=0; i-- ) { // TODO: This could be optimized by keeping track of // which properties are at the edges of selection. wxPGProperty* selProp = selection[i]; if ( state->ArePropertiesAdjacent(m_propHover, selProp, iterFlags) ) { DoAddToSelection(m_propHover); break; } } } } return true; } // ----------------------------------------------------------------------- // Also handles Leaving event bool wxPropertyGrid::HandleMouseUp( int x, unsigned int WXUNUSED(y), wxMouseEvent &WXUNUSED(event) ) { wxPropertyGridState* state = m_pState; bool res = false; int splitterHit; int splitterHitOffset; state->HitTestH( x, &splitterHit, &splitterHitOffset ); // No event type check - basicly calling this method should // just stop dragging. // Left up after dragged? if ( m_dragStatus >= 1 ) { // // End Splitter Dragging // // DO NOT ENABLE FOLLOWING LINE! // (it is only here as a reminder to not to do it) //splitterX = x; // Disable splitter auto-centering m_iFlags |= wxPG_FL_DONT_CENTER_SPLITTER; // This is necessary to return cursor if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED ) { m_canvas->ReleaseMouse(); m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED); } // Set back the default cursor, if necessary if ( splitterHit == -1 || !m_propHover ) { CustomSetCursor( wxCURSOR_ARROW ); } m_dragStatus = 0; // Control background needs to be cleared wxPGProperty* selected = GetSelection(); if ( !(m_iFlags & wxPG_FL_PRIMARY_FILLS_ENTIRE) && selected ) DrawItem( selected ); if ( m_wndEditor ) { m_wndEditor->Show( true ); } #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // Fixes button disappearance bug if ( m_wndEditor2 ) m_wndEditor2->Show( true ); #endif // This clears the focus. m_editorFocused = 0; } return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::OnMouseCommon( wxMouseEvent& event, int* px, int* py ) { int splitterX = GetSplitterPosition(); //int ux, uy; //CalcUnscrolledPosition( event.m_x, event.m_y, &ux, &uy ); int ux = event.m_x; int uy = event.m_y; wxWindow* wnd = GetEditorControl(); // Hide popup on clicks if ( event.GetEventType() != wxEVT_MOTION ) if ( wnd && wnd->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) { ((wxPGOwnerDrawnComboBox*)wnd)->HidePopup(); } wxRect r; if ( wnd ) r = wnd->GetRect(); if ( wnd == (wxWindow*) NULL || m_dragStatus || ( ux <= (splitterX + wxPG_SPLITTERX_DETECTMARGIN2) || ux >= (r.x+r.width) || event.m_y < r.y || event.m_y >= (r.y+r.height) ) ) { *px = ux; *py = uy; return true; } else { if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor ( wxCURSOR_ARROW ); } return false; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseClick( wxMouseEvent &event ) { int x, y; if ( OnMouseCommon( event, &x, &y ) ) { HandleMouseClick(x,y,event); } #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseRightClick( wxMouseEvent &event ) { int x, y; CalcUnscrolledPosition( event.m_x, event.m_y, &x, &y ); HandleMouseRightClick(x,y,event); #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseDoubleClick( wxMouseEvent &event ) { // Always run standard mouse-down handler as well OnMouseClick(event); int x, y; CalcUnscrolledPosition( event.m_x, event.m_y, &x, &y ); HandleMouseDoubleClick(x,y,event); #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseMove( wxMouseEvent &event ) { int x, y; if ( OnMouseCommon( event, &x, &y ) ) { HandleMouseMove(x,y,event); } #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseMoveBottom( wxMouseEvent& WXUNUSED(event) ) { // Called when mouse moves in the empty space below the properties. CustomSetCursor( wxCURSOR_ARROW ); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseUp( wxMouseEvent &event ) { int x, y; if ( OnMouseCommon( event, &x, &y ) ) { HandleMouseUp(x,y,event); } #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseEntry( wxMouseEvent &event ) { // This may get called from child control as well, so event's // mouse position cannot be relied on. if ( event.Entering() ) { if ( !(m_iFlags & wxPG_FL_MOUSE_INSIDE) ) { // TODO: Fix this (detect parent and only do // cursor trick if it is a manager). wxASSERT( GetParent() ); GetParent()->SetCursor(wxNullCursor); m_iFlags |= wxPG_FL_MOUSE_INSIDE; } else GetParent()->SetCursor(wxNullCursor); } else if ( event.Leaving() ) { // Without this, wxSpinCtrl editor will sometimes have wrong cursor m_canvas->SetCursor( wxNullCursor ); // Get real cursor position wxPoint pt = ScreenToClient(::wxGetMousePosition()); if ( ( pt.x <= 0 || pt.y <= 0 || pt.x >= m_width || pt.y >= m_height ) ) { { if ( (m_iFlags & wxPG_FL_MOUSE_INSIDE) ) { m_iFlags &= ~(wxPG_FL_MOUSE_INSIDE); } if ( m_dragStatus ) wxPropertyGrid::HandleMouseUp ( -1, 10000, event ); } } } event.Skip(); } // ----------------------------------------------------------------------- // Common code used by various OnMouseXXXChild methods. bool wxPropertyGrid::OnMouseChildCommon( wxMouseEvent &event, int* px, int *py ) { wxWindow* topCtrlWnd = (wxWindow*)event.GetEventObject(); wxASSERT( topCtrlWnd ); int x, y; event.GetPosition(&x,&y); AdjustPosForClipperWindow( topCtrlWnd, &x, &y ); int splitterX = GetSplitterPosition(); wxRect r = topCtrlWnd->GetRect(); if ( !m_dragStatus && x > (splitterX-r.x+wxPG_SPLITTERX_DETECTMARGIN2) && y >= 0 && y < r.height \ ) { if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor ( wxCURSOR_ARROW ); event.Skip(); } else { CalcUnscrolledPosition( event.m_x + r.x, event.m_y + r.y, \ px, py ); return true; } return false; } void wxPropertyGrid::OnMouseClickChild( wxMouseEvent &event ) { int x,y; if ( OnMouseChildCommon(event,&x,&y) ) { bool res = HandleMouseClick(x,y,event); if ( !res ) event.Skip(); } } void wxPropertyGrid::OnMouseRightClickChild( wxMouseEvent &event ) { int x,y; wxASSERT( m_wndEditor ); // These coords may not be exact (about +-2), // but that should not matter (right click is about item, not position). wxPoint pt = m_wndEditor->GetPosition(); CalcUnscrolledPosition( event.m_x + pt.x, event.m_y + pt.y, &x, &y ); // FIXME: Used to set m_propHover to selection here. Was it really // necessary? bool res = HandleMouseRightClick(x,y,event); if ( !res ) event.Skip(); } void wxPropertyGrid::OnMouseMoveChild( wxMouseEvent &event ) { int x,y; if ( OnMouseChildCommon(event,&x,&y) ) { bool res = HandleMouseMove(x,y,event); if ( !res ) event.Skip(); } } void wxPropertyGrid::OnMouseUpChild( wxMouseEvent &event ) { int x,y; if ( OnMouseChildCommon(event,&x,&y) ) { bool res = HandleMouseUp(x,y,event); if ( !res ) event.Skip(); } } // ----------------------------------------------------------------------- // wxPropertyGrid keyboard event handling // ----------------------------------------------------------------------- void wxPropertyGrid::SendNavigationKeyEvent( int dir ) { wxNavigationKeyEvent evt; evt.SetFlags(wxNavigationKeyEvent::FromTab| (dir?wxNavigationKeyEvent::IsForward: wxNavigationKeyEvent::IsBackward)); evt.SetEventObject(this); m_canvas->GetEventHandler()->AddPendingEvent(evt); } // // Add wxKeyEvent GetModifiers() for wx2.6 // #if !wxCHECK_VERSION(2,8,0) static inline int wxPG_wxKeyEvent_GetModifiers( const wxKeyEvent& event ) { int modifiers = 0; if ( event.ControlDown() ) modifiers |= wxMOD_CONTROL; if ( event.AltDown() ) modifiers |= wxMOD_ALT; if ( event.ShiftDown() ) modifiers |= wxMOD_SHIFT; return modifiers; } #endif // !wxCHECK_VERSION(2,8,0) int wxPropertyGrid::KeyEventToActions(wxKeyEvent &event, int* pSecond) const { // Translates wxKeyEvent to wxPG_ACTION_XXX int keycode = event.GetKeyCode(); #if wxCHECK_VERSION(2,8,0) int modifiers = event.GetModifiers(); #else int modifiers = wxPG_wxKeyEvent_GetModifiers(event); #endif wxASSERT( !(modifiers&~(0xFFFF)) ); int hashMapKey = (keycode & 0xFFFF) | ((modifiers & 0xFFFF) << 16); wxPGHashMapI2I::const_iterator it = m_actionTriggers.find(hashMapKey); if ( it == m_actionTriggers.end() ) return 0; if ( pSecond ) { int second = (it->second>>16) & 0xFFFF; *pSecond = second; } return (it->second & 0xFFFF); } void wxPropertyGrid::AddActionTrigger( int action, int keycode, int modifiers ) { wxASSERT( !(modifiers&~(0xFFFF)) ); int hashMapKey = (keycode & 0xFFFF) | ((modifiers & 0xFFFF) << 16); wxPGHashMapI2I::iterator it = m_actionTriggers.find(hashMapKey); if ( it != m_actionTriggers.end() ) { // This key combination is already used // Can add secondary? wxASSERT_MSG( !(it->second&~(0xFFFF)), wxT("You can only add up to two separate actions per key combination.") ); action = it->second | (action<<16); } m_actionTriggers[hashMapKey] = action; } void wxPropertyGrid::ClearActionTriggers( int action ) { wxPGHashMapI2I::iterator it; bool didSomething; do { didSomething = false; for ( it = m_actionTriggers.begin(); it != m_actionTriggers.end(); it++ ) { if ( it->second == action ) { m_actionTriggers.erase(it); didSomething = true; break; } } } while ( didSomething ); } static void CopyTextToClipboard( const wxString& text ) { if ( wxTheClipboard->Open() ) { // This data objects are held by the clipboard, // so do not delete them in the app. wxTheClipboard->SetData( new wxTextDataObject(text) ); wxTheClipboard->Close(); } } void wxPropertyGrid::HandleKeyEvent(wxKeyEvent &event) { // // Handles key event when editor control is not focused. // wxASSERT( !m_frozen ); if ( m_frozen ) return; // Travelsal between items, collapsing/expanding, etc. int keycode = event.GetKeyCode(); if ( keycode == WXK_TAB ) { if ( HasFlag(wxTAB_TRAVERSAL) ) SendNavigationKeyEvent( event.ShiftDown()?0:1 ); else event.Skip(); return; } // Ignore Alt and Control when they are down alone if ( keycode == WXK_ALT || keycode == WXK_CONTROL ) { event.Skip(); return; } int secondAction; int action = KeyEventToActions(event, &secondAction); wxPGProperty* selected = GetSelection(); if ( selected ) { // Show dialog? if ( ButtonTriggerKeyTest(event) ) return; wxPGProperty* p = selected; if ( action == wxPG_ACTION_COPY ) { CopyTextToClipboard(p->GetDisplayedString()); } else { // Travel and expand/collapse int selectDir = -2; if ( p->GetChildCount() ) { if ( action == wxPG_ACTION_COLLAPSE_PROPERTY || secondAction == wxPG_ACTION_COLLAPSE_PROPERTY ) { if ( (m_windowStyle & wxPG_HIDE_MARGIN) || Collapse(p) ) keycode = 0; } else if ( action == wxPG_ACTION_EXPAND_PROPERTY || secondAction == wxPG_ACTION_EXPAND_PROPERTY ) { if ( (m_windowStyle & wxPG_HIDE_MARGIN) || Expand(p) ) keycode = 0; } } if ( keycode ) { if ( action == wxPG_ACTION_PREV_PROPERTY || secondAction == wxPG_ACTION_PREV_PROPERTY ) { selectDir = -1; } else if ( action == wxPG_ACTION_NEXT_PROPERTY || secondAction == wxPG_ACTION_NEXT_PROPERTY ) { selectDir = 1; } else { event.Skip(); } } if ( selectDir >= -1 ) { p = wxPropertyGridIterator::OneStep( m_pState, wxPG_ITERATE_VISIBLE, p, selectDir ); if ( p ) DoSelectProperty(p); } } } else { // If nothing was selected, select the first item now // (or navigate out of tab). if ( action != wxPG_ACTION_CANCEL_EDIT && secondAction != wxPG_ACTION_CANCEL_EDIT ) { wxPGProperty* p = wxPropertyGridInterface::GetFirst(); if ( p ) DoSelectProperty(p); } } } // ----------------------------------------------------------------------- // Potentially handles a keyboard event for editor controls. // Returns false if event should *not* be skipped (on true it can // be optionally skipped). // Basicly, false means that SelectProperty was called (or was about // to be called, if canDestroy was false). bool wxPropertyGrid::HandleChildKey( wxKeyEvent& event ) { bool res = true; wxPGProperty* selected = GetSelection(); if ( !selected || !m_wndEditor ) { return true; } int action = KeyEventToAction(event); // Unfocus? if ( action == wxPG_ACTION_CANCEL_EDIT ) { // // Esc cancels any changes if ( IsEditorsValueModified() ) { EditorsValueWasNotModified(); // Update the control as well selected->GetEditorClass()->SetControlStringValue( selected, GetEditorControl(), selected->GetDisplayedString() ); } OnValidationFailureReset(selected); res = false; UnfocusEditor(); } else if ( action == wxPG_ACTION_COPY ) { // NB: There is some problem with getting native cut-copy-paste keys to go through // for embedded editor wxTextCtrl. This is why we emulate. // wxTextCtrl* tc = GetEditorTextCtrl(); if ( tc ) { wxString sel = tc->GetStringSelection(); if ( sel.length() ) CopyTextToClipboard(sel); } else { CopyTextToClipboard(selected->GetDisplayedString()); } } else if ( action == wxPG_ACTION_CUT ) { wxTextCtrl* tc = GetEditorTextCtrl(); if ( tc ) { long from, to; tc->GetSelection(&from, &to); if ( from < to ) { CopyTextToClipboard(tc->GetStringSelection()); if ( !tc->HasFlag(wxTE_READONLY) ) tc->Remove(from, to); } } } else if ( action == wxPG_ACTION_PASTE ) { wxTextCtrl* tc = GetEditorTextCtrl(); if ( tc && !tc->HasFlag(wxTE_READONLY) ) { if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported( wxDF_TEXT )) { wxTextDataObject data; wxTheClipboard->GetData( data ); long from, to; tc->GetSelection(&from, &to); if ( from < to ) { tc->Remove(from, to); tc->WriteText(data.GetText()); } else { tc->WriteText(data.GetText()); } } wxTheClipboard->Close(); } } } else if ( action == wxPG_ACTION_SELECT_ALL ) { wxTextCtrl* tc = GetEditorTextCtrl(); if ( tc ) tc->SetSelection(-1, -1); } int keycode = event.GetKeyCode(); // // Do not ever skip page up or page down. Those events would propagate // into the wxScrolledWindow and may cause unwanted scrolling (at least // under wxGTK). if ( keycode == WXK_PAGEUP || keycode == WXK_PAGEDOWN ) res = false; return res; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnKey( wxKeyEvent &event ) { // // Events to editor controls should get relayed here. // wxWindow* focused = wxWindow::FindFocus(); wxWindow* primaryCtrl = GetEditorControl(); if ( primaryCtrl && (focused==primaryCtrl || m_editorFocused) ) { // Child key must be processed here, since it can // destroy the control which is referred by its own // event handling. HandleChildKey( event ); } else HandleKeyEvent( event ); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnKeyUp(wxKeyEvent &event) { m_keyComboConsumed = 0; event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnNavigationKey( wxNavigationKeyEvent& event ) { // Ignore events that occur very close to focus set if ( m_iFlags & wxPG_FL_IGNORE_NEXT_NAVKEY ) { m_iFlags &= ~(wxPG_FL_IGNORE_NEXT_NAVKEY); event.Skip(); return; } wxPGProperty* next = (wxPGProperty*) NULL; wxPGProperty* selected = GetSelection(); int dir = event.GetDirection()?1:-1; if ( selected ) { if ( dir == 1 && (m_wndEditor || m_wndEditor2) ) { wxWindow* focused = wxWindow::FindFocus(); wxWindow* wndToCheck = GetEditorControl(); // ODComboBox focus goes to its text ctrl, so we need to use it instead if ( wndToCheck && wndToCheck->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) { wxTextCtrl* comboTextCtrl = ((wxPGOwnerDrawnComboBox*)wndToCheck)->GetTextCtrl(); if ( comboTextCtrl ) wndToCheck = comboTextCtrl; } /* // Because of problems navigating from wxButton, do not go to it. if ( !wndToCheck ) { // No primary, use secondary wndToCheck = m_wndEditor2; } // If it has editor button, focus to it after the primary editor. // NB: Doesn't work since wxButton on wxMSW doesn't seem to propagate // key events (yes, I'm using wxWANTS_CHARS with it, and yes I // have somewhat debugged in window.cpp itself). else if ( focused == wndToCheck && m_wndEditor2 && !(GetExtraStyle() & wxPG_EX_NO_TAB_TO_BUTTON) ) { wndToCheck = m_wndEditor2; wxLogDebug(wxT("Exp1")); } */ if ( focused != wndToCheck && wndToCheck ) { wndToCheck->SetFocus(); // Select all text in wxTextCtrl etc. if ( m_wndEditor && wndToCheck == m_wndEditor ) selected->GetEditorClass()->OnFocus(selected,wndToCheck); m_editorFocused = 1; next = selected; } } if ( !next ) { next = wxPropertyGridIterator::OneStep(m_pState, wxPG_ITERATE_VISIBLE, selected, dir); if ( next ) { // This allows preventing NavigateOut to occur DoSelectProperty( next, wxPG_SEL_FOCUS ); } } } if ( !next ) event.Skip(); } // ----------------------------------------------------------------------- bool wxPropertyGrid::ButtonTriggerKeyTest( wxKeyEvent &event ) { int keycode = event.GetKeyCode(); // Does the keycode trigger button? if ( keycode == m_pushButKeyCode && m_wndEditor2 && (!m_pushButKeyCodeNeedsAlt || event.AltDown()) && (!m_pushButKeyCodeNeedsCtrl || event.ControlDown()) ) { m_keyComboConsumed = 1; wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED,m_wndEditor2->GetId()); GetEventHandler()->AddPendingEvent(evt); return true; } return false; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnChildKeyDown( wxKeyEvent &event ) { int keycode = event.GetKeyCode(); // Ignore Alt and Control when they are down alone if ( keycode == WXK_ALT || keycode == WXK_CONTROL ) { event.Skip(); return; } if ( ButtonTriggerKeyTest(event) ) return; if ( HandleChildKey(event) == true ) event.Skip(); GetEventHandler()->AddPendingEvent(event); } void wxPropertyGrid::OnChildKeyUp( wxKeyEvent &event ) { m_keyComboConsumed = 0; GetEventHandler()->AddPendingEvent(event); event.Skip(); } // ----------------------------------------------------------------------- // wxPropertyGrid miscellaneous event handling // ----------------------------------------------------------------------- void wxPropertyGrid::OnIdle( wxIdleEvent& WXUNUSED(event) ) { // // Check if the focus is in this control or one of its children wxWindow* newFocused = wxWindow::FindFocus(); if ( newFocused != m_curFocused ) HandleFocusChange( newFocused ); // // Check if top-level parent has changed if ( !(GetExtraStyle() & wxPG_EX_DISABLE_TLP_TRACKING) ) { wxWindow* tlp = ::wxGetTopLevelParent(this); if ( tlp != m_tlp ) { OnTLPChanging(tlp); } } // // Resolve pending property removals if ( m_deletedProperties.size() > 0 ) { wxArrayPGProperty& arr = m_deletedProperties; for ( unsigned int i=0; i 0 ) { wxArrayPGProperty& arr = m_removedProperties; for ( unsigned int i=0; iGetParent(); } // Notify editor control about it receiving focus if ( wasEditorFocused && m_curFocused != newFocused ) { wxPGProperty* p = GetSelection(); if ( p ) { const wxPGEditor* editor = p->GetEditorClass(); ResetEditorAppearance(); editor->OnFocus(p, GetEditorControl()); } } m_curFocused = newFocused; if ( (m_iFlags & wxPG_FL_FOCUSED) != (oldFlags & wxPG_FL_FOCUSED) ) { // On each focus kill, mark the next nav key event // to be ignored (can't do on set focus since the // event would occur before it). if ( !(m_iFlags & wxPG_FL_FOCUSED) ) { m_iFlags |= wxPG_FL_IGNORE_NEXT_NAVKEY; // Need to store changed value CommitChangesFromEditor(); } else { /* // // Preliminary code for tab-order respecting // tab-traversal (but should be moved to // OnNav handler) // wxWindow* prevFocus = event.GetWindow(); wxWindow* useThis = this; if ( m_iFlags & wxPG_FL_IN_MANAGER ) useThis = GetParent(); if ( prevFocus && prevFocus->GetParent() == useThis->GetParent() ) { wxList& children = useThis->GetParent()->GetChildren(); wxNode* node = children.Find(prevFocus); if ( node->GetNext() && useThis == node->GetNext()->GetData() ) DoSelectProperty(GetFirst()); else if ( node->GetPrevious () && useThis == node->GetPrevious()->GetData() ) DoSelectProperty(GetLastProperty()); } */ m_iFlags &= ~(wxPG_FL_IGNORE_NEXT_NAVKEY); } // Redraw selected wxPGProperty* selected = GetSelection(); if ( selected && (m_iFlags & wxPG_FL_INITIALIZED) ) DrawItem( selected ); } } void wxPropertyGrid::OnFocusEvent( wxFocusEvent& event ) { if ( event.GetEventType() == wxEVT_SET_FOCUS ) { HandleFocusChange((wxWindow*)event.GetEventObject()); // Line changed to "else" when applying wxPropertyGrid patch #1675902 //else if ( event.GetWindow() ) } else { HandleFocusChange(event.GetWindow()); } event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnChildFocusEvent( wxChildFocusEvent& event ) { HandleFocusChange((wxWindow*)event.GetEventObject()); #if !wxCHECK_VERSION(2,8,8) event.Skip(); #endif } // ----------------------------------------------------------------------- void wxPropertyGrid::OnScrollEvent( wxScrollWinEvent &event ) { m_iFlags |= wxPG_FL_SCROLLED; event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnCaptureChange( wxMouseCaptureChangedEvent& WXUNUSED(event) ) { if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED ) { m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED); } } // ----------------------------------------------------------------------- // Property text-based storage // ----------------------------------------------------------------------- void wxPGProperty::SetAttributes( const wxPGAttributeStorage& attributes ) { wxPGAttributeStorage::const_iterator it = attributes.StartIteration(); wxVariant variant; while ( attributes.GetNext(it, variant) ) SetAttribute( variant.GetName(), variant ); } // ----------------------------------------------------------------------- // Returns name of property without 'Property' at the end, and 'wx' // in the beginning (if any). wxString wxPropertyGridInterface::GetPropertyShortClassName( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxEmptyString) if ( !p->IsCategory() ) { const wxChar* src = p->GetClassName(); wxString s; if ( src[0] == wxT('w') && src[1] == wxT('x') ) s = &src[2]; else s = src; wxASSERT( (((int)s.length())-8) > 0 ); s.Truncate(s.length()-8); return s; } return wxT("Category"); } wxPGProperty* wxPropertyGridInterface::GetPropertyByNameA( wxPGPropNameStr name ) const { wxPGProperty* p = GetPropertyByName(name); wxASSERT_MSG(p,wxString::Format(wxT("no property with name '%s'"),name.c_str())); return p; } // ---------------------------------------------------------------------------- void wxPropertyGridInterface::PropertiesToNames( wxArrayString* names, const wxArrayPGProperty& properties ) const { unsigned int i; for ( i=0; iAdd( properties[i]->GetName() ); } // ---------------------------------------------------------------------------- void wxPropertyGridInterface::NamesToProperties( wxArrayPGProperty* properties, const wxArrayString& names ) const { unsigned int i; for ( i=0; ipush_back(p); } } // ---------------------------------------------------------------------------- // VariantDatas // ---------------------------------------------------------------------------- #if !wxCHECK_VERSION(2,9,0) IMPLEMENT_ABSTRACT_CLASS(wxPGVariantData, wxVariantData) #endif WX_PG_IMPLEMENT_VARIANT_DATA(wxPGVariantDataPoint, wxPoint) WX_PG_IMPLEMENT_VARIANT_DATA(wxPGVariantDataSize, wxSize) WX_PG_IMPLEMENT_VARIANT_DATA(wxPGVariantDataArrayInt, wxArrayInt) WX_PG_IMPLEMENT_VARIANT_DATA(wxPGVariantDataLongLong, wxLongLong) WX_PG_IMPLEMENT_VARIANT_DATA(wxPGVariantDataULongLong, wxULongLong) WX_PG_IMPLEMENT_WXOBJECT_VARIANT_DATA_WITH_PROPER_EQ(wxPGVariantDataFont, wxFont) #if !wxCHECK_VERSION(2,8,0) WX_PG_IMPLEMENT_WXOBJECT_VARIANT_DATA_WITH_PROPER_EQ(wxPGVariantDataColour, wxColour) #endif #ifdef __WXPYTHON__ // Implement PyObject* wxVariantData manually, since it has // some needs for customization. class wxPGVariantDataPyObject : public wxPGVariantData { WX_PG_DECLARE_DYNAMIC_CLASS_VARIANTDATA(wxPGVariantDataPyObject) protected: PyObject* m_value; public: wxPGVariantDataPyObject() { m_value = NULL; } wxPGVariantDataPyObject( PyObject* value ) { if (!value) value = Py_None; Py_INCREF(value); m_value = value; } virtual ~wxPGVariantDataPyObject() { // Avoid crashing on program exit if ( _PyThreadState_Current != NULL && m_value ) Py_DECREF(m_value); } inline PyObject* GetValue() const { return m_value; } inline PyObject* GetValueRef() const { return m_value; } inline void SetValue(PyObject* value) { if (m_value) Py_DECREF(m_value); if (!value) value = Py_None; Py_INCREF(value); m_value = value; } // TODO virtual bool Eq(wxVariantData&) const { return false; } virtual wxString GetType() const { return wxT("PyObject*"); } virtual wxVariantData* Clone() { return new wxPGVariantDataPyObject(); } virtual bool Read(wxString &) { return false; } virtual bool Write(wxString &) const { return true; } virtual wxVariant GetDefaultValue() const { return wxVariant( new wxPGVariantDataPyObject(NULL) ); } public: virtual void* GetValuePtr() { return (void*)m_value; } }; _WX_PG_VARIANT_DATA_CLASSINFO_CONTAINER(wxPGVariantDataPyObject) WX_PG_IMPLEMENT_DYNAMIC_CLASS_VARIANTDATA(wxPGVariantDataPyObject, wxPGVariantData) PyObject* operator <<( PyObject* value, const wxVariant &variant ) { wxPGVariantDataPyObject *data = wxDynamicCastVariantData( variant.GetData(), wxPGVariantDataPyObject ); wxASSERT( data ); value = data->GetValue(); Py_INCREF(value); return value; } wxVariant& operator <<( wxVariant &variant, PyObject* value ) { wxPGVariantDataPyObject *data = new wxPGVariantDataPyObject( value ); variant.SetData( data ); return variant; } PyObject* PyObjectFromVariant( const wxVariant& v ) { wxPGVariantDataPyObject *data = wxDynamicCastVariantData( v.GetData(), wxPGVariantDataPyObject ); if ( !data ) return NULL; PyObject* retval = data->GetValue(); Py_INCREF(retval); return retval; } wxVariant PyObjectToVariant( PyObject* value ) { wxVariant variant( new wxPGVariantDataPyObject( value ) ); return variant; } #endif // __WXPYTHON__ wxObject* wxPG_VariantToWxObject( const wxVariant& variant, wxClassInfo* classInfo ) { if ( !variant.IsValueKindOf(classInfo) ) return (wxObject*) NULL; wxVariantData* vdata = variant.GetData(); wxPGVariantData* pgvdata = wxDynamicCastVariantData(vdata, wxPGVariantData); if ( pgvdata ) return (wxObject*) pgvdata->GetValuePtr(); if ( wxPGIsVariantClassInfo(wxPGVariantDataGetClassInfo(vdata), wxobject) ) #if wxCHECK_VERSION(2,8,0) return variant.GetWxObjectPtr(); #else return ((wxVariant*)(&variant))->GetWxObjectPtr(); #endif return (wxObject*) NULL; } // ----------------------------------------------------------------------- // wxVariant helpers // ----------------------------------------------------------------------- long wxPGVariantToInt( const wxVariant& variant, long defVal ) { if ( variant.IsNull() ) return defVal; if ( wxPGIsVariantType(variant, long) ) return variant.GetLong(); if ( wxPGIsVariantType(variant, bool) ) return variant.GetBool() ? 1 : 0; #if wxCHECK_VERSION(2,9,0) if ( typeid(*variant.GetData()) == typeid(wxPGVariantDataLongLong) ) #else if ( wxPGVariantDataGetClassInfo(variant.GetData()) == &wxPGVariantDataLongLong::ms_classInfo ) #endif { wxLongLong ll = ((const wxPGVariantDataLongLong&)variant).GetValue(); if ( ll >= LONG_MAX ) return LONG_MAX; else if ( ll <= LONG_MIN ) return LONG_MIN; return ll.ToLong(); } long l = defVal; if ( wxPGIsVariantType(variant, string) ) variant.GetString().ToLong(&l, 0); return l; } // ----------------------------------------------------------------------- bool wxPGVariantToLongLong( const wxVariant& variant, wxLongLong_t* pResult ) { if ( variant.IsNull() ) return false; if ( wxPGIsVariantType(variant, long) ) { *pResult = variant.GetLong(); return true; } #if wxCHECK_VERSION(2,9,0) if ( typeid(*variant.GetData()) == typeid(wxPGVariantDataLongLong) ) #else if ( wxPGVariantDataGetClassInfo(variant.GetData()) == &wxPGVariantDataLongLong::ms_classInfo ) #endif { *pResult = ((const wxPGVariantDataLongLong&)variant).GetValue().GetValue(); return true; } return false; } // ----------------------------------------------------------------------- bool wxPGVariantToULongLong( const wxVariant& variant, wxULongLong_t* pResult ) { if ( variant.IsNull() ) return false; if ( wxPGIsVariantType(variant, long) ) { *pResult = (unsigned long)variant.GetLong(); return true; } #if wxCHECK_VERSION(2,9,0) if ( typeid(*variant.GetData()) == typeid(wxPGVariantDataULongLong) ) #else if ( wxPGVariantDataGetClassInfo(variant.GetData()) == &wxPGVariantDataULongLong::ms_classInfo ) #endif { *pResult = ((const wxPGVariantDataULongLong&)variant).GetValue().GetValue(); return true; } return false; } // ----------------------------------------------------------------------- bool wxPGVariantToDouble( const wxVariant& variant, double* pResult ) { if ( variant.IsNull() ) return false; if ( wxPGIsVariantType(variant, double) ) { *pResult = variant.GetDouble(); return true; } if ( wxPGIsVariantType(variant, long) ) { *pResult = (double)variant.GetLong(); return true; } #if wxCHECK_VERSION(2,9,0) if ( typeid(*variant.GetData()) == typeid(wxPGVariantDataLongLong) ) #else if ( wxPGVariantDataGetClassInfo(variant.GetData()) == &wxPGVariantDataLongLong::ms_classInfo ) #endif { wxLongLong ll = ((const wxPGVariantDataLongLong&)variant).GetValue(); *pResult = ll.ToDouble(); return true; } if ( wxPGIsVariantType(variant, string) ) if ( variant.GetString().ToDouble(pResult) ) return true; return false; } // ----------------------------------------------------------------------- bool wxPGVariantToWxObjectPtr( const wxVariant& value, wxObject** result ) { wxVariantData* vdata = value.GetData(); if ( !vdata->GetValueClassInfo() ) return false; wxPGVariantData* pgvdata = wxDynamicCastVariantData(vdata, wxPGVariantData); if ( pgvdata ) { *result = (wxObject*) pgvdata->GetValuePtr(); return true; } if ( wxPGIsVariantClassInfo(wxPGVariantDataGetClassInfo(vdata), wxobject) ) { *result = value.GetWxObjectPtr(); return true; } return false; } // ----------------------------------------------------------------------- // Editor class specific. // noDefCheck = true prevents infinite recursion. wxPGEditor* wxPropertyGrid::RegisterEditorClass( wxPGEditor* editorclass, const wxString& name, bool noDefCheck ) { wxASSERT( editorclass ); if ( !noDefCheck && wxPGGlobalVars->m_mapEditorClasses.empty() ) RegisterDefaultEditors(); wxPGGlobalVars->m_mapEditorClasses[name] = (void*)editorclass; return editorclass; } // Registers all default editor classes void wxPropertyGrid::RegisterDefaultEditors() { wxPGRegisterDefaultEditorClass( TextCtrl ); wxPGRegisterDefaultEditorClass( Choice ); wxPGRegisterDefaultEditorClass( ComboBox ); wxPGRegisterDefaultEditorClass( TextCtrlAndButton ); #if wxPG_INCLUDE_CHECKBOX wxPGRegisterDefaultEditorClass( CheckBox ); #endif wxPGRegisterDefaultEditorClass( ChoiceAndButton ); // Register SpinCtrl etc. editors before use RegisterAdditionalEditors(); } wxPGEditor* wxPropertyGridInterface::GetEditorByName( const wxString& editor_name ) { wxPGEditor* editor = (wxPGEditor*) wxPGGlobalVars->m_mapEditorClasses[editor_name]; wxASSERT_MSG( editor, wxT("unregistered editor name") ); return editor; } // ----------------------------------------------------------------------- // wxPGAttributeStorage // ----------------------------------------------------------------------- wxPGAttributeStorage::wxPGAttributeStorage() { } wxPGAttributeStorage::~wxPGAttributeStorage() { wxPGHashMapS2P::iterator it; for ( it = m_map.begin(); it != m_map.end(); it++ ) { wxVariantData* data = (wxVariantData*) it->second; data->DecRef(); } } void wxPGAttributeStorage::Set( const wxString& name, const wxVariant& value ) { wxVariantData* data = value.GetData(); // Free old, if any wxPGHashMapS2P::iterator it = m_map.find(name); if ( it != m_map.end() ) { ((wxVariantData*)it->second)->DecRef(); if ( !data ) { // If Null variant, just remove from set m_map.erase(it); return; } } if ( data ) { data->IncRef(); m_map[name] = data; } } // ----------------------------------------------------------------------- // wxPGStringTokenizer // Needed to handle C-style string lists (e.g. "str1" "str2") // ----------------------------------------------------------------------- wxPGStringTokenizer::wxPGStringTokenizer( const wxString& str, wxChar delimeter ) : m_str(&str), m_curPos(str.begin()), m_delimeter(delimeter) { } wxPGStringTokenizer::~wxPGStringTokenizer() { } bool wxPGStringTokenizer::HasMoreTokens() { const wxString& str = *m_str; wxString::const_iterator i = m_curPos; wxUniChar delim = m_delimeter; wxUniChar a; wxUniChar prev_a = wxT('\0'); bool inToken = false; while ( i != str.end() ) { a = wxPGGetIterChar(str, i); if ( !inToken ) { if ( a == delim ) { inToken = true; m_readyToken.clear(); } } else { if ( prev_a != wxT('\\') ) { if ( a != delim ) { if ( a != wxT('\\') ) m_readyToken << a; } else { i++; m_curPos = i; return true; } prev_a = a; } else { m_readyToken << a; prev_a = wxT('\0'); } } i++; } m_curPos = str.end(); if ( inToken ) return true; return false; } wxString wxPGStringTokenizer::GetNextToken() { return m_readyToken; } // ----------------------------------------------------------------------- // wxPGChoiceEntry // ----------------------------------------------------------------------- wxPGChoiceEntry::wxPGChoiceEntry() : wxPGCell(), m_value(wxPG_INVALID_VALUE) { } wxPGChoiceEntry::wxPGChoiceEntry( const wxPGChoiceEntry& entry ) : wxPGCell( entry.GetText(), entry.GetBitmap(), entry.GetFgCol(), entry.GetBgCol() ), m_value(entry.GetValue()) { } // ----------------------------------------------------------------------- // wxPGChoicesData // ----------------------------------------------------------------------- wxPGChoicesData::wxPGChoicesData() { m_refCount = 1; } wxPGChoicesData::~wxPGChoicesData() { Clear(); } void wxPGChoicesData::Clear() { unsigned int i; for ( i=0; iGetCount(); i++ ) m_items.push_back( new wxPGChoiceEntry(*data->Item(i)) ); } // ----------------------------------------------------------------------- // wxPGChoices // ----------------------------------------------------------------------- wxPGChoiceEntry& wxPGChoices::Add( const wxString& label, int value ) { EnsureData(); wxPGChoiceEntry* p = new wxPGChoiceEntry(label, value); m_data->Insert( -1, p ); return *p; } // ----------------------------------------------------------------------- wxPGChoiceEntry& wxPGChoices::Add( const wxString& label, const wxBitmap& bitmap, int value ) { EnsureData(); wxPGChoiceEntry* p = new wxPGChoiceEntry(label, value); p->SetBitmap(bitmap); m_data->Insert( -1, p ); return *p; } // ----------------------------------------------------------------------- wxPGChoiceEntry& wxPGChoices::Insert( const wxPGChoiceEntry& entry, int index ) { EnsureData(); wxPGChoiceEntry* p = new wxPGChoiceEntry(entry); m_data->Insert(index, p); return *p; } // ----------------------------------------------------------------------- wxPGChoiceEntry& wxPGChoices::Insert( const wxString& label, int index, int value ) { EnsureData(); wxPGChoiceEntry* p = new wxPGChoiceEntry(label, value); m_data->Insert( index, p ); return *p; } // ----------------------------------------------------------------------- wxPGChoiceEntry& wxPGChoices::AddAsSorted( const wxString& label, int value ) { EnsureData(); size_t index = 0; while ( index < GetCount() ) { int cmpRes = GetLabel(index).Cmp(label); if ( cmpRes > 0 ) break; index++; } wxPGChoiceEntry* p = new wxPGChoiceEntry(label, value); m_data->Insert( index, p ); return *p; } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxChar** labels, const ValArrItem* values ) { EnsureData(); unsigned int itemcount = 0; const wxChar** p = &labels[0]; while ( *p ) { p++; itemcount++; } unsigned int i; for ( i = 0; i < itemcount; i++ ) { int value = wxPG_INVALID_VALUE; if ( values ) value = values[i]; m_data->Insert( -1, new wxPGChoiceEntry(labels[i], value) ); } } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxArrayString& arr, const ValArrItem* values ) { EnsureData(); unsigned int i; unsigned int itemcount = arr.GetCount(); for ( i = 0; i < itemcount; i++ ) { int value = wxPG_INVALID_VALUE; if ( values ) value = values[i]; m_data->Insert( -1, new wxPGChoiceEntry(arr[i], value) ); } } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxArrayString& arr, const wxArrayInt& arrint ) { EnsureData(); unsigned int i; unsigned int itemcount = arr.GetCount(); for ( i = 0; i < itemcount; i++ ) { int value = wxPG_INVALID_VALUE; if ( &arrint && arrint.size() ) value = arrint[i]; m_data->Insert( -1, new wxPGChoiceEntry(arr[i], value) ); } } // ----------------------------------------------------------------------- void wxPGChoices::RemoveAt(size_t nIndex, size_t count) { wxASSERT( m_data->m_refCount != 0xFFFFFFF ); unsigned int i; for ( i=nIndex; i<(nIndex+count); i++) delete m_data->Item(i); m_data->m_items.RemoveAt(nIndex, count); } // ----------------------------------------------------------------------- int wxPGChoices::Index( const wxString& str ) const { if ( IsOk() ) { unsigned int i; for ( i=0; i< m_data->GetCount(); i++ ) { if ( m_data->Item(i)->GetText() == str ) return i; } } return -1; } // ----------------------------------------------------------------------- int wxPGChoices::Index( int val ) const { if ( IsOk() ) { unsigned int i; for ( i=0; i< m_data->GetCount(); i++ ) { if ( m_data->Item(i)->GetValue() == val ) return i; } } return -1; } // ----------------------------------------------------------------------- wxArrayString wxPGChoices::GetLabels() const { wxArrayString arr; unsigned int i; if ( this && IsOk() ) for ( i=0; i= 0 ) arr.Add(GetValue(index)); else arr.Add(wxPG_INVALID_VALUE); } } return arr; } // ----------------------------------------------------------------------- wxArrayInt wxPGChoices::GetIndicesForStrings( const wxArrayString& strings, wxArrayString* unmatched ) const { wxArrayInt arr; if ( IsOk() ) { unsigned int i; for ( i=0; i< strings.size(); i++ ) { const wxString& str = strings[i]; int index = Index(str); if ( index >= 0 ) arr.Add(index); else if ( unmatched ) unmatched->Add(str); } } return arr; } // ----------------------------------------------------------------------- void wxPGChoices::AssignData( wxPGChoicesData* data ) { Free(); if ( data != wxPGChoicesEmptyData ) { m_data = data; data->m_refCount++; } } // ----------------------------------------------------------------------- void wxPGChoices::Init() { m_data = wxPGChoicesEmptyData; } // ----------------------------------------------------------------------- void wxPGChoices::Free() { if ( m_data != wxPGChoicesEmptyData ) { m_data->DecRef(); m_data = wxPGChoicesEmptyData; } } // ----------------------------------------------------------------------- // wxPropertyGridEvent // ----------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxPropertyGridEvent, wxCommandEvent) DEFINE_EVENT_TYPE( wxEVT_PG_SELECTED ) DEFINE_EVENT_TYPE( wxEVT_PG_CHANGING ) DEFINE_EVENT_TYPE( wxEVT_PG_CHANGED ) DEFINE_EVENT_TYPE( wxEVT_PG_HIGHLIGHTED ) DEFINE_EVENT_TYPE( wxEVT_PG_RIGHT_CLICK ) DEFINE_EVENT_TYPE( wxEVT_PG_PAGE_CHANGED ) DEFINE_EVENT_TYPE( wxEVT_PG_ITEM_EXPANDED ) DEFINE_EVENT_TYPE( wxEVT_PG_ITEM_COLLAPSED ) DEFINE_EVENT_TYPE( wxEVT_PG_DOUBLE_CLICK ) DEFINE_EVENT_TYPE( wxEVT_PG_LABEL_EDIT_BEGIN ) DEFINE_EVENT_TYPE( wxEVT_PG_LABEL_EDIT_ENDING ) // ----------------------------------------------------------------------- void wxPropertyGridEvent::Init() { m_validationInfo = NULL; m_column = 1; m_canVeto = false; m_wasVetoed = false; } // ----------------------------------------------------------------------- wxPropertyGridEvent::wxPropertyGridEvent(wxEventType commandType, int id) : wxCommandEvent(commandType,id) { m_property = NULL; Init(); } // ----------------------------------------------------------------------- wxPropertyGridEvent::wxPropertyGridEvent(const wxPropertyGridEvent& event) : wxCommandEvent(event) { m_eventType = event.GetEventType(); m_eventObject = event.m_eventObject; m_pg = event.m_pg; m_property = event.m_property; m_validationInfo = event.m_validationInfo; m_canVeto = event.m_canVeto; m_wasVetoed = event.m_wasVetoed; } // ----------------------------------------------------------------------- wxPropertyGridEvent::~wxPropertyGridEvent() { } // ----------------------------------------------------------------------- wxEvent* wxPropertyGridEvent::Clone() const { return new wxPropertyGridEvent( *this ); } // ----------------------------------------------------------------------- // wxPropertyGridInterface // - common methods for wxPropertyGrid and wxPropertyGridManager - // ----------------------------------------------------------------------- void wxPropertyGridInterface::DoSetPropertyAttribute( wxPGPropArg id, const wxString& name, wxVariant& value, long argFlags ) { wxPG_PROP_ARG_CALL_PROLOG() p->SetAttribute( name, value ); if ( argFlags & wxPG_RECURSE ) { unsigned int i; for ( i = 0; i < p->GetChildCount(); i++ ) DoSetPropertyAttribute(p->Item(i), name, value, argFlags); } } void wxPropertyGrid::SetPropertyAttributeAll( const wxString& attrName, wxVariant value ) { DoSetPropertyAttribute(GetRoot(), attrName, value, wxPG_RECURSE); } // ----------------------------------------------------------------------- void wxPropertyGridInterface::GetPropertiesWithFlag( wxArrayPGProperty* targetArr, wxPGProperty::FlagType flags, bool inverse, int iterFlags ) const { wxASSERT( targetArr ); wxPGVIterator it = GetVIterator( iterFlags ); for ( ; !it.AtEnd(); it.Next() ) { const wxPGProperty* property = it.GetProperty(); if ( !inverse ) { if ( (property->GetFlags() & flags) == flags ) targetArr->push_back((wxPGProperty*)property); } else { if ( (property->GetFlags() & flags) != flags ) targetArr->push_back((wxPGProperty*)property); } } } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetPropertiesFlag( const wxArrayPGProperty& srcArr, wxPGProperty::FlagType flags, bool inverse ) { unsigned int i; for ( i=0; iSetFlag(flags); else property->ClearFlag(flags); } // If collapsed flag or hidden was manipulated, we need to update virtual // size. wxPropertyGrid* pg = GetPropertyGrid(); if ( flags & (wxPG_PROP_COLLAPSED|wxPG_PROP_HIDDEN) ) { GetState()->VirtualHeightChanged(); pg->RecalculateVirtualSize(); } } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetBoolChoices( const wxString& trueChoice, const wxString& falseChoice ) { wxPGGlobalVars->m_boolChoices[0] = falseChoice; wxPGGlobalVars->m_boolChoices[1] = trueChoice; } // ----------------------------------------------------------------------- wxPGChoices gs_emptyChoices; wxPGChoices& wxPropertyGridInterface::GetPropertyChoices( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(gs_emptyChoices) wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; p->GetChoiceInfo(&ci); if ( !ci.m_choices ) return gs_emptyChoices; return *ci.m_choices; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::DoGetPropertyByName( wxPGPropNameStr name ) const { return m_pState->BaseGetPropertyByName(name); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::GetPropertyByName( wxPGPropNameStr name, wxPGPropNameStr subname ) const { wxPGProperty* p = DoGetPropertyByName(name); if ( !p || !p->GetChildCount() ) return wxNullProperty; return p->GetPropertyByName(subname); } // ----------------------------------------------------------------------- // Since GetPropertyByName is used *a lot*, this makes sense // since non-virtual method can be called with less code. wxPGProperty* wxPropertyGridInterface::GetPropertyByName( wxPGPropNameStr name ) const { wxPGProperty* p = DoGetPropertyByName(name); if ( p ) return p; // Check if its "Property.SubProperty" format int pos = name.Find(wxT('.')); if ( pos <= 0 ) return NULL; return GetPropertyByName(name.substr(0,pos), name.substr(pos+1,name.length()-pos-1)); } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::HideProperty( wxPGPropArg id, bool hide, int flags ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxPropertyGrid* pg = m_pState->GetGrid(); if ( pg == p->GetGrid() ) return pg->DoHideProperty(p, hide, flags); else m_pState->DoHideProperty(p, hide, flags); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::Collapse( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxPropertyGrid* pg = p->GetGridIfDisplayed(); if ( pg ) return pg->DoCollapse(p); return p->GetParentState()->DoCollapse(p); } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::Expand( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxPropertyGrid* pg = p->GetGridIfDisplayed(); if ( pg ) return pg->DoExpand(p); return p->GetParentState()->DoExpand(p); } // ----------------------------------------------------------------------- void wxPropertyGridInterface::SetPropertyLabel( wxPGPropArg id, const wxString& newproplabel ) { wxPG_PROP_ARG_CALL_PROLOG() p->SetLabel( newproplabel ); wxPropertyGridState* state = p->GetParentState(); wxPropertyGrid* pg = state->GetGrid(); if ( pg->HasFlag(wxPG_AUTO_SORT) ) pg->Sort(p->GetParent()); if ( pg->GetState() == state ) { if ( pg->HasFlag(wxPG_AUTO_SORT) ) pg->Refresh(); else pg->DrawItem( p ); } } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::SetPropertyMaxLength( wxPGPropArg id, int maxLen ) { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxPropertyGrid* pg = m_pState->GetGrid(); p->m_maxLen = (short) maxLen; // Adjust control if selected currently if ( pg == p->GetGrid() && p == m_pState->GetSelection() ) { wxWindow* wnd = pg->GetEditorControl(); wxTextCtrl* tc = wxDynamicCast(wnd,wxTextCtrl); if ( tc ) tc->SetMaxLength( maxLen ); else // Not a text ctrl return false; } return true; } // ----------------------------------------------------------------------- // GetPropertyValueAsXXX methods #define IMPLEMENT_GET_VALUE(T,TRET,BIGNAME,DEFRETVAL) \ TRET wxPropertyGridInterface::GetPropertyValueAs##BIGNAME( wxPGPropArg id ) const \ { \ wxPG_PROP_ARG_CALL_PROLOG_RETVAL(DEFRETVAL) \ wxVariant value = p->GetValue(); \ if ( wxStrcmp(value.GetType(), wxPGTypeName_##T) != 0 ) \ { \ wxPGGetFailed(p,wxPGTypeName_##T); \ return (TRET)DEFRETVAL; \ } \ return (TRET)value.Get##BIGNAME(); \ } // String is different than others. wxString wxPropertyGridInterface::GetPropertyValueAsString( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(wxEmptyString) return p->GetValueAsString(wxPG_FULL_VALUE); } bool wxPropertyGridInterface::GetPropertyValueAsBool( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) wxVariant value = p->GetValue(); if ( wxStrcmp(value.GetType(), wxPGTypeName_bool) == 0 ) { return value.GetBool(); } if ( wxStrcmp(value.GetType(), wxPGTypeName_long) == 0 ) { return value.GetLong()?true:false; } wxPGGetFailed(p,wxPGTypeName_bool); return false; } IMPLEMENT_GET_VALUE(long,long,Long,0) IMPLEMENT_GET_VALUE(double,double,Double,0.0) IMPLEMENT_GET_VALUE(void,void*,VoidPtr,NULL) #ifdef __WXPYTHON__ PyObject* wxPropertyGridInterface::GetPropertyValueAsPyObject( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(NULL) wxVariant value = p->GetValue(); wxPGVariantDataPyObject* pgvdata = wxDynamicCastVariantData(value.GetData(), wxPGVariantDataPyObject); if ( value.IsNull() || !pgvdata ) { wxPGGetFailed(p,wxT("PyObject*")); return NULL; } return PyObjectFromVariant(value); } #endif // wxObject is different than others. wxObject* wxPropertyGridInterface::GetPropertyValueAsWxObjectPtr( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL((wxObject*)NULL) wxVariant variant = p->GetValue(); wxObject* result; if ( wxPGVariantToWxObjectPtr(variant, &result) ) return result; return NULL; } // ----------------------------------------------------------------------- bool wxPropertyGridInterface::IsPropertyExpanded( wxPGPropArg id ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) return p->IsExpanded(); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::CreatePropertyByType(const wxString &/*valuetype*/, const wxString &/*label*/, const wxString &/*name*/) { // // TODO: This code (possibly) needs to be ported to wxPropertyGrid 1.3+ // /* wxPGHashMapS2P::iterator it; it = wxPGGlobalVars->m_dictValueType.find(valuetype); if ( it != wxPGGlobalVars->m_dictValueType.end() ) { wxPGValueType* vt = (wxPGValueType*) it->second; wxPGProperty* p = vt->GenerateProperty(label,name); #ifdef __WXDEBUG__ if ( !p ) { wxLogDebug(wxT("WARNING: CreatePropertyByValueType generated NULL property for ValueType \"%s\""),valuetype.c_str()); return (wxPGProperty*) NULL; } #endif return p; } wxLogDebug(wxT("WARNING: No value type registered with name \"%s\""),valuetype.c_str()); */ return (wxPGProperty*) NULL; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridInterface::CreatePropertyByClass(const wxString &/*classname*/, const wxString &/*label*/, const wxString &/*name*/) { // // TODO: This code (possibly) needs to be ported to wxPropertyGrid 1.3+ // /* wxPGHashMapS2P* cis = (wxPGHashMapS2P*) &wxPGGlobalVars->m_dictPropertyClassInfo; const wxString* pClassname = &classname; wxString s; // Translate to long name, if necessary if ( (pClassname->GetChar(0) != wxT('w') || pClassname->GetChar(1) != wxT('x')) && pClassname->Find(wxT("Property")) < 0 ) { if ( classname != wxT("Category") ) s.Printf(wxT("wx%sProperty"),pClassname->c_str()); else s = wxT("wxPropertyCategory"); pClassname = &s; } wxPGHashMapS2P::iterator it; it = cis->find(*pClassname); if ( it != cis->end() ) { wxPGPropertyClassInfo* pci = (wxPGPropertyClassInfo*) it->second; wxPGProperty* p = pci->m_constructor(label,name); return p; } wxLogError(wxT("No such property class: %s"),pClassname->c_str()); */ return (wxPGProperty*) NULL; } // ----------------------------------------------------------------------- // wxPropertyGridInterface wrappers // ----------------------------------------------------------------------- bool wxPropertyGridInterface::ChangePropertyValue( wxPGPropArg id, wxVariant newValue ) { return GetPropertyGrid()->ChangePropertyValue(id, newValue); } // ----------------------------------------------------------------------- // wxPropertyGridIterator // ----------------------------------------------------------------------- void wxPropertyGridIteratorBase::Init( wxPropertyGridState* state, int flags, wxPGProperty* property, int dir ) { wxASSERT( dir == 1 || dir == -1 ); m_state = state; m_baseParent = state->DoGetRoot(); if ( !property && m_baseParent->GetChildCount() ) property = m_baseParent->Item(0); m_property = property; wxPG_ITERATOR_CREATE_MASKS(flags, m_itemExMask, m_parentExMask) // Need to skip first? if ( property && (property->GetFlags() & m_itemExMask) ) { if ( dir == 1 ) Next(); else Prev(); } } void wxPropertyGridIteratorBase::Init( wxPropertyGridState* state, int flags, int startPos, int dir ) { wxPGProperty* property; if ( startPos == wxTOP ) { property = NULL; if ( dir == 0 ) dir = 1; } else if ( startPos == wxBOTTOM ) { property = state->GetLastItem(flags); if ( dir == 0 ) dir = -1; } else { wxASSERT_MSG( false, wxT("Only supported stating positions are wxTOP and wxBOTTOM") ); property = NULL; } Init( state, flags, property, dir ); } void wxPropertyGridIteratorBase::Assign( const wxPropertyGridIteratorBase& it ) { m_property = it.m_property; m_state = it.m_state; m_baseParent = it.m_baseParent; m_itemExMask = it.m_itemExMask; m_parentExMask = it.m_parentExMask; } void wxPropertyGridIteratorBase::Prev() { wxPGProperty* property = m_property; if ( !property ) return; wxPGProperty* parent = property->GetParent(); wxASSERT( parent ); unsigned int index = property->GetIndexInParent(); if ( index > 0 ) { // Previous sibling index--; property = parent->Item(index); // Go to last children? if ( property->GetChildCount() && wxPG_ITERATOR_PARENTEXMASK_TEST(property, m_parentExMask) ) { // First child property = property->Last(); } } else { // Up to a parent if ( parent == m_baseParent ) { m_property = NULL; return; } else { property = parent; } } m_property = property; // If property does not match our criteria, skip it if ( property->GetFlags() & m_itemExMask ) Prev(); } void wxPropertyGridIteratorBase::Next( bool iterateChildren ) { wxPGProperty* property = m_property; if ( !property ) return; if ( property->GetChildCount() && wxPG_ITERATOR_PARENTEXMASK_TEST(property, m_parentExMask) && iterateChildren ) { // First child property = property->Item(0); } else { wxPGProperty* parent = property->GetParent(); wxASSERT( parent ); unsigned int index = property->GetIndexInParent() + 1; if ( index < parent->GetChildCount() ) { // Next sibling property = parent->Item(index); } else { // Next sibling of parent if ( parent == m_baseParent ) { m_property = NULL; } else { m_property = parent; Next(false); } return; } } m_property = property; // If property does not match our criteria, skip it if ( property->GetFlags() & m_itemExMask ) Next(); } // ----------------------------------------------------------------------- // wxPGVIterator_State // ----------------------------------------------------------------------- // Default returned by wxPropertyGridInterface::GetVIterator(). class wxPGVIteratorBase_State : public wxPGVIteratorBase { public: wxPGVIteratorBase_State( wxPropertyGridState* state, int flags ) { m_it.Init( state, flags ); } virtual ~wxPGVIteratorBase_State() { } virtual void Next() { m_it.Next(); } }; wxPGVIterator wxPropertyGridInterface::GetVIterator( int flags ) const { return wxPGVIterator( new wxPGVIteratorBase_State( m_pState, flags ) ); } // EscapeDelimiters() changes ";" into "\;" and "|" into "\|" // in the input string. This is an internal functions which is // used for saving states // NB: Similar function exists in aui/framemanager.cpp static wxString EscapeDelimiters(const wxString& s) { wxString result; result.Alloc(s.length()); const wxChar* ch = s.c_str(); while (*ch) { if (*ch == wxT(';') || *ch == wxT('|') || *ch == wxT(',')) result += wxT('\\'); result += *ch; ++ch; } return result; } wxString wxPropertyGridInterface::SaveEditableState( int includedStates ) const { wxString result; // // Save state on page basis size_t pageIndex = 0; wxPropertyGridState* pageState = GetPageState(pageIndex); wxArrayPtrVoid pageStates; while ( pageState ) { pageStates.Add(pageState); pageIndex += 1; pageState = GetPageState(pageIndex); } for ( pageIndex=0; pageIndex < pageStates.size(); pageIndex++ ) { wxPropertyGridState* pageState = (wxPropertyGridState*) pageStates[pageIndex]; if ( includedStates & SelectionState ) { wxString sel; if ( pageState->GetSelection() ) sel = pageState->GetSelection()->GetName(); result += wxT("selection="); result += EscapeDelimiters(sel); result += wxT(";"); } if ( includedStates & ExpandedState ) { wxArrayPGProperty ptrs; wxPropertyGridConstIterator it = wxPropertyGridConstIterator( pageState, wxPG_ITERATE_ALL_PARENTS_RECURSIVELY|wxPG_ITERATE_HIDDEN, wxNullProperty ); result += wxT("expanded="); for ( ; !it.AtEnd(); it.Next() ) { const wxPGProperty* p = it.GetProperty(); if ( !p->HasFlag(wxPG_PROP_COLLAPSED) ) result += EscapeDelimiters(p->GetName()); result += wxT(","); } if ( result.Last() == wxT(',') ) result.RemoveLast(); result += wxT(";"); } if ( includedStates & ScrollPosState ) { int x, y; GetPropertyGrid()->GetViewStart(&x,&y); result += wxString::Format(wxT("scrollpos=%i,%i;"), x, y); } if ( includedStates & SplitterPosState ) { result += wxT("splitterpos="); for ( size_t i=0; iGetColumnCount(); i++ ) result += wxString::Format(wxT("%i,"), pageState->DoGetSplitterPosition(i)); result.RemoveLast(); // Remove last comma result += wxT(";"); } if ( includedStates & PageState ) { result += wxT("ispageselected="); if ( GetPageState(-1) == pageState ) result += wxT("1;"); else result += wxT("0;"); } if ( includedStates & DescBoxState ) { wxVariant v = GetEditableStateItem(wxT("descboxheight")); if ( !v.IsNull() ) result += wxString::Format(wxT("descboxheight=%i;"), (int)v.GetLong()); } result.RemoveLast(); // Remove last semicolon result += wxT("|"); } // Remove last '|' if ( result.length() ) result.RemoveLast(); return result; } bool wxPropertyGridInterface::RestoreEditableState( const wxString& src, int restoreStates ) { wxPropertyGrid* pg = GetPropertyGrid(); wxPGProperty* newSelection = NULL; size_t pageIndex; long vx = -1; long vy = -1; long selectedPage = -1; bool pgSelectionSet = false; bool res = true; pg->Freeze(); wxArrayString pageStrings = ::wxSplit(src, wxT('|'), wxT('\\')); for ( pageIndex=0; pageIndexDoCollapse(p); } // Then expand those which names are in values for ( size_t n=0; nDoExpand(prop); } } } else if ( key == wxT("scrollpos") ) { if ( restoreStates & ScrollPosState ) { if ( values.size() == 2 ) { values[0].ToLong(&vx); values[1].ToLong(&vy); } else { res = false; } } } else if ( key == wxT("splitterpos") ) { if ( restoreStates & SplitterPosState ) { for ( size_t n=1; n 0 ) pageState->DoSetSplitterPosition(pos, n); } } } else if ( key == wxT("selection") ) { if ( restoreStates & SelectionState ) { if ( values.size() > 0 ) { if ( pageState->IsDisplayed() ) { if ( values[0].length() ) newSelection = GetPropertyByName(value); pgSelectionSet = true; } else { if ( values[0].length() ) pageState->DoSetSelection(GetPropertyByName(value)); else pageState->DoClearSelection(); } } } } else if ( key == wxT("ispageselected") ) { if ( restoreStates & PageState ) { long pageSelStatus; if ( values.size() == 1 && values[0].ToLong(&pageSelStatus) ) { if ( pageSelStatus ) selectedPage = pageIndex; } else { res = false; } } } else if ( key == wxT("descboxheight") ) { if ( restoreStates & DescBoxState ) { long descBoxHeight; if ( values.size() == 1 && values[0].ToLong(&descBoxHeight) ) { SetEditableStateItem(wxT("descboxheight"), descBoxHeight); } else { res = false; } } } else { res = false; } } } } // // Force recalculation of virtual heights of all pages // (may be needed on unclean source string). pageIndex = 0; wxPropertyGridState* pageState = GetPageState(pageIndex); while ( pageState ) { pageState->VirtualHeightChanged(); pageIndex += 1; pageState = GetPageState(pageIndex); } pg->Thaw(); // // Selection of visible grid page must be set after Thaw() call if ( pgSelectionSet ) { if ( newSelection ) pg->SelectProperty(newSelection); else pg->ClearSelection(); } if ( selectedPage != -1 ) { DoSelectPage(selectedPage); } if ( vx >= 0 ) { pg->Scroll(vx, vy); } return res; } // ----------------------------------------------------------------------- // wxPropertyGridState // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // wxPropertyGridState item iteration methods // ----------------------------------------------------------------------- #if wxPG_COMPATIBILITY_1_2_0 // Skips categories and sub-properties (unless in wxParentProperty). wxPGProperty* wxPropertyGridState::GetNextProperty( wxPGProperty* p ) { wxPropertyGridIterator it( (wxPropertyGridState*)this, wxPG_ITERATE_DEFAULT, p ); it.Next(); while ( !it.AtEnd() ) it.Next(); return it.GetProperty(); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::GetNextSiblingProperty( wxPGProperty* p ) { wxPGProperty* parent = p->m_parent; size_t next_ind = p->m_arrIndex + 1; if ( next_ind >= parent->GetCount() ) return (wxPGProperty*)NULL; return parent->Item(next_ind); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::GetPrevSiblingProperty( wxPGProperty* p ) { size_t ind = p->m_arrIndex; if ( ind < 1 ) return (wxPGProperty*)NULL; return p->m_parent->Item(ind-1); } // ----------------------------------------------------------------------- // Skips categories and sub-properties (unless in wxParentProperty). wxPGProperty* wxPropertyGridState::GetPrevProperty( wxPGProperty* p ) { wxPropertyGridIterator it( (wxPropertyGridState*)this, wxPG_ITERATE_DEFAULT, p ); it.Prev(); while ( !it.AtEnd() ) it.Prev(); return it.GetProperty(); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::GetNextCategory( wxPGProperty* p ) const { wxPGProperty* current = (wxPGProperty*)p; wxCHECK_MSG( !IsInNonCatMode() || current->IsCategory(), (wxPGProperty*)NULL, wxT("GetNextCategory should not be called with non-category argument in non-categoric mode.") ); wxPropertyGridConstIterator it( this, wxPG_ITERATE_CATEGORIES, p ); if ( *it != p ) return (wxPGProperty*) *it; it++; return (wxPGProperty*) *it; } #endif // #if wxPG_COMPATIBILITY_1_2_0 // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::GetLastItem( int flags ) { if ( !m_properties->GetCount() ) return (wxPGProperty*) NULL; wxPG_ITERATOR_CREATE_MASKS(flags, int itemExMask, int parentExMask) // First, get last child of last parent wxPGProperty* pwc = (wxPGProperty*)m_properties->Last(); while ( pwc->GetChildCount() && wxPG_ITERATOR_PARENTEXMASK_TEST(pwc, parentExMask) ) pwc = (wxPGProperty*) pwc->Last(); // Then, if it doesn't fit our criteria, back up until we find something that does if ( pwc->GetFlags() & itemExMask ) { wxPropertyGridIterator it( this, flags, pwc ); for ( ; !it.AtEnd(); it.Prev() ); pwc = (wxPGProperty*) it.GetProperty(); } return pwc; } wxPropertyCategory* wxPropertyGridState::GetPropertyCategory( const wxPGProperty* p ) const { const wxPGProperty* parent = (const wxPGProperty*)p; const wxPGProperty* grandparent = (const wxPGProperty*)parent->GetParent(); do { parent = grandparent; grandparent = (wxPGProperty*)parent->GetParent(); if ( parent->IsCategory() && grandparent ) return (wxPropertyCategory*)parent; } while ( grandparent ); return (wxPropertyCategory*) NULL; } // ----------------------------------------------------------------------- // wxPropertyGridState GetPropertyXXX methods // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::GetPropertyByLabel( const wxString& label, wxPGProperty* parent ) const { size_t i; if ( !parent ) parent = (wxPGProperty*) &m_regularArray; for ( i=0; iGetCount(); i++ ) { wxPGProperty* p = parent->Item(i); if ( p->m_label == label ) return p; // Check children recursively. if ( p->GetChildCount() ) { p = GetPropertyByLabel(label,(wxPGProperty*)p); if ( p ) return p; } } return NULL; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::BaseGetPropertyByName( wxPGPropNameStr name ) const { wxPGHashMapS2P::const_iterator it; it = m_dictName.find(name); if ( it != m_dictName.end() ) return (wxPGProperty*) it->second; return (wxPGProperty*) NULL; } // ----------------------------------------------------------------------- // wxPropertyGridState global operations // ----------------------------------------------------------------------- bool wxPropertyGridState::EnableCategories( bool enable ) { // // NB: We can't use wxPropertyGridIterator in this // function, since it depends on m_arrIndexes, // which, among other things, is being fixed here. // ITEM_ITERATION_VARIABLES if ( enable ) { // // Enable categories // if ( !IsInNonCatMode() ) return false; m_properties = &m_regularArray; // fix parents, indexes, and depths ITEM_ITERATION_INIT_FROM_THE_TOP ITEM_ITERATION_LOOP_BEGIN p->m_arrIndex = i; p->m_parent = parent; // If parent was category, and this is not, // then the depth stays the same. if ( parent->IsCategory() && !p->IsCategory() ) p->m_depth = parent->m_depth; else p->m_depth = parent->m_depth + 1; ITEM_ITERATION_LOOP_END } else { // // Disable categories // if ( IsInNonCatMode() ) return false; // Create array, if necessary. if ( !m_abcArray ) InitNonCatMode(); m_properties = m_abcArray; // fix parents, indexes, and depths ITEM_ITERATION_INIT_FROM_THE_TOP ITEM_ITERATION_LOOP_BEGIN p->m_arrIndex = i; p->m_parent = parent; p->m_depth = parent->m_depth + 1; ITEM_ITERATION_LOOP_END } VirtualHeightChanged(); if ( m_pPropGrid->GetState() == this ) m_pPropGrid->RecalculateVirtualSize(); return true; } // ----------------------------------------------------------------------- static int wxPG_SortFunc_ByFunction(void **p1, void **p2) { wxPGProperty *pp1 = *((wxPGProperty**)p1); wxPGProperty *pp2 = *((wxPGProperty**)p2); wxPropertyGrid* pg = pp1->GetGrid(); wxPGSortCallback sortFunction = pg->GetSortFunction(); return sortFunction(pg, pp1, pp2); } static int wxPG_SortFunc_ByLabel(void **p1, void **p2) { wxPGProperty *pp1 = *((wxPGProperty**)p1); wxPGProperty *pp2 = *((wxPGProperty**)p2); return pp1->GetLabel().CmpNoCase( pp2->GetLabel() ); } void wxPropertyGridState::Sort( wxPGProperty* p ) { if ( !p ) p = (wxPGProperty*)m_properties; if ( !p->GetChildCount() ) return; wxPGProperty* pwc = (wxPGProperty*)p; // Can only sort items with children if ( pwc->m_children.GetCount() < 1 ) return; if ( GetGrid()->GetSortFunction() ) pwc->m_children.Sort( wxPG_SortFunc_ByFunction ); else pwc->m_children.Sort( wxPG_SortFunc_ByLabel ); // Fix indexes pwc->FixIndexesOfChildren(); } // ----------------------------------------------------------------------- void wxPropertyGridState::Sort() { Sort( m_properties ); // Sort categories as well if ( !IsInNonCatMode() ) { size_t i; for ( i=0;iGetCount();i++) { wxPGProperty* p = m_properties->Item(i); if ( p->IsCategory() ) Sort( p ); } } } // ----------------------------------------------------------------------- // wxPropertyGridState splitter, column and hittest functions // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::DoGetItemAtY( int y ) const { // Outside? if ( y < 0 ) return (wxPGProperty*) NULL; unsigned int a = 0; return m_properties->GetItemAtY(y, GetGrid()->m_lineHeight, &a); } // ----------------------------------------------------------------------- wxPropertyGridHitTestResult wxPropertyGridState::HitTest( const wxPoint&pt ) const { wxPropertyGridHitTestResult result; result.column = HitTestH( pt.x, &result.splitter, &result.splitterHitOffset ); result.property = DoGetItemAtY( pt.y ); return result; } // ----------------------------------------------------------------------- // Used by SetSplitterLeft() and DotFitColumns() int wxPropertyGridState::GetColumnFitWidth(wxClientDC& dc, wxPGProperty* pwc, unsigned int col, bool subProps) const { wxPropertyGrid* pg = m_pPropGrid; size_t i; int maxW = 0; int w, h; for ( i=0; iGetCount(); i++ ) { wxPGProperty* p = pwc->Item(i); if ( !p->IsCategory() ) { dc.GetTextExtent( p->GetColumnText(col), &w, &h ); if ( col == 0 ) w += ( ((int)p->m_depth-1) * pg->m_subgroup_extramargin ); // account for the bitmap if ( col == 1 ) w += p->GetImageOffset(pg->GetImageRect(p, -1).GetWidth()); w += (wxPG_XBEFORETEXT*2); if ( w > maxW ) maxW = w; } if ( p->GetChildCount() && ( subProps || p->IsCategory() ) ) { w = GetColumnFitWidth( dc, p, col, subProps ); if ( w > maxW ) maxW = w; } } return maxW; } int wxPropertyGridState::DoGetSplitterPosition( int splitterColumn ) const { int n = GetGrid()->m_marginWidth; int i; for ( i=0; i<=splitterColumn; i++ ) n += m_colWidths[i]; return n; } int wxPropertyGridState::GetColumnMinWidth( int WXUNUSED(column) ) const { return wxPG_DRAG_MARGIN; } void wxPropertyGridState::PropagateColSizeDec( int column, int decrease, int dir ) { int origWidth = m_colWidths[column]; m_colWidths[column] -= decrease; int min = GetColumnMinWidth(column); int more = 0; if ( m_colWidths[column] < min ) { more = decrease - (origWidth - min); m_colWidths[column] = min; } // // FIXME: Causes erratic splitter changing, so as a workaround // disabled if two or less columns. if ( m_colWidths.size() <= 2 ) return; column += dir; if ( more && column < (int)m_colWidths.size() && column >= 0 ) PropagateColSizeDec( column, more, dir ); } void wxPropertyGridState::DoSetSplitterPosition( int newXPos, int splitterColumn, bool WXUNUSED(allPages), bool fromAutoCenter ) { wxPropertyGrid* pg = GetGrid(); int adjust = newXPos - DoGetSplitterPosition(splitterColumn); if ( !pg->HasVirtualWidth() ) { // No virtual width int otherColumn; if ( adjust > 0 ) { otherColumn = splitterColumn + 1; if ( otherColumn == (int)m_colWidths.size() ) otherColumn = 0; m_colWidths[splitterColumn] += adjust; PropagateColSizeDec( otherColumn, adjust, 1 ); } else { otherColumn = splitterColumn + 1; if ( otherColumn == (int)m_colWidths.size() ) otherColumn = 0; m_colWidths[otherColumn] -= adjust; PropagateColSizeDec( splitterColumn, -adjust, -1 ); } } else { m_colWidths[splitterColumn] += adjust; } if ( splitterColumn == 0 ) m_fSplitterX = (double) newXPos; if ( !fromAutoCenter ) { // Don't allow initial splitter auto-positioning after this. if ( pg->GetState() == this ) pg->SetInternalFlag(wxPG_FL_SPLITTER_PRE_SET); CheckColumnWidths(); } } // Moves splitter so that all labels are visible, but just. void wxPropertyGridState::SetSplitterLeft( bool subProps ) { wxPropertyGrid* pg = GetGrid(); wxClientDC dc(pg); dc.SetFont(pg->GetFont()); int maxW = GetColumnFitWidth(dc, m_properties, 0, subProps); if ( maxW > 0 ) { maxW += pg->m_marginWidth; DoSetSplitterPosition( maxW ); } pg->SetInternalFlag(wxPG_FL_DONT_CENTER_SPLITTER); } wxSize wxPropertyGridState::DoFitColumns( bool WXUNUSED(allowGridResize) ) { wxPropertyGrid* pg = GetGrid(); wxClientDC dc(pg); dc.SetFont(pg->GetFont()); int marginWidth = pg->m_marginWidth; int accWid = marginWidth; int maxColWidth = 500; for ( unsigned int col=0; col < GetColumnCount(); col++ ) { int fitWid = GetColumnFitWidth(dc, m_properties, col, true); int colMinWidth = GetColumnMinWidth(col); if ( fitWid < colMinWidth ) fitWid = colMinWidth; else if ( fitWid > maxColWidth ) fitWid = maxColWidth; m_colWidths[col] = fitWid; accWid += fitWid; } // Expand last one to fill the width int remaining = m_width - accWid; m_colWidths[GetColumnCount()-1] += remaining; pg->SetInternalFlag(wxPG_FL_DONT_CENTER_SPLITTER); int firstSplitterX = marginWidth + m_colWidths[0]; m_fSplitterX = (double) firstSplitterX; // Don't allow initial splitter auto-positioning after this. if ( pg->GetState() == this ) { pg->SetSplitterPosition(firstSplitterX, false); pg->Refresh(); } int x, y; pg->GetVirtualSize(&x, &y); return wxSize(accWid, y); } void wxPropertyGridState::CheckColumnWidths( int widthChange ) { if ( m_width == 0 ) return; wxPropertyGrid* pg = GetGrid(); #ifdef __WXDEBUG__ const bool debug = false; #endif unsigned int i; unsigned int lastColumn = m_colWidths.size() - 1; int width = m_width; int clientWidth = pg->GetClientSize().x; // // Column to reduce, if needed. Take last one that exceeds minimum width. int reduceCol = -1; bool minimizedCols = false; #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT("ColumnWidthCheck (virtualWidth: %i, clientWidth: %i)"), width, clientWidth); #endif // // Check min sizes for ( i=0; im_marginWidth; for ( i=0; iHasVirtualWidth(),colsWidth); #endif // Then mode-based requirement if ( !pg->HasVirtualWidth() ) { int widthHigher = width - colsWidth; // Adapt colsWidth to width if ( colsWidth < width ) { // Increase column #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT(" Adjust last column to %i"), m_colWidths[lastColumn] + widthHigher); #endif m_colWidths[lastColumn] = m_colWidths[lastColumn] + widthHigher; } else if ( colsWidth > width ) { // Reduce column if ( reduceCol != -1 ) { #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT(" Reduce column %i (by %i)"), reduceCol, -widthHigher); #endif // Reduce widest column, and recheck m_colWidths[reduceCol] = m_colWidths[reduceCol] + widthHigher; CheckColumnWidths(); } } } else { // Only check colsWidth against clientWidth if ( colsWidth < clientWidth ) { m_colWidths[lastColumn] = m_colWidths[lastColumn] + (clientWidth-colsWidth); } m_width = colsWidth; // If width changed, recalculate virtual size if ( pg->GetState() == this ) pg->RecalculateVirtualSize(); } #ifdef __WXDEBUG__ if ( debug ) for ( i=0; iGetInternalFlags() & wxPG_FL_DONT_CENTER_SPLITTER) ) { if ( m_colWidths.size() == 2 && m_columnProportions[0] == m_columnProportions[1] ) { // // When we have two columns of equal proportion, then use this // code. It will look nicer when the scrollbar visibility is // toggled on and off. // // TODO: Adapt this to generic recenter code. // float centerX = (float)(pg->m_width/2); float splitterX; if ( m_fSplitterX < 0.0 ) { #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT(" auto-center splitter reset")); #endif splitterX = centerX; } else if ( widthChange ) { #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT(" auto-center with widthChange=%i"), widthChange); #endif //float centerX = float(pg->GetSize().x) * 0.5; // Recenter? splitterX = m_fSplitterX + (float(widthChange) * 0.5); float deviation = fabs(centerX - splitterX); // If deviating from center, adjust towards it if ( deviation > 20.0 ) { if ( splitterX > centerX) splitterX -= 2; else splitterX += 2; } } else { // No width change, just keep sure we keep splitter position intact splitterX = m_fSplitterX; float deviation = fabs(centerX - splitterX); #ifdef __WXDEBUG__ if ( debug ) wxLogDebug(wxT(" auto-center with deviation=%.2f"), deviation); #endif if ( deviation > 50.0 ) { splitterX = centerX; } } DoSetSplitterPosition((int)splitterX, 0, false, true); m_fSplitterX = splitterX; // needed to retain accuracy } else { // // Generic re-center code // ResetColumnSizes(true); } } } void wxPropertyGridState::ResetColumnSizes( bool fromAutoCenter ) { unsigned int i; // Calculate sum of proportions int psum = 0; for ( i=0; im_width*256) / psum; int cpos = 0; // Apply proportions for ( i=0; i<(m_colWidths.size() - 1); i++ ) { int cwid = (puwid*m_columnProportions[i]) / 256; cpos += cwid; DoSetSplitterPosition(cpos, i, false, fromAutoCenter); } } void wxPropertyGridState::SetColumnCount( int colCount ) { wxASSERT( colCount >= 2 ); m_colWidths.SetCount( colCount, wxPG_DRAG_MARGIN ); m_columnProportions.SetCount( colCount, 1 ); if ( m_colWidths.size() > (unsigned int)colCount ) m_colWidths.RemoveAt( m_colWidths.size()-1, m_colWidths.size() - colCount ); if ( m_pPropGrid->GetState() == this ) m_pPropGrid->RecalculateVirtualSize(); else CheckColumnWidths(); } void wxPropertyGridState::DoSetColumnProportion( unsigned int column, int proportion ) { wxASSERT_MSG( proportion >= 1, wxT("Column proportion must 1 or higher") ); if ( proportion < 1 ) proportion = 1; while ( m_columnProportions.size() <= column ) m_columnProportions.push_back(1); m_columnProportions[column] = proportion; } // Returns column index, -1 for margin int wxPropertyGridState::HitTestH( int x, int* pSplitterHit, int* pSplitterHitOffset ) const { int cx = GetGrid()->m_marginWidth; int col = -1; int prevSplitter = -1; while ( x > cx ) { col++; if ( col >= (int)m_colWidths.size() ) { *pSplitterHit = -1; return col; } prevSplitter = cx; cx += m_colWidths[col]; } // Near prev. splitter if ( col >= 1 ) { int diff = x - prevSplitter; if ( abs(diff) < wxPG_SPLITTERX_DETECTMARGIN1 ) { *pSplitterHit = col - 1; *pSplitterHitOffset = diff; return col; } } // Near next splitter int nextSplitter = cx; if ( col < (int)(m_colWidths.size()-1) ) { int diff = x - nextSplitter; if ( abs(diff) < wxPG_SPLITTERX_DETECTMARGIN1 ) { *pSplitterHit = col; *pSplitterHitOffset = diff; return col; } } *pSplitterHit = -1; return col; } bool wxPropertyGridState::ArePropertiesAdjacent( wxPGProperty* prop1, wxPGProperty* prop2, int iterFlags ) const { const wxPGProperty* ap1 = wxPropertyGridConstIterator::OneStep(this, iterFlags, prop1, 1); if ( ap1 && ap1 == prop2 ) return true; const wxPGProperty* ap2 = wxPropertyGridConstIterator::OneStep(this, iterFlags, prop1, -1); if ( ap2 && ap2 == prop2 ) return true; return false; } bool wxPropertyGridInterface::SetColumnProportion( unsigned int column, int proportion ) { wxCHECK(m_pState, false); wxPropertyGrid* pg = m_pState->GetGrid(); wxCHECK(pg, false); wxCHECK(pg->HasFlag(wxPG_SPLITTER_AUTO_CENTER), false); m_pState->DoSetColumnProportion(column, proportion); return true; } // ----------------------------------------------------------------------- // wxPropertyGridState property value setting and getting // ----------------------------------------------------------------------- bool wxPropertyGridState::DoSetPropertyValueString( wxPGProperty* p, const wxString& value ) { if ( p ) { int flags = wxPG_REPORT_ERROR|wxPG_FULL_VALUE|wxPG_PROGRAMMATIC_VALUE; wxVariant variant = p->GetValueRef(); bool res; if ( p->GetMaxLength() <= 0 ) res = p->ActualStringToValue( variant, value, flags ); else res = p->ActualStringToValue( variant, value.Mid(0,p->GetMaxLength()), flags ); if ( res ) { p->SetValue(variant); } return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoSetPropertyValue( wxPGProperty* p, wxVariant& value ) { if ( p ) { p->SetValue(value); return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoSetPropertyValueWxObjectPtr( wxPGProperty* p, wxObject* value ) { if ( p ) { // TODO: Add proper testing //if ( wxStrcmp( p->GetValue().GetType(), // value->GetClassInfo()->GetClassName() // ) == 0 // ) { // wnd_primary has to be given so the control can be updated as well. wxVariant v(value); DoSetPropertyValue(p, v); return true; } //wxPGTypeOperationFailed ( p, wxT("wxObject"), wxT("Set") ); } return false; } // ----------------------------------------------------------------------- // wxPropertyGridState property operations // ----------------------------------------------------------------------- bool wxPropertyGridState::DoIsPropertySelected( wxPGProperty* prop ) const { const wxArrayPGProperty& selection = m_selection; for ( unsigned int i=0; iGetState() == this ) { // If first item (ie. one with the active editor) was // deselected, then we need to take some extra measures. wxArrayPGProperty sel = m_selection; sel.erase( sel.begin() + i ); wxPGProperty* newFirst; if ( sel.size() ) newFirst = sel[0]; else newFirst = NULL; pg->DoSelectProperty(newFirst, wxPG_SEL_DONT_SEND_EVENT); m_selection = sel; pg->Refresh(); } else { m_selection.erase( m_selection.begin() + i ); } return; } } } // ----------------------------------------------------------------------- void wxPropertyGridState::ClearModifiedStatus( wxPGProperty* p ) { if ( p->m_flags & wxPG_PROP_MODIFIED ) { p->m_flags &= ~(wxPG_PROP_MODIFIED); if ( m_pPropGrid->GetState() == this ) { // Clear active editor bold if ( p == GetSelection() ) m_pPropGrid->RefreshEditor(); m_pPropGrid->DrawItem( p ); } } size_t i; for ( i = 0; i < p->GetChildCount(); i++ ) ClearModifiedStatus( p->Item(i) ); } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoCollapse( wxPGProperty* p ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGProperty* pwc = (wxPGProperty*)p; if ( !pwc->GetChildCount() ) return false; if ( !pwc->IsExpanded() ) return false; pwc->SetExpanded(false); VirtualHeightChanged(); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoExpand( wxPGProperty* p ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGProperty* pwc = (wxPGProperty*)p; if ( !pwc->GetChildCount() ) return false; if ( pwc->IsExpanded() ) return false; pwc->SetExpanded(true); VirtualHeightChanged(); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoSelectProperty( wxPGProperty* p, unsigned int flags ) { if ( this == m_pPropGrid->GetState() ) return m_pPropGrid->DoSelectProperty( p, flags ); DoSetSelection(p); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoHideProperty( wxPGProperty* p, bool hide, int flags ) { p->DoHide(hide, flags); VirtualHeightChanged(); return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoEnableProperty( wxPGProperty* p, bool enable ) { if ( p ) { if ( enable ) { if ( !(p->m_flags & wxPG_PROP_DISABLED) ) return false; // Enabling p->m_flags &= ~(wxPG_PROP_DISABLED); } else { if ( p->m_flags & wxPG_PROP_DISABLED ) return false; // Disabling p->m_flags |= wxPG_PROP_DISABLED; } // Apply same to sub-properties as well unsigned int i; for ( i = 0; i < p->GetChildCount(); i++ ) DoEnableProperty( p->Item(i), enable ); return true; } return false; } // ----------------------------------------------------------------------- // wxPropertyGridState wxVariant related routines // ----------------------------------------------------------------------- // Returns list of wxVariant objects (non-categories and non-sub-properties only). // Never includes sub-properties (unless they are parented by wxParentProperty). wxVariant wxPropertyGridState::DoGetPropertyValues( const wxString& listname, wxPGProperty* baseparent, long flags ) const { wxPGProperty* pwc = (wxPGProperty*) baseparent; // Root is the default base-parent. if ( !pwc ) pwc = m_properties; wxVariantList tempList; wxVariant v( tempList, listname ); if ( pwc->GetChildCount() ) { if ( flags & wxPG_KEEP_STRUCTURE ) { wxASSERT( !pwc->IsFlagSet(wxPG_PROP_AGGREGATE) ); size_t i; for ( i=0; iGetCount(); i++ ) { wxPGProperty* p = pwc->Item(i); if ( !p->GetChildCount() || p->HasFlag(wxPG_PROP_AGGREGATE) ) { wxVariant variant = p->GetValue(); variant.SetName( p->GetBaseName() ); v.Append( variant ); } else { v.Append( DoGetPropertyValues(p->m_name,p,flags|wxPG_KEEP_STRUCTURE) ); } if ( (flags & wxPG_INC_ATTRIBUTES) && p->m_attributes.GetCount() ) v.Append( p->GetAttributesAsList() ); } } else { wxPropertyGridConstIterator it( this, wxPG_ITERATE_DEFAULT, pwc->Item(0) ); it.SetBaseParent( pwc ); for ( ; !it.AtEnd(); it.Next() ) { const wxPGProperty* p = it.GetProperty(); // Use a trick to ignore wxParentProperty itself, but not its sub-properties. if ( !p->GetChildCount() || p->HasFlag(wxPG_PROP_AGGREGATE) ) { wxVariant variant = p->GetValue(); variant.SetName( p->GetName() ); v.Append( variant ); if ( (flags & wxPG_INC_ATTRIBUTES) && p->m_attributes.GetCount() ) v.Append( p->GetAttributesAsList() ); } } } } return v; } // ----------------------------------------------------------------------- void wxPropertyGridState::DoSetPropertyValues( const wxVariantList& list, wxPGProperty* defaultCategory ) { unsigned char origFrozen = 1; if ( m_pPropGrid->GetState() == this ) { origFrozen = m_pPropGrid->m_frozen; if ( !origFrozen ) m_pPropGrid->Freeze(); } wxPropertyCategory* use_category = (wxPropertyCategory*)defaultCategory; if ( !use_category ) use_category = (wxPropertyCategory*)m_properties; // Let's iterate over the list of variants. wxVariantList::const_iterator node; int numSpecialEntries = 0; // // Second pass for special entries for ( node = list.begin(); node != list.end(); node++ ) { wxVariant *current = (wxVariant*)*node; // Make sure it is wxVariant. wxASSERT( current ); wxASSERT( wxStrcmp(current->GetClassInfo()->GetClassName(),wxT("wxVariant")) == 0 ); const wxString& name = current->GetName(); if ( name.length() > 0 ) { // // '@' signified a special entry if ( name[0] == wxT('@') ) { numSpecialEntries++; } else { wxPGProperty* foundProp = BaseGetPropertyByName(name); if ( foundProp ) { wxPGProperty* p = foundProp; // If it was a list, we still have to go through it. if ( wxStrcmp(current->GetType(), wxT("list")) == 0 ) { DoSetPropertyValues( current->GetList(), p->IsCategory()?p:((wxPGProperty*)NULL) ); } else { #ifdef __WXDEBUG__ if ( wxStrcmp(current->GetType(), p->GetValue().GetType()) != 0) { wxLogDebug(wxT("wxPropertyGridState::DoSetPropertyValues Warning: Setting value of property \"%s\" from variant"), p->GetName().c_str()); //wxLogDebug(wxT(" but variant's type name (%s) doesn't match either base type name (%s) nor custom type name (%s)."), // current->GetType().c_str(),vtype->GetTypeName(), // vtype->GetCustomTypeName()); } #endif p->SetValue(*current); } } else { // Is it list? if ( current->GetType() != wxT("list") ) { // Not. } else { // Yes, it is; create a sub category and append contents there. wxPGProperty* newCat = DoInsert(use_category,-1,new wxPropertyCategory(current->GetName(),wxPG_LABEL)); DoSetPropertyValues( current->GetList(), newCat ); } } } } } if ( numSpecialEntries ) { for ( node = list.begin(); node != list.end(); node++ ) { wxVariant *current = (wxVariant*)*node; const wxString& name = current->GetName(); if ( name.length() > 0 ) { // // '@' signified a special entry if ( name[0] == wxT('@') ) { numSpecialEntries--; size_t pos2 = name.rfind(wxT('@')); if ( pos2 > 0 && pos2 < (name.size()-1) ) { wxString propName = name.substr(1, pos2-1); wxString entryType = name.substr(pos2+1, wxString::npos); if ( entryType == wxT("attr") ) { // // List of attributes wxPGProperty* foundProp = BaseGetPropertyByName(propName); if ( foundProp ) { wxASSERT( wxPGIsVariantType(*current, list) ); wxVariantList& list2 = current->GetList(); wxVariantList::const_iterator node2; for ( node2 = list2.begin(); node2 != list2.end(); node2++ ) { wxVariant *attr = (wxVariant*)*node2; foundProp->SetAttribute( attr->GetName(), *attr ); } } else { // ERROR: No such property: 'propName' } } } else { // ERROR: Special entry requires name of format @@ } } } if ( !numSpecialEntries ) break; } } if ( !origFrozen ) { m_pPropGrid->Thaw(); if ( this == m_pPropGrid->GetState() ) m_pPropGrid->RefreshEditor(); } } // ----------------------------------------------------------------------- // wxPropertyGridState property adding and removal // ----------------------------------------------------------------------- // Call for after sub-properties added with AddChild void wxPGProperty::PrepareSubProperties() { wxPropertyGridState* state = GetParentState(); wxASSERT(state); if ( !GetCount() ) return; wxByte depth = m_depth + 1; wxByte depthBgCol = m_depthBgCol; FlagType inheritFlags = m_flags & wxPG_INHERITED_PROPFLAGS; wxByte bgColIndex = m_bgColIndex; wxByte fgColIndex = m_fgColIndex; // // Set some values to the children // size_t i = 0; wxPGProperty* nparent = this; while ( i < nparent->GetCount() ) { wxPGProperty* np = nparent->Item(i); np->m_parentState = state; np->m_flags |= inheritFlags; // Hideable also if parent. np->m_depth = depth; np->m_depthBgCol = depthBgCol; np->m_bgColIndex = bgColIndex; np->m_fgColIndex = fgColIndex; // Also handle children of children if ( np->GetCount() > 0 ) { nparent = np; i = 0; // Init nparent->SetParentalType(wxPG_PROP_AGGREGATE); nparent->SetExpanded(false); depth++; } else { // Next sibling i++; } // After reaching last sibling, go back to processing // siblings of the parent while ( i >= nparent->GetCount() ) { // Exit the loop when top parent hit if ( nparent == this ) break; depth--; i = nparent->GetArrIndex() + 1; nparent = nparent->GetParent(); } } } // ----------------------------------------------------------------------- // Call after fixed sub-properties added/removed after creation. // if oldSelInd >= 0 and < new max items, then selection is // moved to it. Note: oldSelInd -2 indicates that this property // should be selected. void wxPGProperty::SubPropsChanged( int oldSelInd ) { wxPropertyGridState* state = GetParentState(); wxPropertyGrid* grid = state->GetGrid(); PrepareSubProperties(); wxPGProperty* sel = (wxPGProperty*) NULL; if ( oldSelInd >= (int)m_children.GetCount() ) oldSelInd = (int)m_children.GetCount() - 1; if ( oldSelInd >= 0 ) sel = (wxPGProperty*) m_children[oldSelInd]; else if ( oldSelInd == -2 ) sel = this; if ( sel ) state->DoSelectProperty(sel); if ( state == grid->GetState() ) { grid->GetPanel()->Refresh(); } } // ----------------------------------------------------------------------- int wxPropertyGridState::PrepareToAddItem( wxPGProperty* property, wxPGProperty* scheduledParent ) { wxPropertyGrid* propGrid = m_pPropGrid; #if wxPG_COMPATIBILITY_1_2_0 // Make sure deprecated functions are not implemented wxASSERT_MSG( property->GetImageSize().x == -1234, wxT("Custom properties implement OnMeasureImage instead of GetImageSize") ); int oldCommonVal = property->m_commonValue; property->m_commonValue = -1; property->SetValueFromString( wxEmptyString, 0xFFFF ); wxASSERT_MSG( property->m_commonValue == -1234, wxT("Implement StringToValue() instead of SetValueFromString()") ); property->m_commonValue = -1; property->SetValueFromInt( 0, 0xFFFF ); wxASSERT_MSG( property->m_commonValue == -1234, wxT("Implement StringToValue() instead of SetValueFromString()") ); property->m_commonValue = oldCommonVal; #endif // This will allow better behavior. if ( scheduledParent == m_properties ) scheduledParent = (wxPGProperty*) NULL; if ( scheduledParent && !scheduledParent->IsCategory() ) { wxASSERT_MSG( property->GetBaseName().length(), wxT("Property's children must have unique, non-empty names within their scope") ); } property->m_parentState = this; if ( property->IsCategory() ) { // Parent of a category must be either root or another category // (otherwise Bad Things might happen). wxASSERT_MSG( scheduledParent == NULL || scheduledParent == m_properties || scheduledParent->IsCategory(), wxT("Parent of a category must be either root or another category.")); // If we already have category with same name, delete given property // and use it instead as most recent caption item. wxPGProperty* found_id = BaseGetPropertyByName( property->GetBaseName() ); if ( found_id ) { wxPropertyCategory* pwc = (wxPropertyCategory*) found_id; if ( pwc->IsCategory() ) // Must be a category. { delete property; m_currentCategory = pwc; return 2; // Tells the caller what we did. } } } #ifdef __WXDEBUG__ // Warn for identical names in debug mode. if ( BaseGetPropertyByName(property->GetName()) && (!scheduledParent || scheduledParent->IsCategory()) ) { wxLogError(wxT("wxPropertyGrid: Warning - item with name \"%s\" already exists."), property->GetName().c_str()); wxPGGlobalVars->m_warnings++; } #endif if ( scheduledParent ) { // Use parent's colours. property->m_bgColIndex = scheduledParent->m_bgColIndex; property->m_fgColIndex = scheduledParent->m_fgColIndex; // Fix no parent does not yet have parenting flag yet, set one now if ( !scheduledParent->HasFlag(wxPG_PROP_PARENTAL_FLAGS) ) scheduledParent->SetParentalType(wxPG_PROP_MISC_PARENT); //scheduledParent->SetFlag(wxPG_PROP_MISC_PARENT); } // If in hideable adding mode, or if assigned parent is hideable, then // make this one hideable. if ( ( scheduledParent && (scheduledParent->m_flags & wxPG_PROP_HIDDEN) ) || ( propGrid && (propGrid->m_iFlags & wxPG_FL_ADDING_HIDEABLES) ) ) property->SetFlag( wxPG_PROP_HIDDEN ); // Set custom image flag. int custImgHeight = property->OnMeasureImage().y; if ( custImgHeight < 0 /*|| custImgHeight > 1*/ ) { property->m_flags |= wxPG_PROP_CUSTOMIMAGE; } if ( propGrid && (propGrid->GetWindowStyleFlag() & wxPG_LIMITED_EDITING) ) property->m_flags |= wxPG_PROP_NOEDITOR; if ( !property->IsCategory() ) { // This is not a category. //wxASSERT_MSG( property->GetEditorClass(), wxT("Editor class not initialized!") ); // Depth. // unsigned char depth = 1; if ( scheduledParent ) { depth = scheduledParent->m_depth; if ( !scheduledParent->IsCategory() ) depth++; } property->m_depth = depth; unsigned char greyDepth = depth; if ( scheduledParent ) { wxPropertyCategory* pc; if ( scheduledParent->IsCategory() || scheduledParent->IsRoot() ) pc = (wxPropertyCategory*)scheduledParent; else // This conditional compile is necessary to // bypass some compiler bug. pc = GetPropertyCategory(scheduledParent); if ( pc ) greyDepth = pc->GetDepth(); else greyDepth = scheduledParent->m_depthBgCol; } property->m_depthBgCol = greyDepth; // Prepare children pre-added children if ( property->GetCount() ) { property->SetParentalType(wxPG_PROP_AGGREGATE); property->SetExpanded(false); // Properties with children are not expanded by default. if ( propGrid && propGrid->GetWindowStyleFlag() & wxPG_HIDE_MARGIN ) property->SetExpanded(true); // ...unless it cannot be expanded. property->PrepareSubProperties(); return -1; } if ( propGrid && (propGrid->GetExtraStyle() & wxPG_EX_AUTO_UNSPECIFIED_VALUES) ) property->SetFlagRecursively(wxPG_PROP_AUTO_UNSPECIFIED, true); return 0; } else { // This is a category. // depth unsigned char depth = 1; if ( scheduledParent ) { depth = scheduledParent->m_depth + 1; } property->m_depth = depth; property->m_depthBgCol = depth; m_currentCategory = (wxPropertyCategory*)property; wxPropertyCategory* pc = (wxPropertyCategory*)property; // Calculate text extent for caption item. if ( propGrid ) pc->CalculateTextExtent(propGrid, propGrid->GetCaptionFont()); return 1; } } // ----------------------------------------------------------------------- void wxPropertyGridInterface::BeginAddChildren( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG() wxCHECK_RET( p->HasFlag(wxPG_PROP_AGGREGATE), wxT("only call on properties with fixed children") ); p->ClearFlag(wxPG_PROP_AGGREGATE); p->SetFlag(wxPG_PROP_MISC_PARENT); } // ----------------------------------------------------------------------- void wxPropertyGridInterface::EndAddChildren( wxPGPropArg id ) { wxPG_PROP_ARG_CALL_PROLOG() wxCHECK_RET( p->HasFlag(wxPG_PROP_MISC_PARENT), wxT("only call on properties for which BeginAddChildren was called prior") ); p->ClearFlag(wxPG_PROP_MISC_PARENT); p->SetFlag(wxPG_PROP_AGGREGATE); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::DoAppend( wxPGProperty* property ) { wxPropertyCategory* cur_cat = m_currentCategory; if ( property->IsCategory() ) cur_cat = (wxPropertyCategory*) NULL; return DoInsert( cur_cat, -1, property ); } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridState::DoInsert( wxPGProperty* parent, int index, wxPGProperty* property ) { if ( !parent ) parent = m_properties; wxCHECK_MSG( !parent->HasFlag(wxPG_PROP_AGGREGATE), wxNullProperty, wxT("when adding properties to fixed parents, use BeginAddChildren and EndAddChildren.") ); int parenting = PrepareToAddItem( property, (wxPropertyCategory*)parent ); // This type of invalid parenting value indicates we should exit now, returning // id of most recent category. if ( parenting > 1 ) return m_currentCategory; // Note that item must be added into current mode later. // If parent is wxParentProperty, just stick it in... // If parent is root (m_properties), then... // In categoric mode: Add as last item in m_abcArray (if not category). // Add to given index in m_regularArray. // In non-cat mode: Add as last item in m_regularArray. // Add to given index in m_abcArray. // If parent is category, then... // 1) Add to given category in given index. // 2) Add as last item in m_abcArray. if ( !parent->IsCategory() && !parent->IsRoot() ) { // Parent is wxParentingProperty: Just stick it in... parent->AddChild2( property, index ); } else { // Parent is Category or Root. if ( m_properties == &m_regularArray ) { // Categorized mode // Only add non-categories to m_abcArray. if ( m_abcArray && parenting <= 0 ) m_abcArray->AddChild2( property, -1, false ); // Add to current mode. parent->AddChild2( property, index ); } else { // Non-categorized mode. if ( parent != m_properties ) // Parent is category. parent->AddChild2( property, index, false ); else // Parent is root. m_regularArray.AddChild2( property, -1, false ); // Add to current mode (no categories). if ( parenting <= 0 ) m_abcArray->AddChild2( property, index ); } } // category stuff if ( property->IsCategory() ) { // This is a category caption item. // Last caption is not the bottom one (this info required by append) m_lastCaptionBottomnest = 0; } // Only add name to hashmap if parent is root or category if ( property->m_name.length() && (parent->IsCategory() || parent->IsRoot()) ) m_dictName[property->m_name] = (void*) property; VirtualHeightChanged(); property->UpdateParentValues(); // Fix y-position of any open property editor if ( m_pPropGrid ) m_pPropGrid->CorrectEditorWidgetPosY(); m_itemsAdded = 1; return property; } // ----------------------------------------------------------------------- void wxPropertyGridState::DoDelete( wxPGProperty* item, bool doDelete ) { //wxLogDebug(wxT("DoDelete: %s"), item->GetLabel().c_str()); wxCHECK_RET( item->GetParent(), wxT("this property was already deleted") ); wxCHECK_RET( item != &m_regularArray && item != m_abcArray, wxT("wxPropertyGrid: Do not attempt to remove the root item.") ); wxASSERT( item->GetParentState() == this ); wxPropertyGrid* pg = GetGrid(); // Must defer deletion? Yes, if handling a wxPG event. if ( pg && pg->m_processedEvent ) { if ( doDelete ) pg->m_deletedProperties.push_back(item); else pg->m_removedProperties.push_back(item); // Rename the property so it won't remain in the way // of the user code. // Let's trust that no sane property uses prefix like // this. It would be anyway fairly inconvenient (in // current code) to check whether a new name is used // by another property with parent (due to the child // name notation). wxString newName = wxT("_&/_%$") + item->GetBaseName(); pg->DoSetPropertyName(item, newName); return; } if ( DoIsPropertySelected(item) ) { if ( pg && pg->GetState() == this ) { pg->DoRemoveFromSelection(item, wxPG_SEL_DELETING|wxPG_SEL_NOVALIDATE); } else { DoRemoveFromSelection(item); } } item->SetFlag(wxPG_PROP_BEING_DELETED); unsigned int indinparent = item->GetIndexInParent(); wxPGProperty* pwc = (wxPGProperty*)item; wxPGProperty* parent = item->GetParent(); wxCHECK_RET( !parent->HasFlag(wxPG_PROP_AGGREGATE), wxT("wxPropertyGrid: Do not attempt to remove sub-properties.") ); // Delete children if ( item->GetChildCount() && !item->HasFlag(wxPG_PROP_AGGREGATE) ) { // deleting a category if ( item->IsCategory() ) { if ( pwc == m_currentCategory ) m_currentCategory = (wxPropertyCategory*) NULL; } item->DeleteChildren(); } if ( !IsInNonCatMode() ) { // categorized mode - non-categorized array // Remove from non-cat array if ( !item->IsCategory() && (parent->IsCategory() || parent->IsRoot()) ) { if ( m_abcArray ) { m_abcArray->m_children.Remove( item ); } } // categorized mode - categorized array parent->m_children.RemoveAt(indinparent); parent->FixIndexesOfChildren(); } else { // non-categorized mode - categorized array // We need to find location of item. wxPGProperty* cat_parent = &m_regularArray; int cat_index = m_regularArray.GetCount(); size_t i; for ( i = 0; i < m_regularArray.GetCount(); i++ ) { wxPGProperty* p = m_regularArray.Item(i); if ( p == item ) { cat_index = i; break; } if ( p->IsCategory() ) { int subind = ((wxPGProperty*)p)->Index(item); if ( subind != wxNOT_FOUND ) { cat_parent = ((wxPGProperty*)p); cat_index = subind; break; } } } cat_parent->m_children.RemoveAt(cat_index); // non-categorized mode - non-categorized array if ( !item->IsCategory() ) { wxASSERT( item->m_parent == m_abcArray ); item->m_parent->m_children.RemoveAt(indinparent); item->m_parent->FixIndexesOfChildren(indinparent); } } if ( item->GetBaseName().length() && (parent->IsCategory() || parent->IsRoot()) ) m_dictName.erase( wxPGNameConv(item->GetBaseName()) ); #ifdef __WXPYTHON__ // For some reason, Py_DECREF always crashes, even though we make // matching Py_INCREF call in propgrid_cbacks.cpp. Maybe refcount is decremented // somewhere automatically? Unlikely though... //if ( item->m_scriptObject ) // Py_DECREF( item->m_scriptObject ); #endif // We need to clear parent grid's m_propHover, if it matches item if ( pg && pg->m_propHover == item ) pg->m_propHover = NULL; // Mark the property as 'unattached' item->m_parentState = NULL; item->m_parent = NULL; // We can actually delete it now if ( doDelete ) delete item; m_itemsAdded = 1; // Not a logical assignment (but required nonetheless). VirtualHeightChanged(); } // ----------------------------------------------------------------------- // wxPropertyGridState init etc. // ----------------------------------------------------------------------- void wxPropertyGridState::InitNonCatMode() { if ( !m_abcArray ) { m_abcArray = new wxPGRootProperty(); m_abcArray->SetParentState(this); m_abcArray->SetFlag(wxPG_PROP_CHILDREN_ARE_COPIES); } // Must be called when state::m_properties still points to regularArray. wxPGProperty* oldProperties = m_properties; // Must use temp value in state::m_properties for item iteration loop // to run as expected. m_properties = &m_regularArray; if ( m_properties->GetChildCount() ) { // // Prepare m_abcArray wxPropertyGridIterator it( this, wxPG_ITERATE_PROPERTIES ); for ( ; !it.AtEnd(); it.Next() ) { wxPGProperty* p = it.GetProperty(); wxPGProperty* parent = p->GetParent(); if ( parent->IsCategory() || parent->IsRoot() ) { m_abcArray->AddChild2(p); p->m_parent = &m_regularArray; } } } m_properties = oldProperties; } // ----------------------------------------------------------------------- void wxPropertyGridState::DoClear() { if ( m_pPropGrid && m_pPropGrid->GetState() == this ) { m_pPropGrid->DoSelectProperty(NULL, wxPG_SEL_DELETING|wxPG_SEL_NOVALIDATE); } else { m_selection.clear(); } m_regularArray.DoEmpty(); if ( m_abcArray ) m_abcArray->DoEmpty(); m_dictName.clear(); m_currentCategory = (wxPropertyCategory*) NULL; m_lastCaptionBottomnest = 1; m_itemsAdded = 0; m_virtualHeight = 0; m_vhCalcPending = 0; } // ----------------------------------------------------------------------- wxPropertyGridState::wxPropertyGridState() { m_pPropGrid = (wxPropertyGrid*) NULL; m_regularArray.SetParentState(this); m_properties = &m_regularArray; m_abcArray = (wxPGRootProperty*) NULL; m_currentCategory = (wxPropertyCategory*) NULL; m_width = 0; m_virtualHeight = 0; m_lastCaptionBottomnest = 1; m_itemsAdded = 0; m_anyModified = 0; m_vhCalcPending = 0; m_colWidths.push_back( wxPG_DEFAULT_SPLITTERX ); m_colWidths.push_back( wxPG_DEFAULT_SPLITTERX ); m_fSplitterX = wxPG_DEFAULT_SPLITTERX; m_columnProportions.push_back(1); m_columnProportions.push_back(1); // By default, we only have the 'value' column editable m_editableColumns.push_back(1); } // ----------------------------------------------------------------------- wxPropertyGridState::~wxPropertyGridState() { delete m_abcArray; } // ----------------------------------------------------------------------- void wxPropertyGridState::CalculateFontAndBitmapStuff( int WXUNUSED(vspacing) ) { wxPropertyGrid* propGrid = GetGrid(); VirtualHeightChanged(); // Recalculate caption text extents. unsigned int i; for ( i=0;iIsCategory() ) ((wxPropertyCategory*)p)->CalculateTextExtent(propGrid, propGrid->GetCaptionFont()); } } // ----------------------------------------------------------------------- // wxPropertyGridPopulator // ----------------------------------------------------------------------- wxPropertyGridPopulator::wxPropertyGridPopulator() { m_state = NULL; m_pg = NULL; wxPGGlobalVars->m_offline++; } // ----------------------------------------------------------------------- void wxPropertyGridPopulator::SetState( wxPropertyGridState* state ) { m_state = state; m_propHierarchy.clear(); } // ----------------------------------------------------------------------- void wxPropertyGridPopulator::SetGrid( wxPropertyGrid* pg ) { m_pg = pg; pg->Freeze(); } // ----------------------------------------------------------------------- wxPropertyGridPopulator::~wxPropertyGridPopulator() { // // Free unused sets of choices wxPGHashMapS2P::iterator it; for( it = m_dictIdChoices.begin(); it != m_dictIdChoices.end(); ++it ) { wxPGChoicesData* data = (wxPGChoicesData*) it->second; data->DecRef(); } if ( m_pg ) { m_pg->Thaw(); m_pg->GetPanel()->Refresh(); } wxPGGlobalVars->m_offline--; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGridPopulator::Add( const wxString& propClass, const wxString& propLabel, const wxString& propName, const wxString* propValue, wxPGChoices* pChoices ) { wxClassInfo* classInfo = wxClassInfo::FindClass(propClass); wxPGProperty* parent = GetCurParent(); if ( parent->HasFlag(wxPG_PROP_AGGREGATE) ) { ProcessError(wxString::Format(wxT("new children cannot be added to '%s'"),parent->GetName().c_str())); return NULL; } if ( !classInfo || !classInfo->IsKindOf(CLASSINFO(wxPGProperty)) ) { ProcessError(wxString::Format(wxT("'%s' is not valid property class"),propClass.c_str())); return NULL; } wxPGProperty* property = (wxPGProperty*) classInfo->CreateObject(); property->SetLabel(propLabel); property->DoSetName(propName); if ( pChoices && pChoices->IsOk() ) property->SetChoices(*pChoices); m_state->DoInsert(parent, -1, property); if ( propValue ) property->SetValueFromString( *propValue, wxPG_FULL_VALUE| wxPG_PROGRAMMATIC_VALUE ); return property; } // ----------------------------------------------------------------------- void wxPropertyGridPopulator::AddChildren( wxPGProperty* property ) { m_propHierarchy.push_back(property); DoScanForChildren(); m_propHierarchy.pop_back(); } // ----------------------------------------------------------------------- wxPGChoices wxPropertyGridPopulator::ParseChoices( const wxString& choicesString, const wxString& idString ) { wxPGChoices choices; // Using id? if ( choicesString[0] == wxT('@') ) { wxString ids = choicesString.substr(1); wxPGHashMapS2P::iterator it = m_dictIdChoices.find(ids); if ( it == m_dictIdChoices.end() ) ProcessError(wxString::Format(wxT("No choices defined for id '%s'"),ids.c_str())); else choices.AssignData((wxPGChoicesData*)it->second); } else { bool found = false; if ( idString.length() ) { wxPGHashMapS2P::iterator it = m_dictIdChoices.find(idString); if ( it != m_dictIdChoices.end() ) { choices.AssignData((wxPGChoicesData*)it->second); found = true; } } if ( !found ) { // Parse choices string wxString::const_iterator it = choicesString.begin(); wxString label; wxString value; int state = 0; bool labelValid = false; for ( ; it != choicesString.end(); it++ ) { wxChar c = *it; if ( state != 1 ) { if ( c == wxT('"') ) { if ( labelValid ) { long l; if ( !value.ToLong(&l, 0) ) l = wxPG_INVALID_VALUE; choices.Add(label, l); } labelValid = false; //wxLogDebug(wxT("%s, %s"),label.c_str(),value.c_str()); value.clear(); label.clear(); state = 1; } else if ( c == wxT('=') ) { if ( labelValid ) { state = 2; } } else if ( state == 2 && (wxIsalnum(c) || c == wxT('x')) ) { value << c; } } else { if ( c == wxT('"') ) { state = 0; labelValid = true; } else label << c; } } if ( labelValid ) { long l; if ( !value.ToLong(&l, 0) ) l = wxPG_INVALID_VALUE; choices.Add(label, l); } if ( !choices.IsOk() ) { choices.EnsureData(); } // Assign to id if ( idString.length() ) m_dictIdChoices[idString] = choices.GetData(); } } return choices; } // ----------------------------------------------------------------------- bool wxPropertyGridPopulator::ToLongPCT( const wxString& s, long* pval, long max ) { if ( s.Last() == wxT('%') ) { wxString s2 = s.substr(0,s.length()-1); long val; if ( s2.ToLong(&val, 10) ) { *pval = (val*max)/100; return true; } return false; } return s.ToLong(pval, 10); } // ----------------------------------------------------------------------- bool wxPropertyGridPopulator::AddAttribute( const wxString& name, const wxString& type, const wxString& value ) { int l = m_propHierarchy.size(); if ( !l ) return false; wxPGProperty* p = m_propHierarchy[l-1]; wxString valuel = value.Lower(); wxVariant variant; if ( type.length() == 0 ) { long v; // Auto-detect type if ( valuel == wxT("true") || valuel == wxT("yes") || valuel == wxT("1") ) variant = true; else if ( valuel == wxT("false") || valuel == wxT("no") || valuel == wxT("0") ) variant = false; else if ( value.ToLong(&v, 0) ) variant = v; else variant = value; } else { if ( type == wxT("string") ) { variant = value; } else if ( type == wxT("int") ) { long v = 0; value.ToLong(&v, 0); variant = v; } else if ( type == wxT("bool") ) { if ( valuel == wxT("true") || valuel == wxT("yes") || valuel == wxT("1") ) variant = true; else variant = false; } else { ProcessError(wxString::Format(wxT("Invalid attribute type '%s'"),type.c_str())); return false; } } p->SetAttribute( name, variant ); return true; } // ----------------------------------------------------------------------- void wxPropertyGridPopulator::ProcessError( const wxString& msg ) { wxLogError(_("Error in resource: %s"),msg.c_str()); } // ----------------------------------------------------------------------- // Implementation of various deprecated methods that were inline, // made non-inline to eliminate problems with wxDEPRECATED. // ----------------------------------------------------------------------- #if wxPG_COMPATIBILITY_1_2_0 wxPGProperty* wxPropertyGridInterface::AppendCategory( const wxString& label, const wxString& name ) { return Append( new wxPropertyCategory(label,name) ); } void wxPropertyGridInterface::Delete( wxPGPropArg id ) { DeleteProperty(id); } bool wxPropertyGridInterface::Disable( wxPGPropArg id ) { return EnableProperty(id,false); } bool wxPropertyGridInterface::IsPropertyValueType( wxPGPropArg id, const wxChar* typestr ) const { wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false) return (wxStrcmp(p->GetValue().GetType(),typestr) == 0); } bool wxPropertyGridInterface::IsModified( wxPGPropArg id ) const { return IsPropertyModified(id); } void wxPropertyGrid::ClearTargetPage() { Clear(); } void wxPropertyGrid::SetPropertyColour( wxPGPropArg id, const wxColour& col ) { SetPropertyBackgroundColour( id, col ); } #endif // wxPG_COMPATIBILITY_1_2_0 // -----------------------------------------------------------------------