///////////////////////////////////////////////////////////////////////////// // Name: propgrid.cpp // Purpose: wxPropertyGrid // Author: Jaakko Salli // Modified by: // Created: Sep-25-2004 // RCS-ID: $Id: // Copyright: (c) Jaakko Salli // Licence: wxWindows license ///////////////////////////////////////////////////////////////////////////// #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "propgrid.h" #endif // 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/dcclient.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/combobox.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 #include "wx/timer.h" #include "wx/dcbuffer.h" // This define is necessary to prevent macro clearing #define __wxPG_SOURCE_FILE__ #include #include #ifdef __WXPYTHON__ #include #include #endif #if wxPG_USE_RENDERER_NATIVE #include #endif #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_BUTTON_SIZEDEC 0 #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 #define RedrawAllVisible Refresh // // Here are some extra platform dependent defines. // #if defined(__WXMSW__) // tested #define wxPG_DEFAULT_SPLITTERX 110 // default splitter position #define wxPG_CREATE_CONTROLS_HIDDEN 0 // 1 to create controls out of sight, hide them, and then move them into correct 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_TEXTCTRL_BORDER_X 0 // Unremovable border of native textctrl. #define wxPG_NAT_TEXTCTRL_BORDER_Y 0 // Unremovable border of native textctrl. #define wxPG_NAT_BUTTON_BORDER_ANY 1 #define wxPG_NAT_BUTTON_BORDER_X 1 #define wxPG_NAT_BUTTON_BORDER_Y 1 #define wxPG_TEXTCTRLYADJUST (m_spacingy+0) #define wxPG_CHOICEXADJUST (-1) // Extra pixels next to wxChoice/ComboBox. #define wxPG_CHOICEYADJUST 0 // Extra pixels above wxChoice/ComboBox. #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 0 // If 1 then controls are refreshed after selected was drawn. #define wxPG_CHECKMARK_XADJ 1 #define wxPG_CHECKMARK_YADJ (-1) #define wxPG_CHECKMARK_WADJ 0 #define wxPG_CHECKMARK_HADJ 0 #define wxPG_CHECKMARK_DEFLATE 0 #elif defined(__WXGTK__) // tested #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_CREATE_CONTROLS_HIDDEN 0 // 1 to create controls out of sight, hide them, and then move them into correct position #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_TEXTCTRL_BORDER_X 3 // Unremovable border of native textctrl. #define wxPG_NAT_TEXTCTRL_BORDER_Y 3 // Unremovable border of native textctrl. #define wxPG_NAT_BUTTON_BORDER_ANY 1 #define wxPG_NAT_BUTTON_BORDER_X 1 #define wxPG_NAT_BUTTON_BORDER_Y 1 #define wxPG_TEXTCTRLYADJUST 0 #define wxPG_CHOICEXADJUST 2 // Extra pixels next to wxChoice/ComboBox. #define wxPG_CHOICEYADJUST 0 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 1 // If 1 then controls are refreshed after selected was drawn. #define wxPG_CHECKMARK_XADJ 0 #define wxPG_CHECKMARK_YADJ 0 #define wxPG_CHECKMARK_WADJ (-1) #define wxPG_CHECKMARK_HADJ (-1) #define wxPG_CHECKMARK_DEFLATE 3 #elif defined(__WXMAC__) // *not* tested #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_CREATE_CONTROLS_HIDDEN 0 // 1 to create controls out of sight, hide them, and then move them into correct 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_TEXTCTRL_BORDER_X 0 // Unremovable border of native textctrl. #define wxPG_NAT_TEXTCTRL_BORDER_Y 0 // Unremovable border of native textctrl. #define wxPG_NAT_BUTTON_BORDER_ANY 0 #define wxPG_NAT_BUTTON_BORDER_X 0 #define wxPG_NAT_BUTTON_BORDER_Y 0 #define wxPG_TEXTCTRLYADJUST 3 #define wxPG_CHOICEXADJUST 0 // Extra pixels next to wxChoice/ComboBox. #define wxPG_CHOICEYADJUST 0 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 0 // If 1 then controls are refreshed after selected was drawn. #define wxPG_CHECKMARK_XADJ 0 #define wxPG_CHECKMARK_YADJ 0 #define wxPG_CHECKMARK_WADJ 0 #define wxPG_CHECKMARK_HADJ 0 #define wxPG_CHECKMARK_DEFLATE 0 #else // defaults #define wxPG_DEFAULT_SPLITTERX 110 #define wxPG_CREATE_CONTROLS_HIDDEN 0 // 1 to create controls out of sight, hide them, and then move them into correct position #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_TEXTCTRL_BORDER_X 0 // Unremovable border of native textctrl. #define wxPG_NAT_TEXTCTRL_BORDER_Y 0 // Unremovable border of native textctrl. #define wxPG_NAT_BUTTON_BORDER_ANY 0 #define wxPG_NAT_BUTTON_BORDER_X 0 #define wxPG_NAT_BUTTON_BORDER_Y 0 #define wxPG_TEXTCTRLYADJUST 0 #define wxPG_CHOICEXADJUST 0 // Extra pixels next to wxChoice/ComboBox. #define wxPG_CHOICEYADJUST 0 #define wxPG_REFRESH_CONTROLS_AFTER_REPAINT 1 // If 1 then controls are refreshed after selected was drawn. #define wxPG_CHECKMARK_XADJ 0 #define wxPG_CHECKMARK_YADJ 0 #define wxPG_CHECKMARK_WADJ 0 #define wxPG_CHECKMARK_HADJ 0 #define wxPG_CHECKMARK_DEFLATE 0 #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 #define wxPG_CONTROL_MARGIN 0 // space between splitter and control #else #define wxPG_SPLITTERX_DETECTMARGIN1 3 // this much on left #define wxPG_SPLITTERX_DETECTMARGIN2 2 // this much on right #define wxPG_CONTROL_MARGIN 0 // space between splitter and control #endif #define wxCC_CUSTOM_IMAGE_MARGIN1 4 // before image #define wxCC_CUSTOM_IMAGE_MARGIN2 5 // after image #if (!wxPG_NAT_TEXTCTRL_BORDER_X && !wxPG_NAT_TEXTCTRL_BORDER_Y) #define wxPG_ENABLE_CLIPPER_WINDOW 0 #else #define wxPG_ENABLE_CLIPPER_WINDOW 1 #endif //#define wxPG_NAT_CHOICE_BORDER_ANY 0 // for odcombo #undef wxPG_CHOICEXADJUST #define wxPG_CHOICEXADJUST 0 #undef wxPG_CHOICEYADJUST #define wxPG_CHOICEYADJUST 0 #define wxPG_DRAG_MARGIN 30 #define wxPG_CUSTOM_IMAGE_SPACINGY 1 // space between vertical sides of a custom image // Use this macro to generate standard custom image height from #define wxPG_STD_CUST_IMAGE_HEIGHT(LINEHEIGHT) (LINEHEIGHT-3) // How many pixels between textctrl and button #ifdef __WXMAC__ #define wxPG_TEXTCTRL_AND_BUTTON_SPACING 8 #else #define wxPG_TEXTCTRL_AND_BUTTON_SPACING 2 #endif #define wxPG_HIDER_BUTTON_HEIGHT 25 // m_expanded of wxPGPropertyWithChildren is set to this code if children should // not be deleted in destructor. #define wxPG_EXP_OF_COPYARRAY 127 #define wxPG_PIXELS_PER_UNIT m_lineHeight #ifdef wxPG_ICON_WIDTH #define m_iconHeight m_iconWidth #endif #define wxPG_TOOLTIP_DELAY 1000 // Colour for the empty but visible space below last property. #define wxPG_SLACK_BACKROUND m_colPropBack // Milliseconds to wait for two mouse-ups after focus inorder // to trigger a double-click. #define DOUBLE_CLICK_CONVERSION_TRESHOLD 500 // // Parenting types enum { PT_CUSTOMPROPERTY = -2, PT_FIXEDCHILDREN = -1, PT_NONE = 0, PT_CAPTION = 1, PT_ROOT = 2 }; // Helper to decide which way is better (ie. first macro clears // "unspecified" state of siblings of child properties as well, while the latter is // more precise). //#define CLEAR_PROPERTY_UNSPECIFIED_FLAG(p) wxPropertyGridState::ClearPropertyAndChildrenFlags(p,wxPG_PROP_UNSPECIFIED) #define CLEAR_PROPERTY_UNSPECIFIED_FLAG(p) p->ClearFlag(wxPG_PROP_UNSPECIFIED) #define __INTENSE_DEBUGGING__ 0 #define __PAINT_DEBUGGING__ 0 #define __MOUSE_DEBUGGING__ 0 // ----------------------------------------------------------------------- #if wxUSE_INTL void wxPropertyGrid::AutoGetTranslation ( bool enable ) { WX_PG_GLOBALS_LOCKER() wxPGGlobalVars->m_autoGetTranslation = enable; } #else void wxPropertyGrid::AutoGetTranslation ( bool ) { } #endif // ----------------------------------------------------------------------- // This was needed to make quicker progress towards wxPropertyGridState #define FROM_STATE(X) m_pState->X // ----------------------------------------------------------------------- #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 // ----------------------------------------------------------------------- // DeviceContext Init Macros. #define wxPG_CLIENT_DC_INIT() \ wxClientDC dc(this); \ PrepareDC(dc); #define wxPG_CLIENT_DC_INIT_R(RETVAL) \ wxClientDC dc(this); \ PrepareDC(dc); #define wxPG_PAINT_DC_INIT() \ wxPaintDC dc(this); \ PrepareDC(dc); // ----------------------------------------------------------------------- // For wxMSW cursor consistency, we must do mouse capturing even // when using custom controls. #define BEGIN_MOUSE_CAPTURE \ if ( !(m_iFlags & wxPG_FL_MOUSE_CAPTURED) ) \ { \ CaptureMouse(); \ m_iFlags |= wxPG_FL_MOUSE_CAPTURED; \ } #define END_MOUSE_CAPTURE \ if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED ) \ { \ ReleaseMouse(); \ m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED); \ } // ----------------------------------------------------------------------- // NOTES // ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- // TODO // ----------------------------------------------------------------------- // // // For Next Release: // * Fix NULL(?) focus after odcombo closed. // // ----------------------------------------------------------------------- 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 void wxPGDrawFocusRect( wxDC& dc, const wxRect& rect ) { #if defined(__WXMSW__) && !defined(__WXWINCE__) /* RECT mswRect; mswRect.left = rect.x; mswRect.top = rect.y; mswRect.right = rect.x + rect.width; mswRect.bottom = rect.y + rect.height; HDC hdc = (HDC) dc.GetHDC(); SetMapMode(hdc,MM_TEXT); // Just in case... DrawFocusRect(hdc,&mswRect); */ // 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 } // ----------------------------------------------------------------------- // Choice related methods from various classes // ----------------------------------------------------------------------- void wxPropertyContainerMethods::AddPropertyChoice( wxPGId id, const wxString& label, int value ) { wxPG_PROP_ID_CALL_PROLOG() p->InsertChoice(label,-1,value); } void wxPropertyContainerMethods::InsertPropertyChoice( wxPGId id, const wxString& label, int index, int value ) { wxPG_PROP_ID_CALL_PROLOG() p->InsertChoice(label,index,value); } void wxPropertyContainerMethods::DeletePropertyChoice( wxPGId id, int index ) { wxPG_PROP_ID_CALL_PROLOG() p->DeleteChoice(index); } // ----------------------------------------------------------------------- // Statics in one class for easy destruction. // NB: We prefer to use wxModule, as it offers more consistent behaviour // 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) #else // !wxPG_USE_WXMODULE class wxPGGlobalVarsClassManager { public: wxPGGlobalVarsClassManager() {} ~wxPGGlobalVarsClassManager() { delete wxPGGlobalVars; } }; static wxPGGlobalVarsClassManager gs_pgGlobalVarsClassManager; #endif wxPGGlobalVarsClass* wxPGGlobalVars = (wxPGGlobalVarsClass*) NULL; wxPGGlobalVarsClass::wxPGGlobalVarsClass() { m_boolChoices[0] = _("False"); m_boolChoices[1] = _("True"); m_boolChoices[2] = _("Unspecified"); m_numBoolChoices = 2; m_fontFamilyChoices = (wxPGChoices*) NULL; m_autoGetTranslation = false; m_offline = 0; } wxPGGlobalVarsClass::~wxPGGlobalVarsClass() { size_t i; // This will always have one ref delete m_fontFamilyChoices; #if wxUSE_VALIDATORS for ( i=0; isecond; wxASSERT( pcls ); delete pcls; } // Destroy editor class instances. // iterate over all the elements in the class for( vt_it = m_mapEditorClasses.begin(); vt_it != m_mapEditorClasses.end(); ++vt_it ) { delete ((wxPGEditor*)vt_it->second); } } // ----------------------------------------------------------------------- // wxPGProperty // ----------------------------------------------------------------------- wxPGPropertyClassInfo wxBasePropertyClassInfo = {wxT("wxBaseProperty"), (const wxPGPropertyClassInfo*) NULL, (wxPGPropertyConstructor) NULL}; void wxPGProperty::Init() { #ifdef __WXPYTHON__ m_scriptObject = NULL; #endif m_y = -3; m_arrIndex = 0xFFFF; m_parent = (wxPGPropertyWithChildren*) NULL; #if wxPG_USE_CLIENT_DATA m_clientData = NULL; #endif m_dataExt = (wxPGPropertyDataExt*) NULL; m_maxLen = 0; // infinite maximum length m_flags = 0; m_depth = 1; m_parentingType = 0; m_bgColIndex = 0; m_fgColIndex = 0; } void wxPGProperty::Init( const wxString& label, const wxString& name ) { m_label = label; #ifndef __WXPYTHON__ if ( &name != ((wxString*)NULL) ) #else if ( (&name != ((wxString*)NULL)) && name != wxT("_LABEL_AS_NAME") ) #endif DoSetName( name ); else DoSetName( label ); Init(); } wxPGProperty::wxPGProperty() #if wxPG_INCLUDE_WXOBJECT : wxObject() #endif { Init(); } wxPGProperty::wxPGProperty( const wxString& label, const wxString& name ) #if wxPG_INCLUDE_WXOBJECT : wxObject() #endif { Init( label, name ); } wxPGProperty::~wxPGProperty() { #ifdef __WXPYTHON__ #if wxPG_USE_CLIENT_DATA if ( m_clientData ) Py_DECREF( m_clientData ); #endif #endif delete m_dataExt; } bool wxPGProperty::IsSomeParent( wxPGProperty* candidate ) const { wxPGPropertyWithChildren* parent = m_parent; do { if ( parent == (wxPGPropertyWithChildren*)candidate ) return true; parent = parent->m_parent; } while ( parent ); return false; } wxPropertyGridState* wxPGProperty::GetParentState() const { wxASSERT( m_parent ); return m_parent->GetParentState(); } size_t wxPGProperty::GetChildCount() const { int cc = GetParentingType(); if ( cc == 0 ) return 0; return ((wxPGPropertyWithChildren*)this)->GetCount(); } void wxPGProperty::ShowError( const wxString& msg ) { if ( !msg.length() ) return; #if wxUSE_STATUSBAR if ( !wxPGGlobalVars->m_offline ) { wxPropertyGrid* pg = GetParentState()->m_pPropGrid; wxASSERT(pg); wxWindow* topWnd = ::wxGetTopLevelParent(pg); if ( topWnd ) { wxFrame* pFrame = wxDynamicCast(topWnd,wxFrame); if ( pFrame ) { wxStatusBar* pStatusBar = pFrame->GetStatusBar(); if ( pStatusBar ) { pStatusBar->SetStatusText(msg); return; } } } } #endif ::wxLogError(msg); } wxPropertyGrid* wxPGProperty::GetGrid() const { return GetParentState()->GetGrid(); } void wxPGProperty::UpdateControl( wxWindow* primary ) { if ( primary ) GetEditorClass()->UpdateControl(this,primary); } void wxPGProperty::DoSetValue( wxPGVariant ) { // Actually, this should never get called wxFAIL_MSG( wxT("must be overridden") ); } // wxPGRootPropertyClass, at least, should make use of this. wxPGVariant wxPGProperty::DoGetValue() const { return wxPGVariant((long)0); } wxString wxPGProperty::GetValueAsString( int ) const { wxFAIL_MSG( wxT("must be overridden") ); return m_name; } wxVariant wxPGProperty::GetValueAsVariant() const { // Return NULL variant for unspecified value //if ( HasFlag(wxPG_PROP_UNSPECIFIED) ) // return wxVariant(); wxPGVariant value = DoGetValue(); const wxPGValueType* typeClass = GetValueTypePtr(); wxASSERT_MSG( typeClass, wxT("Did you forgot to use wxPG_INIT_REQUIRED_TYPE(T) in constructor?") ); return typeClass->GenerateVariant(value,m_name); } bool wxPGProperty::SetValueFromString( const wxString&, int ) { wxFAIL_MSG( wxT("must be overridden") ); return false; } bool wxPGProperty::SetValueFromInt( long, int ) { wxFAIL_MSG ( wxT("must be overridden") ); return false; } wxSize wxPGProperty::GetImageSize() const { if ( m_dataExt && m_dataExt->m_valueBitmap ) return wxSize(m_dataExt->m_valueBitmap->GetWidth(),-1); return wxSize(0,0); } void wxPGProperty::OnCustomPaint( wxDC& dc, const wxRect& rect, wxPGPaintData& ) { wxCHECK_RET( m_dataExt, wxT("m_dataExt is mandatory") ); wxBitmap* bmp = m_dataExt->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 #ifdef __WXPYTHON__ wxString wxPGProperty::GetType() const { return wxString(); } const wxPGValueType* wxPGProperty::GetValueType() const { wxString s = GetType(); const wxPGValueType* p = wxPropertyContainerMethods::GetValueType(s); wxCHECK_MSG( p, wxPG_VALUETYPE(none), wxT("GetType must return string that identifies a valid type") ); return p; } #endif #if wxPG_VALUETYPE_IS_STRING const wxPGValueType* wxPGProperty::GetValueTypePtr() const { return wxPropertyContainerMethods::GetValueTypeByName(GetValueType()); } #endif // Default extra property event handling - that is, none at all. bool wxPGProperty::OnEvent( wxPropertyGrid*, wxWindow*, wxEvent& ) { return false; } 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. const wxPGValueType* vt = GetValueTypePtr(); wxCHECK_RET( choiceInfo.m_choices, wxT("invalid choiceinfo") ); if ( vt == wxPG_VALUETYPE_PTR(long) ) { DoSetValue( (long) newValue ); } else if ( vt == wxPG_VALUETYPE_PTR(wxString) ) { DoSetValue( choiceInfo.m_choices->GetLabel(newValue) ); } } int wxPGProperty::InsertChoice( const wxString& label, int index, int value ) { wxPropertyGrid* pg = GetGrid(); wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; int sel = GetChoiceInfo(&ci); if ( ci.m_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 == wxPGIdToPtr(pg->GetSelection()) ) GetEditorClass()->InsertItem(pg->GetPrimaryEditor(),label,index); return index; } return -1; } void wxPGProperty::DeleteChoice( int index ) { wxPropertyGrid* pg = GetGrid(); wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; int sel = GetChoiceInfo(&ci); if ( ci.m_choices ) { int newSel = sel; // Adjust current value if ( sel == index ) { SetFlag( wxPG_PROP_UNSPECIFIED ); newSel = 0; } else if ( index < sel ) { newSel--; } ci.m_choices->RemoveAt(index); if ( sel != newSel ) SetChoiceSelection(newSel, ci); if ( this == wxPGIdToPtr(pg->GetSelection()) ) GetEditorClass()->DeleteItem(pg->GetPrimaryEditor(), index); } } int wxPGProperty::GetChoiceInfo( wxPGChoiceInfo* ) { return 0; } void wxPGProperty::SetAttribute( int, wxVariant& ) { } #if wxUSE_VALIDATORS wxValidator* wxPGProperty::DoGetValidator() const { return (wxValidator*) NULL; } #endif bool wxPGProperty::SetChoices( wxPGChoices& choices ) { wxPGChoiceInfo ci; ci.m_choices = (wxPGChoices*) NULL; // Unref existing GetChoiceInfo(&ci); if ( ci.m_choices ) { ci.m_choices->Assign(choices); // This may be needed to trigger some initialization // (but don't do it if property is somewhat uninitialized) if ( m_parent ) DoSetValue(GetValueTypePtr()->GetDefaultValue()); return true; } return false; } const wxPGEditor* wxPGProperty::GetEditorClass() const { const wxPGEditor* editor; if ( !m_dataExt || !m_dataExt->m_customEditor ) { #ifdef __WXPYTHON__ wxString editorName = GetEditor(); if ( editorName.length() ) editor = wxPropertyContainerMethods::GetEditorByName(editorName); else #endif editor = DoGetEditorClass(); } else { editor = m_dataExt->m_customEditor; } return editor; } bool wxPGProperty::IsKindOf( wxPGPropertyClassInfo& info ) { const wxPGPropertyClassInfo* ownInfo = GetClassInfo(); do { if ( ownInfo == &info ) return true; ownInfo = ownInfo->m_baseInfo; } while ( ownInfo ); return false; } // 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::PrepareValueForDialogEditing( wxPropertyGrid* propGrid ) { wxWindow* primary = propGrid->GetEditorControl(); if ( primary && propGrid->IsEditorsValueModified() ) { GetEditorClass()->CopyValueFromControl( this, primary ); return true; } else if ( m_flags & wxPG_PROP_UNSPECIFIED ) { // Set default value in case it was unspecified DoSetValue(GetValueTypePtr()->GetDefaultValue()); } return false; } 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; } bool wxPGProperty::EnsureDataExt() { if ( !m_dataExt ) { m_dataExt = new wxPGPropertyDataExt(); return true; } return false; } void wxPGProperty::SetValueImage( wxBitmap& bmp ) { EnsureDataExt(); delete m_dataExt->m_valueBitmap; if ( &bmp && bmp.Ok() ) { // Resize the image wxSize maxSz = GetGrid()->GetImageSize(); wxSize imSz(bmp.GetWidth(),bmp.GetHeight()); if ( imSz.x != maxSz.x || 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 scaleX = (double)maxSz.x / (double)imSz.x; double scaleY = (double)maxSz.y / (double)imSz.y; dc.SetUserScale(scaleX,scaleY); dc.DrawBitmap( bmp, 0, 0 ); m_dataExt->m_valueBitmap = bmpNew; } else m_dataExt->m_valueBitmap = new wxBitmap(bmp); m_flags |= wxPG_PROP_CUSTOMIMAGE; } else { m_dataExt->m_valueBitmap = (wxBitmap*) NULL; m_flags &= ~(wxPG_PROP_CUSTOMIMAGE); } } wxPGProperty* wxPGProperty::GetMainParent() const { const wxPGProperty* curChild = this; const wxPGPropertyWithChildren* curParent = m_parent; while ( curParent->m_parentingType < 0 ) { curChild = curParent; curParent = curParent->m_parent; } return (wxPGProperty*) curChild; } const wxPGProperty* wxPGProperty::GetLastVisibleSubItem() const { // // Returns last visible sub-item, recursively. if ( GetParentingType() == PT_NONE ) return this; const wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*) this; unsigned int count = pwc->GetCount(); if ( !pwc->IsExpanded() || !count ) return this; return pwc->Last()->GetLastVisibleSubItem(); } bool wxPGProperty::UsesAutoUnspecified() const { if ( GetGrid()->GetExtraStyle() & wxPG_EX_AUTO_UNSPECIFIED_VALUES ) return true; return false; } // ----------------------------------------------------------------------- // wxPGPropertyWithChildren // ----------------------------------------------------------------------- wxPGPropertyClassInfo wxBaseParentPropertyClassInfo = {wxT("wxBaseParentProperty"), &wxBasePropertyClassInfo, (wxPGPropertyConstructor) NULL}; wxPGPropertyWithChildren::wxPGPropertyWithChildren() : wxPGProperty() { m_expanded = 1; m_y = -2; m_parentingType = -1; } wxPGPropertyWithChildren::wxPGPropertyWithChildren( const wxString &label, const wxString& name ) : wxPGProperty(label,name) { m_expanded = 1; m_y = -2; m_parentingType = -1; m_parentState = (wxPropertyGridState*) NULL; } wxPGPropertyWithChildren::~wxPGPropertyWithChildren() { Empty(); // this deletes items } // This is used by Insert etc. void wxPGPropertyWithChildren::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( (void*)prop ); } else { m_children.Insert( (void*)prop, index ); if ( correct_mode ) FixIndexesOfChildren( index ); } prop->m_parent = this; } // This is used by properties that have fixed sub-properties void wxPGPropertyWithChildren::AddChild( wxPGProperty* prop ) { prop->m_arrIndex = m_children.GetCount(); m_children.Add( (void*)prop ); int custImgHeight = prop->GetImageSize().y; if ( custImgHeight < 0 /*|| custImgHeight > 1*/ ) prop->m_flags |= wxPG_PROP_CUSTOMIMAGE; prop->m_parent = this; prop->m_y = -1; // Collapsed } void wxPGPropertyWithChildren::FixIndexesOfChildren( size_t starthere ) { size_t i; for ( i=starthere;im_arrIndex = i; } // Returns (direct) child property with given name (or NULL if not found) wxPGProperty* wxPGPropertyWithChildren::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; wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*) GetPropertyByName(name.substr(0,pos)); if ( !pwc || !pwc->GetParentingType() ) return (wxPGProperty*) NULL; return pwc->GetPropertyByName(name.substr(pos+1,name.length()-pos-1)); } wxPGProperty* wxPGPropertyWithChildren::GetItemAtY( unsigned int y, unsigned int lh ) { // Linear search. unsigned int i = 0; unsigned int iMax = GetCount(); unsigned long py = 0xFFFFFFFF; wxPGProperty* p = (wxPGProperty*) NULL; while ( i < iMax ) { p = Item(i); if ( p->m_y >= 0 ) { py = (unsigned long)p->m_y; if ( (py+lh) > y ) break; } i++; } if ( py <= y && i < iMax ) { // perfectly this item wxASSERT_MSG( p, wxT("invalid property id") ); return p; } else { // If no visible children, we must retract our steps // (should not really happen, so right now we check that it // really doesn't). if ( py == 0xFFFFFFFF ) { wxLogDebug(wxT("wxPropertyGrid: \"%s\" (y=%i) did not have visible children (it should)."),m_label.c_str(),(int)m_y); return (wxPGProperty*) NULL; } // We are about to return a child of previous' visible item. #ifdef __WXDEBUG__ if ( i < 1 ) { wxLogDebug( wxT("WARNING: \"%s\"->GetItemAtY: (i <= 0)"), m_label.c_str() ); wxLogDebug( wxT(" \\--> y = %i, py = %i"), (int)y, (int)py ); if ( p ) wxLogDebug( wxT(" \\--> p = \"%s\""), p->GetLabel().c_str() ); else wxLogDebug( wxT(" \\--> p = None") ); return (wxPGProperty*) NULL; } #endif // Get previous *visible* parent. wxPGPropertyWithChildren* pwc; do { wxASSERT( i > 0 ); i--; pwc = (wxPGPropertyWithChildren*)Item(i); } while ( pwc->m_y < 0 ); if ( pwc->GetParentingType() != 0 ) { #ifdef __WXDEBUG__ if ( !pwc->m_expanded || pwc->m_y < 0 ) { wxLogDebug(wxT("WARNING: wxPGPropertyWithChildren::GetItemAtY: Item %s should have been visible and expanded."),pwc->m_label.c_str()); wxLogDebug(wxT(" (%s[%i]: %s)"),pwc->m_parent->m_label.c_str(),pwc->m_arrIndex,pwc->m_label.c_str()); //wxLogDebug(wxT(" py=%i"),(int)py); return (wxPGProperty*) NULL; } #endif return pwc->GetItemAtY(y,lh); } } return (wxPGProperty*) NULL; } void wxPGPropertyWithChildren::Empty() { size_t i; if ( m_expanded != wxPG_EXP_OF_COPYARRAY ) { for ( i=0; i 0, wxString(), wxT("If user property does not have any children, it must override GetValueAsString.") ); wxString text; int i; int iMax = m_children.GetCount(); if ( iMax > PWC_CHILD_SUMMARY_LIMIT && !(argFlags & wxPG_FULL_VALUE) ) iMax = PWC_CHILD_SUMMARY_LIMIT; int iMaxMinusOne = iMax-1; wxPGProperty* curChild = (wxPGProperty*) m_children.Item(0); for ( i = 0; i < iMax; i++ ) { wxString s; if ( !(curChild->m_flags & wxPG_PROP_UNSPECIFIED) ) s = curChild->GetValueAsString(argFlags); if ( curChild->GetParentingType() == 0 ) 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; curChild = (wxPGProperty*) m_children.Item(i+1); if ( curChild->GetParentingType() == 0 ) text += wxT("; "); else text += wxT(" "); } } if ( (unsigned int)i < m_children.GetCount() ) text += wxT("; ..."); return text; } // Convert semicolon delimited tokens into child values. bool wxPGPropertyWithChildren::SetValueFromString( const wxString& text, int argFlags ) { 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(';'); wxChar a; size_t lastPos = text.length(); size_t tokenStart = 0xFFFFFF; do { a = text[pos]; 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 ) { wxPGProperty* child = Item(curChild); if ( len > 0 ) { bool wasUnspecified = child->IsValueUnspecified(); if ( child->SetValueFromString( token, wxPG_REPORT_ERROR ) ) { // If modified, set mod flag and store value back to parent child->SetFlag( wxPG_PROP_MODIFIED ); // Clear unspecified flag only if SetValueFromString didn't // affect it. if ( child->IsValueUnspecified() && (wasUnspecified || !UsesAutoUnspecified()) ) child->ClearFlag( wxPG_PROP_UNSPECIFIED ); ChildChanged( child ); changed = true; } } else { child->SetFlag( wxPG_PROP_UNSPECIFIED ); 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; pos++; size_t startPos = pos; // Group item - find end do { a = text[pos]; pos++; if ( a == wxT(']') ) depth--; else if ( a == wxT('[') ) depth++; } while ( depth > 0 && a ); token = text.substr(startPos,pos-startPos-1); if ( !token.length() ) break; wxPGProperty* child = Item(curChild); //wxLogDebug(wxT("child(1) %i: %s"),curChild,token.c_str()); if ( child->SetValueFromString( token, wxPG_REPORT_ERROR ) ) { // If modified, set mod flag and store value back to parent child->SetFlag( wxPG_PROP_MODIFIED ); ChildChanged( child ); changed = true; } curChild++; if ( curChild >= iMax ) break; addOnlyIfNotEmpty = true; tokenStart = 0xFFFFFF; } else { tokenStart = pos; if ( a == delimeter ) { pos--; } } } } pos++; } while ( pos <= lastPos ); // This ensures that the last item is set unspecified even // if the blank had no terminating delimiter. if ( curChild < iMax ) { wxPGProperty* child = Item(curChild); child->SetFlag( wxPG_PROP_UNSPECIFIED ); changed = true; } return changed; } void wxPGPropertyWithChildren::RefreshChildren () { } // ----------------------------------------------------------------------- // wxParentProperty // ----------------------------------------------------------------------- wxPGProperty* wxParentProperty( const wxString& label, const wxString& name ) { return new wxParentPropertyClass(label,name); } WX_PG_IMPLEMENT_PROPERTY_CLASS_PLAIN(wxParentProperty,none,TextCtrl) WX_PG_IMPLEMENT_CLASSINFO(wxParentProperty,wxBaseParentPropertyClass) wxParentPropertyClass::wxParentPropertyClass( const wxString& label, const wxString& name ) : wxPGPropertyWithChildren(label,name) { m_parentingType = PT_CUSTOMPROPERTY; } wxParentPropertyClass::~wxParentPropertyClass() { } void wxParentPropertyClass::DoSetValue( wxPGVariant value ) { const wxString& str = wxPGVariantToString(value); m_string = str; SetValueFromString(str,wxPG_REPORT_ERROR); } wxPGVariant wxParentPropertyClass::DoGetValue() const { return wxPGVariant(); } void wxParentPropertyClass::ChildChanged( wxPGProperty* WXUNUSED(p) ) { } wxString wxParentPropertyClass::GetValueAsString( int argFlags ) const { if ( !GetCount() ) return wxEmptyString; return wxPGPropertyWithChildren::GetValueAsString(argFlags); } // ----------------------------------------------------------------------- // wxPGRootPropertyClass // ----------------------------------------------------------------------- WX_PG_IMPLEMENT_PROPERTY_CLASS_PLAIN(wxPGRootProperty,none,TextCtrl) const wxPGPropertyClassInfo* wxPGRootPropertyClass::GetClassInfo() const { return (const wxPGPropertyClassInfo*) NULL; } wxPGRootPropertyClass::wxPGRootPropertyClass() : wxPGPropertyWithChildren() { m_parentingType = PT_ROOT; // this was PT_CAPTION in <= 1.1.6, but changed // so the depth calculations can become // more consistent. m_depth = 0; } wxPGRootPropertyClass::~wxPGRootPropertyClass() { } // ----------------------------------------------------------------------- // wxPropertyCategoryClass // ----------------------------------------------------------------------- wxPGProperty* wxPropertyCategory( const wxString& label, const wxString& name ) { return new wxPropertyCategoryClass(label,name); } WX_PG_IMPLEMENT_CLASSINFO(wxPropertyCategory,wxBaseParentPropertyClass) WX_PG_IMPLEMENT_PROPERTY_CLASS_PLAIN(wxPropertyCategory,none,TextCtrl) wxPropertyCategoryClass::wxPropertyCategoryClass() : wxPGPropertyWithChildren() { // don't set colour - prepareadditem method should do this m_parentingType = 1; m_capFgColIndex = 1; } wxPropertyCategoryClass::wxPropertyCategoryClass( const wxString &label, const wxString& name ) : wxPGPropertyWithChildren(label,name) { // don't set colour - prepareadditem method should do this m_parentingType = 1; m_capFgColIndex = 1; } wxPropertyCategoryClass::~wxPropertyCategoryClass() { } wxString wxPropertyCategoryClass::GetValueAsString( int ) const { return wxEmptyString; } void wxPropertyCategoryClass::CalculateTextExtent( wxWindow* wnd, wxFont& font ) { int x = 0, y = 0; wnd->GetTextExtent( m_label, &x, &y, 0, 0, &font ); m_textExtent = x; } // ----------------------------------------------------------------------- // wxPGEditor // ----------------------------------------------------------------------- wxPGEditor::~wxPGEditor() { } void wxPGEditor::DrawValue( wxDC& dc, wxPGProperty* property, const wxRect& rect ) const { if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) dc.DrawText( property->GetDisplayedString(), rect.x+wxPG_XBEFORETEXT, rect.y ); } void wxPGEditor::SetControlStringValue( wxWindow*, const wxString& ) const { } void wxPGEditor::SetControlIntValue( wxWindow*, int ) const { } int wxPGEditor::InsertItem( wxWindow*, const wxString&, int ) const { return -1; } void wxPGEditor::DeleteItem( wxWindow*, int ) const { return; } void wxPGEditor::OnFocus( wxPGProperty*, wxWindow* ) const { } bool wxPGEditor::CanContainCustomImage() const { return false; } // ----------------------------------------------------------------------- // wxPGClipperWindow // ----------------------------------------------------------------------- #if wxPG_ENABLE_CLIPPER_WINDOW // // Clipper window is used to "remove" borders from controls // which otherwise insist on having them despite of supplied // wxNO_BORDER window style. // class wxPGClipperWindow : public wxWindow { DECLARE_CLASS(wxPGClipperWindow) public: wxPGClipperWindow() : wxWindow() { wxPGClipperWindow::Init(); } wxPGClipperWindow(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize) { Init(); Create(parent,id,pos,size); } void Create(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); virtual ~wxPGClipperWindow(); virtual bool ProcessEvent(wxEvent& event); inline wxWindow* GetControl() const { return m_ctrl; } // This is called before wxControl is constructed. void GetControlRect( int xadj, int yadj, wxPoint& pt, wxSize& sz ); // This is caleed after wxControl has been constructed. void SetControl( wxWindow* ctrl ); virtual void Refresh( bool eraseBackground = true, const wxRect *rect = (const wxRect *) NULL ); virtual void SetFocus(); virtual bool SetFont(const wxFont& font); inline int GetXClip() const { return m_xadj; } inline int GetYClip() const { return m_yadj; } protected: wxWindow* m_ctrl; int m_xadj; // Horizontal border clip. int m_yadj; // Vertical border clip. private: void Init () { m_ctrl = (wxWindow*) NULL; } }; IMPLEMENT_CLASS(wxPGClipperWindow,wxWindow) // This is called before wxControl is constructed. void wxPGClipperWindow::GetControlRect( int xadj, int yadj, wxPoint& pt, wxSize& sz ) { m_xadj = xadj; m_yadj = yadj; pt.x = -xadj; pt.y = -yadj; wxSize own_size = GetSize(); sz.x = own_size.x+(xadj*2); sz.y = own_size.y+(yadj*2); } // This is caleed after wxControl has been constructed. void wxPGClipperWindow::SetControl( wxWindow* ctrl ) { m_ctrl = ctrl; // GTK requires this. ctrl->SetSizeHints(3,3); // Correct size of this window to match the child. wxSize sz = GetSize(); wxSize chsz = ctrl->GetSize(); int hei_adj = chsz.y - (sz.y+(m_yadj*2)); if ( hei_adj ) SetSize(sz.x,chsz.y-(m_yadj*2)); } void wxPGClipperWindow::Refresh( bool eraseBackground, const wxRect *rect ) { wxWindow::Refresh(false,rect); if ( m_ctrl ) // FIXME: Rect to sub-ctrl refresh too m_ctrl->Refresh(eraseBackground); } // Pass focus to control void wxPGClipperWindow::SetFocus() { if ( m_ctrl ) m_ctrl->SetFocus(); else wxWindow::SetFocus(); } bool wxPGClipperWindow::SetFont(const wxFont& font) { bool res = wxWindow::SetFont(font); if ( m_ctrl ) return m_ctrl->SetFont(font); return res; } void wxPGClipperWindow::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size ) { wxWindow::Create(parent,id,pos,size); } wxPGClipperWindow::~wxPGClipperWindow() { } bool wxPGClipperWindow::ProcessEvent(wxEvent& event) { if ( event.GetEventType() == wxEVT_SIZE ) { if ( m_ctrl ) { // Maintain correct size relationship. wxSize sz = GetSize(); m_ctrl->SetSize(sz.x+(m_xadj*2),sz.y+(m_yadj*2)); event.Skip(); return false; } } return wxWindow::ProcessEvent(event); } #endif // wxPG_ENABLE_CLIPPER_WINDOW /*wxWindow* wxPropertyGrid::GetActualEditorControl( wxWindow* ctrl ) { #if wxPG_ENABLE_CLIPPER_WINDOW // Pass real control instead of clipper window if ( ctrl->IsKindOf(CLASSINFO(wxPGClipperWindow)) ) { return ((wxPGClipperWindow*)ctrl)->GetControl(); } #else return ctrl; #endif }*/ // ----------------------------------------------------------------------- // wxPGTextCtrlEditor // ----------------------------------------------------------------------- // Clipper window support macro (depending on whether it is used // for this editor or not) #if wxPG_NAT_TEXTCTRL_BORDER_X || wxPG_NAT_TEXTCTRL_BORDER_Y #define wxPG_NAT_TEXTCTRL_BORDER_ANY 1 #define wxPGDeclareRealTextCtrl(WND) \ wxASSERT( WND ); \ wxTextCtrl* tc = (wxTextCtrl*)((wxPGClipperWindow*)WND)->GetControl() #else #define wxPG_NAT_TEXTCTRL_BORDER_ANY 0 #define wxPGDeclareRealTextCtrl(WND) \ wxASSERT( WND ); \ wxTextCtrl* tc = (wxTextCtrl*)WND #endif WX_PG_IMPLEMENT_EDITOR_CLASS(TextCtrl,wxPGTextCtrlEditor,wxPGEditor) #ifndef __WXPYTHON__ wxWindow* wxPGTextCtrlEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, wxWindow** ) const #else wxPGWindowPair wxPGTextCtrlEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz ) const #endif { wxString text; // If has children and limited editing, then don't create. if ((property->GetFlags() & wxPG_PROP_NOEDITOR) && property->GetParentingType() < 0 && !property->IsKindOf(WX_PG_CLASSINFO(wxCustomProperty))) return (wxWindow*) NULL; int flags = 0; if ( (property->GetFlags() & wxPG_PROP_PASSWORD) && property->IsKindOf(WX_PG_CLASSINFO(wxStringProperty)) ) flags |= wxTE_PASSWORD; if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) text = property->GetValueAsString(property->HasFlag(wxPG_PROP_READONLY)?0:wxPG_EDITABLE_VALUE); wxWindow* wnd = propGrid->GenerateEditorTextCtrl(pos,sz,text,(wxWindow*)NULL,flags, property->GetMaxLength()); return wnd; } void wxPGTextCtrlEditor::DrawValue( wxDC& dc, wxPGProperty* property, const wxRect& rect ) const { if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) { wxString drawStr = property->GetDisplayedString(); // Code below should no longer be needed, as the obfuscation // is now done in GetValueAsString. /*if ( (property->GetFlags() & wxPG_PROP_PASSWORD) && property->IsKindOf(WX_PG_CLASSINFO(wxStringProperty)) ) { size_t a = drawStr.length(); drawStr.Empty(); drawStr.Append(wxT('*'),a); }*/ dc.DrawText( drawStr, rect.x+wxPG_XBEFORETEXT, rect.y ); } } void wxPGTextCtrlEditor::UpdateControl( wxPGProperty* property, wxWindow* ctrl ) const { wxPGDeclareRealTextCtrl(ctrl); tc->SetValue(property->GetDisplayedString()); } // Provided so that, for example, ComboBox editor can use the same code // (multiple inheritance would get way too messy). bool wxPGTextCtrlEditor::OnTextCtrlEvent( wxPropertyGrid* propGrid, wxPGProperty* property, wxWindow* ctrl, wxEvent& event ) { if ( !ctrl ) return false; if ( event.GetEventType() == wxEVT_COMMAND_TEXT_ENTER ) { if ( propGrid->IsEditorsValueModified() ) { return true; } } else if ( event.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED ) { wxPGDeclareRealTextCtrl(ctrl); // If value is unspecified and character count is zero, // then do not set as modified. if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) || !tc || (tc->IsKindOf(CLASSINFO(wxTextCtrl)) && (tc->GetLastPosition() > 0)) ) { // We must check this since an 'empty' text event // may be triggered when creating the property. if ( !(propGrid->GetInternalFlags() & wxPG_FL_IN_SELECT_PROPERTY) ) { // // Pass this event outside wxPropertyGrid so that, // if necessary, program can tell when user is editing // a textctrl. // FIXME: Is it safe to change event id in the middle of event // processing (seems to work, but...)? event.Skip(); event.SetId(propGrid->GetId()); } propGrid->EditorsValueWasModified(); } } return false; } bool wxPGTextCtrlEditor::OnEvent( wxPropertyGrid* propGrid, wxPGProperty* property, wxWindow* ctrl, wxEvent& event ) const { return wxPGTextCtrlEditor::OnTextCtrlEvent(propGrid,property,ctrl,event); } bool wxPGTextCtrlEditor::CopyTextCtrlValueFromControl( wxPGProperty* property, wxWindow* ctrl ) { #if wxPG_ENABLE_CLIPPER_WINDOW // Pass real control instead of clipper window if ( ctrl->IsKindOf(CLASSINFO(wxPGClipperWindow)) ) { ctrl = ((wxPGClipperWindow*)ctrl)->GetControl(); } #endif wxTextCtrl* tc = (wxTextCtrl*)ctrl; bool res = property->SetValueFromString(tc->GetValue(),0); // Changing unspecified always causes event (returning // true here should be enough to trigger it). if ( !res && property->IsFlagSet(wxPG_PROP_UNSPECIFIED) ) res = true; return res; } bool wxPGTextCtrlEditor::CopyValueFromControl( wxPGProperty* property, wxWindow* ctrl ) const { return wxPGTextCtrlEditor::CopyTextCtrlValueFromControl(property,ctrl); } void wxPGTextCtrlEditor::SetValueToUnspecified( wxWindow* ctrl ) const { wxPGDeclareRealTextCtrl(ctrl); tc->Remove(0,tc->GetValue().length()); } void wxPGTextCtrlEditor::SetControlStringValue( wxWindow* ctrl, const wxString& txt ) const { wxPGDeclareRealTextCtrl(ctrl); tc->SetValue(txt); } void wxPGTextCtrlEditor::OnFocus( wxPGProperty*, wxWindow* wnd ) const { wxPGDeclareRealTextCtrl(wnd); tc->SetSelection(-1,-1); } wxPGTextCtrlEditor::~wxPGTextCtrlEditor() { } // ----------------------------------------------------------------------- // wxPGChoiceEditor // ----------------------------------------------------------------------- extern const wxChar* wxPG_ClassName_wxBoolProperty; // in props.cpp WX_PG_IMPLEMENT_EDITOR_CLASS(Choice,wxPGChoiceEditor,wxPGEditor) // This is a special enhanced double-click processor class. // In essence, it allows for double-clicks for which the // first click "created" the control. class wxPGDoubleClickProcessor : public wxEvtHandler { public: wxPGDoubleClickProcessor( wxPGOwnerDrawnComboBox* combo ) : wxEvtHandler() { m_timeLastMouseUp = 0; m_combo = combo; m_downReceived = false; } protected: void OnMouseEvent( wxMouseEvent& event ) { wxLongLong t = ::wxGetLocalTimeMillis(); int evtType = event.GetEventType(); if ( m_combo->HasFlag(wxPGCC_DCLICK_CYCLES) && !m_combo->IsPopupShown() ) { // Just check that it is in the text area wxPoint pt = event.GetPosition(); if ( m_combo->GetTextRect().wxPGRectContains(pt) ) { if ( evtType == wxEVT_LEFT_DOWN ) { // Set value to avoid up-events without corresponding downs m_downReceived = true; } else if ( evtType == wxEVT_LEFT_DCLICK ) { // We'll make our own double-clicks event.SetEventType(0); return; } else if ( evtType == wxEVT_LEFT_UP ) { if ( m_downReceived || m_timeLastMouseUp == 1 ) { wxLongLong timeFromLastUp = (t-m_timeLastMouseUp); if ( timeFromLastUp < DOUBLE_CLICK_CONVERSION_TRESHOLD ) { event.SetEventType(wxEVT_LEFT_DCLICK); m_timeLastMouseUp = 1; } else { m_timeLastMouseUp = t; } } } } } event.Skip(); } void OnSetFocus( wxFocusEvent& event ) { m_timeLastMouseUp = ::wxGetLocalTimeMillis(); event.Skip(); } private: wxLongLong m_timeLastMouseUp; wxPGOwnerDrawnComboBox* m_combo; bool m_downReceived; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGDoubleClickProcessor, wxEvtHandler) EVT_MOUSE_EVENTS(wxPGDoubleClickProcessor::OnMouseEvent) EVT_SET_FOCUS(wxPGDoubleClickProcessor::OnSetFocus) END_EVENT_TABLE() class wxPGComboBox : public wxPGOwnerDrawnComboBox { public: wxPGComboBox() : wxPGOwnerDrawnComboBox() { m_dclickProcessor = (wxPGDoubleClickProcessor*) NULL; } ~wxPGComboBox() { if ( m_dclickProcessor ) { RemoveEventHandler(m_dclickProcessor); delete m_dclickProcessor; } } bool Create(wxWindow *parent, wxWindowID id, const wxString& value = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, int n = 0, const wxString choices[] = (const wxString *) NULL, long style = 0, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxComboBoxNameStr) { if ( !wxPGOwnerDrawnComboBox::Create( parent, id, value, pos, size, n, choices, style, validator, name ) ) return false; m_dclickProcessor = new wxPGDoubleClickProcessor(this); PushEventHandler(m_dclickProcessor); return true; } virtual bool OnDrawListItem( wxDC& dc, const wxRect& rect, int item, int flags ) { wxPropertyGrid* pg = wxDynamicCast(GetParent(),wxPropertyGrid); wxASSERT(pg); pg->OnComboItemPaint((wxPGCustomComboControl*)this,item,dc,(wxRect&)rect,flags); return true; } virtual wxCoord OnMeasureListItem( int item ) { wxPropertyGrid* pg = wxDynamicCast(GetParent(),wxPropertyGrid); wxASSERT(pg); wxRect rect; rect.x = -1; rect.width = 0; pg->OnComboItemPaint((wxPGCustomComboControl*)this,item,*((wxDC*)NULL),rect,0); return rect.height; } virtual wxCoord OnMeasureListItemWidth( int item ) { wxPropertyGrid* pg = wxDynamicCast(GetParent(),wxPropertyGrid); wxASSERT(pg); wxRect rect; rect.x = -1; rect.width = -1; pg->OnComboItemPaint((wxPGCustomComboControl*)this,item,*((wxDC*)NULL),rect,0); return rect.width; } private: wxPGDoubleClickProcessor* m_dclickProcessor; }; void wxPropertyGrid::OnComboItemPaint( wxPGCustomComboControl* pCc, int item, wxDC& dc, wxRect& rect, int flags ) { wxPGOwnerDrawnComboBox* pCb = (wxPGOwnerDrawnComboBox*)pCc; // Sanity check wxASSERT( IsKindOf(CLASSINFO(wxPropertyGrid)) ); wxPGProperty* p = m_selected; // // Decide what custom image size to use wxSize cis = GetImageSize(p); if ( rect.x < 0 && !(m_iFlags & wxPG_FL_SELECTED_IS_PAINT_FLEXIBLE) ) { // Default measure behaviour (no flexible, custom paint image only) if ( rect.width < 0 ) { wxCoord x, y; GetTextExtent(pCb->GetString(item), &x, &y, 0, 0, &m_font); rect.width = cis.x + wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2 + 9 + x; } rect.height = cis.y + 2; return; } wxPGPaintData paintdata; paintdata.m_parent = NULL; paintdata.m_choiceItem = item; // This is by the current (1.0.0b) spec - if painting control, item is -1 if ( (flags & wxPGCC_PAINTING_CONTROL) ) paintdata.m_choiceItem = -1; if ( &dc ) dc.SetBrush(*wxWHITE_BRUSH); if ( rect.x >= 0 ) { // // DrawItem call wxPoint pt(rect.x + wxPG_CONTROL_MARGIN - wxPG_CHOICEXADJUST - 1, rect.y + 1); if ( cis.x > 0 && ( !p->m_dataExt || !p->m_dataExt->m_valueBitmap || item == pCb->GetSelection() ) && ( item >= 0 || (flags & wxPGCC_PAINTING_CONTROL) ) ) { pt.x += wxCC_CUSTOM_IMAGE_MARGIN1; wxRect r(pt.x,pt.y,cis.x,cis.y); if ( flags & wxPGCC_PAINTING_CONTROL ) { //r.width = cis.x; r.height = wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight); } if ( m_iFlags & wxPG_FL_SELECTED_IS_FULL_PAINT ) r.width = rect.width; paintdata.m_drawnWidth = r.width; dc.SetPen(m_colPropFore); if ( item >= 0 ) p->OnCustomPaint( dc, r, paintdata ); else dc.DrawRectangle( r ); if ( (m_iFlags & wxPG_FL_SELECTED_IS_FULL_PAINT) ) { if ( paintdata.m_drawnWidth > 0 ) return; // Revert pt.x pt.x -= (wxCC_CUSTOM_IMAGE_MARGIN1+1); } else pt.x += paintdata.m_drawnWidth + wxCC_CUSTOM_IMAGE_MARGIN2 - 1; } else // TODO: This aligns text so that it seems to be horizontally // on the same line as property values. Not really // sure if its needed, but seems to not cause any harm. pt.x -= 1; // // Draw text // pt.y += (rect.height-m_fontHeight)/2 - 1; wxString text; if ( !(flags & wxPGCC_PAINTING_CONTROL) ) { text = pCb->GetString(item); } else { if ( !p->IsValueUnspecified() ) text = p->GetValueAsString(0); } dc.DrawText( text, pt.x + wxPG_XBEFORETEXT, pt.y ); } else { // // MeasureItem call p->OnCustomPaint( dc, rect, paintdata ); rect.height = paintdata.m_drawnHeight + 2; rect.width = cis.x + wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2 + 9; } } // CreateControls calls this with CB_READONLY in extraStyle wxWindow* wxPGChoiceEditor::CreateControlsBase( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, long extraStyle ) const { wxString defString; wxPGChoiceInfo choiceInfo; // Get choices. choiceInfo.m_arrWxString = (wxString*) NULL; choiceInfo.m_arrWxChars = (const wxChar**) NULL; choiceInfo.m_itemCount = 0; int index = property->GetChoiceInfo( &choiceInfo ); if ( property->GetFlags() & wxPG_PROP_UNSPECIFIED ) { index = -1; } else { defString = property->GetDisplayedString(); } // SLAlloc allows fast conversion using potentially pre-allocated wxStrings // (and appending is out of question due to performance problems on some platforms). // If itemcount is < 0, fill wxArrayString using GetEntry if ( choiceInfo.m_itemCount < 0 ) { wxBaseEnumPropertyClass* ep = (wxBaseEnumPropertyClass*) property; size_t i = 0; const wxString* entryLabel; int entryValue; wxArrayString& sl = propGrid->SLGet(); entryLabel = ep->GetEntry(i,&entryValue); while ( entryLabel ) { if ( sl.GetCount() > i ) sl[i] = *entryLabel; else sl.Add(*entryLabel); i++; entryLabel = ep->GetEntry(i,&entryValue); } choiceInfo.m_itemCount = ((int)i) - 1; } else if ( !choiceInfo.m_arrWxString ) { wxASSERT( choiceInfo.m_arrWxChars || !choiceInfo.m_itemCount ); propGrid->SLAlloc( choiceInfo.m_itemCount, choiceInfo.m_arrWxChars ); if ( choiceInfo.m_itemCount ) choiceInfo.m_arrWxString = &propGrid->SLGet().Item(0); } //wxPGOwnerDrawnComboBox* cb; wxPGComboBox* cb; wxPoint po(pos); wxSize si(sz); po.y += wxPG_CHOICEYADJUST; si.y -= (wxPG_CHOICEYADJUST*2); /*#if wxPG_NAT_CHOICE_BORDER_ANY po.x += (wxPG_CHOICEXADJUST+wxPG_NAT_CHOICE_BORDER_X); si.x -= (wxPG_CHOICEXADJUST+wxPG_NAT_CHOICE_BORDER_X); wxPGClipperWindow* wnd = new wxPGClipperWindow(propGrid,wxPG_SUBID1,po,si); wxWindow* ctrlParent = wnd; wnd->GetControlRect(wxPG_NAT_CHOICE_BORDER_X,wxPG_NAT_CHOICE_BORDER_Y,po,si); #else*/ po.x += wxPG_CHOICEXADJUST; si.x -= wxPG_CHOICEXADJUST; wxWindow* ctrlParent = propGrid; //#endif // NB: Using wxWidgets wxOwnerDrawnComboBox needs adding wxTE_PROCESS_ENTER // into the flags. int odcbFlags = extraStyle | wxNO_BORDER | wxPGCC_PROCESS_ENTER | wxPGCC_ALT_KEYS; if ( !(property->GetFlags() & wxPG_PROP_CUSTOMIMAGE) ) odcbFlags |= wxODCB_STD_CONTROL_PAINT; if ( (property->GetFlags() & wxPG_PROP_USE_DCC) && (property->GetClassName()==wxPG_ClassName_wxBoolProperty) ) odcbFlags |= wxPGCC_DCLICK_CYCLES; cb = new wxPGComboBox(); #ifdef __WXMSW__ cb->Hide(); #endif cb->Create(ctrlParent, wxPG_SUBID1, wxString(), po, si, choiceInfo.m_itemCount,choiceInfo.m_arrWxString, //(wxComboPaintCallback) &wxPropertyGrid::OnComboItemPaint, odcbFlags); int extRight = propGrid->GetClientSize().x - (po.x+si.x); cb->SetButtonPosition(si.y,0,wxRIGHT); cb->SetPopupExtents( 1, extRight ); cb->SetTextIndent(wxPG_XBEFORETEXT-2); if ( (property->GetFlags() & wxPG_PROP_CUSTOMIMAGE) && !(propGrid->GetInternalFlags() & wxPG_FL_SELECTED_IS_FULL_PAINT) ) { wxSize imageSize = propGrid->GetImageSize(property); cb->SetCustomPaintWidth( imageSize.x+6 ); } if ( index >= 0 && index < (int)cb->GetCount() ) { cb->SetSelection( index ); if ( defString.length() ) cb->SetValue( defString ); } else if ( !(extraStyle & wxCB_READONLY) && defString.length() ) cb->SetValue( defString ); else cb->SetSelection( -1 ); if ( property->HasFlag(wxPG_PROP_READONLY) ) cb->Disable(); #ifdef __WXMSW__ cb->Show(); #endif return (wxWindow*) cb; } void wxPGChoiceEditor::UpdateControl( wxPGProperty* property, wxWindow* ctrl ) const { wxASSERT( ctrl ); wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; wxASSERT( cb->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox))); int ind = property->GetChoiceInfo( (wxPGChoiceInfo*)NULL ); cb->SetSelection(ind); } #ifndef __WXPYTHON__ wxWindow* wxPGChoiceEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, wxWindow** ) const #else wxPGWindowPair wxPGChoiceEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz ) const #endif { return CreateControlsBase(propGrid,property,pos,sz,wxCB_READONLY); } int wxPGChoiceEditor::InsertItem( wxWindow* ctrl, const wxString& label, int index ) const { wxASSERT( ctrl ); wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; wxASSERT( cb->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox))); if (index < 0) index = cb->GetCount(); return cb->Insert(label,index); } void wxPGChoiceEditor::DeleteItem( wxWindow* ctrl, int index ) const { wxASSERT( ctrl ); wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; wxASSERT( cb->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox))); cb->Delete(index); } bool wxPGChoiceEditor::OnEvent( wxPropertyGrid* WXUNUSED(propGrid), wxPGProperty* WXUNUSED(property), wxWindow* WXUNUSED(ctrl), wxEvent& event ) const { if ( event.GetEventType() == wxEVT_COMMAND_COMBOBOX_SELECTED ) { /*if ( CopyValueFromControl( property, ctrl ) ) { return true; } propGrid->EditorsValueWasNotModified(); //wxPropertyGridState::ClearPropertyAndChildrenFlags(property,wxPG_PROP_UNSPECIFIED); CLEAR_PROPERTY_UNSPECIFIED_FLAG(property);*/ return true; } return false; } bool wxPGChoiceEditor::CopyValueFromControl( wxPGProperty* property, wxWindow* ctrl ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; int index = cb->GetSelection(); if ( index != property->GetChoiceInfo( (wxPGChoiceInfo*) NULL ) || // Changing unspecified always causes event (returning // true here should be enough to trigger it). property->IsFlagSet(wxPG_PROP_UNSPECIFIED) ) { property->SetValueFromInt(index,0); return true; } return false; } void wxPGChoiceEditor::SetControlStringValue( wxWindow* ctrl, const wxString& txt ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; wxASSERT( cb ); cb->SetValue(txt); } void wxPGChoiceEditor::SetControlIntValue( wxWindow* ctrl, int value ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; wxASSERT( cb ); cb->SetSelection(value); } void wxPGChoiceEditor::SetValueToUnspecified( wxWindow* ctrl ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; cb->SetSelection(-1); } bool wxPGChoiceEditor::CanContainCustomImage() const { return true; } wxPGChoiceEditor::~wxPGChoiceEditor() { } // ----------------------------------------------------------------------- // wxPGComboBoxEditor // ----------------------------------------------------------------------- WX_PG_IMPLEMENT_EDITOR_CLASS(ComboBox,wxPGComboBoxEditor,wxPGChoiceEditor) void wxPGComboBoxEditor::UpdateControl( wxPGProperty* property, wxWindow* ctrl ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; cb->SetValue(property->GetDisplayedString()); // TODO: If string matches any selection, then select that. } #ifndef __WXPYTHON__ wxWindow* wxPGComboBoxEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, wxWindow** ) const #else wxPGWindowPair wxPGComboBoxEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz ) const #endif { return CreateControlsBase(propGrid,property,pos,sz,0); } bool wxPGComboBoxEditor::OnEvent( wxPropertyGrid* propGrid, wxPGProperty* property, wxWindow* ctrl, wxEvent& event ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*) NULL; wxWindow* textCtrl = (wxWindow*) NULL; if ( ctrl ) { cb = (wxPGOwnerDrawnComboBox*)ctrl; textCtrl = cb->GetTextCtrl(); } if ( wxPGTextCtrlEditor::OnTextCtrlEvent(propGrid,property,textCtrl,event) ) return true; return wxPGChoiceEditor::OnEvent(propGrid,property,ctrl,event); } bool wxPGComboBoxEditor::CopyValueFromControl( wxPGProperty* property, wxWindow* ctrl ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; bool res = property->SetValueFromString(cb->GetValue(),0); // Changing unspecified always causes event (returning // true here should be enough to trigger it). if ( !res && property->IsFlagSet(wxPG_PROP_UNSPECIFIED) ) res = true; return res; } void wxPGComboBoxEditor::OnFocus( wxPGProperty*, wxWindow* ctrl ) const { wxPGOwnerDrawnComboBox* cb = (wxPGOwnerDrawnComboBox*)ctrl; cb->GetTextCtrl()->SetSelection(-1,-1); } wxPGComboBoxEditor::~wxPGComboBoxEditor() { } // ----------------------------------------------------------------------- // wxPGChoiceAndButtonEditor // ----------------------------------------------------------------------- // This simpler implement_editor macro doesn't define class body. WX_PG_IMPLEMENT_EDITOR_CLASS(ChoiceAndButton,wxPGChoiceAndButtonEditor,wxPGChoiceEditor) #ifndef __WXPYTHON__ wxWindow* wxPGChoiceAndButtonEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, wxWindow** psecondary ) const #else wxPGWindowPair wxPGChoiceAndButtonEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz ) const #endif { // Use one two units smaller to match size of the combo's dropbutton. // (normally a bigger button is used because it looks better) int bt_wid = sz.y; bt_wid -= 2; wxSize bt_sz(bt_wid,bt_wid); // Position of button. wxPoint bt_pos(pos.x+sz.x-bt_sz.x,pos.y); #ifdef __WXMAC__ bt_pos.y -= 1; #else bt_pos.y += 1; #endif wxWindow* bt = propGrid->GenerateEditorButton( bt_pos, bt_sz ); // Size of choice. wxSize ch_sz(sz.x-bt->GetSize().x,sz.y); #ifdef __WXMAC__ ch_sz.x -= wxPG_TEXTCTRL_AND_BUTTON_SPACING; #endif wxWindow* ch = wxPG_EDITOR(Choice)->CreateControls(propGrid,property, pos,ch_sz #ifndef __WXPYTHON__ , (wxWindow**)NULL); #else ).m_primary; #endif #ifdef __WXMSW__ bt->Show(); #endif #ifndef __WXPYTHON__ *psecondary = bt; return ch; #else return wxPGWindowPair(ch, bt); #endif } wxPGChoiceAndButtonEditor::~wxPGChoiceAndButtonEditor() { } // ----------------------------------------------------------------------- // wxPGTextCtrlAndButtonEditor // ----------------------------------------------------------------------- // This simpler implement_editor macro doesn't define class body. WX_PG_IMPLEMENT_EDITOR_CLASS(TextCtrlAndButton,wxPGTextCtrlAndButtonEditor,wxPGTextCtrlEditor) #ifndef __WXPYTHON__ wxWindow* wxPGTextCtrlAndButtonEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz, wxWindow** psecondary ) const { wxWindow* wnd = propGrid->GenerateEditorTextCtrlAndButton( pos, sz, psecondary, property->GetFlags() & wxPG_PROP_NOEDITOR, property); return wnd; } #else wxPGWindowPair wxPGTextCtrlAndButtonEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& sz ) const { wxWindow* wnd2; wxWindow* wnd = propGrid->GenerateEditorTextCtrlAndButton( pos, sz, &wnd2, property->GetFlags() & wxPG_PROP_NOEDITOR, property); return wxPGWindowPair(wnd, wnd2); } #endif wxPGTextCtrlAndButtonEditor::~wxPGTextCtrlAndButtonEditor() { } // ----------------------------------------------------------------------- // wxPGCheckBoxEditor // ----------------------------------------------------------------------- #if wxPG_INCLUDE_CHECKBOX WX_PG_IMPLEMENT_EDITOR_CLASS(CheckBox,wxPGCheckBoxEditor,wxPGEditor) // state argument: 0x01 = set if checked // 0x02 = set if rectangle should be bold static void DrawSimpleCheckBox( wxDC& dc, const wxRect& rect, int box_hei, int state, const wxColour& linecol ) { // Box rectangle. wxRect r(rect.x+wxPG_XBEFORETEXT,rect.y+((rect.height-box_hei)/2),box_hei,box_hei); // Draw check mark first because it is likely to overdraw the // surrounding rectangle. if ( state & 1 ) { wxRect r2(r.x+wxPG_CHECKMARK_XADJ, r.y+wxPG_CHECKMARK_YADJ, r.width+wxPG_CHECKMARK_WADJ, r.height+wxPG_CHECKMARK_HADJ); #if wxPG_CHECKMARK_DEFLATE r2.Deflate(wxPG_CHECKMARK_DEFLATE); #endif dc.DrawCheckMark(r2); // This would draw a simple cross check mark. // dc.DrawLine(r.x,r.y,r.x+r.width-1,r.y+r.height-1); // dc.DrawLine(r.x,r.y+r.height-1,r.x+r.width-1,r.y); } if ( !(state & 2) ) { // Pen for thin rectangle. dc.SetPen(linecol); } else { // Pen for bold rectangle. wxPen linepen(linecol,2,wxSOLID); linepen.SetJoin(wxJOIN_MITER); // This prevents round edges. dc.SetPen(linepen); r.x++; r.y++; r.width--; r.height--; } dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.DrawRectangle(r); dc.SetPen(*wxTRANSPARENT_PEN); } // // Real simple custom-drawn checkbox-without-label class. // class wxSimpleCheckBox : public wxControl { public: void SetValue( int value ); wxSimpleCheckBox( wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize ) : wxControl(parent,id,pos,size,wxNO_BORDER|wxWANTS_CHARS) { // Due to SetOwnFont stuff necessary for GTK+ 1.2, we need to have this SetFont( parent->GetFont() ); m_state = 0; m_boxHeight = ((wxPropertyGrid*)parent)->GetFontHeight(); SetBackgroundStyle( wxBG_STYLE_COLOUR ); } virtual ~wxSimpleCheckBox(); virtual bool ProcessEvent(wxEvent& event); int m_state; int m_boxHeight; static wxBitmap* ms_doubleBuffer; }; wxSimpleCheckBox::~wxSimpleCheckBox() { delete ms_doubleBuffer; ms_doubleBuffer = NULL; } wxBitmap* wxSimpleCheckBox::ms_doubleBuffer = (wxBitmap*) NULL; // value = 2 means toggle (sorry, too lazy to do constants) void wxSimpleCheckBox::SetValue( int value ) { if ( value > 1 ) { m_state++; if ( m_state > 1 ) m_state = 0; } else { m_state = value; } Refresh(); wxCommandEvent evt(wxEVT_COMMAND_CHECKBOX_CLICKED,GetParent()->GetId()); ((wxPropertyGrid*)GetParent())->OnCustomEditorEvent(evt); } bool wxSimpleCheckBox::ProcessEvent(wxEvent& event) { wxPropertyGrid* propGrid = (wxPropertyGrid*) GetParent(); if ( event.GetEventType() == wxEVT_NAVIGATION_KEY ) { //wxLogDebug(wxT("wxEVT_NAVIGATION_KEY")); //SetFocusFromKbd(); //event.Skip(); //return wxControl::ProcessEvent(event); } else if ( ( (event.GetEventType() == wxEVT_LEFT_DOWN || event.GetEventType() == wxEVT_LEFT_DCLICK) && ((wxMouseEvent&)event).m_x > (wxPG_XBEFORETEXT-2) && ((wxMouseEvent&)event).m_x <= (wxPG_XBEFORETEXT-2+m_boxHeight) ) ) { SetValue(2); return true; } else if ( event.GetEventType() == wxEVT_PAINT ) { wxSize clientSize = GetClientSize(); wxPaintDC dc(this); /* // Buffered paint DC doesn't seem to do much good if ( !ms_doubleBuffer || clientSize.x > ms_doubleBuffer->GetWidth() || clientSize.y > ms_doubleBuffer->GetHeight() ) { delete ms_doubleBuffer; ms_doubleBuffer = new wxBitmap(clientSize.x+25,clientSize.y+25); } wxBufferedPaintDC dc(this,*ms_doubleBuffer); */ wxRect rect(0,0,clientSize.x,clientSize.y); rect.x -= 1; rect.width += 1; m_boxHeight = propGrid->GetFontHeight(); wxColour bgcol = GetBackgroundColour(); dc.SetBrush( bgcol ); dc.SetPen( bgcol ); dc.DrawRectangle( rect ); wxColour txcol = GetForegroundColour(); int state = m_state; if ( m_font.GetWeight() == wxBOLD ) state |= 2; DrawSimpleCheckBox(dc,rect,m_boxHeight,state,txcol); // If focused, indicate it somehow. /* if ( wxWindow::FindFocus() == this ) { rect.x += 1; rect.width -= 1; wxPGDrawFocusRect(dc,rect); } */ return true; } else if ( event.GetEventType() == wxEVT_SIZE || event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_KILL_FOCUS ) { Refresh(); } else if ( event.GetEventType() == wxEVT_KEY_DOWN ) { wxKeyEvent& keyEv = (wxKeyEvent&) event; if ( keyEv.GetKeyCode() == WXK_TAB ) { propGrid->SendNavigationKeyEvent( keyEv.ShiftDown()?0:1 ); return true; } else if ( keyEv.GetKeyCode() == WXK_SPACE ) { SetValue(2); return true; } } return wxControl::ProcessEvent(event); } #ifndef __WXPYTHON__ wxWindow* wxPGCheckBoxEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& size, wxWindow** ) const #else wxPGWindowPair wxPGCheckBoxEditor::CreateControls( wxPropertyGrid* propGrid, wxPGProperty* property, const wxPoint& pos, const wxSize& size ) const #endif { wxPoint pt = pos; pt.x -= wxPG_XBEFOREWIDGET; wxSize sz = size; sz.x += wxPG_XBEFOREWIDGET; wxSimpleCheckBox* cb = new wxSimpleCheckBox(propGrid,wxPG_SUBID1,pt,sz); cb->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); cb->Connect( wxPG_SUBID1, wxEVT_LEFT_DOWN, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &wxPropertyGrid::OnCustomEditorEvent, NULL, propGrid ); cb->Connect( wxPG_SUBID1, wxEVT_LEFT_DCLICK, (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &wxPropertyGrid::OnCustomEditorEvent, NULL, propGrid ); if ( property->GetChoiceInfo((wxPGChoiceInfo*)NULL) && !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) cb->m_state = 1; // If mouse cursor was on the item, toggle the value now. if ( propGrid->GetInternalFlags() & wxPG_FL_ACTIVATION_BY_CLICK ) { wxPoint pt = propGrid->ScreenToClient(::wxGetMousePosition()); if ( pt.x <= (cb->GetPosition().x+wxPG_XBEFORETEXT-2+cb->m_boxHeight) ) { cb->m_state++; if ( cb->m_state > 1 ) cb->m_state = 0; property->ClearFlag(wxPG_PROP_UNSPECIFIED); property->SetValueFromInt(cb->m_state,0); propGrid->PropertyWasModified(property); } } return cb; } void wxPGCheckBoxEditor::DrawValue( wxDC& dc, wxPGProperty* property, const wxRect& rect ) const { int state = 0; if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) { state = property->GetChoiceInfo((wxPGChoiceInfo*)NULL); if ( dc.GetFont().GetWeight() == wxBOLD ) state |= 2; } DrawSimpleCheckBox(dc,rect,dc.GetCharHeight(),state,dc.GetTextForeground()); } void wxPGCheckBoxEditor::UpdateControl( wxPGProperty* property, wxWindow* ctrl ) const { wxASSERT( ctrl ); ((wxSimpleCheckBox*)ctrl)->m_state = property->GetChoiceInfo((wxPGChoiceInfo*)NULL); ctrl->Refresh(); } bool wxPGCheckBoxEditor::OnEvent( wxPropertyGrid* WXUNUSED(propGrid), wxPGProperty* WXUNUSED(property), wxWindow* WXUNUSED(ctrl), wxEvent& event ) const { if ( event.GetEventType() == wxEVT_COMMAND_CHECKBOX_CLICKED ) { /*if ( CopyValueFromControl( property, ctrl ) ) return true; propGrid->EditorsValueWasNotModified(); CLEAR_PROPERTY_UNSPECIFIED_FLAG(property);*/ return true; } return false; } bool wxPGCheckBoxEditor::CopyValueFromControl( wxPGProperty* property, wxWindow* ctrl ) const { wxSimpleCheckBox* cb = (wxSimpleCheckBox*)ctrl; int index = cb->m_state; if ( index != property->GetChoiceInfo( (wxPGChoiceInfo*) NULL ) || // Changing unspecified always causes event (returning // true here should be enough to trigger it). property->IsFlagSet(wxPG_PROP_UNSPECIFIED) ) { property->SetValueFromInt(index,0); return true; } return false; } void wxPGCheckBoxEditor::SetControlIntValue( wxWindow* ctrl, int value ) const { if ( value != 0 ) value = 1; ((wxSimpleCheckBox*)ctrl)->m_state = value; ctrl->Refresh(); } void wxPGCheckBoxEditor::SetValueToUnspecified( wxWindow* ctrl ) const { ((wxSimpleCheckBox*)ctrl)->m_state = 0; ctrl->Refresh(); } wxPGCheckBoxEditor::~wxPGCheckBoxEditor() { } #endif // wxPG_INCLUDE_CHECKBOX // ----------------------------------------------------------------------- // 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()); } // ----------------------------------------------------------------------- // wxPGTLWHandler // Intercepts Close-events sent to wxPropertyGrid's top-level parent, // and tries to commit property value. // ----------------------------------------------------------------------- class wxPGTLWHandler : public wxEvtHandler { public: wxPGTLWHandler( wxPropertyGrid* pg ) : wxEvtHandler() { m_pg = pg; } protected: void OnClose( wxCloseEvent& event ) { // ClearSelection forces value validation/commit. if ( event.CanVeto() && !m_pg->ClearSelection() ) { event.Veto(); return; } event.Skip(); } private: wxPropertyGrid* m_pg; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxPGTLWHandler, wxEvtHandler) EVT_CLOSE(wxPGTLWHandler::OnClose) END_EVENT_TABLE() // ----------------------------------------------------------------------- // wxPropertyGrid // ----------------------------------------------------------------------- IMPLEMENT_CLASS(wxPropertyGrid, wxScrolledWindow) BEGIN_EVENT_TABLE(wxPropertyGrid, wxScrolledWindow) EVT_MOTION(wxPropertyGrid::OnMouseMove) EVT_IDLE(wxPropertyGrid::OnIdle) EVT_LEFT_DOWN(wxPropertyGrid::OnMouseClick) EVT_LEFT_UP(wxPropertyGrid::OnMouseUp) EVT_RIGHT_UP(wxPropertyGrid::OnMouseRightClick) EVT_LEFT_DCLICK(wxPropertyGrid::OnMouseDoubleClick) EVT_PAINT(wxPropertyGrid::OnPaint) EVT_SIZE(wxPropertyGrid::OnResize) EVT_KEY_DOWN(wxPropertyGrid::OnKey) EVT_KEY_UP(wxPropertyGrid::OnKeyUp) EVT_CHAR(wxPropertyGrid::OnKey) EVT_ENTER_WINDOW(wxPropertyGrid::OnMouseEntry) EVT_LEAVE_WINDOW(wxPropertyGrid::OnMouseEntry) EVT_MOUSE_CAPTURE_CHANGED(wxPropertyGrid::OnCaptureChange) EVT_SCROLLWIN(wxPropertyGrid::OnScrollEvent) EVT_NAVIGATION_KEY(wxPropertyGrid::OnNavigationKey) EVT_TEXT(wxPG_SUBID1,wxPropertyGrid::OnCustomEditorEvent) EVT_COMBOBOX(wxPG_SUBID1,wxPropertyGrid::OnCustomEditorEvent) EVT_BUTTON(wxPG_SUBID2,wxPropertyGrid::OnCustomEditorEvent) 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 wxChar* 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 wxChar* name ) { if ( !(style&wxBORDER_MASK) ) style |= wxSIMPLE_BORDER; 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; } // ----------------------------------------------------------------------- static void wxPGRegisterStandardPropertyClasses(); // // Initialize values to defaults // void wxPropertyGrid::Init1() { WX_PG_GLOBALS_LOCKER() #if !wxPG_USE_WXMODULE if ( !wxPGGlobalVars ) wxPGGlobalVars = new wxPGGlobalVarsClass(); #endif // Register type classes, if necessary. if ( wxPGGlobalVars->m_dictValueType.empty() ) RegisterDefaultValues(); // Register editor classes, if necessary. if ( wxPGGlobalVars->m_mapEditorClasses.empty() ) RegisterDefaultEditors(); // Register property classes, if necessary if ( wxPGGlobalVars->m_dictPropertyClassInfo.empty() ) wxPGRegisterStandardPropertyClasses(); m_iFlags = 0; m_pState = (wxPropertyGridState*) NULL; m_wndPrimary = m_wndSecondary = (wxWindow*) NULL; m_selected = (wxPGProperty*) NULL; m_propHover = (wxPGProperty*) NULL; m_eventObject = this; m_curFocused = (wxWindow*) NULL; m_tlwHandler = NULL; m_processingEvent = 0; m_dragStatus = 0; m_mouseSide = 16; m_editorFocused = 0; m_coloursCustomized = 0; m_frozen = 0; #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_calcVisHeight = 0; m_gutterWidth = wxPG_GUTTER_MIN; m_subgroup_extramargin = 10; m_lineHeight = 0; m_width = m_height = m_fWidth = 0; m_bottomy = 0; m_splitterx = wxPG_DEFAULT_SPLITTERX; m_fSplitterX = (float) wxPG_DEFAULT_SPLITTERX; #if !wxPG_HEAVY_GFX m_splitterpen.SetColour( *wxBLACK ); m_splitterpen.SetWidth( 4 ); m_splitterprevdrawnx = -1; #endif SetButtonShortcut(0); m_keyComboConsumed = 0; m_ignoredEvents = 0; } // ----------------------------------------------------------------------- // // 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(); FROM_STATE(m_properties) = FROM_STATE(m_abcArray); } GetClientSize(&m_width,&m_height); #if !wxPG_HEAVY_GFX m_splitterpen.SetColour( *wxBLACK ); m_splitterpen.SetWidth( 4 ); #endif #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; if ( !m_font.Ok() ) { wxFont useFont = wxScrolledWindow::GetFont(); wxScrolledWindow::SetOwnFont( useFont ); } else // This should be otherwise called by SetOwnFont CalculateFontAndBitmapStuff( wxPG_DEFAULT_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 wxPGTLWHandler* handler = new wxPGTLWHandler(this); m_tlp = ::wxGetTopLevelParent(this); m_tlwHandler = handler; m_tlp->PushEventHandler(handler); // set virtual size to this window size wxSize wndsize = GetSize(); SetVirtualSize(wndsize.GetWidth(), wndsize.GetWidth()); m_timeCreated = ::wxGetLocalTimeMillis(); m_iFlags |= wxPG_FL_INITIALIZED; // 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; DoSelectProperty(NULL); // This should do prevent things from going too badly wrong m_iFlags &= ~(wxPG_FL_INITIALIZED); END_MOUSE_CAPTURE wxPGTLWHandler* handler = (wxPGTLWHandler*) m_tlwHandler; m_tlp->RemoveEventHandler(handler); delete handler; #ifdef __WXDEBUG__ if ( IsEditorsValueModified() ) ::wxMessageBox(wxT("Most recent change in property editor was lost!!!\n\n(if you don't want this to happen, close your frames and dialogs using Close(false).)"), wxT("wxPropertyGrid Debug Warning") ); #endif #if wxPG_DOUBLE_BUFFER if ( m_doubleBuffer ) delete m_doubleBuffer; #endif delete m_windowsToDelete; m_selected = (wxPGProperty*) NULL; 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; iSetDelay ( wxPG_TOOLTIP_DELAY );*/ } else if ( (old_style & wxPG_TOOLTIPS) && !(style & wxPG_TOOLTIPS) ) { // // Tooltips disabled // wxScrolledWindow::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 ); RedrawAllVisible(); } } } // ----------------------------------------------------------------------- void wxPropertyGrid::Freeze() { m_frozen++; wxScrolledWindow::Freeze(); } // ----------------------------------------------------------------------- void wxPropertyGrid::Thaw() { m_frozen--; wxScrolledWindow::Thaw(); #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT Refresh(); #endif // Force property re-selection if ( m_selected ) DoSelectProperty(m_selected, wxPG_SEL_FORCE); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetExtraStyle( long exStyle ) { 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; if ( exStyle & wxPG_EX_AUTO_UNSPECIFIED_VALUES ) { wxPGGlobalVars->m_numBoolChoices = 3; } else { wxPGGlobalVars->m_numBoolChoices = 2; } } // ----------------------------------------------------------------------- // 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; } // ----------------------------------------------------------------------- // 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; // button spacing m_buttonSpacingY = (m_lineHeight - m_iconHeight) / 2; if ( m_buttonSpacingY < 0 ) m_buttonSpacingY = 0; 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; } // ----------------------------------------------------------------------- void wxPropertyGrid::ResetColours() { m_coloursCustomized = 0; RegainColours(); Refresh(); } // ----------------------------------------------------------------------- bool wxPropertyGrid::SetFont( const wxFont& font ) { #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::SetFont") ); #endif // Must disable active editor. if ( m_selected ) { bool selRes = ClearSelection(); wxPG_CHECK_MSG_DBG( selRes, false, wxT("failed to deselect a property (editor probably had invalid value)") ); } // TODO: Following code is disabled with wxMac because // it is reported to fail. I (JMS) cannot debug it // personally right now. #if !defined(__WXMAC__) bool res = wxScrolledWindow::SetFont( font ); if ( res ) { CalculateFontAndBitmapStuff( m_vspacing ); if ( m_pState ) { // Recalculate caption text extents. // TODO: This should also be done to other pages of manager // (so add wxPropertyGridManager::SetFont), but since font // is usually set before categories are added, this is // quite low priority. size_t i; for ( i=0;iGetParentingType() > 0 ) ((wxPropertyCategoryClass*)p)->CalculateTextExtent(this,m_captionFont); } CalculateYs(NULL,-1); } Refresh(); } return res; #else // ** wxMAC Only ** // TODO: Remove after SetFont crash fixed. if ( m_iFlags & wxPG_FL_INITIALIZED ) { wxLogDebug(wxT("WARNING: propGrid.cpp: wxPropertyGrid::SetFont has been disabled on wxMac since there has been crash reported in it. If you are willing to debug the cause, replace line '#if !defined(__WXMAC__)' with line '#if 1' in wxPropertyGrid::SetFont.")); } return false; #endif } // ----------------------------------------------------------------------- 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::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; if ( (p->m_bgColIndex == 0) || (flags & (wxPG_RECURSE_STARTS|wxPG_FORCE)) ) p->m_bgColIndex = ind; if ( p->GetParentingType() != 0 && (flags & wxPG_RECURSE) ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; unsigned int i; for ( i=0; iGetCount(); i++ ) SetBackgroundColourIndex(pwc->Item(i), index, flags & ~(wxPG_RECURSE_STARTS)); } } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyBackgroundColour( wxPGId id, const wxColour& colour ) { wxPG_PROP_ID_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 property background colours allowed.") ); m_arrBgBrushes.Add( (void*)new wxPGBrush(colour) ); } // Set indexes SetBackgroundColourIndex(p, colInd, wxPG_RECURSE|wxPG_RECURSE_STARTS); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- wxColour wxPropertyGrid::GetPropertyBackgroundColour( wxPGId id ) const { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxColour()) return ((wxPGBrush*)m_arrBgBrushes.Item(p->m_bgColIndex))->GetColour(); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetTextColourIndex( wxPGProperty* p, int index, int flags ) { unsigned char ind = index; if ( (p->m_fgColIndex == 0) || (flags & (wxPG_RECURSE_STARTS|wxPG_FORCE)) ) p->m_fgColIndex = ind; if ( p->GetParentingType() != 0 && (flags & wxPG_RECURSE) ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; unsigned int i; for ( i=0; iGetCount(); i++ ) SetTextColourIndex( pwc->Item(i), index, flags&~(wxPG_RECURSE_STARTS) ); } } // ----------------------------------------------------------------------- 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 different property foreground colours allowed.") ); m_arrFgCols.Add( (void*)new wxPGColour(colour) ); } return colInd; } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyTextColour( wxPGId id, const wxColour& colour ) { wxPG_PROP_ID_CALL_PROLOG() // Set indexes SetTextColourIndex(p, CacheColour(colour), wxPG_RECURSE|wxPG_RECURSE_STARTS ); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetCaptionTextColour( wxPGId id, const wxColour& colour ) { wxPG_PROP_ID_CALL_PROLOG() wxCHECK_RET( p->GetParentingType() == PT_CAPTION, wxT("Only call SetCaptionTextColour for caption properties") ); // Set indexes wxPropertyCategoryClass* cat = (wxPropertyCategoryClass*) p; cat->SetTextColIndex(CacheColour(colour)); // If this was on a visible grid, then draw it. DrawItemAndChildren(p); } // ----------------------------------------------------------------------- wxColour wxPropertyGrid::GetPropertyTextColour( wxPGId id ) const { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxColour()) return wxColour(*((wxPGColour*)m_arrFgCols.Item(p->m_fgColIndex))); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyColourToDefault( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG() SetBackgroundColourIndex( p, 0, wxPG_RECURSE|wxPG_FORCE ); SetTextColourIndex( p, 0, wxPG_RECURSE|wxPG_FORCE ); if ( p->GetParentingType() == PT_CAPTION ) { wxPropertyCategoryClass* cat = (wxPropertyCategoryClass*) p; cat->SetTextColIndex(1); } } // ----------------------------------------------------------------------- // wxPropertyGrid property adding and removal // ----------------------------------------------------------------------- wxPGId wxPropertyGrid::Append( wxPGProperty* property ) { return FROM_STATE(Append(property)); } // ----------------------------------------------------------------------- wxPGId wxPropertyGrid::_Insert( wxPGProperty* priorthis, wxPGProperty* property ) { wxASSERT( priorthis ); return FROM_STATE(DoInsert(priorthis->GetParent(), priorthis->GetArrIndex(), property)); } // ----------------------------------------------------------------------- void wxPropertyContainerMethods::Delete( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG() wxPropertyGridState* state = p->GetParentState(); wxPropertyGrid* grid = state->GetGrid(); if ( grid->GetState() == state ) { bool selRes = grid->DoSelectProperty(wxPGIdGen(NULL), wxPG_SEL_DELETING); wxPG_CHECK_RET_DBG( selRes, wxT("failed to deselect a property (editor probably had invalid value)") ); } state->DoDelete( p ); if ( grid->GetState() == state && !grid->IsFrozen() ) { // This should be enough to resolve even the worst // graphics glitch imaginable. grid->Update(); grid->Refresh(); } } // ----------------------------------------------------------------------- wxPGId wxPropertyContainerMethods::ReplaceProperty( wxPGId id, wxPGProperty* property ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) wxPGProperty* replaced = wxPGIdToPtr(id); wxCHECK_MSG( replaced && property, wxNullProperty, wxT("NULL property") ); wxCHECK_MSG( replaced->GetParentingType() == 0 || replaced->GetParentingType() == -1, 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 wxPGPropertyWithChildren* parent = replaced->GetParent(); int ind = replaced->GetIndexInParent(); wxPropertyGridState* state = replaced->GetParentState(); Delete(replaced); // Must use generic Delete state->DoInsert(parent,ind,property); return wxPGIdGen(property); } // ----------------------------------------------------------------------- void wxPropertyGrid::PrepareAfterItemsAdded() { if ( !FROM_STATE(m_itemsAdded) ) return; #if __INTENSE_DEBUGGING__ wxLogDebug(wxT("PrepareAfterItemsAdded( in thread 0x%lX )"), (unsigned long)wxThread::GetCurrentId()); #endif FROM_STATE(m_itemsAdded) = 0; if ( m_windowStyle & wxPG_AUTO_SORT ) { Sort (); } else { if ( m_bottomy < 1 ) CalculateYs( NULL, -1 ); else { RecalculateVirtualSize(); // Update visibles array (maybe not necessary here, but just in case) CalculateVisibles ( -1, 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(),wxPG_TO_WXCHAR_PTR(p->GetValueTypePtr()->GetCustomTypeName()),typestr ); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyValue( wxPGId id, const wxPGValueType* typeclass, const wxPGVariant& value ) { wxPG_PROP_ID_CALL_PROLOG() if ( p && m_pState->SetPropertyValue(p,typeclass,value) ) DrawItemAndValueRelated( p ); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyValue( wxPGId id, const wxChar* typestring, const wxPGVariant& value ) { wxPG_PROP_ID_CALL_PROLOG() if ( p && m_pState->SetPropertyValue(p,typestring,value) ) DrawItemAndValueRelated( p ); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyValueString( wxPGId id, const wxString& value ) { wxPG_PROP_ID_CALL_PROLOG() if ( m_pState->SetPropertyValueString(p,value) ) DrawItemAndValueRelated( p ); } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyValueWxObjectPtr( wxPGId id, wxObject* value ) { wxPG_PROP_ID_CALL_PROLOG() if ( m_pState->SetPropertyValueWxObjectPtr(p,value) ) DrawItemAndValueRelated( p ); } // ----------------------------------------------------------------------- #ifndef __WXPYTHON__ void wxPropertyGrid::SetPropertyValue( wxPGId id, wxVariant& value ) { wxPG_PROP_ID_CALL_PROLOG() if ( m_pState->SetPropertyValue(p,value) ) DrawItemAndValueRelated( p ); } #endif // ----------------------------------------------------------------------- bool wxPropertyGrid::ClearPropertyValue( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(false) if ( m_pState->ClearPropertyValue(p) ) { DrawItemAndChildren( p ); return true; } return false; } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyUnspecified( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG() m_pState->SetPropertyUnspecified(p); DrawItemAndChildren(p); wxPGPropertyWithChildren* parent = p->GetParent(); while ( parent->GetParentingType() <= PT_FIXEDCHILDREN ) { DrawItem(parent); parent = parent->GetParent(); } } // ----------------------------------------------------------------------- // wxPropertyGrid miscellaneous GetPropertyXXX methods // ----------------------------------------------------------------------- wxPropertyCategoryClass* wxPropertyGrid::_GetPropertyCategory( wxPGProperty* p ) { wxPGProperty* parent = (wxPGPropertyWithChildren*)p; wxPGProperty* grandparent = (wxPGProperty*)parent->GetParent(); do { parent = grandparent; grandparent = (wxPGProperty*)parent->GetParent(); if ( parent->GetParentingType() >= PT_CAPTION && grandparent ) return (wxPropertyCategoryClass*)parent; } while ( grandparent ); return (wxPropertyCategoryClass*) NULL; } // ----------------------------------------------------------------------- // wxPropertyGrid property operations // ----------------------------------------------------------------------- bool wxPropertyGrid::EnableProperty( wxPGId id, bool enable ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(false) if ( enable ) { if ( !(p->m_flags & wxPG_PROP_DISABLED) ) return false; // If active, Set active Editor. if ( p == m_selected ) DoSelectProperty( p, wxPG_SEL_FORCE ); } else { if ( p->m_flags & wxPG_PROP_DISABLED ) return false; // If active, Disable as active Editor. if ( p == m_selected ) DoSelectProperty( p, wxPG_SEL_FORCE ); } m_pState->EnableProperty(p,enable); DrawItemAndChildren( p ); return true; } // ----------------------------------------------------------------------- void wxPropertyGrid::LimitPropertyEditing( wxPGId id, bool limit ) { wxPG_PROP_ID_CALL_PROLOG() m_pState->LimitPropertyEditing(p,limit); if ( p == m_selected ) DoSelectProperty( p, wxPG_SEL_FORCE ); } // ----------------------------------------------------------------------- void wxPropertyGrid::_SetPropertyLabel( wxPGProperty* p, const wxString& newproplabel ) { wxCHECK_RET( p, wxT("invalid property id") ); p->m_label = newproplabel; if ( m_windowStyle & wxPG_AUTO_SORT ) { Sort(p->GetParent()); Refresh(); } else DrawItem ( p ); } // ----------------------------------------------------------------------- void wxPropertyGrid::DoSetPropertyName( wxPGProperty* p, const wxString& newname ) { wxCHECK_RET( p, wxT("invalid property id") ); #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::SetPropertyName( %s -> %s )"), p->GetName().c_str(), newname.c_str() ); #endif if ( p->GetName().Len() ) FROM_STATE(m_dictName).erase ( wxPGNameConv(p->GetName()) ); if ( newname.Len() ) FROM_STATE(m_dictName)[newname] = (void*) p; p->DoSetName(newname); } // ----------------------------------------------------------------------- bool wxPropertyGrid::EnsureVisible( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(false) Update(); bool changed = false; // Is it inside collapsed section? if ( p->m_y < 0 ) { // expand parents wxPGProperty* parent = p->GetParent(); wxPGProperty* grandparent = parent->GetParent(); if ( grandparent && grandparent != FROM_STATE(m_properties) ) Expand ( grandparent ); Expand ( parent ); changed = true; } // Need to scroll? int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; if ( p->m_y < vy ) { Scroll (vx, p->m_y/wxPG_PIXELS_PER_UNIT ); m_iFlags |= wxPG_FL_SCROLLED; changed = true; } else if ( (p->m_y+m_lineHeight) > (vy+m_height) ) { Scroll (vx, (p->m_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 // ----------------------------------------------------------------------- // Fixes position of wxTextCtrl-like control (wxSpinCtrl usually // fits into that category as well). void wxPropertyGrid::FixPosForTextCtrl( wxWindow* ctrl ) { // Center the control vertically wxRect finalPos = ctrl->GetRect(); int y_adj = (m_lineHeight - finalPos.height)/2 + wxPG_TEXTCTRLYADJUST; // Prevent over-sized control int sz_dec = (y_adj + finalPos.height) - m_lineHeight; if ( sz_dec < 0 ) sz_dec = 0; finalPos.y += y_adj; finalPos.height -= (y_adj+sz_dec); // STUPID HACK: wxTextCtrl has different indentation with different // fonts, so this is to solve most common case (ie. using MS Shell Dlg 2 // or Tahoma - which are usually the same). /*#ifdef __WXMSW__ wxString faceName = m_font.GetFaceName(); int textCtrlXAdjust = wxPG_TEXTCTRLXADJUST; if ( (faceName == wxT("MS Shell Dlg 2") || faceName == wxT("Tahoma")) && m_font.GetWeight() != wxFONTWEIGHT_BOLD ) textCtrlXAdjust = 0; #else*/ const int textCtrlXAdjust = wxPG_TEXTCTRLXADJUST; //#endif finalPos.x += textCtrlXAdjust; finalPos.width -= textCtrlXAdjust; ctrl->SetSize(finalPos); } // ----------------------------------------------------------------------- // Control font changer helper. void wxPropertyGrid::SetCurControlBoldFont() { wxASSERT( m_wndPrimary ); m_wndPrimary->SetFont( m_captionFont ); } // ----------------------------------------------------------------------- wxWindow* wxPropertyGrid::GenerateEditorTextCtrl( const wxPoint& pos, const wxSize& sz, const wxString& value, wxWindow* secondary, int extraStyle, int maxLen ) { int tcFlags = wxTE_PROCESS_ENTER | extraStyle; if ( m_selected->HasFlag(wxPG_PROP_READONLY) ) tcFlags |= wxTE_READONLY; wxPoint p(pos.x,pos.y); wxSize s(sz.x,sz.y); // Need to reduce width of text control on Mac #if defined(__WXMAC__) s.x -= 8; #endif // Take button into acccount if ( secondary ) { s.x -= (secondary->GetSize().x + wxPG_TEXTCTRL_AND_BUTTON_SPACING); m_iFlags &= ~(wxPG_FL_PRIMARY_FILLS_ENTIRE); } // If the height is significantly higher, then use border, and fill the rect exactly. bool hasSpecialSize = false; if ( (sz.y - m_lineHeight) > 5 ) hasSpecialSize = true; #if wxPG_NAT_TEXTCTRL_BORDER_ANY // Create clipper window wxPGClipperWindow* wnd = new wxPGClipperWindow(); #if defined(__WXMSW__) wnd->Hide(); #endif wnd->Create(this,wxPG_SUBID1,p,s); // This generates rect of the control inside the clipper window if ( !hasSpecialSize ) wnd->GetControlRect(wxPG_NAT_TEXTCTRL_BORDER_X, wxPG_NAT_TEXTCTRL_BORDER_Y, p, s); else wnd->GetControlRect(0, 0, p, s); wxWindow* ctrlParent = wnd; #else wxWindow* ctrlParent = this; if ( !hasSpecialSize ) tcFlags |= wxNO_BORDER; #endif wxTextCtrl* tc = new wxTextCtrl(); #if defined(__WXMSW__) && !wxPG_NAT_TEXTCTRL_BORDER_ANY tc->Hide(); #endif tc->Create(ctrlParent,wxPG_SUBID1,value, p, s,tcFlags); #if wxPG_NAT_TEXTCTRL_BORDER_ANY wxWindow* ed = wnd; wnd->SetControl(tc); #else wxWindow* ed = tc; #endif // Center the control vertically if ( !hasSpecialSize ) FixPosForTextCtrl(ed); #ifdef __WXMSW__ ed->Show(); if ( secondary ) secondary->Show(); #endif // Set maximum length if ( maxLen > 0 ) tc->SetMaxLength( maxLen ); return (wxWindow*) ed; } // ----------------------------------------------------------------------- wxWindow* wxPropertyGrid::GenerateEditorButton( const wxPoint& pos, const wxSize& sz ) { #ifdef __WXMAC__ // Decorations are chunky on Mac, and we can't make the button square, so // do things a bit differently on this platform. wxPoint p(pos.x+sz.x, pos.y+wxPG_BUTTON_SIZEDEC-wxPG_NAT_BUTTON_BORDER_Y); wxSize s(25, -1); wxButton* but = new wxButton(); but->Create(this,wxPG_SUBID2,wxT("..."),p,s,wxWANTS_CHARS); // Now that we know the size, move to the correct position p.x = pos.x + sz.x - but->GetSize().x - 2; but->Move(p); #else wxSize s(sz.y-(wxPG_BUTTON_SIZEDEC*2)+(wxPG_NAT_BUTTON_BORDER_Y*2), sz.y-(wxPG_BUTTON_SIZEDEC*2)+(wxPG_NAT_BUTTON_BORDER_Y*2)); // Reduce button width to lineheight if ( s.x > m_lineHeight ) s.x = m_lineHeight; wxPoint p(pos.x+sz.x-s.x, pos.y+wxPG_BUTTON_SIZEDEC-wxPG_NAT_BUTTON_BORDER_Y); wxButton* but = new wxButton(); #ifdef __WXMSW__ but->Hide(); #endif but->Create(this,wxPG_SUBID2,wxT("..."),p,s,wxWANTS_CHARS); but->SetFont( m_captionFont ); #endif if ( m_selected->HasFlag(wxPG_PROP_READONLY) ) but->Disable(); return but; } // ----------------------------------------------------------------------- wxWindow* wxPropertyGrid::GenerateEditorTextCtrlAndButton( const wxPoint& pos, const wxSize& sz, wxWindow** psecondary, int limitedEditing, wxPGProperty* property ) { wxButton* but = (wxButton*)GenerateEditorButton(pos,sz); *psecondary = (wxWindow*)but; if ( limitedEditing ) { #ifdef __WXMSW__ // There is button Show in GenerateEditorTextCtrl as well but->Show(); #endif return (wxWindow*) NULL; } wxString text; if ( !(property->GetFlags() & wxPG_PROP_UNSPECIFIED) ) text = property->GetValueAsString(property->HasFlag(wxPG_PROP_READONLY)?0:wxPG_EDITABLE_VALUE); return GenerateEditorTextCtrl(pos,sz,text,but,property->m_maxLen); } // ----------------------------------------------------------------------- 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 x = m_splitterx; int y = p->m_y; wxCHECK_MSG( y >= 0, wxPoint(-1,-1), wxT("invalid y?") ); wxCHECK_MSG( y < (int)m_bottomy, 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-m_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 } // ----------------------------------------------------------------------- void wxPropertyGrid::SLAlloc( unsigned int itemcount, const wxChar** items ) { wxArrayString& sl = m_sl; unsigned int i; unsigned int sl_oldcount = sl.GetCount(); if ( sl_oldcount > itemcount ) sl_oldcount = itemcount; #if wxUSE_INTL if ( !wxPGGlobalVars->m_autoGetTranslation ) { #endif for ( i=0; i= wxT(' ') #if !wxUSE_UNICODE || a < 0 #endif ) { // 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 // ----------------------------------------------------------------------- #define II_INVALID_I 0x00FFFFFF #define ITEM_ITERATION_VARIABLES \ wxPGPropertyWithChildren* parent; \ unsigned int i; \ unsigned int iMax; #define ITEM_ITERATION_DCAE_VARIABLES \ wxPGPropertyWithChildren* parent; \ unsigned int i; \ unsigned int iMax; #define ITEM_ITERATION_INIT_FROM_THE_TOP \ parent = FROM_STATE(m_properties); \ i = 0; #define ITEM_ITERATION_INIT(startparent,startindex) \ parent = startparent; \ i = (unsigned int)startindex; \ if ( parent == (wxPGPropertyWithChildren*) NULL ) \ { \ parent = FROM_STATE(m_properties); \ i = 0; \ } #define ITEM_ITERATION_LOOP_BEGIN \ unsigned char parent_expanded; \ do \ { \ parent_expanded = (unsigned char)parent->m_expanded; \ if ( parent->m_parent && !parent->m_parent->m_expanded ) \ parent_expanded = 0; \ iMax = parent->GetCount(); \ while ( i < iMax ) \ { \ wxPGProperty* p = parent->Item(i); \ int parenting = p->GetParentingType(); #define ITEM_ITERATION_LOOP_END \ if ( parenting ) \ { \ i = 0; \ parent = (wxPGPropertyWithChildren*)p; \ if ( parent_expanded ) \ parent_expanded = (unsigned char)parent->m_expanded; \ else \ parent_expanded = 0; \ iMax = parent->GetCount(); \ } \ else \ i++; \ } \ i = parent->m_arrIndex + 1; \ parent = parent->m_parent; \ } \ while ( parent != NULL ); // DCAE = Don't care about parent_expanded (this is the least space hungry method). #define ITEM_ITERATION_DCAE_LOOP_BEGIN \ do \ { \ iMax = parent->GetCount(); \ while ( i < iMax ) \ { \ wxPGProperty* p = parent->Item(i); \ int parenting = p->GetParentingType(); #define ITEM_ITERATION_DCAE_LOOP_END \ if ( parenting ) \ { \ i = 0; \ parent = (wxPGPropertyWithChildren*)p; \ iMax = parent->GetCount(); \ } \ else \ i++; \ } \ i = parent->m_arrIndex + 1; \ parent = parent->m_parent; \ } \ while ( parent != NULL ); // DCAE_ISP = Don't care about parent_expanded, Ignore sub-properties. // Note that this treats fixed sub-properties same as sub-properties // of wxParentProperty. Mode conversion requires this behaviour. #define ITEM_ITERATION_DCAE_ISP_LOOP_BEGIN \ do \ { \ iMax = parent->GetCount(); \ while ( i < iMax ) \ { \ wxPGProperty* p = parent->Item(i); \ int parenting = p->GetParentingType(); #define ITEM_ITERATION_DCAE_ISP_LOOP_END \ if ( parenting > 0 ) \ { \ i = 0; \ parent = (wxPGPropertyWithChildren*)p; \ iMax = parent->GetCount(); \ } \ else \ i++; \ } \ i = parent->m_arrIndex + 1; \ parent = parent->m_parent; \ } \ while ( parent != (wxPGPropertyWithChildren*) NULL ); // VO = Visible only (including those outside the scrolled section). #define ITEM_ITERATION_VO_LOOP_BEGIN \ if ( (parent == FROM_STATE(m_properties) || parent->m_y >= 0) && parent->m_expanded ) \ { \ do \ { \ iMax = parent->GetCount(); \ while ( i < iMax ) \ { \ wxPGProperty* p = parent->Item(i); \ if ( p->m_y >= 0 ) \ { \ int parenting = p->GetParentingType(); #define ITEM_ITERATION_VO_LOOP_END \ if ( parenting && ((wxPGPropertyWithChildren*)p)->m_expanded ) \ { \ parent = (wxPGPropertyWithChildren*)p; \ i = 0; \ break; \ } \ } \ i++; \ } \ if ( i >= iMax ) \ { \ i = parent->m_arrIndex + 1; \ parent = parent->m_parent; \ } \ } \ while ( parent != (wxPGPropertyWithChildren*) NULL ); \ } // ----------------------------------------------------------------------- // wxPropertyGrid visibility related methods // ----------------------------------------------------------------------- void wxPropertyGrid::CalculateYs( wxPGPropertyWithChildren* startparent, int startindex ) { // Selection must be temporarily cleared during y-recalc wxPGProperty* prevSelected = m_selected; if ( prevSelected ) { bool selRes = ClearSelection(); wxPG_CHECK_RET_DBG( selRes, wxT("failed to deselect a property (editor probably had invalid value)") ); } ITEM_ITERATION_VARIABLES #if __INTENSE_DEBUGGING__ wxLogDebug(wxT("CalculateYs(startsfrom: %s[%i] ) "), startparent?startparent->m_label.c_str():wxT("NULL"), startindex); #endif ITEM_ITERATION_INIT(startparent,startindex) wxASSERT( !m_frozen ); int cury = 0; int lh = m_lineHeight; if ( startparent != NULL ) cury = parent->Item(i)->m_y; wxASSERT_MSG( cury >= 0, wxT("CalculateYs first item was not visible!!!") ); long hide_state = m_iFlags & wxPG_FL_HIDE_STATE; bool inside_hidden_part = false; //parent_expanded = (unsigned char)parent->m_expanded; wxPGPropertyWithChildren* nearest_expanded = (wxPGPropertyWithChildren*) NULL; // Find first visible and expanded parent. while ( !parent->IsExpanded() || ( (parent->m_flags & wxPG_PROP_HIDEABLE) && hide_state ) ) { parent = parent->GetParent(); i = 0; } wxASSERT( parent ); //parent = nearest_expanded; do { iMax = parent->GetCount(); if ( !inside_hidden_part ) { while ( i < iMax ) { wxPGProperty* p = parent->Item(i); int parenting = p->GetParentingType(); if ( !(p->m_flags & wxPG_PROP_HIDEABLE) || (!hide_state) ) { // item is visible (all parents are expanded, non-hideable or not in hide state) p->m_y = (int)cury; cury += lh; } else { p->m_y = -1; } if ( parenting ) { wxPGPropertyWithChildren* p2 = (wxPGPropertyWithChildren*)p; if ( !p2->m_expanded || ( (p2->m_flags & wxPG_PROP_HIDEABLE) && hide_state ) ) { inside_hidden_part = true; nearest_expanded = parent; } parent = p2; i = 0; break; } i++; } } else { while ( i < iMax ) { wxPGProperty* p = parent->Item(i); int parenting = p->GetParentingType(); p->m_y = -1; if ( parenting ) { parent = (wxPGPropertyWithChildren*)p; i = 0; break; } i++; } } if ( i >= iMax ) { i = parent->m_arrIndex + 1; parent = parent->m_parent; if ( inside_hidden_part && parent == nearest_expanded ) { inside_hidden_part = false; } } } while ( parent != (wxPGPropertyWithChildren*) NULL ); m_bottomy = cury; #if __INTENSE_DEBUGGING__ wxLogDebug(wxT(" \\-> m_bottomy = %i"),(int)m_bottomy); #endif // Forces a new DoGetBestSize() call. wxScrolledWindow::InvalidateBestSize(); // Visibles need to be recalculated *always* after y recalculation // (but make sure it stays here, above RecalculateVirtualSize). CalculateVisibles( -1, true ); RecalculateVirtualSize(); // Reselect if ( prevSelected ) DoSelectProperty( prevSelected, wxPG_SEL_NONVISIBLE ); } // ----------------------------------------------------------------------- // Call when scroll position changes. Do not pre-fill m_prevVY. void wxPropertyGrid::CalculateVisibles( int vy, bool full_recalc ) { if ( vy < 0 ) { int vx; GetViewStart(&vx,&vy); vy *= wxPG_PIXELS_PER_UNIT; if ( full_recalc ) m_prevVY = -1; } // Control not yet properly built. if ( vy >= (int)m_bottomy ) return; if ( m_height < 0 ) return; // Hide popup // FIXME: Delete after transient popup support fully added if ( m_wndPrimary && m_wndPrimary->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) ((wxPGOwnerDrawnComboBox*)m_wndPrimary)->HidePopup(); int vy2 = vy + m_height; if ( vy2 > (int)m_bottomy ) vy2 = m_bottomy; unsigned int arr_index = 0; unsigned int vis_height = vy2-vy; unsigned int new_item_count = vis_height/m_lineHeight; if ( vis_height % m_lineHeight ) new_item_count++; wxPGArrayProperty& arr = m_arrVisible; arr.SetCount ( new_item_count ); #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::CalculateVisibles ( vy=%i, vy2=%i, m_height=%i, newitemcount=%i, lineheight=%i )"), (int)vy, (int)vy2, (int)m_height, (int)new_item_count, (int)m_lineHeight ); #endif //wxASSERT( vy != m_prevVY ); wxASSERT( vy >= 0 ); if ( !new_item_count ) { arr.Empty(); return; } ITEM_ITERATION_VARIABLES wxPGProperty* base = NULL; // Will simpler operation be enough? if ( m_prevVY >= 0 ) { if ( m_calcVisHeight == m_height ) { if ( m_iFlags & wxPG_FL_SCROLLED ) { int diff = vy - m_prevVY; if ( diff == m_lineHeight ) { // Scrolled one down base = DoGetItemAtY_Full( vy2 - 1 ); wxASSERT( base ); arr_index = new_item_count - 1; for ( i=0; iarr_index; i-- ) arr.Item(i) = arr.Item(i-1); arr.Item(arr_index) = base; base = (wxPGProperty*) NULL; } else base = DoGetItemAtY_Full( vy ); } else base = DoGetItemAtY_Full( vy ); } else if ( m_prevVY == vy && !(m_iFlags & wxPG_FL_SCROLLED) ) { if ( m_height > m_calcVisHeight ) { // Increased height - add missing items arr_index = (m_calcVisHeight-1)/m_lineHeight; if ( arr_index >= new_item_count ) { // Now, were probably below last item here //if ( (vy+m_calcVisHeight) >= (int)m_bottomy ) base = NULL; /*else arr_index = arr.GetCount()-1;*/ } else { base = (wxPGProperty*) arr.Item( arr_index ); } } else { // Decreased height - do nothing //base = NULL; } } else base = DoGetItemAtY_Full( vy ); } else { base = DoGetItemAtY_Full( vy ); } if ( base ) { ITEM_ITERATION_INIT(base->m_parent,base->m_arrIndex) #if __INTENSE_DEBUGGING__ wxLogDebug( wxT(" Starting at index %i"), (int)arr_index ); #endif ITEM_ITERATION_VO_LOOP_BEGIN //wxASSERT( p->m_y >= 0 ); // update visibility limit reached? if ( p->m_y >= vy2 ) { parent = NULL; break; } #ifdef __WXDEBUG__ if ( arr_index >= arr.GetCount() ) { wxLogDebug(wxT(" wxPropertyGrid::CalculateVisibles Loop overflow (index=%i,vy+vis_height=%i,p->m_y=%i)"), (int)arr_index,(int)(vy+vis_height),(int)p->m_y); } #endif arr.Item(arr_index) = (void*)p; arr_index++; ITEM_ITERATION_VO_LOOP_END } // Adjust controls /*if ( m_selected ) { int adjust = prevVY - vy; if ( adjust ) { wxPoint cp(0,adjust); if ( m_wndPrimary ) m_wndPrimary->Move ( m_wndPrimary->GetPosition() + cp ); if ( m_wndSecondary ) m_wndSecondary->Move ( m_wndSecondary->GetPosition() + cp ); } }*/ m_iFlags &= ~(wxPG_FL_SCROLLED); m_prevVY = vy; m_calcVisHeight = m_height; } // ----------------------------------------------------------------------- // This version uses the visible item cache. wxPGProperty* wxPropertyGrid::DoGetItemAtY( int y ) { //wxASSERT( m_prevVY >= 0 ); // Outside(check 1)? if ( y >= (int)m_bottomy || y < 0 ) { /* #if __PAINT_DEBUGGING__ wxLogDebug(wxT("WARNING: DoGetItemAtY(a): y = %i"),y); #endif */ return (wxPGProperty*) NULL; } int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; // Need to recalculate visibility cache // Note: need to check for y < m_prevVY is a hack. if ( m_prevVY != vy ||y < m_prevVY ) //m_iFlags & wxPG_FL_SCROLLED || CalculateVisibles( vy, true ); // Outside(check 2)? if ( y >= (vy+m_height) ) { /* #if __PAINT_DEBUGGING__ wxLogDebug(wxT("WARNING: DoGetItemAtY(b): y = %i"),y); #endif */ return (wxPGProperty*) NULL; } unsigned int index = (unsigned int)((y - vy) / m_lineHeight); // Consistency checks if ( !m_arrVisible.GetCount() ) return (wxPGProperty*) NULL; if ( index >= m_arrVisible.GetCount() ) { #ifdef __WXDEBUG__ wxLogDebug(wxT(" index = %i"),(int)index); wxLogDebug(wxT(" (height/lineheight+1) = %i"),(int)((m_height/m_lineHeight)+1)); wxLogDebug(wxT(" m_arrVisible.GetCount() = %i"),(int)m_arrVisible.GetCount()); // This was wxCHECK_MSG, but I don't want it to show, since it can happen from // time to time, and I probably won't fix in the current version of wxPropertyGrid. wxLogDebug( wxT("Not enough entries in m_arrVisible (y was < m_bottomy).") ); #endif return (wxPGProperty*) NULL; } if ( index >= m_arrVisible.GetCount() ) { index = m_arrVisible.GetCount()-1; } return (wxPGProperty*)m_arrVisible.Item(index); } // ----------------------------------------------------------------------- // wxPropertyGrid graphics related methods // ----------------------------------------------------------------------- void wxPropertyGrid::OnPaint( wxPaintEvent& WXUNUSED(event) ) { wxPG_PAINT_DC_INIT() // Don't paint after destruction has begun if ( !(m_iFlags & wxPG_FL_INITIALIZED) ) return; #if __PAINT_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::OnPaint()") ); #endif // Find out where the window is scrolled to int vx,vy; // Top left corner of client GetViewStart(&vx,&vy); vy *= wxPG_PIXELS_PER_UNIT; // Update everything inside the box wxRect r = GetUpdateRegion().GetBox(); r.y += vy; // Repaint this rectangle //if ( r.height > 0 ) DrawItems ( dc, r.y, r.y + r.height, #if wxPG_ALLOW_CLIPPING NULL //&r #else NULL #endif ); // We assume that the size set when grid is shown // is what is desired. m_iFlags |= wxPG_FL_GOOD_SIZE_SET; } // ----------------------------------------------------------------------- // // This is the one called by OnPaint event handler and others. // topy and bottomy are already unscrolled // Clears any area in coordinates that doesn't have items. // 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; #if __PAINT_DEBUGGING__ wxLogDebug(wxT("wxPropertyGrid::DrawItems ( %i -> %i, clipRect = 0x%X )"),topy,bottomy, (unsigned int)clipRect); #endif // items added check if ( FROM_STATE(m_itemsAdded) ) PrepareAfterItemsAdded(); unsigned int vx, vy; // Top left corner of client GetViewStart((int*)&vx,(int*)&vy); vy *= wxPG_PIXELS_PER_UNIT; unsigned int client_bottom = (unsigned int)m_height + vy; // Clip topy and bottomy if ( bottomy > client_bottom ) bottomy = client_bottom; if ( topy < vy ) topy = vy; #if __PAINT_DEBUGGING__ wxLogDebug(wxT(" \\--> ( final area %i -> %i )"),topy,bottomy); #endif // // Determine first and last item to draw // (don't draw if already over the top) // if ( topy < client_bottom && topy < m_bottomy && FROM_STATE(m_properties)->GetCount() > 0 ) { wxPGProperty* firstItem = DoGetItemAtY(topy); if ( firstItem == (wxPGProperty*) NULL ) { #ifdef __WXDEBUG__ wxString msg; msg.Printf(wxT("WARNING: wxPropertyGrid::DrawItems(): firstItem == NULL!")); wxMessageBox(msg); wxLogDebug(msg); wxLogDebug(wxT(" More info: y: %i -> %i visible_window: %i -> %i"), (int)topy,(int)bottomy,(int)vy,(int)client_bottom); // This is here for debugging purposes. DoGetItemAtY(topy); #endif return; } wxPGProperty* lastItem = (wxPGProperty*) NULL; // lastItem may be NULL on call to DoDrawItems // in this case lastItem will truly become the last item if ( bottomy > topy && bottomy < m_bottomy ) { lastItem = DoGetItemAtY(bottomy-1); #if __PAINT_DEBUGGING__ wxLogDebug( wxT(" \\--> WARNING: lastItem acquisition failed (should not)!")); #endif } DoDrawItems( dc, firstItem, lastItem, clipRect ); } // Clear area beyond m_bottomy? if ( bottomy > m_bottomy ) { wxColour& bgc = wxPG_SLACK_BACKROUND; //wxColour& bgc = wxColour(255,0,255); dc.SetPen ( wxPen(bgc) ); dc.SetBrush ( wxBrush(bgc) ); unsigned int clear_top = m_bottomy; if ( topy > clear_top ) clear_top = topy; dc.DrawRectangle ( 0, clear_top, m_width, m_height-(clear_top-vy) ); } } // ----------------------------------------------------------------------- #define DECLARE_ITEM_ITERATION_UVC_VARIABLES \ unsigned int ind; \ wxPGProperty* p; // UVC = Use Visibility Cache // VISTART = index to first item from visibility cache to use. // BOTTOMY = Logical y coordinate of last item to draw. #define ITEM_ITERATION_UVC_LOOP_BEGIN(VISTART,BOTTOMY) \ ind = VISTART; \ do \ { \ p = (wxPGProperty*)m_arrVisible.Item(ind); \ ind++; \ int parenting = p->GetParentingType(); #define ITEM_ITERATION_UVC_LOOP_END(BOTTOMY) \ } while ( p->m_y < BOTTOMY ); \ void wxPropertyGrid::DoDrawItems( wxDC& dcMain, const wxPGProperty* firstItem, const wxPGProperty* lastItem, const wxRect* clipRect ) { if ( m_frozen || m_height < 1 ) return; //wxCHECK_RET( !FROM_STATE(m_itemsAdded), wxT("m_itemsAdded must be zero at this point") ); // items added check if ( FROM_STATE(m_itemsAdded) ) PrepareAfterItemsAdded(); wxCHECK_RET( firstItem != NULL, wxT("invalid first item") ); wxASSERT( FROM_STATE(m_properties->GetCount()) ); // Make sure visibility cache is up-to-date int vy; int vx; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; if ( vy != m_prevVY ) CalculateVisibles(vy,true); if ( vy != m_prevVY ) return; // Determine last item, if not given (but requires clipRect). if ( lastItem == NULL ) { if ( clipRect != NULL ) { unsigned int bottomy = clipRect->y + clipRect->height; if ( bottomy <= (unsigned int)firstItem->m_y ) lastItem = firstItem; } if ( lastItem == NULL ) { lastItem = DoGetItemAtY(vy+m_height-1); if ( lastItem == NULL ) lastItem = GetLastItem(true); } } DoDrawItems2(dcMain, firstItem, lastItem, clipRect); } // // Uses three pass approach, so it is optimized for drawing // multiple items at once. // // IMPORTANT NOTES: // - Clipping rectangle must be of physical coordinates. // // void wxPropertyGrid::DoDrawItems2( wxDC& dcMain, const wxPGProperty* firstItem, const wxPGProperty* lastItem, const wxRect* clipRect ) const { int lh = m_lineHeight; int vy; int vx; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; int firstItemTopY = firstItem->m_y; int lastItemBottomY = lastItem->m_y+lh-1; int yRelMod = 0; // Entire range outside scrolled, visible area? if ( firstItemTopY >= (vy+m_height) || lastItemBottomY <= vy ) return; wxCHECK_RET( firstItemTopY < lastItemBottomY, wxT("invalid y values") ); wxDC* dcPtr; #if wxPG_DOUBLE_BUFFER wxMemoryDC* bufferDC = NULL; const wxRect* blitClipRect = NULL; int renderHeight = lastItem->m_y - firstItemTopY + m_lineHeight; if ( !(GetExtraStyle() & wxPG_EX_NATIVE_DOUBLE_BUFFERING) ) { if ( !m_doubleBuffer ) return; // Must fit to double-buffer #ifdef __WXDEBUG__ if ( (lastItemBottomY - firstItemTopY) > m_doubleBuffer->GetHeight() ) { wxString msg; msg.Printf( wxT("wxPropertyGrid: DOUBLE-BUFFER TOO SMALL ( drawn %i vs db height %i vs client_height %i)!"), (int)(lastItemBottomY - firstItemTopY), (int)(m_doubleBuffer->GetHeight()), (int)m_height ); wxLogError(msg); wxLogDebug(msg); } #endif bufferDC = new wxMemoryDC(); bufferDC->SelectObject( *m_doubleBuffer ); dcPtr = bufferDC; blitClipRect = clipRect; //if ( m_iFlags & wxPG_FL_CHANGED || // !(m_iFlags & wxPG_FL_HANDLING_PAINT_EVENT) ) //{ } else #endif { dcPtr = &dcMain; } wxDC& dc = *dcPtr; #if __PAINT_DEBUGGING__ 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 ); #endif wxPGPaintData paintdata; wxRect r; DECLARE_ITEM_ITERATION_UVC_VARIABLES // Get first and last indexes to visibility cache unsigned int viStart = (firstItemTopY - vy) / lh; int vi_end_y = lastItem->m_y; if ( viStart >= m_arrVisible.GetCount() ) { wxLogDebug(wxT("WARNING: viStart >= m_arrVisible.GetCount() ( %i >= %i )"), (int)viStart, (int)m_arrVisible.GetCount() ); return; } #ifdef __WXDEBUG__ unsigned int viEnd = (lastItem->m_y - vy) / lh; if ( viEnd >= m_arrVisible.GetCount() ) { wxLogDebug(wxT("WARNING: viEnd >= m_arrVisible.GetCount() ( %i >= %i )"), (int)viEnd, (int)m_arrVisible.GetCount() ); return; } #endif int x = m_marginWidth; int y; long window_style = m_windowStyle; int extraStyle = GetExtraStyle(); // // With wxPG_DOUBLE_BUFFER, do double buffering // - buffer's y = 0, so align cliprect and coordinates to that // #if wxPG_DOUBLE_BUFFER if ( bufferDC ) { wxRect cr2; //yRelMod = firstItemTopY; yRelMod = vy; // // clipRect conversion if ( clipRect ) { cr2 = *clipRect; cr2.y -= yRelMod; clipRect = &cr2; } //int renderHeight = lastItem->m_y - firstItemTopY + m_lineHeight; //lastItemBottomY -= firstItemTopY; //firstItemTopY = 0; firstItemTopY -= vy; lastItemBottomY -= vy; } #endif const wxFont& normalfont = m_font; bool reallyFocused = (m_iFlags & wxPG_FL_FOCUSED) ? true : false; bool isEnabled = IsEnabled(); // // 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); // pen that has same colour as text wxPen outlinepen(m_colPropFore,1,wxSOLID); if ( clipRect ) dc.SetClippingRegion( *clipRect ); // // Clear margin with background colour // dc.SetBrush( marginBrush ); if ( !(window_style & wxPG_HIDE_MARGIN) ) { dc.SetPen( *wxTRANSPARENT_PEN ); dc.DrawRectangle(-1,firstItemTopY-1,m_marginWidth+2,lastItemBottomY-firstItemTopY+3); } /* // This colorizer helps to debug painting. bool small_draw = false; if ( renderHeight < (m_height-(lh*3)) ) { if ( firstItem == lastItem ) { bgbrush = wxBrush(wxColour(255,128,128)); linepen = wxPen(wxColour(128,0,255)); //boxbrush = wxBrush(wxColour(192,192,192)); } else { bgbrush = wxBrush(wxColour(128,255,128)); linepen = wxPen(wxColour(0,0,255)); //boxbrush = wxBrush(wxColour(230,230,230)); } small_draw = true; } */ //dc.SetPen ( *wxTRANSPARENT_PEN ); //dc.SetFont(normalfont); wxPGProperty* selected = m_selected; /*#if wxPG_REFRESH_CONTROLS_AFTER_REPAINT bool selected_painted = false; #endif*/ // NOTE: Clipping and pen/brush switching are main reasons for multi-pass approach. // // zero'th pass: Wireframes. // (this could be embedded in another loop) dc.SetBrush( marginBrush ); unsigned long cur_first_ind = viStart; unsigned long next_cur_first_ind = 0xFFFFFFFF; wxPGPropertyWithChildren* cur_category = (wxPGPropertyWithChildren*) NULL; int vcache_last_item_y = vy + m_height; if ( vcache_last_item_y > (int)m_bottomy ) vcache_last_item_y = m_bottomy; vcache_last_item_y -= lh; ITEM_ITERATION_UVC_LOOP_BEGIN(viStart,vi_end_y) wxPGPropertyWithChildren* parent = p->GetParent(); // Should not happen (but this is better than a crash)... wxCHECK_RET( parent, wxT("NULL parent") ); // Does this wireframe end? // Conditions: // A) This is last item within its parent, and parent is category. // B) Next is category. // C) This is collapsed category. // D) This is the last item drawn. if ( p->m_y >= vi_end_y ) { // This is the last item. //wxLogDebug(wxT("--> last item")); next_cur_first_ind = ind; } else if ( p->m_arrIndex >= (parent->GetCount()-1) && parent->GetParentingType() >= PT_CAPTION && ( parenting <= 0 /*|| ((wxPGPropertyWithChildren*)p)->GetCount() < 1*/ ) ) { // This is last item within its parent, and parent is category, but this isn't // an non-empty category. //wxLogDebug(wxT("--> category ends")); cur_category = (wxPropertyCategoryClass*)parent; next_cur_first_ind = ind; } else if ( ((wxPGProperty*)m_arrVisible.Item(ind))->GetParentingType() >= PT_CAPTION ) { // Next item is a category. //wxLogDebug(wxT("--> next item is category")); next_cur_first_ind = ind; } else if ( parenting > 0 && (!((wxPGPropertyWithChildren*)p)->IsExpanded() || !((wxPGPropertyWithChildren*)p)->GetCount()) ) { // This is collapsed category. //wxLogDebug(wxT("--> collapsed category")); cur_category = (wxPropertyCategoryClass*)p; next_cur_first_ind = ind; } // When new category begins or old ends, draw wireframe for items in-between if ( next_cur_first_ind < 0xFFFFFF ) { wxPGProperty* cur_first = (wxPGProperty*)m_arrVisible.Item(cur_first_ind); wxPGPropertyWithChildren* cur_last_item = (wxPGPropertyWithChildren*)p; if ( !cur_category ) { if ( cur_first->GetParentingType() >= PT_CAPTION ) { cur_category = (wxPropertyCategoryClass*)cur_first; } else if ( !(m_windowStyle & wxPG_HIDE_CATEGORIES) ) { cur_category = _GetPropertyCategory(cur_first); /*if ( !cur_category ) cur_category = (wxPropertyCategoryClass*)FROM_STATE(m_properties);*/ } } int draw_top = cur_first->m_y - yRelMod; int draw_bottom = cur_last_item->m_y + lh - yRelMod; int frame_top = draw_top; int frame_bottom = draw_bottom; int margin_top = draw_top; int margin_bottom = draw_bottom; int ly = frame_top + lh - 1; if ( cur_first->GetParentingType() >= PT_CAPTION ) { wxPropertyCategoryClass* pc = ((wxPropertyCategoryClass*)cur_first); frame_top += lh; if ( !pc->IsExpanded() ) { // Category collapsed. frame_top = frame_bottom + 1; } } int grey_x = x; if ( cur_category /*!(window_style & wxPG_HIDE_CATEGORIES)*/ ) grey_x += ((unsigned int)((cur_category->GetDepth()-1)*m_subgroup_extramargin)); //wxLogDebug( wxT("wireframe: %s -> %s (grey_x:%i)"), cur_first->GetLabel().c_str(), // cur_last_item->GetLabel().c_str(),((int)grey_x-x)); dc.SetPen( *wxTRANSPARENT_PEN ); // Clear extra margin area. dc.DrawRectangle( x-1, margin_top, grey_x - x + 1, margin_bottom-margin_top ); dc.SetPen( linepen ); if ( frame_bottom > frame_top ) { //if ( cat_top < firstItemTopY ) // cat_top = firstItemTopY; // Margin Edge dc.DrawLine ( grey_x, frame_top, grey_x, frame_bottom ); // Splitter dc.DrawLine ( m_splitterx, frame_top, m_splitterx, frame_bottom ); // Horizontal Lines while ( ly < (frame_bottom-1) ) { dc.DrawLine ( grey_x, ly, m_width, ly ); ly += lh; } } int use_depth = grey_x; // Default is to simply tidy up this wireframe. // Properly draw top line of next wireframe, if adjacent. // Get next item. wxPGProperty* next_item; //if ( ind < m_arrVisible.GetCount() ) if ( cur_last_item->m_y < vcache_last_item_y ) { next_item = (wxPGProperty*)m_arrVisible.Item(ind); } else { // Was not in visibility cache, so use clumsier method. next_item = GetNeighbourItem(cur_last_item,true,1); if (!next_item) next_item = cur_last_item; // This will serve our purpose. } //wxLogDebug(wxT("next_item: %s"),next_item->GetLabel().c_str()); // Just take the depth and is-it-category out of it. int next_parenting = next_item->GetParentingType(); int last_parenting = cur_last_item->GetParentingType(); // A) If both are categories, draw line with background colour. // B) If only next is category, use its category's depth. // C) If only last is category, use grey_x as depth. // D) If neither is a category, use smaller. if ( next_parenting > 0 ) { // Next is category. if ( last_parenting > 0 ) { // Last is a category too - draw complete line with background colour. dc.SetPen ( marginPen ); use_depth = x; } } else { // Next is not a category. wxPropertyCategoryClass* next_cat = _GetPropertyCategory(next_item); int depth_next = x; if ( next_cat && /*cur_category*/ !(window_style & wxPG_HIDE_CATEGORIES) ) { //wxLogDebug(wxT("next_item_cat: %s"),next_cat->GetLabel().c_str()); depth_next += ((unsigned int)((next_cat->GetDepth()-1)*m_subgroup_extramargin)); } if ( last_parenting <= 0 ) { // Last is not a category - use lesser depth if ( depth_next < grey_x ) use_depth = depth_next; //wxLogDebug(wxT("- neither is category")); } else { // Last is a category use_depth = depth_next; //wxLogDebug(wxT("last only is category")); } } //wxLogDebug(wxT("last_line_use_depth: %i"),(int)use_depth); dc.DrawLine( use_depth, ly, m_width, ly ); cur_first_ind = next_cur_first_ind; next_cur_first_ind = 0xFFFFFFFF; //cur_first = (wxPGPropertyWithChildren*)p; cur_category = (wxPGPropertyWithChildren*) NULL; } //cur_last_item = p; ITEM_ITERATION_UVC_LOOP_END(vi_end_y) // // First pass: Category background and text, Images, Label+value background. // //wxLogDebug(wxT(" \\--> first pass...")); y = firstItemTopY; dc.SetFont( m_captionFont ); dc.SetPen( *wxTRANSPARENT_PEN ); ITEM_ITERATION_UVC_LOOP_BEGIN(viStart,vi_end_y) y += m_spacingy; int text_x = x + ((unsigned int)((p->GetDepth()-1)*m_subgroup_extramargin)); if ( parenting > 0 ) { dc.SetBrush( capbgbrush ); // Category label background colour. // Category - draw background, text and possibly selection rectangle. wxPropertyCategoryClass* pc = (wxPropertyCategoryClass*)p; // Note how next separator line is overdrawn if next item is category . int useLh = lh; if ( ind < (m_arrVisible.GetCount()) && ( ((wxPGProperty*)m_arrVisible[ind])->GetParentingType() <= 0 ) ) useLh -= 1; if ( isEnabled && p->IsEnabled() ) dc.SetTextForeground( *(wxPGColour*)m_arrFgCols[pc->GetTextColIndex()] ); else dc.SetTextForeground( m_colDisPropFore ); dc.DrawRectangle( text_x, y-m_spacingy, m_width-text_x, useLh ); dc.DrawText( pc->GetLabel(), text_x+wxPG_XBEFORETEXT, y ); // active caption gets nice dotted rectangle if ( p == selected ) { wxRect focusRect(text_x+wxPG_XBEFORETEXT-wxPG_CAPRECTXMARGIN, y-wxPG_CAPRECTYMARGIN, pc->GetTextExtent()+(wxPG_CAPRECTXMARGIN*2), m_fontHeight+(wxPG_CAPRECTYMARGIN*2)); wxPGDrawFocusRect(dc,focusRect); dc.SetPen( *wxTRANSPARENT_PEN ); } } else { // Basic background colour. dc.SetBrush( *(wxPGBrush*)m_arrBgBrushes[p->m_bgColIndex] ); //wxLogDebug(wxT("%s: %i"),p->m_label.c_str(),(int)p->m_depthBgCol); int greyDepth = 0; if ( !(window_style & wxPG_HIDE_CATEGORIES) ) greyDepth = (((int)p->m_depthBgCol)-1) * m_subgroup_extramargin; // In two parts to retain splitter if ( p == m_selected ) { // Selected get different label background. if ( reallyFocused ) dc.SetBrush( m_colSelBack ); else dc.SetBrush( m_colLine ); dc.DrawRectangle( x+greyDepth+1, y-m_spacingy, m_splitterx-greyDepth-x-1, lh-1 ); } else { dc.DrawRectangle( x+greyDepth+1, y-m_spacingy, m_splitterx-greyDepth-x-1, lh-1 ); } dc.DrawRectangle( m_splitterx+1, y-m_spacingy, m_width-m_splitterx, lh-1 ); } y += m_fontHeight+m_spacingy+1; ITEM_ITERATION_UVC_LOOP_END(vi_end_y) dc.SetFont( normalfont ); // // Second pass: Expander Buttons, Labels. // // Second pass happens entirely on the left side, so sometimes // we can just skip it. if ( clipRect == NULL || clipRect->x < m_splitterx ) { //wxLogDebug(wxT(" \\--> second pass...")); y = firstItemTopY; r = wxRect(0,y,m_splitterx,lastItemBottomY); dc.SetClippingRegion ( r ); dc.SetFont(normalfont); ITEM_ITERATION_UVC_LOOP_BEGIN(viStart,vi_end_y) if ( isEnabled && (p->IsEnabled() || !(extraStyle & wxPG_EX_GREY_LABEL_WHEN_DISABLED)) ) dc.SetTextForeground( *(wxPGColour*)m_arrFgCols[p->m_fgColIndex] ); else dc.SetTextForeground( m_colDisPropFore ); // // Expand/collapse button image. if ( parenting != 0 && !(window_style & wxPG_HIDE_MARGIN) && ((wxPGPropertyWithChildren*)p)->GetChildCount() ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; int depth = p->m_depth - 1; #ifdef wxPG_ICON_WIDTH int imageX = m_gutterWidth + ( depth * m_subgroup_extramargin ); #endif y += m_buttonSpacingY; #if (wxPG_USE_RENDERER_NATIVE) // Prepare rectangle to be used r.x = imageX; r.y = y; r.width = m_iconWidth; r.height = m_iconHeight; #elif wxPG_ICON_WIDTH // Drawing expand/collapse button manually dc.SetPen(m_colPropFore); if ( parenting > 0 ) { dc.SetBrush(*wxTRANSPARENT_BRUSH); } else { dc.SetBrush(m_colPropBack); } dc.DrawRectangle( imageX, y, m_iconWidth, m_iconWidth ); int _y = y+(m_iconWidth/2); dc.DrawLine(imageX+2,_y,imageX+m_iconWidth-2,_y); #else wxBitmap* bmp; #endif if ( pwc->m_expanded ) { // 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 = imageX+(m_iconWidth/2); dc.DrawLine(_x,y+2,_x,y+m_iconWidth-2); #else bmp = m_expandbmp; #endif } #if (wxPG_USE_RENDERER_NATIVE) // #elif wxPG_ICON_WIDTH // #else dc.DrawBitmap( *bmp, m_gutterWidth, y, true ); #endif y -= m_buttonSpacingY; } y += m_spacingy; if ( parenting <= 0 ) { // Non-categories. int text_x = x; // Use basic depth if in non-categoric mode and parent is base array. if ( !(window_style & wxPG_HIDE_CATEGORIES) || p->GetParent() != FROM_STATE(m_properties) ) { text_x += ((unsigned int)((p->m_depth-1)*m_subgroup_extramargin)); } /* else { wxLogDebug( wxT("%s"), p->GetLabel().c_str() ); text_x = x; } */ if ( p != selected ) { dc.DrawText( p->m_label, text_x+wxPG_XBEFORETEXT, y ); } else { // Selected gets different colour. if ( reallyFocused ) dc.SetTextForeground( m_colSelFore ); dc.DrawText( p->m_label, text_x+wxPG_XBEFORETEXT, y ); } } else { /*// switch background colour bgbrush.SetColour ( ((wxPropertyCategoryClass*)p)->m_colCellBg ); dc.SetBrush ( bgbrush );*/ } y += m_fontHeight+m_spacingy+1; ITEM_ITERATION_UVC_LOOP_END(vi_end_y) } // // Third pass: Values // dc.DestroyClippingRegion(); if ( clipRect ) { // third pass happens entirely on the right side, so sometimes // we can just skip it if ( (clipRect->x + clipRect->width) < m_splitterx ) x = -1; dc.SetClippingRegion ( *clipRect ); } // This used with value drawer method. wxRect valueRect(0,0, m_width-(m_splitterx+wxPG_CONTROL_MARGIN), m_fontHeight); wxSize imageSize; if ( x != -1 ) { r.x = m_splitterx+1+wxPG_CONTROL_MARGIN; r.width = m_width-m_splitterx-wxPG_CONTROL_MARGIN; //r.x = m_splitterx+wxPG_DIST_SPLITTER_TO_IMAGE; //r.width = m_width-m_splitterx-wxPG_DIST_SPLITTER_TO_IMAGE-1; r.height = lh-1; /*#if wxCC_CORRECT_CONTROL_POSITION const int vy2 = vy; #endif*/ //wxLogDebug(wxT(" \\--> third pass...")); // Altough this line may seem unnecessary, it isn't dc.SetFont(normalfont); dc.SetPen( *wxTRANSPARENT_PEN ); // Prepare paintdata. paintdata.m_parent = this; paintdata.m_choiceItem = -1; // Not drawing list item at this time. y = firstItemTopY; ITEM_ITERATION_UVC_LOOP_BEGIN(viStart,vi_end_y) if ( parenting <= 0 ) { r.y = y; y += m_spacingy; // background dc.SetBrush( *(wxPGBrush*)m_arrBgBrushes[p->m_bgColIndex] ); if ( isEnabled && p->IsEnabled() ) dc.SetTextForeground( *(wxPGColour*)m_arrFgCols[p->m_fgColIndex] ); else dc.SetTextForeground( m_colDisPropFore ); // draw value string only if editor widget not open // (exception: no primary editor widget or it is hidden) if ( p != selected || !m_wndPrimary // "if not primary shown" is required because // primary is not usually shown during splitter // movement. || m_dragStatus > 0 ) { valueRect.x = m_splitterx+wxPG_CONTROL_MARGIN; valueRect.y = y; // Draw background if ( p != selected ) { dc.DrawRectangle( r ); } else { if ( m_wndPrimary ) dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); else dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); dc.DrawRectangle( r ); } // Set bold font? if ( p->m_flags & wxPG_PROP_MODIFIED && (window_style & wxPG_BOLD_MODIFIED) ) dc.SetFont( m_captionFont ); const wxPGEditor* editor = p->GetEditorClass(); bool fullPaint = false; if ( p->m_flags & wxPG_PROP_CUSTOMIMAGE ) { imageSize = p->GetImageSize(); wxRect imageRect(r.x + wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1, r.y+wxPG_CUSTOM_IMAGE_SPACINGY, wxPG_CUSTOM_IMAGE_WIDTH, r.height-(wxPG_CUSTOM_IMAGE_SPACINGY*2)); if ( imageSize.x == wxPG_FULL_CUSTOM_PAINT_WIDTH ) { fullPaint = true; imageRect.width = m_width - imageRect.x; } dc.SetPen( outlinepen ); paintdata.m_drawnWidth = imageRect.width; if ( !(p->m_flags & wxPG_PROP_UNSPECIFIED) ) { p->OnCustomPaint( dc, imageRect, paintdata ); } else { dc.SetBrush(*wxWHITE_BRUSH); dc.DrawRectangle(imageRect); } dc.SetPen( *wxTRANSPARENT_PEN ); } else paintdata.m_drawnWidth = 0; if ( paintdata.m_drawnWidth > 0 ) valueRect.x += paintdata.m_drawnWidth + wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2; else fullPaint = false; if ( !fullPaint ) editor->DrawValue(dc,p,valueRect); // Return original font? if ( p->m_flags & wxPG_PROP_MODIFIED && (window_style & wxPG_BOLD_MODIFIED) ) dc.SetFont(normalfont); } else { if ( !(m_iFlags & wxPG_FL_PRIMARY_FILLS_ENTIRE) || m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE ) { //wxLogDebug(wxT("Primary doesn't fill entire")); dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); dc.DrawRectangle( r ); } if ( m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE ) { wxRect imagerect(r.x + wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1, r.y+wxPG_CUSTOM_IMAGE_SPACINGY,wxPG_CUSTOM_IMAGE_WIDTH, r.height-(wxPG_CUSTOM_IMAGE_SPACINGY*2)); dc.SetPen ( outlinepen ); if ( !(p->m_flags & wxPG_PROP_UNSPECIFIED) ) { p->OnCustomPaint( dc, imagerect, paintdata ); } else { dc.SetBrush(*wxWHITE_BRUSH); dc.DrawRectangle(imagerect); } } dc.SetPen( *wxTRANSPARENT_PEN ); } y += m_fontHeight+m_spacingy + 1; } else { // caption item y += lh; } //if ( y > lastItemBottomY ) { parent = NULL; break; } ITEM_ITERATION_UVC_LOOP_END(vi_end_y) } dc.DestroyClippingRegion(); // Is this really necessary? #if wxPG_DOUBLE_BUFFER //} //else wxLogDebug(wxT("Used Cache")); if ( bufferDC ) { if ( blitClipRect ) dcMain.SetClippingRegion( *blitClipRect ); //wxLogDebug(wxT(" \\--> (0,%i)"),(int)final_y); dcMain.Blit ( 0, firstItem->m_y, m_width, renderHeight, &dc, 0, firstItem->m_y-vy, wxCOPY ); //dcMain.Blit ( 0, 0, m_width, m_height, // &dc, 0, 0, wxCOPY ); dcMain.DestroyClippingRegion(); // Is this really necessary? delete bufferDC; } #endif #if __PAINT_DEBUGGING__ wxLogDebug(wxT(" \\--> ends...")); #endif } // ----------------------------------------------------------------------- wxRect wxPropertyGrid::GetPropertyRect( const wxPGProperty* p1, const wxPGProperty* p2 ) const { wxRect r; if ( m_width < 10 || m_height < 10 || !FROM_STATE(m_properties)->GetCount() || p1 == (wxPGProperty*) NULL ) return wxRect(0,0,0,0); int vx,vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; // // Return rect which encloses the given property range int visTop = p1->m_y; int visBottom = m_bottomy; if ( p2 ) visBottom = p2->m_y + m_lineHeight; // If seleced property is inside the range, we'll extend the range to include // control's size. wxPGProperty* selected = m_selected; if ( selected && selected->m_y >= visTop && selected->m_y < visBottom ) { wxWindow* editor = GetEditorControl(); if ( editor ) { int visBottom2 = selected->m_y + editor->GetSize().y; if ( visBottom2 > visBottom ) visBottom = visBottom2; } } return wxRect(0,visTop-vy,m_width,visBottom-visTop); } // ----------------------------------------------------------------------- void wxPropertyGrid::DrawItems( const wxPGProperty* p1, const wxPGProperty* p2 ) { if ( m_frozen ) return; if ( FROM_STATE(m_itemsAdded) ) PrepareAfterItemsAdded(); wxRect r = GetPropertyRect(p1, p2); if ( r.width > 0 ) RefreshRect(r); } // ----------------------------------------------------------------------- // In addition to calling DoDrawItems directly, this is the // only alternative for using wxClientDC - others just call // RefreshRect. void wxPropertyGrid::DrawItem( wxDC& dc, wxPGProperty* p ) { wxCHECK_RET( p, wxT("invalid property id") ); // do not draw a single item if multiple pending if ( FROM_STATE(m_itemsAdded) ) return; if ( p->m_y < 0 ) return; #if __PAINT_DEBUGGING__ wxLogDebug(wxT("wxPropertyGrid::DrawItem( %s )"), p->GetLabel().c_str() ); #endif DoDrawItems( dc, p, p, NULL ); } // ----------------------------------------------------------------------- void wxPropertyGrid::RefreshProperty( wxPGProperty* p ) { if ( p == m_selected ) DoSelectProperty(p, wxPG_SEL_FORCE); DrawItemAndChildren(p); } // ----------------------------------------------------------------------- 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->GetParentingType() < PT_NONE ) { 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 ( FROM_STATE(m_itemsAdded) || p->m_y < 0 || m_frozen ) return; #if __PAINT_DEBUGGING__ wxLogDebug(wxT("wxPropertyGrid::DrawItemAndChildren( %s )"), p->GetLabel().c_str() ); #endif // Update child control. if ( m_selected && m_selected->GetParent() == p ) m_selected->UpdateControl(m_wndPrimary); const wxPGProperty* lastDrawn = p->GetLastVisibleSubItem(); DrawItems(p, lastDrawn); } // ----------------------------------------------------------------------- void wxPropertyGrid::Refresh( bool WXUNUSED(eraseBackground), const wxRect *rect ) { // Refresh implies forced redraw //m_iFlags |= wxPG_FL_CHANGED; wxWindow::Refresh(false,rect); #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // I think this really helps only GTK+1.2 if ( m_wndPrimary ) m_wndPrimary->Refresh(); if ( m_wndSecondary ) m_wndSecondary->Refresh(); #endif } // ----------------------------------------------------------------------- /* void wxPropertyGrid::RedrawAllVisible () { // TODO: Is this safe? //Update(); if ( m_frozen || !IsShown() ) return; wxPG_CLIENT_DC_INIT() #if __PAINT_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::RedrawAllVisible()") ); #endif int vx,vy; // Top left corner of client GetViewStart(&vx,&vy); vy *= wxPG_PIXELS_PER_UNIT; int y1 = vy; int y2 = y1 + m_height; // Repaint this rectangle DrawItems ( dc, y1, y2, (wxRect*) NULL ); } */ // ----------------------------------------------------------------------- #if wxPG_HEAVY_GFX void wxPropertyGrid::DrawSplitterDragColumn( wxDC&, int ) { } #else void wxPropertyGrid::DrawSplitterDragColumn( wxDC& dc, int x ) { int vx, vy; GetViewStart(&vx,&vy); vy *= wxPG_PIXELS_PER_UNIT; dc.SetLogicalFunction(wxINVERT); dc.DestroyClippingRegion(); dc.SetPen( m_splitterpen ); dc.DrawLine(x,vy,x,vy+m_height); } #endif // ----------------------------------------------------------------------- // wxPropertyGrid global operations // ----------------------------------------------------------------------- void wxPropertyGrid::Clear() { if ( m_selected ) { bool selRes = DoSelectProperty(wxPGIdGen(NULL), wxPG_SEL_DELETING); // This must be before state clear wxPG_CHECK_RET_DBG( selRes, wxT("failed to deselect a property (editor probably had invalid value)") ); } FROM_STATE(Clear()); m_propHover = NULL; m_bottomy = 0; m_prevVY = 0; m_arrVisible.Empty(); 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 ) { if ( !ClearSelection() ) return 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 ) { FROM_STATE(m_itemsAdded) = 1; // force PrepareAfterItemsAdded(); } else { CalculateYs(NULL,-1); //CalculateVisibles( -1 ); } } else FROM_STATE(m_itemsAdded) = 1; Refresh(); return true; } // ----------------------------------------------------------------------- void wxPropertyGrid::SwitchState( wxPropertyGridState* pNewState ) { wxASSERT( pNewState ); wxPGProperty* oldSelection = m_selected; // Deselect if ( m_selected ) { bool selRes = ClearSelection(); wxPG_CHECK_RET_DBG( selRes, wxT("failed to deselect a property (editor probably had invalid value)") ); } m_pState->m_selected = oldSelection; bool orig_mode = m_pState->IsInNonCatMode(); bool new_state_mode = pNewState->IsInNonCatMode(); m_pState = pNewState; m_bottomy = 0; // This is necessary or y's won't get updated. 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 ( FROM_STATE(m_itemsAdded) ) PrepareAfterItemsAdded(); else CalculateYs(NULL,-1); // Reselect if ( FROM_STATE(m_selected) ) DoSelectProperty( FROM_STATE(m_selected) ); //RedrawAllVisible(); Refresh(); } else m_pState->m_itemsAdded = 1; } // ----------------------------------------------------------------------- void wxPropertyGrid::Sort( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG() m_pState->Sort( p ); // Because order changed, Y's need to be changed as well if ( p->GetParentState() == m_pState ) CalculateYs ( p->m_parent, p->m_arrIndex ); } // ----------------------------------------------------------------------- void wxPropertyGrid::Sort() { bool selRes = ClearSelection(); // This must be before state clear wxPG_CHECK_RET_DBG( selRes, wxT("failed to deselect a property (editor probably had invalid value)") ); m_pState->Sort(); CalculateYs( NULL, -1 ); } // ----------------------------------------------------------------------- // Call to SetSplitterPosition will always disable splitter auto-centering // if parent window is shown. void wxPropertyGrid::DoSetSplitterPosition( int newxpos, bool refresh ) { if ( ( newxpos < wxPG_DRAG_MARGIN ) ) return; // ( m_width > wxPG_DRAG_MARGIN && newxpos > (m_width-wxPG_DRAG_MARGIN) ) #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::DoSetSplitterPosition ( %i )"), newxpos ); #endif #if wxPG_HEAVY_GFX m_splitterx = newxpos; m_fSplitterX = (float) newxpos; if ( refresh ) { if ( m_selected ) CorrectEditorWidgetSizeX( m_splitterx, m_width ); Refresh(); //RedrawAllVisible(); // no flicker } #else if ( !m_dragStatus ) { // Only do this if this was not a call from HandleMouseUp m_startingSplitterX = m_splitterx; m_splitterx = newxpos; m_fSplitterX = (float) newxpos; } // Clear old if ( m_splitterprevdrawnx != -1 ) { wxPG_CLIENT_DC_INIT() DrawSplitterDragColumn( dc, m_splitterprevdrawnx ); m_splitterprevdrawnx = -1; } // Redraw only if drag really moved if ( m_splitterx != m_startingSplitterX && refresh ) { if ( m_selected) CorrectEditorWidgetSizeX( m_splitterx, m_width ); Update(); // This fixes a graphics-mess in wxMSW Refresh(); //RedrawAllVisible(); // no flicker } #endif // Don't allow initial splitter auto-positioning after this. m_iFlags |= wxPG_FL_SPLITTER_PRE_SET; } // ----------------------------------------------------------------------- void wxPropertyGrid::CenterSplitter( bool enable_auto_centering ) { SetSplitterPosition ( m_width/2, true ); if ( enable_auto_centering && ( m_windowStyle & wxPG_SPLITTER_AUTO_CENTER ) ) m_iFlags &= ~(wxPG_FL_DONT_CENTER_SPLITTER); } // ----------------------------------------------------------------------- // Moves splitter so that all labels are visible, but just. void wxPropertyGrid::SetSplitterLeft( bool subProps ) { wxClientDC dc(this); dc.SetFont(m_font); int maxW = m_pState->GetLeftSplitterPos(dc, m_pState->m_properties, subProps ); if ( maxW > 0 ) SetSplitterPosition( maxW ); 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 ) { int vx,vy1;// Top left corner of client GetViewStart(&vx,&vy1); vy1 *= wxPG_PIXELS_PER_UNIT; int vy2 = vy1 + m_height; if ( (p->m_y + m_lineHeight) < vy1 ) { // Too high return DoGetItemAtY( vy1 ); } else if ( p->m_y > vy2 ) { // Too low return DoGetItemAtY( vy2 ); } // Itself paint visible return p; } // ----------------------------------------------------------------------- wxPGProperty* wxPropertyGrid::GetNeighbourItem( wxPGProperty* item, bool need_visible, int dir ) const { wxPGPropertyWithChildren* parent = item->m_parent; unsigned int indinparent = item->GetIndexInParent(); if ( dir > 0 ) { if ( item->GetChildCount() == 0 || (!((wxPGPropertyWithChildren*)item)->m_expanded && need_visible) ) { // current item did not have any expanded children if ( indinparent < (parent->GetCount()-1) ) { // take next in parent's array item = parent->Item(indinparent+1); } else { // no more in parent's array; move up until found; wxPGPropertyWithChildren* p2 = parent; parent = parent->m_parent; item = (wxPGProperty*) NULL; while ( parent ) { if ( p2->m_arrIndex < (parent->GetCount()-1) ) { item = parent->Item(p2->m_arrIndex+1); break; } p2 = parent; parent = parent->m_parent; } } } else { // take first of current item's children wxPGPropertyWithChildren* p2 = (wxPGPropertyWithChildren*)item; item = p2->Item(0); //indinparent = 0; } } else { // items in array left? if ( indinparent > 0 ) { // take prev in parent's array item = parent->Item(indinparent-1); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)item; // Recurse to it's last child while ( item->GetParentingType() != 0 && pwc->GetCount() && ( pwc->m_expanded || !need_visible ) ) { item = pwc->Last(); pwc = (wxPGPropertyWithChildren*)item; } } else { // If we were at first, go to parent item = parent; } } if ( item == FROM_STATE(m_properties) ) return (wxPGProperty*) NULL; // If item was hidden and need_visible, get next. if ( (m_iFlags & wxPG_FL_HIDE_STATE) && need_visible && item ) { if ( item->m_flags & wxPG_PROP_HIDEABLE ) { // Speed-up: If parent is hidden as well, then skip to last child or to itself if ( parent->m_flags & wxPG_PROP_HIDEABLE ) { item = parent; // if dir up if ( dir > 0 ) item = parent->Last(); // if dir down } return GetNeighbourItem ( item, need_visible, dir ); } } return item; } // ----------------------------------------------------------------------- // This is used in DoDrawItems. wxPGProperty* wxPropertyGrid::GetLastItem ( bool need_visible, bool allowSubprops ) { if ( FROM_STATE(m_properties)->GetCount() < 1 ) return (wxPGProperty*) NULL; wxPGProperty* p = FROM_STATE(m_properties)->Last(); int parenting = p->GetParentingType(); while ( parenting != 0 && ( allowSubprops || parenting >= PT_CAPTION ) ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; parenting = 0; if ( pwc->GetCount() ) { if ( (!need_visible || pwc->m_expanded) ) { p = pwc->Last(); parenting = p->GetParentingType(); } else parenting = 0; } } // If item was hidden and need_visible, get previous. if ( (m_iFlags & wxPG_FL_HIDE_STATE) && need_visible && p && ( p->m_flags & wxPG_PROP_HIDEABLE ) ) return GetNeighbourItem( p, need_visible, -1 ); 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 ) { if ( m_wndPrimary && IsEditorsValueModified() && (m_iFlags & wxPG_FL_INITIALIZED) ) { wxCHECK_MSG( m_selected, false, wxT("no selection") ); bool wasUnspecified = m_selected->IsValueUnspecified(); // JACS - necessary to avoid new focus being found spuriously within OnIdle // due to another window getting focus wxWindow* oldFocus = m_curFocused; if ( !(flags & (wxPG_SEL_NOVALIDATE|wxPG_SEL_FORCE)) && !DoEditorValidate() ) { if (oldFocus) { oldFocus->SetFocus(); m_curFocused = oldFocus; } return false; } // Save value (only if truly modified). if ( !m_selected->GetEditorClass()->CopyValueFromControl( m_selected, m_wndPrimary ) ) EditorsValueWasNotModified(); if ( m_selected->IsValueUnspecified() && !wasUnspecified && UsesAutoUnspecified() ) flags |= wxPG_SEL_SETUNSPEC; DoPropertyChanged( m_selected, flags ); return true; } return true; } // ----------------------------------------------------------------------- // flags are same as with DoSelectProperty void wxPropertyGrid::DoPropertyChanged( wxPGProperty* p, unsigned int selFlags ) { if ( m_processingEvent ) return; #if __INTENSE_DEBUGGING__ wxLogDebug(wxT("wxPropertyGrid::DoPropertyChanged( %s )"),p->GetLabel().c_str()); #endif m_pState->m_anyModified = 1; m_processingEvent = 1; // No longer unspecified (but not if the value was set to unspecified by // user modification) if ( !(selFlags & wxPG_SEL_SETUNSPEC) ) CLEAR_PROPERTY_UNSPECIFIED_FLAG(p); if ( m_iFlags & wxPG_FL_VALUE_MODIFIED ) { m_iFlags &= ~(wxPG_FL_VALUE_MODIFIED); // Set as Modified (not if dragging just began) if ( !(p->m_flags & wxPG_PROP_MODIFIED) ) { p->m_flags |= wxPG_PROP_MODIFIED; if ( p == m_selected && (m_windowStyle & wxPG_BOLD_MODIFIED) ) { if ( m_wndPrimary ) SetCurControlBoldFont(); } } wxPGProperty* curChild = p; wxPGPropertyWithChildren* curParent = p->m_parent; // Also update parent(s), if any // (but not if its wxCustomProperty) while ( curParent && curParent->GetParentingType() < 0 /*&& wxStrcmp(curParent->GetClassName(),wxT("wxCustomProperty")) != 0*/ ) { // Set as Modified if ( !(curParent->m_flags & wxPG_PROP_MODIFIED) ) { curParent->m_flags |= wxPG_PROP_MODIFIED; if ( curParent == m_selected && (m_windowStyle & wxPG_BOLD_MODIFIED) ) { if ( m_wndPrimary ) SetCurControlBoldFont(); } } curParent->ChildChanged( curChild ); DrawItem( curParent ); curChild = curParent; curParent = curParent->GetParent(); } // Draw the actual property if ( ( p != m_selected ) || !m_wndPrimary || ( p->GetParentingType() < 0 ) || ( p->m_flags & wxPG_PROP_CUSTOMIMAGE ) ) { DrawItemAndChildren( p ); } if ( curChild != p && !(selFlags & wxPG_SEL_SETUNSPEC) ) //m_pState->ClearPropertyAndChildrenFlags(curChild,wxPG_PROP_UNSPECIFIED); CLEAR_PROPERTY_UNSPECIFIED_FLAG(curChild); wxPGProperty* changedProperty; // Call wx event handler for property (or its topmost parent, but only // when dealing with legitemate sub-properties - see above). if ( curChild->GetParentingType() != PT_CUSTOMPROPERTY ) changedProperty = curChild; else changedProperty = p; // Maybe need to update control #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT if ( m_wndPrimary ) m_wndPrimary->Refresh(); if ( m_wndSecondary ) m_wndSecondary->Refresh(); #endif SendEvent( wxEVT_PG_CHANGED, changedProperty, selFlags ); } m_processingEvent = 0; } // ----------------------------------------------------------------------- // Runs wxValidator for the selected property bool wxPropertyGrid::DoEditorValidate() { #if wxUSE_VALIDATORS if ( m_iFlags & wxPG_FL_VALIDATION_FAILED ) { return false; } wxWindow* wnd = GetEditorControl(); wxValidator* validator = m_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; } // ----------------------------------------------------------------------- #if wxUSE_VALIDATORS bool wxPGInDialogValidator::DoValidate( wxPropertyGrid* propGrid, wxValidator* validator, const wxString& value ) { if ( !validator ) return true; wxTextCtrl* tc = m_textCtrl; if ( !tc ) { { tc = new wxTextCtrl( propGrid, wxPG_SUBID_TEMP1, wxEmptyString, wxPoint(30000,30000)); tc->Hide(); } m_textCtrl = tc; } //wxString oldValue = tc->GetValue(); tc->SetValue(value); validator->SetWindow(tc); bool res = validator->Validate(propGrid); //tc->SetValue(oldValue); return res; } #else bool wxPGInDialogValidator::DoValidate( wxPropertyGrid* WXUNUSED(propGrid), wxValidator* WXUNUSED(validator), const wxString& WXUNUSED(value) ) { return true; } #endif // ----------------------------------------------------------------------- // NB: It may really not be wxCommandEvent - must check if necessary // (usually not). void wxPropertyGrid::OnCustomEditorEvent( wxCommandEvent &event ) { wxPGProperty* selected = m_selected; // // Somehow, event is handled after property has been deselected. // Possibly, but very rare. if ( !selected ) return; bool wasUnspecified = selected->IsValueUnspecified(); bool usesAutoUnspecified = UsesAutoUnspecified(); wxWindow* wnd = m_wndPrimary; bool res1, res2; m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED); // First call editor class' event handler. const wxPGEditor* editor = selected->GetEditorClass(); res1 = editor->OnEvent( this, selected, wnd, event ); if ( res1 ) { // If changes, validate them if ( DoEditorValidate() ) { if ( editor->CopyValueFromControl( selected, wnd ) ) { } else { // False alarm res1 = false; EditorsValueWasNotModified(); // However, even moot editing will clear the unspecified status if ( wasUnspecified || !usesAutoUnspecified ) CLEAR_PROPERTY_UNSPECIFIED_FLAG(selected); } } else { res1 = false; EditorsValueWasNotModified(); if ( wasUnspecified || !usesAutoUnspecified ) CLEAR_PROPERTY_UNSPECIFIED_FLAG(selected); return; } } // Then the property's custom handler (must be always called). res2 = selected->OnEvent( this, wnd, event ); if ( res1 || res2 ) { // Setting this is not required if res was true, so we do it now. m_iFlags |= wxPG_FL_VALUE_MODIFIED; int selFlags = ( !wasUnspecified && selected->IsValueUnspecified() && usesAutoUnspecified ) ? wxPG_SEL_SETUNSPEC : 0; DoPropertyChanged(selected, selFlags); } else // Let unhandled button click events go to the parent if ( event.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED ) { wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED,GetId()); GetEventHandler()->AddPendingEvent(evt); } } // ----------------------------------------------------------------------- // When a property's value was modified internally (using SetValueFromString // or SetValueFromInt, for example), then this should be called afterwards. // NB: Avoid using this method, if possible. void wxPropertyGrid::PropertyWasModified( wxPGProperty* p, int selFlags ) { wxCHECK_RET( p, wxT("invalid property id") ); EditorsValueWasModified(); DoPropertyChanged(p, selFlags); } // ----------------------------------------------------------------------- // wxPropertyGrid editor control helper methods // ----------------------------------------------------------------------- wxWindow* wxPropertyGrid::GetEditorControl() const { wxWindow* ctrl = m_wndPrimary; if ( !ctrl ) return ctrl; // If it's clipper window, return its child instead #if wxPG_ENABLE_CLIPPER_WINDOW if ( ctrl->IsKindOf(CLASSINFO(wxPGClipperWindow)) ) { return ((wxPGClipperWindow*)ctrl)->GetControl(); } #endif return ctrl; } // ----------------------------------------------------------------------- // inline because it is used exactly once in the code inline wxRect wxPropertyGrid::GetEditorWidgetRect( wxPGProperty* p ) { //wxASSERT( p->m_y >= 0 ); // item is not visible int itemy = p->m_y; int vx,vy;// Top left corner of client GetViewStart(&vx,&vy); vy *= wxPG_PIXELS_PER_UNIT; int cust_img_space = 0; //m_iFlags &= ~(wxPG_FL_CUR_USES_CUSTOM_IMAGE); // TODO: If custom image detection changes from current, change this. if ( m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE /*p->m_flags & wxPG_PROP_CUSTOMIMAGE*/ ) { //m_iFlags |= wxPG_FL_CUR_USES_CUSTOM_IMAGE; int imwid = p->GetImageSize().x; if ( imwid < 1 ) imwid = wxPG_CUSTOM_IMAGE_WIDTH; cust_img_space = imwid + wxCC_CUSTOM_IMAGE_MARGIN1 + wxCC_CUSTOM_IMAGE_MARGIN2; } return wxRect ( m_splitterx+cust_img_space+wxPG_XBEFOREWIDGET+wxPG_CONTROL_MARGIN+1, itemy-vy, m_width-m_splitterx-wxPG_XBEFOREWIDGET-wxPG_CONTROL_MARGIN-cust_img_space-1, m_lineHeight-1 ); } // ----------------------------------------------------------------------- // return size of custom paint image wxSize wxPropertyGrid::GetImageSize( wxPGId id ) const { if ( wxPGIdIsOk(id) ) { wxSize cis = wxPGIdToPtr(id)->GetImageSize(); 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; } // If called with NULL property, then return default image // size for properties that use image. return wxSize(wxPG_CUSTOM_IMAGE_WIDTH,wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight)); } // ----------------------------------------------------------------------- void wxPropertyGrid::CorrectEditorWidgetSizeX( int newSplitterx, int newWidth ) { wxASSERT( m_selected ); int secWid = 0; if ( m_wndSecondary ) { // if width change occurred, move secondary wnd by that amount wxRect r = m_wndSecondary->GetRect(); secWid = r.width; r.x = newWidth - secWid; //r.y += yAdj; m_wndSecondary->SetSize ( r ); // if primary is textctrl, then we have to add some extra space #ifdef __WXMAC__ if ( m_wndPrimary ) #else if ( m_wndPrimary && m_wndPrimary->IsKindOf(CLASSINFO(wxTextCtrl)) ) #endif secWid += wxPG_TEXTCTRL_AND_BUTTON_SPACING; } if ( m_wndPrimary ) { wxRect r = m_wndPrimary->GetRect(); r.x = newSplitterx+m_ctrlXAdjust; //r.y += yAdj; r.width = newWidth - r.x - secWid; m_wndPrimary->SetSize(r); } /* int sec_wid = 0; int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; int propY = m_selected->m_y - vy; if ( m_wndSecondary ) { // if width change occurred, move secondary wnd by that amount wxRect r = m_wndSecondary->GetRect(); int adjust = r.y % wxPG_PIXELS_PER_UNIT; if ( adjust > (wxPG_PIXELS_PER_UNIT/2) ) adjust = adjust - wxPG_PIXELS_PER_UNIT; int y = propY + adjust; sec_wid = r.width; m_wndSecondary->Move ( new_width-r.width,y ); // if primary is textctrl, then we have to add some extra space if ( m_wndPrimary && m_wndPrimary->IsKindOf(CLASSINFO(wxTextCtrl)) ) sec_wid += wxPG_TEXTCTRL_AND_BUTTON_SPACING; } if ( m_wndPrimary ) { wxRect r = m_wndPrimary->GetRect(); int adjust = r.y % wxPG_PIXELS_PER_UNIT; if ( adjust > (wxPG_PIXELS_PER_UNIT/2) ) adjust = adjust - wxPG_PIXELS_PER_UNIT; wxLogDebug(wxT("adjust: %i"),adjust); int y = propY + adjust; m_wndPrimary->SetSize( new_splitterx+m_ctrlXAdjust, y, new_width-(new_splitterx+m_ctrlXAdjust)-sec_wid, r.height ); } */ if ( m_wndSecondary ) m_wndSecondary->Refresh(); } // ----------------------------------------------------------------------- /*void wxPropertyGrid::CorrectEditorWidgetSizeY( int cy ) { if ( m_selected ) { wxPoint cp(0,cy); if ( m_wndPrimary ) m_wndPrimary->Move ( m_wndPrimary->GetPosition() + cp ); if ( m_wndSecondary ) m_wndSecondary->Move ( m_wndSecondary->GetPosition() + cp ); } }*/ // ----------------------------------------------------------------------- // 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; SetCursor ( *cursor ); //if ( m_wndPrimary ) m_wndPrimary->SetCursor(wxNullCursor); m_curcursor = type; } // ----------------------------------------------------------------------- // wxPropertyGrid property selection // ----------------------------------------------------------------------- #define CONNECT_CHILD(EVT,FUNCTYPE,FUNC) \ wnd->Connect(id, EVT, \ (wxObjectEventFunction) (wxEventFunction) \ FUNCTYPE (&wxPropertyGrid::FUNC), \ NULL, this ); /* class MyEvtHandler : public wxEvtHandler { public: virtual bool ProcessEvent( wxEvent& event ) { if ( event.GetEventType() == wxEVT_NAVIGATION_KEY ) wxLogDebug(wxT("wxEVT_NAVIGATION_KEY(id=%i)"),event.GetId()); else if ( event.GetEventType() == wxEVT_KEY_DOWN ) wxLogDebug(wxT("wxEVT_KEY_DOWN")); event.Skip(); return wxEvtHandler::ProcessEvent(event); } }; */ // Setups event handling for child control void wxPropertyGrid::SetupEventHandling( wxWindow* argWnd, int id ) { wxWindow* wnd = argWnd; #if wxPG_ENABLE_CLIPPER_WINDOW // Pass real control instead of clipper window if ( wnd->IsKindOf(CLASSINFO(wxPGClipperWindow)) ) { wnd = ((wxPGClipperWindow*)argWnd)->GetControl(); } #endif if ( argWnd == m_wndPrimary ) { CONNECT_CHILD(wxEVT_MOTION,(wxMouseEventFunction),OnMouseMoveChild) CONNECT_CHILD(wxEVT_LEFT_UP,(wxMouseEventFunction),OnMouseUpChild) CONNECT_CHILD(wxEVT_LEFT_DOWN,(wxMouseEventFunction),OnMouseClickChild) //CONNECT_CHILD(wxEVT_LEFT_DCLICK,(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::FreeEditors() { // Do not free editors immediately if processing events if ( !m_windowsToDelete ) m_windowsToDelete = new wxArrayPtrVoid; if ( m_wndSecondary ) { m_windowsToDelete->push_back(m_wndSecondary); m_wndSecondary->Hide(); m_wndSecondary = (wxWindow*) NULL; } if ( m_wndPrimary ) { m_windowsToDelete->push_back(m_wndPrimary); m_wndPrimary->Hide(); m_wndPrimary = (wxWindow*) NULL; } } // Call with NULL to de-select property bool wxPropertyGrid::DoSelectProperty( wxPGProperty* p, unsigned int flags ) { #if __INTENSE_DEBUGGING__ 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 )")); #endif // // Delete windows pending for deletion if ( m_windowsToDelete && !m_processingEvent && m_windowsToDelete->size() ) { unsigned int i; for ( i=0; isize(); i++ ) delete ((wxWindow*)((*m_windowsToDelete)[i])); m_windowsToDelete->clear(); } wxPGProperty* prev = m_selected; // // If we are frozen, then just set the values. if ( m_frozen ) { m_iFlags &= ~(wxPG_FL_ABNORMAL_EDITOR); m_editorFocused = 0; m_selected = p; FROM_STATE(m_selected) = p; // 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 ( m_selected == p && !(flags & wxPG_SEL_FORCE) ) { // Only set focus if not deselecting if ( p ) { if ( flags & wxPG_SEL_FOCUS ) { if ( m_wndPrimary ) { m_wndPrimary->SetFocus(); m_editorFocused = 1; } } else { wxScrolledWindow::SetFocus(); m_editorFocused = 0; } } return true; } wxClientDC dc(this); PrepareDC(dc); // Don't put this earlier, due to return statements m_iFlags |= wxPG_FL_IN_SELECT_PROPERTY; // // First, deactivate previous if ( m_selected ) { #if __INTENSE_DEBUGGING__ wxLogDebug(wxT(" (closing previous (%s))"), m_selected->m_label.c_str() ); #endif // Must double-check if this is an selected in case of forceswitch if ( p != prev ) { 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); return false; } } FreeEditors(); m_iFlags &= ~(wxPG_FL_SELECTED_IS_PAINT_FLEXIBLE|wxPG_FL_SELECTED_IS_FULL_PAINT); m_selected = (wxPGProperty*) NULL; FROM_STATE(m_selected) = (wxPGProperty*) NULL; // Make sure the previous selection is refreshed // JACS: must use paint handler whenever possible Refresh(false); /*if ( m_iFlags & wxPG_FL_ABNORMAL_EDITOR ) Refresh(false); else if ( prev->m_y < (int)m_bottomy ) DoDrawItems( dc, prev, prev, NULL ); */ m_iFlags &= ~(wxPG_FL_VALUE_MODIFIED|wxPG_FL_ABNORMAL_EDITOR); } // // Then, activate the one given. if ( p ) { m_editorFocused = 0; m_selected = p; FROM_STATE(m_selected) = p; m_iFlags |= wxPG_FL_PRIMARY_FILLS_ENTIRE; if ( p != prev ) m_iFlags &= ~(wxPG_FL_VALIDATION_FAILED); //m_wndPrimary = (wxWindow*) NULL; wxASSERT( m_wndPrimary == (wxWindow*) NULL ); // Do we need OnMeasureCalls? wxSize imsz = p->GetImageSize(); if ( imsz.y < -1 ) m_iFlags |= wxPG_FL_SELECTED_IS_PAINT_FLEXIBLE; // Is the entire cell/row custom painted? if ( imsz.x == wxPG_FULL_CUSTOM_PAINT_WIDTH ) m_iFlags |= wxPG_FL_SELECTED_IS_FULL_PAINT; // // Only create editor for non-disabled non-caption if ( p->GetParentingType() <= 0 && !(p->m_flags & wxPG_PROP_DISABLED) ) { // do this for non-caption items // 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) && !p->GetEditorClass()->CanContainCustomImage() ) m_iFlags |= wxPG_FL_CUR_USES_CUSTOM_IMAGE; wxRect grect = GetEditorWidgetRect(p); wxPoint good_pos = grect.GetPosition(); #if wxPG_CREATE_CONTROLS_HIDDEN int coord_adjust = m_height - good_pos.y; good_pos.y += coord_adjust; #endif const wxPGEditor* editor = p->GetEditorClass(); wxCHECK_MSG(editor, false, wxT("NULL editor class not allowed")); #ifndef __WXPYTHON__ m_wndPrimary = editor->CreateControls(this, p, good_pos, grect.GetSize(), &m_wndSecondary); #else wxPGWindowPair wndPair = editor->CreateControls(this, p, good_pos, grect.GetSize()); m_wndPrimary = wndPair.m_primary; m_wndSecondary = wndPair.m_secondary; #endif // NOTE: It is allowed for m_wndPrimary to be NULL - in this case // value is drawn as normal, and m_wndSecondary is assumed // to be a right-aligned button that triggers a separate editor // window. if ( m_wndPrimary ) { //wxLogDebug(wxT("%s Editor created for %s"),editor->GetName(),p->GetName().c_str()); // Set validator, if any /*#if wxUSE_VALIDATORS if ( validator ) m_wndPrimary->SetValidator(*validator); #endif*/ if ( m_wndPrimary->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 #if defined(__WXMSW__) && !defined(__WXWINCE__) wxTextCtrl* tc = wxDynamicCast(m_wndPrimary, wxTextCtrl); if ( tc ) ::SendMessage(GetHwndOf(tc), EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0)); #endif // Store x relative to splitter (we'll need it). m_ctrlXAdjust = m_wndPrimary->GetPosition().x - m_splitterx; // Check if background clear is not necessary wxPoint pos = m_wndPrimary->GetPosition(); if ( pos.x > (m_splitterx+1) || pos.y > p->m_y ) { m_iFlags &= ~(wxPG_FL_PRIMARY_FILLS_ENTIRE); } m_wndPrimary->SetSizeHints(3,3); #if wxPG_CREATE_CONTROLS_HIDDEN m_wndPrimary->Show(false); m_wndPrimary->Freeze(); good_pos = m_wndPrimary->GetPosition(); good_pos.y -= coord_adjust; m_wndPrimary->Move( good_pos ); #endif SetupEventHandling(m_wndPrimary, wxPG_SUBID1); // Focus and select all (wxTextCtrl, wxComboBox etc) if ( flags & wxPG_SEL_FOCUS ) { wxWindow* ctrl = m_wndPrimary; ctrl->SetFocus(); #if wxPG_NAT_TEXTCTRL_BORDER_ANY // Take into account textctrl in clipper window if ( ctrl->IsKindOf(CLASSINFO(wxPGClipperWindow)) ) ctrl = ((wxPGClipperWindow*)ctrl)->GetControl(); #endif p->GetEditorClass()->OnFocus(p,m_wndPrimary); } } if ( m_wndSecondary ) { m_wndSecondary->SetSizeHints(3,3); #if wxPG_CREATE_CONTROLS_HIDDEN wxRect sec_rect = m_wndSecondary->GetRect(); sec_rect.y -= coord_adjust; // Fine tuning required to fix "oversized" // button disappearance bug. if ( sec_rect.y < 0 ) { sec_rect.height += sec_rect.y; sec_rect.y = 0; } m_wndSecondary->SetSize( sec_rect ); #endif m_wndSecondary->Show(); SetupEventHandling(m_wndSecondary,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_wndPrimary ) m_wndSecondary->SetFocus(); */ } if ( flags & wxPG_SEL_FOCUS ) m_editorFocused = 1; } else { // wxGTK atleast seems to need this (wxMSW not) SetFocus(); } m_iFlags &= ~(wxPG_FL_VALUE_MODIFIED); //Update(); // If it's inside collapsed section, expand parent, scroll, etc. // Also, if it was partially visible, scroll it into view. int vx, vy; GetViewStart(&vx,&vy); vy*=wxPG_PIXELS_PER_UNIT; int vy2 = vy + m_height; if ( (p->m_y < vy || (p->m_y <= vy2 && (p->m_y+m_lineHeight) > vy2)) && !(flags & wxPG_SEL_NONVISIBLE) ) EnsureVisible( wxPGIdGen(p) ); if ( m_wndPrimary ) { // Clear its background // (why can't this be optimized by some other drawing?) if ( !(m_iFlags & wxPG_FL_PRIMARY_FILLS_ENTIRE) ) { dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush( wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW) ); dc.DrawRectangle(m_splitterx+1,p->m_y, m_width-m_splitterx,m_lineHeight-1); } #if wxPG_CREATE_CONTROLS_HIDDEN m_wndPrimary->Thaw(); #endif m_wndPrimary->Show(true); } DoDrawItems( dc, p, p, (const wxRect*) NULL ); } } #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 = (wxStatusBar*) NULL; if ( !(m_iFlags & wxPG_FL_NOSTATUSBARHELP) ) { wxFrame* frame = wxDynamicCast(::wxGetTopLevelParent(this),wxFrame); if ( frame ) statusbar = frame->GetStatusBar(); } if ( statusbar ) { const wxString* pHelpString = (const wxString*) NULL; if ( p && p->m_dataExt ) { pHelpString = &p->m_dataExt->m_helpString; 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_iFlags &= ~(wxPG_FL_IN_SELECT_PROPERTY); // call wx event handler (here so that it also occurs on deselection) SendEvent( wxEVT_PG_SELECTED, m_selected, flags ); return true; } // ----------------------------------------------------------------------- // This method is not inline because it called dozens of times // (i.e. two-arg function calls create smaller code size). bool wxPropertyGrid::ClearSelection() { return DoSelectProperty((wxPGProperty*)NULL); } // ----------------------------------------------------------------------- // wxPropertyGrid expand/collapse state and priority (compact mode) related // ----------------------------------------------------------------------- bool wxPropertyGrid::_Collapse( wxPGProperty* p, bool sendEvents ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->GetParentingType() == 0 ) return false; if ( !pwc->m_expanded ) return false; // If active editor was inside collapsed section, then disable it if ( m_selected && m_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; // m_expanded must be set just before call to CalculateYs pwc->m_expanded = 0; // Redraw etc. only if collapsed was visible. if (pwc->m_y >= 0 && !m_frozen && ( pwc->GetParentingType() != 1 || !(m_windowStyle & wxPG_HIDE_CATEGORIES) ) ) { /*int y_adjust = 0; if ( m_selected && m_selected->m_y > pwc->m_y ) { wxPGProperty* next_vis = GetNeighbourItem(pwc,true,1); wxASSERT( next_vis ); y_adjust = next_vis->m_y - pwc->m_y - m_lineHeight; }*/ CalculateYs( pwc->m_parent, pwc->m_arrIndex ); // Fix control position. /*if ( y_adjust ) CorrectEditorWidgetSizeY ( -y_adjust );*/ // 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; if ( sendEvents ) SendEvent( wxEVT_PG_ITEM_COLLAPSED, p ); return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::_Expand( wxPGProperty* p, bool sendEvents ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->GetParentingType() == 0 ) return false; if ( pwc->m_expanded ) 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; // m_expanded must be set just before call to CalculateYs pwc->m_expanded = 1; // Redraw etc. only if expanded was visible. if ( pwc->m_y >= 0 && !m_frozen && ( pwc->GetParentingType() != 1 || !(m_windowStyle & wxPG_HIDE_CATEGORIES) ) ) { CalculateYs( pwc->m_parent, pwc->m_arrIndex ); /*int y_adjust = pwc->GetCount()*m_lineHeight; // Fix widget position as well if ( m_selected && m_selected->m_y > pwc->m_y ) CorrectEditorWidgetSizeY ( y_adjust );*/ // Redraw #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT Refresh(); #else //wxPG_CLIENT_DC_INIT_R(true) //DrawItems( dc, pwc->m_y, m_bottomy ); DrawItems(pwc,(wxPGProperty*) NULL); #endif } // Clear dont-center-splitter flag if it wasn't set m_iFlags = m_iFlags & ~(wxPG_FL_DONT_CENTER_SPLITTER) | old_flag; if ( sendEvents ) SendEvent( wxEVT_PG_ITEM_EXPANDED, p ); return true; } // ----------------------------------------------------------------------- bool wxPropertyGrid::Compact( bool compact ) { #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::Compact()") ); #endif if ( compact ) { if ( !(m_iFlags & wxPG_FL_HIDE_STATE) ) { // Deselect selected if it was hideable if ( m_selected && ( m_selected->m_flags & wxPG_PROP_HIDEABLE ) ) { if ( !ClearSelection() ) return false; } m_iFlags |= wxPG_FL_HIDE_STATE; if ( !m_frozen ) { CalculateYs( NULL, -1 ); RedrawAllVisible(); } } } else { if ( m_iFlags & wxPG_FL_HIDE_STATE ) { m_iFlags &= ~(wxPG_FL_HIDE_STATE); if ( !m_frozen ) { CalculateYs( NULL, -1 ); RedrawAllVisible(); } } } return true; } // ----------------------------------------------------------------------- // Used by HideProperty as well bool wxPropertyGrid::SetPropertyPriority( wxPGProperty* p, int priority ) { /* // Old code (Commented Aug-09-2007) if ( m_frozen ) return m_pState->SetPropertyPriority(p,priority); if ( (m_iFlags & wxPG_FL_HIDE_STATE) && m_selected && ( m_selected == p || m_selected->IsSomeParent(p) ) ) { if ( !ClearSelection() ) return false; } m_pState->SetPropertyPriority(p,priority); if ( m_iFlags & wxPG_FL_HIDE_STATE ) { CalculateYs(NULL,-1); RedrawAllVisible(); } return true; */ // Stefan Battmer: // Changed in a way that this update is only forced when the // properties new priority actually differs from the current // priority to improve update speed if ( p ) { int oldPriority = ( p->IsFlagSet(wxPG_PROP_HIDEABLE) ) ? wxPG_LOW : wxPG_HIGH; if( oldPriority != priority ) { if ( m_frozen ) return m_pState->SetPropertyPriority(p,priority); if ( (m_iFlags & wxPG_FL_HIDE_STATE) && m_selected && ( m_selected == p || m_selected->IsSomeParent(p) ) ) { if ( !ClearSelection() ) return false; } m_pState->SetPropertyPriority(p,priority); if ( m_iFlags & wxPG_FL_HIDE_STATE ) { CalculateYs(NULL,-1); RedrawAllVisible(); } return true; } } return false; } // ----------------------------------------------------------------------- // wxPropertyGrid size related methods // ----------------------------------------------------------------------- // This is called by CalculateYs (so those calling it won't need to call this) void wxPropertyGrid::RecalculateVirtualSize() { int x = m_width; int y = m_bottomy; //SetClientSize(x,y); // Now adjust virtual size. SetVirtualSize(x, y); PGAdjustScrollbars(y); // // FIXME: Is this really needed? I mean, can't OnResize handle this? int width, height; GetClientSize(&width,&height); if ( m_selected && width != m_width ) { CorrectEditorWidgetSizeX( m_splitterx, width ); } m_width = width; m_height = height; } // ----------------------------------------------------------------------- void wxPropertyGrid::PGAdjustScrollbars( int y ) { // Adjust scrollbars. y += wxPG_PIXELS_PER_UNIT+2; // One more scrollbar unit + 2 pixels. int y_amount = y/wxPG_PIXELS_PER_UNIT; int y_pos = GetScrollPos( wxVERTICAL ); SetScrollbars( 0, wxPG_PIXELS_PER_UNIT, 0, y_amount, 0, y_pos, true ); } // ----------------------------------------------------------------------- /* bool wxPropertyGrid::DetectScrollbar() { // Call at every time scrollbar may have appeared/disappeared // Returns true if scrollbar was toggled bool toggled = false; // Use functions instead of m_width for total independence wxCoord width = GetSize().x; wxCoord cwidth = GetClientSize().x; if ( abs(width-cwidth) >= wxPG_MIN_SCROLLBAR_WIDTH ) { // There is a scrollbar. if ( !(m_iFlags & wxPG_FL_SCROLLBAR_DETECTED) ) { //wxLogDebug(wxT("Scrollbar Appeared")); toggled = true; m_iFlags |= wxPG_FL_SCROLLBAR_DETECTED; } } else if ( m_iFlags & wxPG_FL_SCROLLBAR_DETECTED ) { //wxLogDebug(wxT("Scrollbar Disappeared")); toggled = true; m_iFlags &= ~(wxPG_FL_SCROLLBAR_DETECTED); } return toggled; } */ void wxPropertyGrid::OnResize( wxSizeEvent& event ) { if ( !(m_iFlags & wxPG_FL_INITIALIZED) ) return; if ( FROM_STATE(m_itemsAdded) && !m_frozen ) PrepareAfterItemsAdded(); int width, height; GetClientSize(&width,&height); #if __INTENSE_DEBUGGING__ wxLogDebug(wxT("wxPropertyGrid::OnResize ( %i, %i )"),width,height); #endif //int old_width = m_width; //int old_height = m_height; int old_fwidth = m_fWidth; // non-client width int old_splitterx = m_splitterx; int fwidth = event.GetSize().x; m_fWidth = fwidth; m_width = width; m_height = height; int widthDiff = fwidth - old_fwidth; #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 ); } } } // Consider full update on every resize //m_iFlags |= wxPG_FL_CHANGED; #endif // // Center splitter when... // * always when propGrid not shown yet or its full size is not realized yet // and then only if splitter's position was not pre-set // * auto-centering is enabled and scrollbar was not toggled // // Need to center splitter? //if ( width!=old_width ) { bool needSplitterCheck = true; //if ( !sb_vis_toggled ) { if ( m_windowStyle & wxPG_SPLITTER_AUTO_CENTER ) { float centerX = float(width) * 0.5; float splitterX = m_fSplitterX + (float(widthDiff) * 0.5); float deviation = fabs(centerX - splitterX); //wxLogDebug(wxT("deviation: %.1f"),deviation); // If deviated too far from the center, reset it if ( deviation > 30.0 ) splitterX = centerX; DoSetSplitterPosition( (int)splitterX, false ); m_fSplitterX = splitterX; // needed to retain accuracy needSplitterCheck = false; } else if ( !(m_iFlags & wxPG_FL_SPLITTER_PRE_SET) ) { long timeSinceCreation = (::wxGetLocalTimeMillis() - m_timeCreated).ToLong(); if ( m_pState->m_properties->GetCount() || timeSinceCreation > 750 ) { SetSplitterLeft( false ); needSplitterCheck = false; } else { DoSetSplitterPosition( width / 2, false ); m_iFlags &= ~(wxPG_FL_SPLITTER_PRE_SET); needSplitterCheck = false; } } } if ( needSplitterCheck && (m_splitterx + wxPG_DRAG_MARGIN) > width ) { long timeSinceCreation = (::wxGetLocalTimeMillis() - m_timeCreated).ToLong(); if ( timeSinceCreation >= 750 ) { DoSetSplitterPosition( width - wxPG_DRAG_MARGIN - 1, false ); } } } // Need to correct widget position? if ( m_selected /*&& (width != old_width || sb_vis_toggled)*/ ) { // Take splitter position change into account CorrectEditorWidgetSizeX( m_splitterx, width ); } if ( !m_frozen ) { // Need to recalculate visibles array? //if ( height != old_height ) if ( height > m_calcVisHeight ) CalculateVisibles( -1, false ); /*if ( sb_vis_toggled ) { Refresh(); } else*/ if ( m_splitterx != old_splitterx ) { Refresh(); /*if ( abs(height-old_height) < 100 ) { Update(); // Necessary, atleast on wxMSW RedrawAllVisible(); } else { Refresh(); }*/ } } // Without this, virtual size (atleast under wxGTK) will be skewed RecalculateVirtualSize(); } // ----------------------------------------------------------------------- // wxPropertyGrid mouse event handling // ----------------------------------------------------------------------- // selFlags uses same values DoSelectProperty's flags void wxPropertyGrid::SendEvent( int eventType, wxPGProperty* p, unsigned int selFlags ) { // Send property grid event of specific type and with specific property wxPropertyGridEvent evt( eventType, GetId() ); evt.SetPropertyGrid(this); evt.SetEventObject(m_eventObject); evt.SetProperty(p); wxEvtHandler* evtHandler = GetEventHandler(); // Always need to process event immediately if the property in question is // about to be deleted. if ( (selFlags & wxPG_SEL_DELETING) || (GetExtraStyle() & wxPG_EX_PROCESS_EVENTS_IMMEDIATELY) ) { evtHandler->ProcessEvent(evt); } else { evt.SetPending(true); evtHandler->AddPendingEvent(evt); } } // ----------------------------------------------------------------------- // Return false if should be skipped bool wxPropertyGrid::HandleMouseClick( int x, unsigned int y, wxMouseEvent &event ) { bool res = true; #if __MOUSE_DEBUGGING__ wxLogDebug( wxT(" \\--> HandleMouseClick") ); #endif // Need to set focus? if ( !(m_iFlags & wxPG_FL_FOCUSED) ) { SetFocus(); } if ( y < m_bottomy ) { wxPGProperty* p = DoGetItemAtY(y); if ( p ) { int parenting = p->GetParentingType(); int depth = (int)p->GetDepth() - 1; int marginEnds = m_marginWidth + ( depth * m_subgroup_extramargin ); if ( x >= marginEnds ) { // Outside margin. if ( parenting > 0 ) { // This is category. wxPropertyCategoryClass* pwc = (wxPropertyCategoryClass*)p; int text_x = 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 >= text_x && ( x < (text_x+pwc->GetTextExtent()+(wxPG_CAPRECTXMARGIN*2)) || x < m_splitterx ) ) { if ( !DoSelectProperty( p ) ) return res; // On double-click, expand/collapse. if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) ) { if ( pwc->m_expanded ) _Collapse ( p, true ); else _Expand ( p, true ); } } } else if ( x > (m_splitterx + wxPG_SPLITTERX_DETECTMARGIN2) || x < (m_splitterx - wxPG_SPLITTERX_DETECTMARGIN1) ) { // Click on value. unsigned int selFlag = 0; if ( x > m_splitterx ) { m_iFlags |= wxPG_FL_ACTIVATION_BY_CLICK; selFlag = wxPG_SEL_FOCUS; } if ( !DoSelectProperty( p, selFlag ) ) return res; m_iFlags &= ~(wxPG_FL_ACTIVATION_BY_CLICK); if ( p->GetParentingType() < 0 ) // On double-click, expand/collapse. if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->m_expanded ) _Collapse ( p, true ); else _Expand ( 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 CenterSplitter( true ); // TODO: Would this be more natural? // .NET grid doesn't do it but maybe we should. //CustomSetCursor ( wxCURSOR_ARROW ); } else if ( m_dragStatus == 0 ) { // // Begin draggin the splitter // #if __MOUSE_DEBUGGING__ wxLogDebug( wxT(" dragging begins at splitter + %i"), (int)(x - m_splitterx) ); #endif if ( m_wndPrimary ) { // Changes must be committed here or the // value won't be drawn correctly if ( !CommitChangesFromEditor() ) return res; m_wndPrimary->Show ( false ); } BEGIN_MOUSE_CAPTURE m_dragStatus = 1; m_dragOffset = x - m_splitterx; wxPG_CLIENT_DC_INIT() #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // Fixes button disappearance bug if ( m_wndSecondary ) m_wndSecondary->Show ( false ); #endif m_startingSplitterX = m_splitterx; #if wxPG_HEAVY_GFX #else Update(); // clear graphics mess DrawSplitterDragColumn( dc, m_splitterx ); m_splitterprevdrawnx = m_splitterx; #endif } } } } else { // Click on margin. if ( parenting != 0 ) { int nx = x + m_marginWidth - marginEnds; // Normalize x. if ( (nx >= m_gutterWidth && nx < (m_gutterWidth+m_iconWidth)) ) { int y2 = y - p->m_y; if ( (y2 >= m_buttonSpacingY && y2 < (m_buttonSpacingY+m_iconHeight)) ) { // On click on expander button, expand/collapse if ( ((wxPGPropertyWithChildren*)p)->m_expanded ) _Collapse ( p, true ); else _Expand ( p, true ); } } } } } } return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::HandleMouseRightClick( int WXUNUSED(x), unsigned int y, wxMouseEvent& WXUNUSED(event) ) { if ( y < m_bottomy ) { // Select property here as well wxPGProperty* p = m_propHover; if ( p != m_selected ) DoSelectProperty( p ); // Send right click event. SendEvent( wxEVT_PG_RIGHT_CLICK, p ); return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGrid::HandleMouseDoubleClick( int WXUNUSED(x), unsigned int y, wxMouseEvent& WXUNUSED(event) ) { if ( y < m_bottomy ) { // Select property here as well wxPGProperty* p = m_propHover; if ( p != m_selected ) DoSelectProperty( p ); // Send double-click event. SendEvent( wxEVT_PG_DOUBLE_CLICK, m_propHover ); return true; } return false; } // ----------------------------------------------------------------------- /* // Splits text into lines so that each will have width less than arg maxWidth. // * Returns string with line breaks inserted into appropriate positions. // * Keeps words together. // * Useful in conjunction with wxWindow::SetToolTip and wxDC::DrawLabel. static wxString SplitTextByPixelWidth(wxDC& dc, const wxString& text, int lineWidth) { if ( !text.length() ) return text; wxString resultLine; wxArrayInt extents; unsigned int index = 0; unsigned int maxIndex = text.length() - 1; unsigned int prevSplitIndex = 0; unsigned int prevCanSplitIndex = 0; int lineCheckWidth = lineWidth; wxChar prevA = wxT('\0'); dc.GetPartialTextExtents(text,extents); wxASSERT( text.length() == extents.GetCount() ); while ( index <= maxIndex ) { const wxChar A = text[index]; if ( !wxIsalnum(prevA) ) { // Can split here prevCanSplitIndex = index; } else { // Can't split here } if ( ( (extents[index] >= lineCheckWidth || A == wxT('\n')) && index > prevCanSplitIndex ) || index == maxIndex ) { // Need to split now unsigned int useSplit = prevCanSplitIndex; if ( useSplit <= prevSplitIndex || index >= maxIndex ) useSplit = index; resultLine << text.Mid(prevSplitIndex,useSplit-prevSplitIndex); if ( index >= maxIndex ) break; else if ( A != wxT('\n') ) { resultLine.Append(_T("\n")); //resultLine.Append(text.Mid(useSplit,text.length()-useSplit)); //break; } prevSplitIndex = useSplit; lineCheckWidth = extents[useSplit] + lineWidth; //widSum = 0; index = useSplit; prevA = wxT('\0'); } else { index++; prevA = A; } } return resultLine; } */ // ----------------------------------------------------------------------- #if wxPG_SUPPORT_TOOLTIPS void wxPropertyGrid::SetToolTip( const wxString& tipString ) { if ( tipString.length() ) { //wxClientDC dc(this); //wxString finalString = SplitTextByPixelWidth(dc,tipString,350); //wxScrolledWindow::SetToolTip(finalString); wxScrolledWindow::SetToolTip(tipString); } else { #if wxPG_ALLOW_EMPTY_TOOLTIPS wxScrolledWindow::SetToolTip( m_emptyString ); #else wxScrolledWindow::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() ) { //wxLogDebug(wxT("MOUSE CAPTURE SAFETY RELEASE TRIGGERED")); HandleMouseUp(x,y,event); } if ( m_dragStatus > 0 ) { if ( x > (m_marginWidth + wxPG_DRAG_MARGIN) && x < (m_width - wxPG_DRAG_MARGIN) ) { #if wxPG_HEAVY_GFX int new_splitterx = x - m_dragOffset; // Splitter redraw required? if ( new_splitterx != m_splitterx ) { if ( m_selected ) CorrectEditorWidgetSizeX( new_splitterx, m_width ); // Move everything m_splitterx = new_splitterx; m_fSplitterX = (float) new_splitterx; Update(); RedrawAllVisible(); } #else if ( x != m_splitterx ) { wxPG_CLIENT_DC_INIT_R(false) if ( m_splitterprevdrawnx != -1 ) DrawSplitterDragColumn( dc, m_splitterprevdrawnx ); m_splitterx = x; m_fSplitterX = (float) x; DrawSplitterDragColumn( dc, x ); m_splitterprevdrawnx = x; } #endif 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 // On which item it hovers if ( ( !m_propHover && y < m_bottomy) || ( m_propHover && ( sy < m_propHover->m_y || sy >= (m_propHover->m_y+ih) ) ) ) { // Mouse moves on another property m_propHover = DoGetItemAtY(y); // Send hover event SendEvent( wxEVT_PG_HIGHLIGHTED, m_propHover ); } #if wxPG_SUPPORT_TOOLTIPS // Store which side we are on m_mouseSide = 0; if ( x >= m_splitterx ) m_mouseSide = 2; else if ( x >= m_marginWidth ) 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 = GetToolTip(); if ( m_propHover != prevHover || prevSide != m_mouseSide ) { if ( m_propHover && m_propHover->GetParentingType() <= 0 ) { 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 = m_splitterx-m_marginWidth-3; } else if ( m_mouseSide == 2 ) { tipString = m_propHover->GetDisplayedString(); space = m_width - m_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, &m_font ); if ( tw > space ) { SetToolTip( tipString ); } } else { if ( tooltip ) { #if wxPG_ALLOW_EMPTY_TOOLTIPS wxScrolledWindow::SetToolTip( m_emptyString ); #else wxScrolledWindow::SetToolTip( NULL ); #endif } } } } else { if ( tooltip ) { #if wxPG_ALLOW_EMPTY_TOOLTIPS wxScrolledWindow::SetToolTip( m_emptyString ); #else wxScrolledWindow::SetToolTip( NULL ); #endif } } } } #endif if ( x > (m_splitterx + wxPG_SPLITTERX_DETECTMARGIN2) || x < (m_splitterx - wxPG_SPLITTERX_DETECTMARGIN1) || y >= m_bottomy || (m_windowStyle & 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->GetParentingType() <= 0 && !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 ); } } } return true; } // ----------------------------------------------------------------------- // Also handles Leaving event bool wxPropertyGrid::HandleMouseUp( int x, unsigned int y, wxMouseEvent &WXUNUSED(event) ) { bool res = false; #if __MOUSE_DEBUGGING__ wxLogDebug( wxT(" \\--> HandleMouseUp") ); #endif // No event type check - basicly calling this method should // just stop dragging. //if( event.LeftUp() || event.Leaving() ) //{ // Left up after dragged? if ( m_dragStatus >= 1 ) { // // End Splitter Dragging // #if __MOUSE_DEBUGGING__ wxLogDebug( wxT(" dragging ends") ); #endif // DO NOT ENABLE FOLLOWING LINE! // (it is only here as a reminder to not to do it) //m_splitterx = x; #if wxPG_HEAVY_GFX //Refresh(); #else DoSetSplitterPosition( -1 ); // -1 tells not to make change // Hack to clear-up editor graphics mess (on wxMSW, atleast) if ( m_selected ) DrawItem ( m_selected ); #endif // Disable splitter auto-centering m_iFlags |= wxPG_FL_DONT_CENTER_SPLITTER; // This is necessary to return cursor END_MOUSE_CAPTURE // Set back the default cursor, if necessary if ( x > (m_splitterx + wxPG_SPLITTERX_DETECTMARGIN2) || x < (m_splitterx - wxPG_SPLITTERX_DETECTMARGIN1) || y >= m_bottomy ) { CustomSetCursor( wxCURSOR_ARROW ); } m_dragStatus = 0; #if wxPG_HEAVY_GFX // Control background needs to be cleared if ( !(m_iFlags & wxPG_FL_PRIMARY_FILLS_ENTIRE) && m_selected ) DrawItem ( m_selected ); #endif if ( m_wndPrimary ) { m_wndPrimary->Show ( true ); } #if wxPG_REFRESH_CONTROLS_AFTER_REPAINT // Fixes button disappearance bug if ( m_wndSecondary ) m_wndSecondary->Show ( true ); #endif // This clears the focus. m_editorFocused = 0; } //} return res; } // ----------------------------------------------------------------------- bool wxPropertyGrid::OnMouseCommon( wxMouseEvent& event, int* px, int* py ) { int ux, uy; CalcUnscrolledPosition( event.m_x, event.m_y, &ux, &uy ); // Hide popup on clicks // FIXME: Not necessary after transient window implemented if ( event.GetEventType() != wxEVT_MOTION ) if ( m_wndPrimary && m_wndPrimary->IsKindOf(CLASSINFO(wxPGOwnerDrawnComboBox)) ) { ((wxPGOwnerDrawnComboBox*)m_wndPrimary)->HidePopup(); } //if (printmsg) wxLogDebug( wxT("On") wxT(#func) wxT("( %i, %i )"),(int)ux,(int)uy ); wxRect r; wxWindow* wnd = m_wndPrimary; if ( wnd ) r = wnd->GetRect(); if ( wnd == (wxWindow*) NULL || m_dragStatus || ( ux <= (m_splitterx + wxPG_SPLITTERX_DETECTMARGIN2) || 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); } event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseRightClick( wxMouseEvent &event ) { int x, y; CalcUnscrolledPosition( event.m_x, event.m_y, &x, &y ); HandleMouseRightClick(x,y,event); event.Skip(); } // ----------------------------------------------------------------------- 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); event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseMove( wxMouseEvent &event ) { int x, y; if ( OnMouseCommon ( event, &x, &y ) ) { HandleMouseMove(x,y,event); } event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseUp( wxMouseEvent &event ) { int x, y; if ( OnMouseCommon ( event, &x, &y ) ) { HandleMouseUp(x,y,event); } event.Skip(); } // ----------------------------------------------------------------------- void wxPropertyGrid::OnMouseEntry( wxMouseEvent &event ) { // This may get called from child control as well, so event's // mouse position cannot be relied on. //int x = event.m_x; //int y = event.m_y; if ( event.Entering() ) { if ( !(m_iFlags & wxPG_FL_MOUSE_INSIDE) ) { #if __MOUSE_DEBUGGING__ wxLogDebug(wxT("Mouse Enters Window")); #endif //SetCursor ( *wxSTANDARD_CURSOR ); // 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; //if ( m_wndPrimary ) m_wndPrimary->Show ( true ); } else GetParent()->SetCursor(wxNullCursor); } else if ( event.Leaving() ) { // Without this, wxSpinCtrl editor will sometimes have wrong cursor 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 ( CommitChangesFromEditor() ) { if ( (m_iFlags & wxPG_FL_MOUSE_INSIDE) ) { #if __MOUSE_DEBUGGING__ wxLogDebug(wxT("Mouse Leaves Window")); #endif m_iFlags &= ~(wxPG_FL_MOUSE_INSIDE); //if ( m_wndPrimary ) m_wndPrimary->Show ( false ); } if ( m_dragStatus ) wxPropertyGrid::HandleMouseUp ( -1, 10000, event ); } } else { /*#if wxPG_NO_CHILD_EVT_MOTION // cursor must be reset because EVT_MOTION handler is not there to do it if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor ( wxCURSOR_ARROW ); #endif*/ } } event.Skip(); } // ----------------------------------------------------------------------- // if (printmsg) wxLogDebug( wxT("On") wxT(#func) wxT("Child ( %i, %i )"),(int)event.m_x,(int)event.m_y ); // 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); #if wxPG_ENABLE_CLIPPER_WINDOW // Take clipper window into account if (topCtrlWnd->GetPosition().x < 1 && !topCtrlWnd->IsKindOf(CLASSINFO(wxPGClipperWindow))) { topCtrlWnd = topCtrlWnd->GetParent(); wxASSERT( topCtrlWnd->IsKindOf(CLASSINFO(wxPGClipperWindow)) ); x -= ((wxPGClipperWindow*)topCtrlWnd)->GetXClip(); y -= ((wxPGClipperWindow*)topCtrlWnd)->GetYClip(); } #endif wxRect r = topCtrlWnd->GetRect(); if ( !m_dragStatus && x > (m_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::OnMouseEntryChild ( wxMouseEvent &event ) { wxLogDebug(wxT("Entering/Leaving Child...")); event.Skip(); }*/ void wxPropertyGrid::OnMouseClickChild( wxMouseEvent &event ) { int x,y; if ( OnMouseChildCommon(event,&x,&y) ) { bool res = HandleMouseClick(x,y,event); if ( !res ) event.Skip(); /*if ( event.GetEventType() == wxEVT_LEFT_DCLICK ) { HandleMouseDoubleClick( x, y, event ); event.Skip(); }*/ } } void wxPropertyGrid::OnMouseRightClickChild( wxMouseEvent &event ) { int x,y; wxASSERT( m_wndPrimary ); // These coords may not be exact (about +-2), // but that should not matter (right click is about item, not position). wxPoint pt = m_wndPrimary->GetPosition(); CalcUnscrolledPosition( event.m_x + pt.x, event.m_y + pt.y, &x, &y ); wxASSERT( m_selected ); m_propHover = m_selected; 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); GetEventHandler()->AddPendingEvent(evt); } void wxPropertyGrid::HandleKeyEvent(wxKeyEvent &event) { // // Handles key event when editor control is not focused. // #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::HandleKeyEvent(%i)"),(int)event.GetKeyCode() ); #endif wxASSERT( !m_frozen ); if ( m_frozen ) return; // Travelsal between items, collapsing/expanding, etc. int keycode = event.GetKeyCode(); if ( keycode == WXK_TAB ) { SendNavigationKeyEvent( event.ShiftDown()?0:1 ); return; } // Ignore Alt and Control when they are down alone if ( keycode == WXK_ALT || keycode == WXK_CONTROL ) { event.Skip(); return; } if ( m_selected ) { // Show dialog? if ( ButtonTriggerKeyTest(event) ) return; wxPGProperty* p = m_selected; int selectDir = -2; if ( p->GetParentingType() != 0 && !(p->m_flags & wxPG_PROP_DISABLED) ) { //wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( keycode == WXK_LEFT ) { if ( (m_windowStyle & wxPG_HIDE_MARGIN) || Collapse ( p ) ) keycode = 0; } else if ( keycode == WXK_RIGHT ) { if ( (m_windowStyle & wxPG_HIDE_MARGIN) || Expand ( p ) ) keycode = 0; } } if ( keycode ) { if ( keycode == WXK_UP || keycode == WXK_LEFT ) { selectDir = 0; } else if ( keycode == WXK_DOWN || keycode == WXK_RIGHT ) { selectDir = 1; } else { event.Skip(); } } if ( selectDir >= -1 ) { p = GetNeighbourItem( p, true, selectDir ); if ( p ) DoSelectProperty(p); } } else { // If nothing was selected, select the first item now // (or navigate out of tab). if ( keycode != WXK_ESCAPE ) { wxPGProperty* p = 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 canDestroy ) { int keycode = event.GetKeyCode(); bool res = true; #if __INTENSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid::HandleChildKey(%i)"),(int)event.GetKeyCode() ); #endif // Unfocus? if ( keycode == WXK_ESCAPE ) { // Esc cancels any changes EditorsValueWasNotModified(); wxPGProperty* p = m_selected; res = false; if ( canDestroy ) { DoSelectProperty( (wxPGProperty*)NULL, wxPG_SEL_NOVALIDATE ); DoSelectProperty( p ); } } return res; } // ----------------------------------------------------------------------- void wxPropertyGrid::OnKey( wxKeyEvent &event ) { // // Events to editor controls should get relayed here. // wxWindow* focused = wxWindow::FindFocus(); //wxLogDebug(wxT("OnKey")); if ( m_wndPrimary && (focused==m_wndPrimary || m_editorFocused #if wxPG_ENABLE_CLIPPER_WINDOW || ((m_wndPrimary->IsKindOf(CLASSINFO(wxPGClipperWindow))) && ((wxPGClipperWindow*)m_wndPrimary)->GetControl() == focused) #endif ) ) { // Child key must be processed here, since it can // destroy the control which is referred by its own // event handling. HandleChildKey( event, true ); } 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; int dir = event.GetDirection()?1:0; if ( m_selected ) { if ( dir == 1 && (m_wndPrimary || m_wndSecondary) ) { 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_wndSecondary; } // 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_wndSecondary && !(GetExtraStyle() & wxPG_EX_NO_TAB_TO_BUTTON) ) { wndToCheck = m_wndSecondary; wxLogDebug(wxT("Exp1")); } */ if ( focused != wndToCheck && wndToCheck ) { wndToCheck->SetFocus(); // Select all text in wxTextCtrl etc. if ( m_wndPrimary && wndToCheck == m_wndPrimary ) m_selected->GetEditorClass()->OnFocus(m_selected,wndToCheck); m_editorFocused = 1; next = m_selected; } } if ( !next ) { next = GetNeighbourItem(m_selected,true,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_wndSecondary && (!m_pushButKeyCodeNeedsAlt || event.AltDown()) && (!m_pushButKeyCodeNeedsCtrl || event.ControlDown()) ) { m_keyComboConsumed = 1; wxCommandEvent evt(wxEVT_COMMAND_BUTTON_CLICKED,m_wndSecondary->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; // Since event handling may destroy the control which // triggered this event, we need to send it separately // to the wxPropertyGrid itself. Also, to allow pushed // event handler to grab ENTER, ESC and such, this // has been changed to add all keys as events. if ( HandleChildKey(event,false) == 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 ); } // Called by focus event handlers. newFocused is the window that becomes focused. void wxPropertyGrid::HandleFocusChange( wxWindow* newFocused ) { unsigned int oldFlags = m_iFlags; //wxLogDebug(wxT("HandleFocusChange: %s"),newFocused?newFocused->GetClassInfo()->GetClassName():wxT("NULL")); m_iFlags &= ~(wxPG_FL_FOCUSED); wxWindow* parent = newFocused; // This must be one of nextFocus' parents. while ( parent ) { // Use m_eventObject, which is either wxPropertyGrid or // wxPropertyGridManager, as appropriate. if ( parent == m_eventObject ) { m_iFlags |= wxPG_FL_FOCUSED; break; } parent = parent->GetParent(); } 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 if ( m_selected && (m_iFlags & wxPG_FL_INITIALIZED) ) DrawItem( m_selected ); } } void wxPropertyGrid::OnFocusEvent( wxFocusEvent& event ) { #if 1 if ( event.GetEventType() == wxEVT_SET_FOCUS ) HandleFocusChange((wxWindow*)event.GetEventObject()); // Line changed to "else" when applying patch #1675902 //else if ( event.GetWindow() ) else HandleFocusChange(event.GetWindow()); event.Skip(); #else unsigned int oldFlags = m_iFlags; // // Determine the current focus state if ( event.GetEventType() == wxEVT_SET_FOCUS || event.GetEventType() == wxEVT_CHILD_FOCUS ) { m_iFlags |= wxPG_FL_FOCUSED; } else { wxWindow* nextFocus = event.GetWindow(); m_iFlags &= ~(wxPG_FL_FOCUSED); wxWindow* parent = nextFocus; //wxLogDebug(wxT("KillFocus: %s"),parent->GetClassInfo()->GetClassName()); // This must be one of nextFocus' parents. while ( parent ) { if ( parent == this ) { m_iFlags |= wxPG_FL_FOCUSED; break; } parent = parent->GetParent(); } } 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 if ( m_selected && (m_iFlags & wxPG_FL_INITIALIZED) ) DrawItem( m_selected ); } event.Skip(); #endif } void wxPropertyGrid::OnChildFocusEvent( wxChildFocusEvent& event ) { HandleFocusChange((wxWindow*)event.GetEventObject()); event.Skip(); } // ----------------------------------------------------------------------- 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 ) { #if __MOUSE_DEBUGGING__ wxLogDebug( wxT("wxPropertyGrid: mouse capture lost") ); #endif m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED); } } // ----------------------------------------------------------------------- // Property text-based storage // ----------------------------------------------------------------------- #define wxPG_PROPERTY_FLAGS_COUNT 8 // property-flag-to-text array static const wxChar* gs_property_flag_to_string[wxPG_PROPERTY_FLAGS_COUNT] = { wxT("Modified"), wxT("Disabled"), wxT("LowPriority"), (const wxChar*) NULL, // wxPG_PROP_CUSTOMIMAGE is auto-generated flag wxT("LimitedEditing"), wxT("Unspecified"), (const wxChar*) NULL, // Special flags cannot be stored as-is (const wxChar*) NULL // }; wxString wxPGProperty::GetAttributes( unsigned int flagmask ) { wxASSERT(this); wxString s; unsigned int i; unsigned int flags = ((unsigned int)m_flags) & flagmask & ~(wxPG_PROP_CUSTOMIMAGE | wxPG_PROP_CLASS_SPECIFIC_1 | wxPG_PROP_CLASS_SPECIFIC_2); if ( !flags ) return wxEmptyString; for ( i=0; iGetParentingType() != 1 ) { 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); //s.LowerCase(); return s; } return wxT("Category"); } wxPGId wxPropertyContainerMethods::GetPropertyByNameA( wxPGPropNameStr name ) const { wxPGId id = GetPropertyByName(name); wxASSERT_MSG(wxPGIdIsOk(id),wxString::Format(wxT("no property with name '%s'"),name.c_str())); return id; } // ---------------------------------------------------------------------------- // VariantDatas // ---------------------------------------------------------------------------- #if wxPG_PGVARIANT_IS_VARIANT IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataPoint, wxVariantData) IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataSize, wxVariantData) IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataArrayInt, wxVariantData) IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataLongLong, wxVariantData) IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataULongLong, wxVariantData) #ifdef __WXPYTHON__ IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataPyObject, wxVariantData) #endif #endif // ----------------------------------------------------------------------- // Value type related methods (should all be pretty much static). wxPGValueType::~wxPGValueType() { } wxPG_CONST_WXCHAR_PTR wxPGValueType::GetCustomTypeName() const { return GetTypeName(); } // Implement default types. WX_PG_IMPLEMENT_VALUE_TYPE(wxString,wxStringProperty,wxPGTypeName_wxString,GetString,wxEmptyString) WX_PG_IMPLEMENT_VALUE_TYPE(long,wxIntProperty,wxPGTypeName_long,GetLong,(long)0) WX_PG_IMPLEMENT_VALUE_TYPE(double,wxFloatProperty,wxPGTypeName_double,GetDouble,0.0) WX_PG_IMPLEMENT_VALUE_TYPE(wxArrayString,wxArrayStringProperty,wxPGTypeName_wxArrayString,GetArrayString,wxArrayString()) // Bool is a special case... thanks to the C++'s bool vs int vs long inconsistency issues. const wxPGValueType *wxPGValueType_bool = (wxPGValueType *) NULL; class wxPGValueTypeboolClass : public wxPGValueType { public: virtual wxPG_CONST_WXCHAR_PTR GetTypeName() const { return wxPGTypeName_long; } virtual wxPG_CONST_WXCHAR_PTR GetCustomTypeName() const { return wxPGTypeName_bool; } virtual wxPGVariant GetDefaultValue() const { return wxPGVariant((long)0); } virtual wxVariant GenerateVariant( wxPGVariant value, const wxString& name ) const { return wxVariant ( value.GetBool(), name ); } virtual wxPGProperty* GenerateProperty( const wxString& label, const wxString& name ) const { return wxPG_NEWPROPERTY(Bool,label,name,false); } virtual void SetValueFromVariant( wxPGProperty* property, wxVariant& value ) const { #if defined(__WXDEBUG__) || defined(__WXPYTHON__) wxCHECK_RET( wxStrcmp(wxPGTypeName_bool,value.GetType().c_str()) == 0, wxT("SetValueFromVariant: wxVariant type mismatch.") ); #endif property->DoSetValue(value.GetBool()?(long)1:(long)0); } }; // Implement nonetype. const wxPGValueType *wxPGValueType_none = (wxPGValueType*) NULL; class wxPGValueTypenoneClass : public wxPGValueType { public: virtual wxPG_CONST_WXCHAR_PTR GetTypeName() const { return wxT("null"); } virtual wxPGVariant GetDefaultValue() const { return wxPGVariant((long)0); } virtual wxVariant GenerateVariant( wxPGVariant, const wxString& name ) const { return wxVariant( (long)0, name ); } virtual wxPGProperty* GenerateProperty( const wxString&, const wxString& ) const { return (wxPGProperty*) NULL; } virtual void SetValueFromVariant( wxPGProperty*, wxVariant& ) const { } }; // Implement void* type. const wxPGValueType *wxPGValueType_void = (wxPGValueType*) NULL; class wxPGValueTypevoidClass : public wxPGValueType { public: virtual wxPG_CONST_WXCHAR_PTR GetTypeName() const { return wxPGTypeName_void; } virtual wxPGVariant GetDefaultValue() const { return wxPGVariant((void*)NULL); } virtual wxVariant GenerateVariant( wxPGVariant value, const wxString& name ) const { return wxVariant( wxPGVariantToVoidPtr(value), name ); } virtual wxPGProperty* GenerateProperty( const wxString&, const wxString& ) const { return (wxPGProperty*) NULL; } virtual void SetValueFromVariant( wxPGProperty* property, wxVariant& value ) const { #if defined(__WXDEBUG__) || defined(__WXPYTHON__) wxCHECK_RET( wxStrcmp(GetTypeName(),value.GetType().c_str()) == 0, wxT("SetValueFromVariant: wxVariant type mismatch.") ); #endif property->DoSetValue(value.GetVoidPtr()); } }; #ifdef __WXPYTHON__ // Implement PyObject* type. const wxPGValueType *wxPGValueType_PyObject = (wxPGValueType*) NULL; class wxPGValueTypePyObjectClass : public wxPGValueType { public: virtual wxPG_CONST_WXCHAR_PTR GetTypeName() const { return wxT("PyObject"); } virtual wxPGVariant GetDefaultValue() const { return wxVariant( new wxPGVariantDataPyObject(Py_None) ); } virtual wxVariant GenerateVariant( wxPGVariant value, const wxString& name ) const { value.SetName( name ); return value; // Can be done since under wxPython, wxPGVariant is wxVariant } virtual wxPGProperty* GenerateProperty( const wxString&, const wxString& ) const { return (wxPGProperty*) NULL; } virtual void SetValueFromVariant( wxPGProperty* property, wxVariant& value ) const { #if defined(__WXDEBUG__) || defined(__WXPYTHON__) wxCHECK_RET( wxStrcmp(GetTypeName(),value.GetType().c_str()) == 0, wxT("SetValueFromVariant: wxVariant type mismatch.") ); #endif property->DoSetValue(value); } }; #endif // __WXPYTHON__ // Registers all default value types void wxPropertyGrid::RegisterDefaultValues() { wxPGRegisterDefaultValueType( none ); wxPGRegisterDefaultValueType( wxString ); wxPGRegisterDefaultValueType( long ); wxPGRegisterDefaultValueType( bool ); wxPGRegisterDefaultValueType( double ); wxPGRegisterDefaultValueType( void ); wxPGRegisterDefaultValueType( wxArrayString ); #ifdef __WXPYTHON__ wxPGRegisterDefaultValueType( PyObject ); #endif } // noDefCheck = true prevents infinite recursion. wxPGValueType* wxPropertyGrid::RegisterValueType( wxPGValueType* valueclass, bool noDefCheck, const wxString& className ) { wxASSERT( valueclass ); WX_PG_GLOBALS_LOCKER() if ( !noDefCheck && wxPGGlobalVars->m_dictValueType.empty() ) RegisterDefaultValues(); wxString temp_str; wxPG_CONST_WXCHAR_PTR name_ = valueclass->GetType(); const wxChar* name = wxPG_TO_WXCHAR_PTR(name_); wxPGValueType* p_at_slot = (wxPGValueType*) wxPGGlobalVars->m_dictValueType[name]; if ( !p_at_slot ) { wxPGGlobalVars->m_dictValueType[name] = (void*) valueclass; #if wxPG_VALUETYPE_IS_STRING wxPGGlobalVars->m_dictValueTypeByClass[className] = (void*) valueclass; #else wxUnusedVar(className); #endif return valueclass; } // Delete given object instance, but only if it wasn't the same as in the hashmap. if ( p_at_slot != valueclass ) { delete valueclass; } return p_at_slot; } /* * wxPGVariantDataWxObj */ //IMPLEMENT_DYNAMIC_CLASS(wxPGVariantDataWxObj, wxVariantData) wxPGVariantDataWxObj::wxPGVariantDataWxObj() : wxVariantData() { } wxPGVariantDataWxObj::~wxPGVariantDataWxObj() { } #if wxUSE_STD_IOSTREAM bool wxPGVariantDataWxObj::Write(wxSTD ostream&) const { // Not implemented return true; } #endif bool wxPGVariantDataWxObj::Write(wxString&) const { // Not implemented return true; } #if wxUSE_STD_IOSTREAM bool wxPGVariantDataWxObj::Read(wxSTD istream& WXUNUSED(str)) { // Not implemented return false; } #endif bool wxPGVariantDataWxObj::Read(wxString& WXUNUSED(str)) { // Not implemented return false; } // ----------------------------------------------------------------------- // Editor class specific. // noDefCheck = true prevents infinite recursion. wxPGEditor* wxPropertyGrid::RegisterEditorClass( wxPGEditor* editorclass, const wxString& name, bool noDefCheck ) { wxASSERT( editorclass ); WX_PG_GLOBALS_LOCKER() 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* wxPropertyContainerMethods::GetEditorByName( const wxString& editor_name ) { wxPGEditor* editor = (wxPGEditor*) wxPGGlobalVars->m_mapEditorClasses[editor_name]; wxASSERT_MSG( editor, wxT("unregistered editor name") ); return editor; } // ----------------------------------------------------------------------- // 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; //wxASSERT_MSG( m_curPos != str.end(), wxT("Do not call wxPGStringTokenizer methods after HasMoreTokens has returned false.")); 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 { //wxLogDebug(m_readyToken); 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; /* const wxChar* ptr = m_curPos; const wxChar* ptr_end = &m_str->c_str()[m_str->length()]; size_t store_index = 0xFFFFFFFF; #if !wxUSE_STL wxChar* store_ptr_base = (wxChar*) NULL; #endif wxChar delim = m_delimeter; wxChar a = *ptr; wxChar prev_a = 0; while ( a ) { if ( store_index == 0xFFFFFFFF ) { if ( a == delim ) { size_t req_len = ptr_end-ptr+1; #if wxUSE_STL if ( m_readyToken.length() < req_len ) m_readyToken.resize( req_len, wxT(' ') ); #else store_ptr_base = m_readyToken.GetWriteBuf( req_len ); #endif store_index = 0; prev_a = 0; } } else { if ( prev_a != wxT('\\') ) { if ( a != delim ) { if ( a != wxT('\\') ) { #if wxUSE_STL m_readyToken[store_index] = a; #else store_ptr_base[store_index] = a; #endif store_index++; } } else { #if wxUSE_STL m_readyToken[store_index] = 0; m_readyToken.resize(store_index,wxT(' ')); #else store_ptr_base[store_index] = 0; m_readyToken.UngetWriteBuf( store_index ); #endif m_curPos = ptr+1; return true; } prev_a = a; } else { #if wxUSE_STL m_readyToken[store_index] = a; #else store_ptr_base[store_index] = a; #endif store_index++; prev_a = 0; } } ptr++; a = *ptr; } #if !wxUSE_STL if ( store_index != 0xFFFFFFFF ) m_readyToken.UngetWriteBuf( store_index ); #endif m_curPos = (const wxChar*) NULL; return false; */ } wxString wxPGStringTokenizer::GetNextToken() { //wxASSERT_MSG( m_curPos != m_str->end(), wxT("Do not call wxPGStringTokenizer methods after HasMoreTokens has returned false.")); return m_readyToken; } // ----------------------------------------------------------------------- // wxPGChoicesData // ----------------------------------------------------------------------- wxPGChoicesData::wxPGChoicesData() { m_refCount = 1; } wxPGChoicesData::~wxPGChoicesData() { } // ----------------------------------------------------------------------- // wxPGChoices // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxChar* label, int value ) { EnsureData(); if ( value != wxPG_INVALID_VALUE && m_data->m_arrLabels.GetCount() == m_data->m_arrValues.GetCount() ) m_data->m_arrValues.Add( value ); else if ( m_data->m_arrValues.GetCount() > 0 ) m_data->m_arrValues.Add( 0 ); m_data->m_arrLabels.Add ( label ); } // ----------------------------------------------------------------------- #if wxCHECK_VERSION(2,9,0) void wxPGChoices::Insert( const wxString& label, int index, int value ) #else void wxPGChoices::Insert( const wxChar* label, int index, int value ) #endif { EnsureData(); if ( value != wxPG_INVALID_VALUE && m_data->m_arrLabels.GetCount() == m_data->m_arrValues.GetCount() ) m_data->m_arrValues.Insert( value, index ); else if ( m_data->m_arrValues.GetCount() > 0 ) m_data->m_arrValues.Insert( 0, index ); m_data->m_arrLabels.Insert( label, index ); } // ----------------------------------------------------------------------- void wxPGChoices::AddAsSorted( const wxString& label, int value ) { //wxASSERT_MSG( IsOk(), // wxT("do not add items to invalid wxPGChoices") ); EnsureData(); size_t index = 0; wxArrayString& labels = m_data->m_arrLabels; wxArrayInt& values = m_data->m_arrValues; while ( index < labels.GetCount() ) { int cmpRes = labels[index].Cmp(label); if ( cmpRes > 0 ) break; index++; } if ( value != wxPG_INVALID_VALUE && labels.GetCount() == values.GetCount() ) values.Insert ( value, index ); labels.Insert ( label, index ); } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxChar** labels, const long* values ) { //wxASSERT_MSG( IsOk(), // wxT("do not add items to invalid wxPGChoices") ); EnsureData(); unsigned int itemcount = 0; const wxChar** p = &labels[0]; while ( *p ) { p++; itemcount++; } wxArrayString& i_labels = m_data->m_arrLabels; wxArrayInt& i_values = m_data->m_arrValues; unsigned int i; for ( i = 0; i < itemcount; i++ ) { i_labels.Add ( labels[i] ); } if ( values ) { for ( i = 0; i < itemcount; i++ ) { i_values.Add ( values[i] ); } } } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxArrayString& arr, const long* values ) { //wxASSERT_MSG( IsOk(), // wxT("do not add items to invalid wxPGChoices") ); EnsureData(); wxArrayString& labels = m_data->m_arrLabels; wxArrayInt& i_values = m_data->m_arrValues; unsigned int i; unsigned int itemcount = arr.GetCount(); for ( i = 0; i < itemcount; i++ ) { labels.Add ( arr[i] ); } if ( values ) { for ( i = 0; i < itemcount; i++ ) i_values.Add ( values[i] ); } } // ----------------------------------------------------------------------- void wxPGChoices::Add( const wxArrayString& arr, const wxArrayInt& arrint ) { //wxASSERT_MSG( IsOk(), // wxT("do not add items to invalid wxPGChoices") ); EnsureData(); wxArrayString& labels = m_data->m_arrLabels; wxArrayInt& values = m_data->m_arrValues; unsigned int i; unsigned int itemcount = arr.GetCount(); for ( i = 0; i < itemcount; i++ ) { labels.Add ( arr[i] ); } if ( &arrint && arrint.GetCount() ) for ( i = 0; i < itemcount; i++ ) { values.Add ( arrint[i] ); } } // ----------------------------------------------------------------------- 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->m_refCount--; if ( m_data->m_refCount < 1 ) delete m_data; m_data = wxPGChoicesEmptyData; } } // ----------------------------------------------------------------------- // wxPropertyGridEvent // ----------------------------------------------------------------------- IMPLEMENT_DYNAMIC_CLASS(wxPropertyGridEvent, wxCommandEvent) DEFINE_EVENT_TYPE( wxEVT_PG_SELECTED ) 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_COMPACT_MODE_ENTERED ) DEFINE_EVENT_TYPE( wxEVT_PG_EXPANDED_MODE_ENTERED ) wxPropertyGridEvent::wxPropertyGridEvent(wxEventType commandType, int id) : wxCommandEvent(commandType,id) { m_property = NULL; m_pending = false; } // ----------------------------------------------------------------------- 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_pending = false; } // ----------------------------------------------------------------------- wxPropertyGridEvent::~wxPropertyGridEvent() { } // ----------------------------------------------------------------------- wxEvent* wxPropertyGridEvent::Clone() const { return new wxPropertyGridEvent( *this ); } // ----------------------------------------------------------------------- // wxPropertyContainerMethods // - common methods for wxPropertyGrid and wxPropertyGridManager - // ----------------------------------------------------------------------- void wxPropertyContainerMethods::DoSetPropertyAttribute( wxPGId id, int attrid, wxVariant& value, long argFlags ) { wxPG_PROP_ID_CALL_PROLOG() p->SetAttribute(attrid,value); if ( ( argFlags & wxPG_RECURSE ) && p->GetParentingType() != 0 ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) DoSetPropertyAttribute(pwc->Item(i),attrid,value,argFlags); } } // ----------------------------------------------------------------------- void wxPropertyGrid::SetPropertyAttributeAll( int attrid, wxVariant value ) { DoSetPropertyAttribute(GetRoot(),attrid,value,wxPG_RECURSE); } // ----------------------------------------------------------------------- void wxPropertyContainerMethods::SetBoolChoices( const wxChar* true_choice, const wxChar* false_choice ) { WX_PG_GLOBALS_LOCKER() wxPGGlobalVars->m_boolChoices[0] = false_choice; wxPGGlobalVars->m_boolChoices[1] = true_choice; } // ----------------------------------------------------------------------- wxPGChoices gs_emptyChoices; wxPGChoices& wxPropertyContainerMethods::GetPropertyChoices( wxPGId id ) { wxPG_PROP_ID_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; } // ----------------------------------------------------------------------- wxPGChoices& wxPropertyContainerMethods::GetPropertyChoices( wxPGPropNameStr name ) { wxPG_PROP_NAME_CALL_PROLOG_RETVAL(gs_emptyChoices) return GetPropertyChoices(wxPGIdGen(p)); } // ----------------------------------------------------------------------- wxPGId wxPropertyContainerMethods::DoGetPropertyByName( wxPGPropNameStr name ) const { return m_pState->BaseGetPropertyByName(name); } // ----------------------------------------------------------------------- wxPGId wxPropertyContainerMethods::GetPropertyByName( wxPGPropNameStr name, wxPGPropNameStr subname ) const { wxPGId id = DoGetPropertyByName(name); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*) wxPGIdToPtr(id); if ( !pwc || !pwc->GetParentingType() ) return wxNullProperty; return wxPGIdGen(pwc->GetPropertyByName(subname)); } // ----------------------------------------------------------------------- // Since GetPropertyByName is used *a lot*, this makes sense // since non-virtual method can be called with less code. wxPGId wxPropertyContainerMethods::GetPropertyByName( wxPGPropNameStr name ) const { wxPGId id = DoGetPropertyByName(name); if ( wxPGIdIsOk(id) ) return id; // Check if its "Property.SubProperty" format int pos = name.Find(wxT('.')); if ( pos <= 0 ) return id; return GetPropertyByName(name.substr(0,pos), name.substr(pos+1,name.length()-pos-1)); } // ----------------------------------------------------------------------- bool wxPropertyContainerMethods::HideProperty( wxPGId id, bool hide ) { // Hiding properties requires that we are always in the compact mode m_pState->GetGrid()->Compact(true); return SetPropertyPriority(id,hide?wxPG_LOW:wxPG_HIGH); } // ----------------------------------------------------------------------- // Used by HideProperty as well bool wxPropertyContainerMethods::SetPropertyPriority( wxPGId id, int priority ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(false) wxPropertyGrid* pg = m_pState->GetGrid(); if ( pg == p->GetGrid() ) return pg->SetPropertyPriority(p,priority); else m_pState->SetPropertyPriority(p,priority); return true; } // ----------------------------------------------------------------------- bool wxPropertyContainerMethods::SetPropertyMaxLength( wxPGId id, int maxLen ) { wxPG_PROP_ID_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 wxPropertyContainerMethods::GetPropertyValueAs##BIGNAME( wxPGId id ) wxPG_GETVALUE_CONST \ { \ wxPG_PROP_ID_CALL_PROLOG_RETVAL(DEFRETVAL) \ if ( p->GetValueTypePtr()->GetTypeName() != wxPGTypeName_##T ) \ { \ wxPGGetFailed(p,wxPGTypeName_##T); \ return (TRET)DEFRETVAL; \ } \ return (TRET)wxPGVariantTo##BIGNAME(p->DoGetValue()); \ } // String is different than others. wxString wxPropertyContainerMethods::GetPropertyValueAsString( wxPGId id ) wxPG_GETVALUE_CONST { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxEmptyString) return p->GetValueAsString(wxPG_FULL_VALUE); } IMPLEMENT_GET_VALUE(long,long,Long,0) IMPLEMENT_GET_VALUE(long,bool,Bool,false) IMPLEMENT_GET_VALUE(double,double,Double,0.0) IMPLEMENT_GET_VALUE(void,void*,VoidPtr,NULL) #ifdef __WXPYTHON__ IMPLEMENT_GET_VALUE(PyObject,PyObject*,PyObject,Py_None) #endif #if !wxPG_PGVARIANT_IS_VARIANT IMPLEMENT_GET_VALUE(wxArrayString,const wxArrayString&,ArrayString,*((wxArrayString*)NULL)) #endif // wxObject is different than others. const wxObject* wxPropertyContainerMethods::GetPropertyValueAsWxObjectPtr( wxPGId id ) wxPG_GETVALUE_CONST { wxPG_PROP_ID_CALL_PROLOG_RETVAL((const wxObject*)NULL) wxPG_CONST_WXCHAR_PTR typestr = p->GetValueTypePtr()->GetTypeName(); if ( typestr[0] != wxT('w') || typestr[1] != wxT('x') ) { wxPGGetFailed(p,wxT("wxObject")); return (const wxObject*) NULL; } return (const wxObject*)wxPGVariantGetWxObjectPtr(p->DoGetValue()); } // ----------------------------------------------------------------------- bool wxPropertyContainerMethods::IsPropertyExpanded( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(false) wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->GetParentingType() == 0 ) return false; return pwc->IsExpanded(); } // ----------------------------------------------------------------------- // returns value type class for type name wxPGValueType* wxPropertyContainerMethods::GetValueType(const wxString &type) { wxPGHashMapS2P::iterator it; it = wxPGGlobalVars->m_dictValueType.find(type); if ( it != wxPGGlobalVars->m_dictValueType.end() ) return (wxPGValueType*) it->second; return (wxPGValueType*) NULL; } // ----------------------------------------------------------------------- #if wxPG_VALUETYPE_IS_STRING wxPGValueType* wxPropertyContainerMethods::GetValueTypeByName(const wxString &className) { wxPGHashMapS2P::iterator it; it = wxPGGlobalVars->m_dictValueTypeByClass.find(className); if ( it != wxPGGlobalVars->m_dictValueTypeByClass.end() ) return (wxPGValueType*) it->second; return (wxPGValueType*) NULL; } #endif // ----------------------------------------------------------------------- wxPGProperty* wxPropertyContainerMethods::CreatePropertyByType(const wxString &valuetype, const wxString &label, const wxString &name) { 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* wxPropertyContainerMethods::CreatePropertyByClass(const wxString &classname, const wxString &label, const wxString &name) { 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; } // ----------------------------------------------------------------------- // lazy way to prevent RegisterPropertyClass infinite recursion static int gs_registering_standard_props = 0; bool wxPropertyContainerMethods::RegisterPropertyClass( const wxChar* name, wxPGPropertyClassInfo* classinfo ) { WX_PG_GLOBALS_LOCKER() // Standard classes must be registered first! if ( !gs_registering_standard_props && wxPGGlobalVars->m_dictPropertyClassInfo.empty() ) wxPGRegisterStandardPropertyClasses(); wxPGHashMapS2P::iterator it; it = wxPGGlobalVars->m_dictPropertyClassInfo.find(name); // only register if not registered already if ( it == wxPGGlobalVars->m_dictPropertyClassInfo.end() ) { wxPGGlobalVars->m_dictPropertyClassInfo[name] = classinfo; return true; } wxLogDebug(wxT("WARNING: Property class named \"%s\" was already registered."),name); return false; } // ----------------------------------------------------------------------- static void wxPGRegisterStandardPropertyClasses() { if ( gs_registering_standard_props ) return; gs_registering_standard_props = 1; // no need to reset this wxPGRegisterPropertyClass(wxStringProperty); wxPGRegisterPropertyClass(wxIntProperty); wxPGRegisterPropertyClass(wxUIntProperty); wxPGRegisterPropertyClass(wxFloatProperty); wxPGRegisterPropertyClass(wxBoolProperty); wxPGRegisterPropertyClass(wxEnumProperty); wxPGRegisterPropertyClass(wxFlagsProperty); wxPGRegisterPropertyClass(wxLongStringProperty); wxPGRegisterPropertyClass(wxPropertyCategory); wxPGRegisterPropertyClass(wxParentProperty); wxPGRegisterPropertyClass(wxCustomProperty); // TODO: Are these really "standard" ? wxPGRegisterPropertyClass(wxArrayStringProperty); wxPGRegisterPropertyClass(wxFileProperty); wxPGRegisterPropertyClass(wxDirProperty); #ifdef __WXPYTHON__ wxPropertyContainerMethods::RegisterAdvancedPropertyClasses(); #endif } // ----------------------------------------------------------------------- // wxPropertyGridState // ----------------------------------------------------------------------- // reset helper macro #undef FROM_STATE #define FROM_STATE(A) A // ----------------------------------------------------------------------- // wxPropertyGridState item iteration methods // ----------------------------------------------------------------------- // Skips categories and sub-properties (unless in wxCustomProperty/wxParentProperty). wxPGId wxPropertyGridState::GetFirstProperty() const { if ( !m_properties->GetCount() ) return wxPGIdGen((wxPGProperty*)NULL); wxPGProperty* p = m_properties->Item(0); int parenting = p->GetParentingType(); if ( parenting > 0 ) return GetNextProperty ( wxPGIdGen(p) ); return wxPGIdGen(p); } // ----------------------------------------------------------------------- // Skips categories and sub-properties (unless in wxParentProperty). wxPGId wxPropertyGridState::GetNextProperty( wxPGId id ) const { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; // Go with first child? int parenting = pwc->GetParentingType(); if ( parenting == 0 || parenting == -1 || !pwc->GetCount() ) { // No... wxPGPropertyWithChildren* parent = pwc->m_parent; // As long as last item, go up and get parent' sibling while ( pwc->m_arrIndex >= (parent->GetCount()-1) ) { pwc = parent; if ( pwc == m_properties ) return wxPGIdGen((wxPGProperty*)NULL); parent = parent->m_parent; } pwc = (wxPGPropertyWithChildren*)parent->Item(pwc->m_arrIndex+1); // Go with the next sibling of parent's parent? } else { // Yes... pwc = (wxPGPropertyWithChildren*)pwc->Item(0); } // If it's category or parentproperty, then go recursive parenting = pwc->GetParentingType(); if ( parenting > PT_NONE ) return GetNextProperty( wxPGIdGen(pwc) ); return wxPGIdGen(pwc); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::GetNextSibling( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) wxPGPropertyWithChildren* parent = p->m_parent; size_t next_ind = p->m_arrIndex + 1; if ( next_ind >= parent->GetCount() ) return wxPGIdGen((wxPGProperty*)NULL); return wxPGIdGen(parent->Item(next_ind)); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::GetPrevSibling( wxPGId id ) { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) size_t ind = p->m_arrIndex; if ( ind < 1 ) return wxPGIdGen((wxPGProperty*)NULL); return wxPGIdGen(p->m_parent->Item(ind-1)); } // ----------------------------------------------------------------------- // Skips categories and sub-properties (unless in wxParentProperty). wxPGId wxPropertyGridState::GetPrevProperty( wxPGId id ) const { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) wxPGPropertyWithChildren* p2 = (wxPGPropertyWithChildren*) p; wxPGPropertyWithChildren* parent = p2->m_parent; // Is there a previous sibling? if ( p2->m_arrIndex > 0 ) { // There is! p2 = (wxPGPropertyWithChildren*)parent->Item ( p2->m_arrIndex-1 ); int parenting = p2->GetParentingType(); // Do we return it's last child? while ( (parenting > 0 || parenting == PT_CUSTOMPROPERTY) && p2->GetCount() ) { p2 = (wxPGPropertyWithChildren*)p2->Last(); parenting = p2->GetParentingType(); } } else if ( parent != m_properties ) // Return parent if it isnt' the root p2 = parent; else return wxPGIdGen((wxPGProperty*)NULL); // Skip category and parentproperty. int parenting = p2->GetParentingType(); if ( parenting > PT_NONE ) return GetPrevProperty ( wxPGIdGen(p2) ); return wxPGIdGen(p2); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::GetFirstCategory() const { //if ( IsInNonCatMode() ) // return wxPGIdGen((wxPGProperty*)NULL); wxPGProperty* found = (wxPGProperty*)NULL; size_t i; for ( i=0; iGetParentingType() > 0 ) { found = p; break; } } return wxPGIdGen(found); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::GetNextCategory( wxPGId id ) const { wxPG_PROP_ID_CALL_PROLOG_RETVAL(wxNullProperty) wxPGPropertyWithChildren* current = (wxPGPropertyWithChildren*)p; wxCHECK_MSG( !IsInNonCatMode() || current->GetParentingType() == 1, wxPGIdGen((wxPGProperty*)NULL), wxT("GetNextCategory should not be called with non-category argument in non-categoric mode.") ); wxPGPropertyWithChildren* parent = current->m_parent; wxPGProperty* found = (wxPGProperty*) NULL; size_t i; // Find sub-category, if any. if ( current->GetParentingType() > 0 ) { // Find first sub-category in current's array. for ( i = 0; iGetCount(); i++ ) { wxPGProperty* p = current->Item(i); if ( p->GetParentingType() > 0 ) { found = p; break; } } if ( found ) return wxPGIdGen(found); } // Find next category in parent's array. // (and go up in hierarchy until one found or // top is reached). do { for ( i = current->m_arrIndex+1; iGetCount(); i++ ) { wxPGProperty* p = parent->Item(i); if ( p->GetParentingType() > 0 ) { found = p; break; } } current = parent; parent = parent->m_parent; } while ( !found && parent ); return wxPGIdGen(found); } // ----------------------------------------------------------------------- // wxPropertyGridState GetPropertyXXX methods // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::GetPropertyByLabel( const wxString& label, wxPGPropertyWithChildren* parent ) const { size_t i; if ( !parent ) parent = (wxPGPropertyWithChildren*) &m_regularArray; for ( i=0; iGetCount(); i++ ) { wxPGProperty* p = parent->Item(i); if ( p->m_label == label ) return wxPGIdGen(p); // Check children recursively. if ( p->GetParentingType() != 0 ) { p = wxPGIdToPtr(GetPropertyByLabel(label,(wxPGPropertyWithChildren*)p)); if ( p ) return wxPGIdGen(p); } } return wxPGIdGen((wxPGProperty*) NULL); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::BaseGetPropertyByName( wxPGPropNameStr name ) const { wxPGHashMapS2P::const_iterator it; it = m_dictName.find(name); if ( it != m_dictName.end() ) return wxPGIdGen( (wxPGProperty*) it->second ); return wxPGIdGen( (wxPGProperty*) NULL ); } // ----------------------------------------------------------------------- // wxPropertyGridState global operations // ----------------------------------------------------------------------- bool wxPropertyGridState::EnableCategories( bool enable ) { 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->GetParentingType() == 1 && p->GetParentingType() <= 0 ) 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_DCAE_ISP_LOOP_BEGIN ITEM_ITERATION_DCAE_LOOP_BEGIN p->m_arrIndex = i; p->m_parent = parent; p->m_depth = parent->m_depth + 1; //ITEM_ITERATION_DCAE_ISP_LOOP_END ITEM_ITERATION_DCAE_LOOP_END } return true; } // ----------------------------------------------------------------------- static int wxPG_SortFunc(void **p1, void **p2) { wxPGProperty *pp1 = *((wxPGProperty**)p1); wxPGProperty *pp2 = *((wxPGProperty**)p2); return pp1->GetLabel().compare( pp2->GetLabel() ); } void wxPropertyGridState::Sort( wxPGProperty* p ) { if ( !p ) p = (wxPGProperty*)m_properties; wxCHECK_RET( p->GetParentingType() != 0, wxT("cannot sort non-parenting property") ); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; // Can only sort items with children if ( pwc->m_children.GetCount() < 1 ) return; pwc->m_children.Sort( wxPG_SortFunc ); // 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->GetParentingType() > 0 ) Sort ( p ); } } } // ----------------------------------------------------------------------- bool wxPropertyGridState::ExpandAll( unsigned char doExpand ) { ITEM_ITERATION_DCAE_VARIABLES bool isGrid = m_pPropGrid->GetState() == this; if ( isGrid && m_selected && m_selected->GetParent() != m_properties ) { if ( !m_pPropGrid->ClearSelection() ) return false; } if ( !doExpand ) { if ( isGrid ) { if ( !m_pPropGrid->ClearSelection() ) return false; } else m_selected = (wxPGProperty*) NULL; } ITEM_ITERATION_INIT_FROM_THE_TOP ITEM_ITERATION_DCAE_LOOP_BEGIN if ( parenting != 0 ) ((wxPGPropertyWithChildren*)p)->m_expanded = doExpand; ITEM_ITERATION_DCAE_LOOP_END if ( m_pPropGrid->GetState() == this ) { m_pPropGrid->CalculateYs((wxPGPropertyWithChildren*)NULL,-1); m_pPropGrid->RedrawAllVisible(); } return true; } // ----------------------------------------------------------------------- // Used by SetSplitterLeft int wxPropertyGridState::GetLeftSplitterPos(wxClientDC& dc, wxPGPropertyWithChildren* pwc, bool subProps) { 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->GetParentingType() <= 0 ) { dc.GetTextExtent( p->GetLabel(), &w, &h ); w += pg->m_marginWidth + ( ((int)p->m_depth-1) * pg->m_subgroup_extramargin ) + (wxPG_XBEFORETEXT*2); if ( w > maxW ) maxW = w; } if ( p->GetParentingType() && ( subProps || p->GetParentingType() > 0 ) ) { w = GetLeftSplitterPos( dc, (wxPGPropertyWithChildren*) p, subProps ); if ( w > maxW ) maxW = w; } } return maxW; } // ----------------------------------------------------------------------- // wxPropertyGridState property value setting and getting // ----------------------------------------------------------------------- void wxPropertyGridState::SetPropVal( wxPGProperty* p, const wxPGVariant& value ) { p->DoSetValue(value); if ( m_selected==p && this==m_pPropGrid->GetState() ) p->UpdateControl(m_pPropGrid->m_wndPrimary); } // ----------------------------------------------------------------------- bool wxPropertyGridState::ClearPropertyValue( wxPGProperty* p ) { if ( p ) { const wxPGValueType* valueclass = p->GetValueTypePtr(); if ( valueclass != wxPG_VALUETYPE_PTR(none) ) { // wnd_primary has to be given so the editor control can be updated as well. SetPropVal(p,valueclass->GetDefaultValue()); return true; } } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyValue( wxPGProperty* p, const wxPGValueType* typeclass, const wxPGVariant& value ) { if ( p ) { if ( p->GetValueTypePtr()->GetTypeName() == typeclass->GetTypeName() ) { CLEAR_PROPERTY_UNSPECIFIED_FLAG(p); SetPropVal(p,value); return true; } wxPGTypeOperationFailed ( p, typeclass->GetTypeName(), wxT("Set") ); } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyValue( wxPGProperty* p, const wxChar* typestring, const wxPGVariant& value ) { if ( p ) { if ( wxStrcmp(p->GetValueTypePtr()->GetCustomTypeName(),typestring) == 0 ) { // wnd_primary has to be given so the control can be updated as well. SetPropVal(p,value); return true; } wxPGTypeOperationFailed ( p, typestring, wxT("Set") ); } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyValueString( wxPGProperty* p, const wxString& value ) { if ( p ) { int flags = wxPG_REPORT_ERROR|wxPG_FULL_VALUE; CLEAR_PROPERTY_UNSPECIFIED_FLAG(p); if ( p->GetMaxLength() <= 0 ) p->SetValueFromString( value, flags ); else p->SetValueFromString( value.Mid(0,p->GetMaxLength()), flags ); if ( m_selected==p && this==m_pPropGrid->GetState() ) p->UpdateControl(m_pPropGrid->m_wndPrimary); return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyValue( wxPGProperty* p, wxVariant& value ) { if ( p ) { CLEAR_PROPERTY_UNSPECIFIED_FLAG(p); p->GetValueTypePtr()->SetValueFromVariant(p,value); if ( m_selected==p && this==m_pPropGrid->GetState() ) p->UpdateControl(m_pPropGrid->m_wndPrimary); return true; } return false; } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyValueWxObjectPtr( wxPGProperty* p, wxObject* value ) { if ( p ) { if ( wxStrcmp( p->GetValueTypePtr()->GetTypeName(), value->GetClassInfo()->GetClassName() ) == 0 ) { CLEAR_PROPERTY_UNSPECIFIED_FLAG(p); // wnd_primary has to be given so the control can be updated as well. SetPropVal(p,wxPGVariantFromWxObject(value)); return true; } wxPGTypeOperationFailed ( p, wxT("wxObject"), wxT("Set") ); } return false; } // ----------------------------------------------------------------------- void wxPropertyGridState::SetPropertyUnspecified( wxPGProperty* p ) { wxCHECK_RET( p, wxT("invalid property id") ); if ( !(p->m_flags & wxPG_PROP_UNSPECIFIED) ) { // Flag should be set first - editor class methods may need it p->m_flags |= wxPG_PROP_UNSPECIFIED; wxASSERT( m_pPropGrid ); if ( m_pPropGrid->GetState() == this ) { if ( m_pPropGrid->m_selected == p && m_pPropGrid->m_wndPrimary ) { p->GetEditorClass()->SetValueToUnspecified(m_pPropGrid->m_wndPrimary); } } if ( p->GetParentingType() != 0 ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) SetPropertyUnspecified( pwc->Item(i) ); } } } // ----------------------------------------------------------------------- // wxPropertyGridState property operations // ----------------------------------------------------------------------- void wxPropertyGridState::LimitPropertyEditing( wxPGProperty* p, bool limit ) { if ( p ) { if ( limit ) p->m_flags |= wxPG_PROP_NOEDITOR; else p->m_flags &= ~(wxPG_PROP_NOEDITOR); } } // ----------------------------------------------------------------------- void wxPropertyGridState::ClearModifiedStatus( wxPGProperty* p ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)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 == m_selected && m_pPropGrid->m_wndPrimary ) m_pPropGrid->m_wndPrimary->SetFont( m_pPropGrid->GetFont() ); m_pPropGrid->DrawItem( p ); } } if ( pwc->GetParentingType() != 0 ) { size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) ClearModifiedStatus( pwc->Item(i) ); } } // ----------------------------------------------------------------------- bool wxPropertyGridState::Collapse( wxPGProperty* p ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->GetParentingType() == 0 ) return false; if ( !pwc->m_expanded ) return false; // m_expanded must be set just before call to CalculateYs pwc->m_expanded = 0; return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::Expand( wxPGProperty* p ) { wxCHECK_MSG( p, false, wxT("invalid property id") ); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; if ( pwc->GetParentingType() == 0 ) return false; if ( pwc->m_expanded ) return false; // m_expanded must be set just before call to CalculateYs pwc->m_expanded = 1; return true; } // ----------------------------------------------------------------------- bool wxPropertyGridState::DoSelectProperty( wxPGProperty* p, unsigned int flags ) { if ( this == m_pPropGrid->GetState() ) return m_pPropGrid->DoSelectProperty( p, flags ); m_selected = p; return true; } // ----------------------------------------------------------------------- void wxPropertyGridState::SetPropertyLabel( wxPGProperty* p, const wxString& newlabel ) { wxCHECK_RET(p, wxT("invalid property id")); p->SetLabel(newlabel); if ( m_pPropGrid->GetWindowStyleFlag() & wxPG_AUTO_SORT ) Sort(p->GetParent()); } // ----------------------------------------------------------------------- bool wxPropertyGridState::SetPropertyPriority( wxPGProperty* p, int priority ) { int parenting = p->GetParentingType(); if ( priority == wxPG_HIGH ) p->ClearFlag( wxPG_PROP_HIDEABLE ); else p->SetFlag( wxPG_PROP_HIDEABLE ); if ( parenting != 0 ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) SetPropertyPriority(pwc->Item(i),priority); } return true; } // ----------------------------------------------------------------------- void wxPropertyGridState::SetPropertyAndChildrenFlags( wxPGProperty* p, long flags ) { p->m_flags |= flags; if ( p->GetParentingType() != 0 ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) ClearPropertyAndChildrenFlags ( pwc->Item(i), flags ); } } // ----------------------------------------------------------------------- void wxPropertyGridState::ClearPropertyAndChildrenFlags( wxPGProperty* p, long flags ) { p->m_flags &= ~(flags); if ( p->GetParentingType() != 0 ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) ClearPropertyAndChildrenFlags ( pwc->Item(i), flags ); } } // ----------------------------------------------------------------------- bool wxPropertyGridState::EnableProperty( 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; } if ( p->GetParentingType() == 0 ) return true; // Apply same to sub-properties as well wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)p; size_t i; for ( i = 0; i < pwc->GetCount(); i++ ) EnableProperty ( pwc->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::GetPropertyValues( const wxString& listname, wxPGId baseparent, long flags ) const { ITEM_ITERATION_DCAE_VARIABLES wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)wxPGIdToPtr(baseparent); // Root is the default base-parent. if ( !pwc ) pwc = m_properties; wxVariantList temp_list; wxVariant v( temp_list, listname ); if ( flags & wxPG_KEEP_STRUCTURE ) { wxASSERT( (pwc->GetParentingType() < -1) || (pwc->GetParentingType() > 0) ); size_t i; for ( i=0; iGetCount(); i++ ) { wxPGProperty* p = pwc->Item(i); int parenting = p->GetParentingType(); if ( parenting == 0 || parenting == -1 ) { v.Append( p->GetValueAsVariant() ); } else { v.Append( GetPropertyValues(p->m_name,wxPGIdGen(p),wxPG_KEEP_STRUCTURE) ); } } } else { ITEM_ITERATION_INIT((wxPGPropertyWithChildren*)wxPGIdToPtr(baseparent),0) ITEM_ITERATION_DCAE_ISP_LOOP_BEGIN // Use a trick to ignore wxParentProperty itself, but not its sub-properties. if ( parenting == PT_CUSTOMPROPERTY ) { parenting = PT_CAPTION; } else if ( parenting <= 0 ) { v.Append ( p->GetValueAsVariant() ); } ITEM_ITERATION_DCAE_ISP_LOOP_END } return v; } // ----------------------------------------------------------------------- void wxPropertyGridState::SetPropertyValues( const wxVariantList& list, wxPGId default_category ) { unsigned char origFrozen = 1; if ( m_pPropGrid->GetState() == this ) { origFrozen = m_pPropGrid->m_frozen; if ( !origFrozen ) m_pPropGrid->Freeze(); } wxPropertyCategoryClass* use_category = (wxPropertyCategoryClass*)wxPGIdToPtr(default_category); if ( !use_category ) use_category = (wxPropertyCategoryClass*)m_properties; // Let's iterate over the list of variants. wxVariantList::const_iterator node; //for ( wxVariantList::Node *node = list.GetFirst(); node; node = node->GetNext() ) 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 ); if ( current->GetName().length() > 0 ) { wxPGId foundProp = BaseGetPropertyByName(current->GetName()); if ( wxPGIdIsOk(foundProp) ) { wxPGProperty* p = wxPGIdToPtr(foundProp); const wxPGValueType* vtype = p->GetValueTypePtr(); // If it was a list, we still have to go through it. if ( current->GetType() == wxT("list") ) { SetPropertyValues( current->GetList(), wxPGIdGen( p->GetParentingType()>0?p:((wxPGProperty*)NULL) ) ); } else { #ifdef __WXDEBUG__ if ( current->GetType() != vtype->GetTypeName() && current->GetType() != vtype->GetCustomTypeName() ) { wxLogDebug(wxT("wxPropertyGridState::SetPropertyValues Warning: Setting value of property \"%s\" from variant"), p->m_name.c_str()); wxLogDebug(wxT(" but variant's type name (%s) doesn't match either base type name (%s) nor custom type name (%s)."), #ifndef __WXPYTHON__ current->GetType().c_str(),vtype->GetTypeName(),vtype->GetCustomTypeName()); #else current->GetType().c_str(),vtype->GetTypeName().c_str(),vtype->GetCustomTypeName().c_str()); #endif } #endif vtype->SetValueFromVariant(p,*current); } } else { // Is it list? if ( current->GetType() != wxT("list") ) { // Not. AppendIn(use_category,current->GetName(),wxPG_LABEL,(wxVariant&)*current); } else { // Yes, it is; create a sub category and append contents there. wxPGId newCat = DoInsert(use_category,-1,new wxPropertyCategoryClass(current->GetName(),wxPG_LABEL)); SetPropertyValues( current->GetList(), newCat ); } } } } if ( !origFrozen ) { m_pPropGrid->Thaw(); if ( this == m_pPropGrid->GetState() ) { m_selected->UpdateControl(m_pPropGrid->m_wndPrimary); } } } // ----------------------------------------------------------------------- // wxPropertyGridState property adding and removal // ----------------------------------------------------------------------- // Call for after sub-properties added with AddChild void wxPGPropertyWithChildren::PrepareSubProperties() { // TODO: When in 1.0.5, move extra stuff from AddChild to here. wxPropertyGridState* state = GetParentState(); wxASSERT(state); if ( !GetCount() ) return; wxByte depth = m_depth + 1; wxByte depthBgCol = m_depthBgCol; wxByte inheritFlags = m_flags & wxPG_INHERITED_PROPFLAGS; wxByte bgColIndex = m_bgColIndex; wxByte fgColIndex = m_fgColIndex; // // Set some values to the children // size_t i = 0; wxPGPropertyWithChildren* nparent = this; while ( i < nparent->GetCount() ) { wxPGProperty* np = nparent->Item(i); 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->GetParentingType() != 0 && ((wxPGPropertyWithChildren*)np)->GetCount() > 0 ) { nparent = (wxPGPropertyWithChildren*) np; i = 0; // Init nparent->m_expanded = 0; nparent->m_parentState = state; 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 wxPGPropertyWithChildren::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() ) { if ( m_expanded ) grid->CalculateYs( GetParent(), m_arrIndex ); grid->Refresh(); } } // ----------------------------------------------------------------------- int wxPropertyGridState::PrepareToAddItem( wxPGProperty* property, wxPGPropertyWithChildren* scheduledParent ) { wxPropertyGrid* propGrid = m_pPropGrid; wxASSERT( propGrid ); int parenting = property->GetParentingType(); // This will allow better behaviour. if ( scheduledParent == m_properties ) scheduledParent = (wxPGPropertyWithChildren*) NULL; if ( parenting > 0 ) { /* if ( scheduledParent ) wxLogDebug(wxT("scheduledParent= %s, %i"), scheduledParent->GetName().c_str(), (int)scheduledParent->GetParentingType()); */ // Parent of a category must be either root or another category // (otherwise Bad Things might happen). wxASSERT_MSG( scheduledParent == (wxPGPropertyWithChildren*) NULL || scheduledParent == m_properties || scheduledParent->GetParentingType() > 0, wxT("Parent of a category must be either root or another category.")); /* wxASSERT_MSG( m_properties == &m_regularArray, wxT("Do not add categories in non-categoric mode!")); */ // If we already have category with same name, delete given property // and use it instead as most recent caption item. wxPGId found_id = BaseGetPropertyByName( property->GetName() ); if ( wxPGIdIsOk(found_id) ) { wxPropertyCategoryClass* pwc = (wxPropertyCategoryClass*)wxPGIdToPtr(found_id); if ( pwc->GetParentingType() > 0 ) // 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 ( property->GetName().length() && wxPGIdIsOk(BaseGetPropertyByName(property->GetName())) && (!scheduledParent || scheduledParent->GetParentingType() >= 1) ) wxLogError(wxT("wxPropertyGrid: Warning - item with name \"%s\" already exists."), property->GetName().c_str()); #endif // Make sure nothing is selected. if ( propGrid && propGrid->m_selected ) { bool selRes = propGrid->ClearSelection(); wxPG_CHECK_MSG_DBG( selRes, -1, wxT("failed to deselect a property (editor probably had invalid value)") ); } property->m_y = -1; if ( scheduledParent ) { // Use parent's colours. property->m_bgColIndex = scheduledParent->m_bgColIndex; property->m_fgColIndex = scheduledParent->m_fgColIndex; } // If in hideable adding mode, or if assigned parent is hideable, then // make this one hideable. if ( ( scheduledParent && (scheduledParent->m_flags & wxPG_PROP_HIDEABLE) ) || ( propGrid && (propGrid->m_iFlags & wxPG_FL_ADDING_HIDEABLES) ) ) property->SetFlag ( wxPG_PROP_HIDEABLE ); // Set custom image flag. int custImgHeight = property->GetImageSize().y; if ( custImgHeight < 0 /*|| custImgHeight > 1*/ ) { property->m_flags |= wxPG_PROP_CUSTOMIMAGE; } if ( propGrid->GetWindowStyleFlag() & wxPG_LIMITED_EDITING ) property->m_flags |= wxPG_PROP_NOEDITOR; if ( parenting < 1 ) { // 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->GetParentingType() != PT_CAPTION ) depth++; } property->m_depth = depth; unsigned char greyDepth = depth; if ( scheduledParent ) { wxPropertyCategoryClass* pc; if ( scheduledParent->GetParentingType() >= PT_CAPTION ) pc = (wxPropertyCategoryClass*)scheduledParent; else // This conditional compile is necessary to // bypass some compiler bug. pc = wxPropertyGrid::_GetPropertyCategory(scheduledParent); if ( pc ) greyDepth = pc->GetDepth(); else greyDepth = scheduledParent->m_depthBgCol; } property->m_depthBgCol = greyDepth; // Add children to propertywithchildren. if ( parenting < PT_NONE ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)property; pwc->m_parentState = this; pwc->m_expanded = 0; // Properties with children are not expanded by default. if ( propGrid && propGrid->GetWindowStyleFlag() & wxPG_HIDE_MARGIN ) pwc->m_expanded = 1; // ...unless it cannot not be expanded. if ( pwc->GetCount() ) { pwc->PrepareSubProperties(); } // // If children were added prior to append, then this is considered // a "fixed" parent (otherwise the PT_CUSTOMPROPERTY is set, see below, // to mark it as customizable). /*if ( pwc->GetCount() ) { pwc->PrepareSubProperties(); } else { pwc->m_parentingType = PT_CUSTOMPROPERTY; }*/ } } 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 = (wxPropertyCategoryClass*)property; wxPropertyCategoryClass* pc = (wxPropertyCategoryClass*)property; pc->m_parentState = this; // Calculate text extent for caption item. pc->CalculateTextExtent(propGrid,propGrid->GetCaptionFont()); } return parenting; } // ----------------------------------------------------------------------- void wxPropertyContainerMethods::BeginAddChildren( wxPGId id ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*) wxPGIdToPtr(id); wxCHECK_RET( pwc, wxT("NULL property") ); wxCHECK_RET( pwc->GetParentingType() == PT_FIXEDCHILDREN, wxT("only call on properties with fixed children") ); pwc->m_parentingType = PT_CUSTOMPROPERTY; } // ----------------------------------------------------------------------- void wxPropertyContainerMethods::EndAddChildren( wxPGId id ) { wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*) wxPGIdToPtr(id); wxCHECK_RET( pwc, wxT("NULL property") ); wxCHECK_RET( pwc->GetParentingType() == PT_CUSTOMPROPERTY, wxT("only call on properties for which BeginAddChildren was called prior") ); pwc->m_parentingType = PT_FIXEDCHILDREN; } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::Append( wxPGProperty* property ) { wxPropertyCategoryClass* cur_cat = m_currentCategory; if ( property->GetParentingType() > 0 ) cur_cat = (wxPropertyCategoryClass*) NULL; return DoInsert( cur_cat, -1, property ); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::DoInsert( wxPGPropertyWithChildren* parent, int index, wxPGProperty* property ) { if ( !parent ) parent = m_properties; wxPropertyGrid* propGrid = m_pPropGrid; wxCHECK_MSG( parent->GetParentingType() != PT_NONE, wxNullProperty, wxT("this parent cannot accomodate children") ); wxCHECK_MSG( parent->GetParentingType() != PT_FIXEDCHILDREN, wxNullProperty, wxT("when adding properties to fixed parents, use BeginAddChildren and EndAddChildren.") ); int parenting = PrepareToAddItem( property, (wxPropertyCategoryClass*)parent ); // This type of invalid parenting value indicates we should exit now, returning // id of most recent category. if ( parenting > PT_CAPTION ) return wxPGIdGen(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. int parents_parenting = parent->GetParentingType(); if ( parents_parenting < 0 ) { // 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 ( parenting > PT_NONE ) { // 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 ( parent->GetParentingType() >= PT_CAPTION && property->m_name.length() ) m_dictName[property->m_name] = (void*) property; m_itemsAdded = 1; if ( propGrid ) propGrid->m_bottomy = 0; // this signals y recalculation return wxPGIdGen(property); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridState::AppendIn( wxPGPropertyWithChildren* pwc, const wxString& label, const wxString& propname, wxVariant& value ) { wxPGProperty* p = wxPropertyContainerMethods:: CreatePropertyByType(value.GetType(),label,propname); if ( p ) { p->GetValueTypePtr()->SetValueFromVariant(p,value); return DoInsert(pwc,-1,p); } return wxPGIdGen((wxPGProperty*)NULL); } // ----------------------------------------------------------------------- void wxPropertyGridState::DoDelete( wxPGProperty* item ) { wxCHECK_RET( item != &m_regularArray && item != m_abcArray, wxT("wxPropertyGrid: Do not attempt to remove the root item.") ); size_t i; int parenting = item->GetParentingType(); unsigned int indinparent = item->GetIndexInParent(); wxPGPropertyWithChildren* pwc = (wxPGPropertyWithChildren*)item; wxCHECK_RET( item->GetParent()->GetParentingType() != -1, wxT("wxPropertyGrid: Do not attempt to remove sub-properties.") ); if ( parenting > 0 ) { // deleting a category // erase category entries from the hash table for ( i=0; iGetCount(); i++ ) { wxPGProperty* sp = pwc->Item( i ); if ( sp->GetName().Len() ) m_dictName.erase( wxPGNameConv(sp->GetName()) ); } if ( pwc == m_currentCategory ) m_currentCategory = (wxPropertyCategoryClass*) NULL; if ( m_abcArray ) { // Remove children from non-categorized array. for ( i=0; iGetCount(); i++ ) { wxPGProperty * p = pwc->Item( i ); wxASSERT( p != NULL ); if ( p->GetParentingType() <= PT_NONE ) m_abcArray->m_children.Remove( (void*)p ); } if ( IsInNonCatMode() ) m_abcArray->FixIndexesOfChildren(); } } if ( !IsInNonCatMode() ) { // categorized mode - non-categorized array // Remove from non-cat array, but only if parent is in it if ( parenting <= 0 && item->GetParent()->GetParentingType() == PT_CAPTION ) { if ( m_abcArray ) { m_abcArray->m_children.Remove( item ); } } // categorized mode - categorized array item->m_parent->m_children.RemoveAt(indinparent); item->m_parent->FixIndexesOfChildren(/*indinparent*/); } else { // non-categorized mode - categorized array // We need to find location of item. wxPGPropertyWithChildren* 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->GetParentingType() > 0 ) { int subind = ((wxPGPropertyWithChildren*)p)->Index(item); if ( subind != wxNOT_FOUND ) { cat_parent = ((wxPGPropertyWithChildren*)p); cat_index = subind; break; } } } cat_parent->m_children.RemoveAt(cat_index); // non-categorized mode - non-categorized array if ( parenting <= 0 ) { wxASSERT( item->m_parent == m_abcArray ); item->m_parent->m_children.RemoveAt(indinparent); item->m_parent->FixIndexesOfChildren(indinparent); } } if ( item->GetName().Len() ) m_dictName.erase( wxPGNameConv(item->GetName()) ); #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 can actually delete it now delete item; m_itemsAdded = 1; // Not a logical assignment (but required nonetheless). if ( this == m_pPropGrid->GetState() ) { //m_pPropGrid->m_clearThisMany = 1; m_pPropGrid->m_bottomy = 0; // this signals y recalculation } } // ----------------------------------------------------------------------- // wxPropertyGridState init etc. // ----------------------------------------------------------------------- void wxPropertyGridState::InitNonCatMode() { ITEM_ITERATION_DCAE_VARIABLES if ( !m_abcArray ) { m_abcArray = new wxPGRootPropertyClass(); m_abcArray->SetParentState(this); m_abcArray->m_expanded = wxPG_EXP_OF_COPYARRAY; } // Must be called when FROM_STATE(m_properties) still points to regularArray. wxPGPropertyWithChildren* oldProperties = m_properties; // Must use temp value in FROM_STATE(m_properties) for item iteration loop // to run as expected. m_properties = &m_regularArray; // Copy items. ITEM_ITERATION_INIT_FROM_THE_TOP ITEM_ITERATION_DCAE_ISP_LOOP_BEGIN if ( parenting < 1 && ( parent == m_properties || parent->GetParentingType() > 0 ) ) { m_abcArray->AddChild2 ( p ); p->m_parent = &FROM_STATE(m_regularArray); } //else wxLogDebug("OUT: %s",p->m_label.c_str()); ITEM_ITERATION_DCAE_ISP_LOOP_END m_properties = oldProperties; } // ----------------------------------------------------------------------- void wxPropertyGridState::Clear() { m_regularArray.Empty(); if ( m_abcArray ) m_abcArray->Empty(); m_dictName.clear(); m_currentCategory = (wxPropertyCategoryClass*) NULL; m_lastCaptionBottomnest = 1; m_itemsAdded = 0; m_selected = (wxPGProperty*) NULL; } // ----------------------------------------------------------------------- wxPropertyGridState::wxPropertyGridState() { m_pPropGrid = (wxPropertyGrid*) NULL; m_regularArray.SetParentState(this); m_properties = &m_regularArray; m_abcArray = (wxPGRootPropertyClass*) NULL; m_currentCategory = (wxPropertyCategoryClass*) NULL; m_selected = (wxPGProperty*) NULL; m_lastCaptionBottomnest = 1; m_itemsAdded = 0; m_anyModified = 0; } // ----------------------------------------------------------------------- wxPropertyGridState::~wxPropertyGridState() { delete m_abcArray; } // ----------------------------------------------------------------------- // wxPropertyGridPopulator // ----------------------------------------------------------------------- void wxPropertyGridPopulator::Init( wxPropertyGrid* pg, wxPGId popRoot ) { WX_PG_GLOBALS_LOCKER() m_propGrid = pg; m_popRoot = popRoot; wxPGGlobalVars->m_offline++; } // ----------------------------------------------------------------------- wxPropertyGridPopulator::~wxPropertyGridPopulator() { // // Free unused sets of choices wxPGHashMapP2P::iterator it; for( it = m_dictIdChoices.begin(); it != m_dictIdChoices.end(); ++it ) { wxPGChoicesData* data = (wxPGChoicesData*) it->second; data->m_refCount--; if ( data->m_refCount < 1 ) delete data; } wxPGGlobalVars->m_offline--; } // ----------------------------------------------------------------------- bool wxPropertyGridPopulator::HasChoices( wxPGChoicesId id ) const { wxPGHashMapP2P::const_iterator it = m_dictIdChoices.find(id); return ( it != m_dictIdChoices.end() ); } // ----------------------------------------------------------------------- bool wxPropertyGridPopulator::BeginChildren() { if ( wxPGIdIsOk(m_lastProperty) && wxPGIdToPtr(m_lastProperty)->CanHaveExtraChildren() ) { wxLogDebug(wxT("New Parent: %s"),wxPGIdToPtr(m_lastProperty)->GetLabel().c_str()); m_curParent = m_lastProperty; return true; } return false; } // ----------------------------------------------------------------------- void wxPropertyGridPopulator::AddChoices(wxPGChoicesId choicesId, const wxArrayString& choiceLabels, const wxArrayInt& choiceValues) { #ifdef __WXDEBUG__ // Make sure the id is not used yet wxPGHashMapP2P::iterator it = m_dictIdChoices.find(choicesId); wxCHECK_RET( it == m_dictIdChoices.end(), wxT("added set of choices to same id twice (use HasChoices if necessary)") ); #endif wxCHECK_RET( choicesId != (wxPGChoicesId)0, wxT("choicesId must not be 0/NULL")); wxPGChoices chs(choiceLabels,choiceValues); wxPGChoicesData* data = chs.ExtractData(); m_dictIdChoices[choicesId] = (void*) data; // Artifically reduce refcount to 0 (since nothing uses it yet) //data->m_refCount = 0; } // ----------------------------------------------------------------------- wxPGId wxPropertyGridPopulator::DoAppend(wxPGProperty* p, const wxString& value, const wxString& attributes, wxPGChoicesId choicesId, const wxArrayString& choiceLabels, const wxArrayInt& choiceValues) { wxASSERT( m_propGrid ); // Make sure m_curParent is ok if ( !wxPGIdIsOk(m_curParent) ) { if ( !wxPGIdIsOk(m_popRoot) ) m_popRoot = m_propGrid->GetRoot(); m_curParent = m_popRoot; } if ( p ) { // Set choices if ( choicesId ) { wxPGHashMapP2P::iterator it = m_dictIdChoices.find(choicesId); wxPGChoices chs; if ( it != m_dictIdChoices.end() ) { // Already found wxPGChoicesData* foundData = (wxPGChoicesData*) it->second; chs.AssignData(foundData); } else { chs.Set(choiceLabels,choiceValues); m_dictIdChoices[choicesId] = (void*) chs.GetData(); } p->SetChoices(chs); } // Value setter must be before append if ( value.length() ) { p->SetValueFromString(value,wxPG_FULL_VALUE); } // Set attributes if ( attributes.length() ) wxPropertyGrid::SetPropertyAttributes(p,attributes); // Append to grid m_propGrid->AppendIn(m_curParent,p); m_lastProperty = p; } return wxPGIdGen(p); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridPopulator::AppendByClass(const wxString& classname, const wxString& label, const wxString& name, const wxString& value, const wxString& attributes, wxPGChoicesId choicesId, const wxArrayString& choiceLabels, const wxArrayInt& choiceValues) { wxPGProperty* p = m_propGrid->CreatePropertyByClass(classname,label,name); return DoAppend(p,value,attributes,choicesId,choiceLabels,choiceValues); } // ----------------------------------------------------------------------- wxPGId wxPropertyGridPopulator::AppendByType(const wxString& valuetype, const wxString& label, const wxString& name, const wxString& value, const wxString& attributes, wxPGChoicesId choicesId, const wxArrayString& choiceLabels, const wxArrayInt& choiceValues) { wxPGProperty* p = m_propGrid->CreatePropertyByType(valuetype,label,name); return DoAppend(p,value,attributes,choicesId,choiceLabels,choiceValues); } // -----------------------------------------------------------------------