Editor controls (and their event handlers) deleted from within wxPG event handler shouldn't by deleted in global idle event handler but only in local wxPG event handler because global idle events can be generated also by calling e.g. wxYield when wxPG is not in the real idle state. Closes #16617 git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@78030 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
6609 lines
184 KiB
C++
6609 lines
184 KiB
C++
/////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/propgrid/propgrid.cpp
|
|
// Purpose: wxPropertyGrid
|
|
// Author: Jaakko Salli
|
|
// Modified by:
|
|
// Created: 2004-09-25
|
|
// Copyright: (c) Jaakko Salli
|
|
// Licence: wxWindows licence
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
// For compilers that support precompilation, includes "wx/wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
#ifdef __BORLANDC__
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#if wxUSE_PROPGRID
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include "wx/defs.h"
|
|
#include "wx/object.h"
|
|
#include "wx/hash.h"
|
|
#include "wx/string.h"
|
|
#include "wx/log.h"
|
|
#include "wx/event.h"
|
|
#include "wx/window.h"
|
|
#include "wx/panel.h"
|
|
#include "wx/dc.h"
|
|
#include "wx/dcmemory.h"
|
|
#include "wx/button.h"
|
|
#include "wx/pen.h"
|
|
#include "wx/brush.h"
|
|
#include "wx/cursor.h"
|
|
#include "wx/dialog.h"
|
|
#include "wx/settings.h"
|
|
#include "wx/msgdlg.h"
|
|
#include "wx/choice.h"
|
|
#include "wx/stattext.h"
|
|
#include "wx/scrolwin.h"
|
|
#include "wx/dirdlg.h"
|
|
#include "wx/sizer.h"
|
|
#include "wx/textdlg.h"
|
|
#include "wx/filedlg.h"
|
|
#include "wx/statusbr.h"
|
|
#include "wx/intl.h"
|
|
#include "wx/frame.h"
|
|
#include "wx/textctrl.h"
|
|
#endif
|
|
|
|
|
|
// This define is necessary to prevent macro clearing
|
|
#define __wxPG_SOURCE_FILE__
|
|
|
|
#include "wx/propgrid/propgrid.h"
|
|
#include "wx/propgrid/editors.h"
|
|
|
|
#if wxPG_USE_RENDERER_NATIVE
|
|
#include "wx/renderer.h"
|
|
#endif
|
|
|
|
#include "wx/odcombo.h"
|
|
|
|
#include "wx/timer.h"
|
|
#include "wx/dcbuffer.h"
|
|
#include "wx/scopeguard.h"
|
|
|
|
// Two pics for the expand / collapse buttons.
|
|
// Files are not supplied with this project (since it is
|
|
// recommended to use either custom or native rendering).
|
|
// If you want them, get wxTreeMultiCtrl by Jorgen Bodde,
|
|
// and copy xpm files from archive to wxPropertyGrid src directory
|
|
// (and also comment/undef wxPG_ICON_WIDTH in propGrid.h
|
|
// and set wxPG_USE_RENDERER_NATIVE to 0).
|
|
#ifndef wxPG_ICON_WIDTH
|
|
#if defined(__WXMAC__)
|
|
#include "mac_collapse.xpm"
|
|
#include "mac_expand.xpm"
|
|
#elif defined(__WXGTK__)
|
|
#include "linux_collapse.xpm"
|
|
#include "linux_expand.xpm"
|
|
#else
|
|
#include "default_collapse.xpm"
|
|
#include "default_expand.xpm"
|
|
#endif
|
|
#endif
|
|
|
|
|
|
//#define wxPG_TEXT_INDENT 4 // For the wxComboControl
|
|
//#define wxPG_ALLOW_CLIPPING 1 // If 1, GetUpdateRegion() in OnPaint event handler is not ignored
|
|
#define wxPG_GUTTER_DIV 3 // gutter is max(iconwidth/gutter_div,gutter_min)
|
|
#define wxPG_GUTTER_MIN 3 // gutter before and after image of [+] or [-]
|
|
#define wxPG_YSPACING_MIN 1
|
|
#define wxPG_DEFAULT_VSPACING 2 // This matches .NET propertygrid's value,
|
|
// but causes normal combobox to spill out under MSW
|
|
|
|
//#define wxPG_OPTIMAL_WIDTH 200 // Arbitrary
|
|
|
|
//#define wxPG_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 wxPG_NAT_CHOICE_BORDER_ANY 0
|
|
|
|
//#define wxPG_HIDER_BUTTON_HEIGHT 25
|
|
|
|
#define wxPG_PIXELS_PER_UNIT m_lineHeight
|
|
|
|
#ifdef wxPG_ICON_WIDTH
|
|
#define m_iconHeight m_iconWidth
|
|
#endif
|
|
|
|
//#define wxPG_TOOLTIP_DELAY 1000
|
|
|
|
// This is the number of pixels the expander button inside
|
|
// property cells (i.e. not in the grey margin area are
|
|
// adjusted.
|
|
#define IN_CELL_EXPANDER_BUTTON_X_ADJUST 2
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
#if wxUSE_INTL
|
|
void wxPropertyGrid::AutoGetTranslation ( bool enable )
|
|
{
|
|
wxPGGlobalVars->m_autoGetTranslation = enable;
|
|
}
|
|
#else
|
|
void wxPropertyGrid::AutoGetTranslation ( bool ) { }
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
const char wxPropertyGridNameStr[] = "wxPropertyGrid";
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Statics in one class for easy destruction.
|
|
// -----------------------------------------------------------------------
|
|
|
|
#include "wx/module.h"
|
|
|
|
class wxPGGlobalVarsClassManager : public wxModule
|
|
{
|
|
DECLARE_DYNAMIC_CLASS(wxPGGlobalVarsClassManager)
|
|
public:
|
|
wxPGGlobalVarsClassManager() {}
|
|
virtual bool OnInit() wxOVERRIDE { wxPGGlobalVars = new wxPGGlobalVarsClass(); return true; }
|
|
virtual void OnExit() wxOVERRIDE { wxDELETE(wxPGGlobalVars); }
|
|
};
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxPGGlobalVarsClassManager, wxModule)
|
|
|
|
|
|
// When wxPG is loaded dynamically after the application is already running
|
|
// then the built-in module system won't pick this one up. Add it manually.
|
|
void wxPGInitResourceModule()
|
|
{
|
|
wxModule* module = new wxPGGlobalVarsClassManager;
|
|
wxModule::RegisterModule(module);
|
|
wxModule::InitializeModules();
|
|
}
|
|
|
|
wxPGGlobalVarsClass* wxPGGlobalVars = NULL;
|
|
|
|
|
|
wxPGGlobalVarsClass::wxPGGlobalVarsClass()
|
|
{
|
|
wxPGProperty::sm_wxPG_LABEL = new wxString(wxPG_LABEL_STRING);
|
|
|
|
m_boolChoices.Add(_("False"));
|
|
m_boolChoices.Add(_("True"));
|
|
|
|
m_fontFamilyChoices = NULL;
|
|
|
|
m_defaultRenderer = new wxPGDefaultRenderer();
|
|
|
|
m_autoGetTranslation = false;
|
|
|
|
m_offline = 0;
|
|
|
|
m_extraStyle = 0;
|
|
|
|
wxVariant v;
|
|
|
|
// Prepare some shared variants
|
|
m_vEmptyString = wxString();
|
|
m_vZero = (long) 0;
|
|
m_vMinusOne = (long) -1;
|
|
m_vTrue = true;
|
|
m_vFalse = false;
|
|
|
|
// Prepare cached string constants
|
|
m_strstring = wxS("string");
|
|
m_strlong = wxS("long");
|
|
m_strbool = wxS("bool");
|
|
m_strlist = wxS("list");
|
|
m_strDefaultValue = wxS("DefaultValue");
|
|
m_strMin = wxS("Min");
|
|
m_strMax = wxS("Max");
|
|
m_strUnits = wxS("Units");
|
|
m_strHint = wxS("Hint");
|
|
#if wxPG_COMPATIBILITY_1_4
|
|
m_strInlineHelp = wxS("InlineHelp");
|
|
#endif
|
|
|
|
m_warnings = 0;
|
|
}
|
|
|
|
|
|
wxPGGlobalVarsClass::~wxPGGlobalVarsClass()
|
|
{
|
|
delete m_defaultRenderer;
|
|
|
|
// This will always have one ref
|
|
delete m_fontFamilyChoices;
|
|
|
|
#if wxUSE_VALIDATORS
|
|
for ( size_t i = 0; i < m_arrValidators.size(); i++ )
|
|
delete ((wxValidator*)m_arrValidators[i]);
|
|
#endif
|
|
|
|
//
|
|
// Destroy value type class instances.
|
|
wxPGHashMapS2P::iterator vt_it;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Make sure the global pointers have been reset
|
|
wxASSERT(wxPG_EDITOR(TextCtrl) == NULL);
|
|
wxASSERT(wxPG_EDITOR(ChoiceAndButton) == NULL);
|
|
|
|
delete wxPGProperty::sm_wxPG_LABEL;
|
|
}
|
|
|
|
void wxPropertyGridInitGlobalsIfNeeded()
|
|
{
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid
|
|
// -----------------------------------------------------------------------
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxPropertyGrid, wxControl)
|
|
|
|
BEGIN_EVENT_TABLE(wxPropertyGrid, wxControl)
|
|
EVT_IDLE(wxPropertyGrid::OnIdle)
|
|
EVT_PAINT(wxPropertyGrid::OnPaint)
|
|
EVT_SIZE(wxPropertyGrid::OnResize)
|
|
EVT_ENTER_WINDOW(wxPropertyGrid::OnMouseEntry)
|
|
EVT_LEAVE_WINDOW(wxPropertyGrid::OnMouseEntry)
|
|
EVT_MOUSE_CAPTURE_CHANGED(wxPropertyGrid::OnCaptureChange)
|
|
EVT_SCROLLWIN(wxPropertyGrid::OnScrollEvent)
|
|
EVT_CHILD_FOCUS(wxPropertyGrid::OnChildFocusEvent)
|
|
EVT_SET_FOCUS(wxPropertyGrid::OnFocusEvent)
|
|
EVT_KILL_FOCUS(wxPropertyGrid::OnFocusEvent)
|
|
EVT_SYS_COLOUR_CHANGED(wxPropertyGrid::OnSysColourChanged)
|
|
EVT_MOTION(wxPropertyGrid::OnMouseMove)
|
|
EVT_LEFT_DOWN(wxPropertyGrid::OnMouseClick)
|
|
EVT_LEFT_UP(wxPropertyGrid::OnMouseUp)
|
|
EVT_RIGHT_UP(wxPropertyGrid::OnMouseRightClick)
|
|
EVT_LEFT_DCLICK(wxPropertyGrid::OnMouseDoubleClick)
|
|
EVT_KEY_DOWN(wxPropertyGrid::OnKey)
|
|
END_EVENT_TABLE()
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGrid::wxPropertyGrid()
|
|
: wxControl(), wxScrollHelper(this)
|
|
{
|
|
Init1();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGrid::wxPropertyGrid( wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString& name )
|
|
: wxControl(), wxScrollHelper(this)
|
|
{
|
|
Init1();
|
|
Create(parent,id,pos,size,style,name);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::Create( wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString& name )
|
|
{
|
|
|
|
if (!(style&wxBORDER_MASK))
|
|
{
|
|
style |= wxBORDER_THEME;
|
|
}
|
|
|
|
style |= wxVSCROLL;
|
|
|
|
// Filter out wxTAB_TRAVERSAL - we will handle TABs manually
|
|
style &= ~(wxTAB_TRAVERSAL);
|
|
style |= wxWANTS_CHARS;
|
|
|
|
wxControl::Create(parent, id, pos, size,
|
|
style | wxScrolledWindowStyle,
|
|
wxDefaultValidator,
|
|
name);
|
|
|
|
Init2();
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
//
|
|
// Initialize values to defaults
|
|
//
|
|
void wxPropertyGrid::Init1()
|
|
{
|
|
// Register editor classes, if necessary.
|
|
if ( wxPGGlobalVars->m_mapEditorClasses.empty() )
|
|
wxPropertyGrid::RegisterDefaultEditors();
|
|
|
|
m_validatingEditor = 0;
|
|
m_iFlags = 0;
|
|
m_pState = NULL;
|
|
m_wndEditor = m_wndEditor2 = NULL;
|
|
m_selColumn = 1;
|
|
m_colHover = 1;
|
|
m_propHover = NULL;
|
|
m_labelEditor = NULL;
|
|
m_labelEditorProperty = NULL;
|
|
m_eventObject = this;
|
|
m_curFocused = NULL;
|
|
m_processedEvent = NULL;
|
|
m_tlp = NULL;
|
|
m_sortFunction = NULL;
|
|
m_inDoPropertyChanged = false;
|
|
m_inCommitChangesFromEditor = false;
|
|
m_inDoSelectProperty = false;
|
|
m_inOnValidationFailure = false;
|
|
m_permanentValidationFailureBehavior = wxPG_VFB_DEFAULT;
|
|
m_dragStatus = 0;
|
|
m_mouseSide = 16;
|
|
m_editorFocused = 0;
|
|
|
|
// Set up default unspecified value 'colour'
|
|
m_unspecifiedAppearance.SetFgCol(*wxLIGHT_GREY);
|
|
|
|
// Set default keys
|
|
AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RIGHT );
|
|
AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
|
|
AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_LEFT );
|
|
AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
|
|
AddActionTrigger( wxPG_ACTION_EXPAND_PROPERTY, WXK_RIGHT);
|
|
AddActionTrigger( wxPG_ACTION_COLLAPSE_PROPERTY, WXK_LEFT);
|
|
AddActionTrigger( wxPG_ACTION_CANCEL_EDIT, WXK_ESCAPE );
|
|
AddActionTrigger( wxPG_ACTION_PRESS_BUTTON, WXK_DOWN, wxMOD_ALT );
|
|
AddActionTrigger( wxPG_ACTION_PRESS_BUTTON, WXK_F4 );
|
|
|
|
m_coloursCustomized = 0;
|
|
|
|
m_doubleBuffer = NULL;
|
|
|
|
#ifndef wxPG_ICON_WIDTH
|
|
m_expandbmp = NULL;
|
|
m_collbmp = NULL;
|
|
m_iconWidth = 11;
|
|
m_iconHeight = 11;
|
|
#else
|
|
m_iconWidth = wxPG_ICON_WIDTH;
|
|
#endif
|
|
|
|
m_prevVY = -1;
|
|
|
|
m_gutterWidth = wxPG_GUTTER_MIN;
|
|
m_subgroup_extramargin = 10;
|
|
|
|
m_lineHeight = 0;
|
|
|
|
m_width = m_height = 0;
|
|
|
|
m_commonValues.push_back(new wxPGCommonValue(_("Unspecified"), wxPGGlobalVars->m_defaultRenderer) );
|
|
m_cvUnspecified = 0;
|
|
|
|
m_chgInfo_changedProperty = NULL;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
//
|
|
// Initialize after parent etc. set
|
|
//
|
|
void wxPropertyGrid::Init2()
|
|
{
|
|
wxASSERT( !(m_iFlags & wxPG_FL_INITIALIZED ) );
|
|
|
|
#ifdef __WXMAC__
|
|
// Smaller controls on Mac
|
|
SetWindowVariant(wxWINDOW_VARIANT_SMALL);
|
|
#endif
|
|
|
|
// Now create state, if one didn't exist already
|
|
// (wxPropertyGridManager might have created it for us).
|
|
if ( !m_pState )
|
|
{
|
|
m_pState = CreateState();
|
|
m_pState->m_pPropGrid = this;
|
|
m_iFlags |= wxPG_FL_CREATEDSTATE;
|
|
}
|
|
|
|
if ( !(m_windowStyle & wxPG_SPLITTER_AUTO_CENTER) )
|
|
m_pState->m_dontCenterSplitter = true;
|
|
|
|
if ( m_windowStyle & wxPG_HIDE_CATEGORIES )
|
|
{
|
|
m_pState->InitNonCatMode();
|
|
|
|
m_pState->m_properties = m_pState->m_abcArray;
|
|
}
|
|
|
|
GetClientSize(&m_width,&m_height);
|
|
|
|
#ifndef wxPG_ICON_WIDTH
|
|
// create two bitmap nodes for drawing
|
|
m_expandbmp = new wxBitmap(expand_xpm);
|
|
m_collbmp = new wxBitmap(collapse_xpm);
|
|
|
|
// calculate average font height for bitmap centering
|
|
|
|
m_iconWidth = m_expandbmp->GetWidth();
|
|
m_iconHeight = m_expandbmp->GetHeight();
|
|
#endif
|
|
|
|
m_curcursor = wxCURSOR_ARROW;
|
|
m_cursorSizeWE = new wxCursor( wxCURSOR_SIZEWE );
|
|
|
|
// adjust bitmap icon y position so they are centered
|
|
m_vspacing = wxPG_DEFAULT_VSPACING;
|
|
|
|
CalculateFontAndBitmapStuff( wxPG_DEFAULT_VSPACING );
|
|
|
|
// Allocate cell datas
|
|
m_propertyDefaultCell.SetEmptyData();
|
|
m_categoryDefaultCell.SetEmptyData();
|
|
|
|
RegainColours();
|
|
|
|
// This helps with flicker
|
|
SetBackgroundStyle( wxBG_STYLE_CUSTOM );
|
|
|
|
// Hook the top-level parent
|
|
m_tlpClosed = NULL;
|
|
m_tlpClosedTime = 0;
|
|
|
|
// set virtual size to this window size
|
|
wxSize wndsize = GetSize();
|
|
SetVirtualSize(wndsize.GetWidth(), wndsize.GetWidth());
|
|
|
|
m_timeCreated = ::wxGetLocalTimeMillis();
|
|
|
|
m_iFlags |= wxPG_FL_INITIALIZED;
|
|
|
|
m_ncWidth = wndsize.GetWidth();
|
|
|
|
// Need to call OnResize handler or size given in constructor/Create
|
|
// will never work.
|
|
wxSizeEvent sizeEvent(wndsize,0);
|
|
OnResize(sizeEvent);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGrid::~wxPropertyGrid()
|
|
{
|
|
size_t i;
|
|
|
|
#if wxUSE_THREADS
|
|
wxCriticalSectionLocker(wxPGGlobalVars->m_critSect);
|
|
#endif
|
|
|
|
//
|
|
// Remove grid and property pointers from live wxPropertyGridEvents.
|
|
for ( i=0; i<m_liveEvents.size(); i++ )
|
|
{
|
|
wxPropertyGridEvent* evt = m_liveEvents[i];
|
|
evt->SetPropertyGrid(NULL);
|
|
evt->SetProperty(NULL);
|
|
}
|
|
m_liveEvents.clear();
|
|
|
|
if ( m_processedEvent )
|
|
{
|
|
// All right... we are being deleted while wxPropertyGrid event
|
|
// is being sent. Make sure that event propagates as little
|
|
// as possible (although usually this is not enough to prevent
|
|
// a crash).
|
|
m_processedEvent->Skip(false);
|
|
m_processedEvent->StopPropagation();
|
|
|
|
// Let's use wxMessageBox to make the message appear more
|
|
// reliably (and *before* the crash can happen).
|
|
::wxMessageBox("wxPropertyGrid was being destroyed in an event "
|
|
"generated by it. This usually leads to a crash "
|
|
"so it is recommended to destroy the control "
|
|
"at idle time instead.");
|
|
}
|
|
|
|
DoSelectProperty(NULL, wxPG_SEL_NOVALIDATE|wxPG_SEL_DONT_SEND_EVENT);
|
|
|
|
// This should do prevent things from going too badly wrong
|
|
m_iFlags &= ~(wxPG_FL_INITIALIZED);
|
|
|
|
if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED )
|
|
ReleaseMouse();
|
|
|
|
// Call with NULL to disconnect event handling
|
|
if ( GetExtraStyle() & wxPG_EX_ENABLE_TLP_TRACKING )
|
|
{
|
|
OnTLPChanging(NULL);
|
|
|
|
wxASSERT_MSG( !IsEditorsValueModified(),
|
|
wxS("Most recent change in property editor was ")
|
|
wxS("lost!!! (if you don't want this to happen, ")
|
|
wxS("close your frames and dialogs using ")
|
|
wxS("Close(false).)") );
|
|
}
|
|
|
|
// Delete pending editor controls
|
|
DeletePendingObjects();
|
|
|
|
if ( m_doubleBuffer )
|
|
delete m_doubleBuffer;
|
|
|
|
if ( m_iFlags & wxPG_FL_CREATEDSTATE )
|
|
delete m_pState;
|
|
|
|
delete m_cursorSizeWE;
|
|
|
|
#ifndef wxPG_ICON_WIDTH
|
|
delete m_expandbmp;
|
|
delete m_collbmp;
|
|
#endif
|
|
|
|
// Delete common value records
|
|
for ( i=0; i<m_commonValues.size(); i++ )
|
|
{
|
|
// Use temporary variable to work around possible strange VC6 (asserts because m_size is zero)
|
|
wxPGCommonValue* value = m_commonValues[i];
|
|
delete value;
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::Destroy()
|
|
{
|
|
if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED )
|
|
ReleaseMouse();
|
|
|
|
return wxControl::Destroy();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridPageState* wxPropertyGrid::CreateState() const
|
|
{
|
|
return new wxPropertyGridPageState();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid overridden wxWindow methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetWindowStyleFlag( long style )
|
|
{
|
|
long old_style = m_windowStyle;
|
|
|
|
if ( m_iFlags & wxPG_FL_INITIALIZED )
|
|
{
|
|
wxASSERT( m_pState );
|
|
|
|
if ( !(style & wxPG_HIDE_CATEGORIES) && (old_style & wxPG_HIDE_CATEGORIES) )
|
|
{
|
|
// Enable categories
|
|
EnableCategories( true );
|
|
}
|
|
else if ( (style & wxPG_HIDE_CATEGORIES) && !(old_style & wxPG_HIDE_CATEGORIES) )
|
|
{
|
|
// Disable categories
|
|
EnableCategories( false );
|
|
}
|
|
if ( !(old_style & wxPG_AUTO_SORT) && (style & wxPG_AUTO_SORT) )
|
|
{
|
|
//
|
|
// Autosort enabled
|
|
//
|
|
if ( !IsFrozen() )
|
|
PrepareAfterItemsAdded();
|
|
else
|
|
m_pState->m_itemsAdded = true;
|
|
}
|
|
#if wxPG_SUPPORT_TOOLTIPS
|
|
if ( !(old_style & wxPG_TOOLTIPS) && (style & wxPG_TOOLTIPS) )
|
|
{
|
|
//
|
|
// Tooltips enabled
|
|
//
|
|
/*
|
|
wxToolTip* tooltip = new wxToolTip ( wxEmptyString );
|
|
SetToolTip ( tooltip );
|
|
tooltip->SetDelay ( wxPG_TOOLTIP_DELAY );
|
|
*/
|
|
}
|
|
else if ( (old_style & wxPG_TOOLTIPS) && !(style & wxPG_TOOLTIPS) )
|
|
{
|
|
//
|
|
// Tooltips disabled
|
|
//
|
|
SetToolTip( NULL );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
wxControl::SetWindowStyleFlag ( style );
|
|
|
|
if ( m_iFlags & wxPG_FL_INITIALIZED )
|
|
{
|
|
if ( (old_style & wxPG_HIDE_MARGIN) != (style & wxPG_HIDE_MARGIN) )
|
|
{
|
|
CalculateFontAndBitmapStuff( m_vspacing );
|
|
Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoThaw()
|
|
{
|
|
if ( !IsFrozen() )
|
|
{
|
|
wxControl::DoThaw();
|
|
RecalculateVirtualSize();
|
|
Refresh();
|
|
|
|
// Force property re-selection
|
|
// NB: We must copy the selection.
|
|
wxArrayPGProperty selection = m_pState->m_selection;
|
|
DoSetSelection(selection, wxPG_SEL_FORCE | wxPG_SEL_NONVISIBLE);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoAddToSelection( wxPGProperty* prop, int selFlags )
|
|
{
|
|
wxCHECK( prop, false );
|
|
|
|
if ( !(GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION) )
|
|
return DoSelectProperty(prop, selFlags);
|
|
|
|
wxArrayPGProperty& selection = m_pState->m_selection;
|
|
|
|
if ( !selection.size() )
|
|
{
|
|
return DoSelectProperty(prop, selFlags);
|
|
}
|
|
else
|
|
{
|
|
// For categories, only one can be selected at a time
|
|
if ( prop->IsCategory() || selection[0]->IsCategory() )
|
|
return true;
|
|
|
|
selection.push_back(prop);
|
|
|
|
if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) )
|
|
{
|
|
SendEvent( wxEVT_PG_SELECTED, prop, NULL );
|
|
}
|
|
|
|
DrawItem(prop);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoRemoveFromSelection( wxPGProperty* prop, int selFlags )
|
|
{
|
|
wxCHECK( prop, false );
|
|
bool res;
|
|
|
|
wxArrayPGProperty& selection = m_pState->m_selection;
|
|
if ( selection.size() <= 1 )
|
|
{
|
|
res = DoSelectProperty(NULL, selFlags);
|
|
}
|
|
else
|
|
{
|
|
m_pState->DoRemoveFromSelection(prop);
|
|
DrawItem(prop);
|
|
res = true;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoSelectAndEdit( wxPGProperty* prop,
|
|
unsigned int colIndex,
|
|
unsigned int selFlags )
|
|
{
|
|
//
|
|
// NB: Enable following if label editor background colour is
|
|
// ever changed to any other than m_colSelBack.
|
|
//
|
|
// We use this workaround to prevent visible flicker when editing
|
|
// a cell. Atleast on wxMSW, there is a difficult to find
|
|
// (and perhaps prevent) redraw somewhere between making property
|
|
// selected and enabling label editing.
|
|
//
|
|
//wxColour prevColSelBack = m_colSelBack;
|
|
//m_colSelBack = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
|
|
|
|
bool res;
|
|
|
|
if ( colIndex == 1 )
|
|
{
|
|
res = DoSelectProperty(prop, selFlags);
|
|
}
|
|
else
|
|
{
|
|
// send event
|
|
DoClearSelection(false, wxPG_SEL_NO_REFRESH);
|
|
|
|
if ( m_pState->m_editableColumns.Index(colIndex) == wxNOT_FOUND )
|
|
{
|
|
res = DoAddToSelection(prop, selFlags);
|
|
}
|
|
else
|
|
{
|
|
res = DoAddToSelection(prop, selFlags|wxPG_SEL_NO_REFRESH);
|
|
|
|
DoBeginLabelEdit(colIndex, selFlags);
|
|
}
|
|
}
|
|
|
|
//m_colSelBack = prevColSelBack;
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::AddToSelectionFromInputEvent( wxPGProperty* prop,
|
|
unsigned int colIndex,
|
|
wxMouseEvent* mouseEvent,
|
|
int selFlags )
|
|
{
|
|
const wxArrayPGProperty& selection = GetSelectedProperties();
|
|
bool alreadySelected = m_pState->DoIsPropertySelected(prop);
|
|
bool res = true;
|
|
|
|
// Set to 2 if also add all items in between
|
|
int addToExistingSelection = 0;
|
|
|
|
if ( GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION )
|
|
{
|
|
if ( mouseEvent )
|
|
{
|
|
if ( mouseEvent->GetEventType() == wxEVT_RIGHT_DOWN ||
|
|
mouseEvent->GetEventType() == wxEVT_RIGHT_UP )
|
|
{
|
|
// Allow right-click for context menu without
|
|
// disturbing the selection.
|
|
if ( GetSelectedProperties().size() <= 1 ||
|
|
!alreadySelected )
|
|
return DoSelectAndEdit(prop, colIndex, selFlags);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( mouseEvent->ControlDown() )
|
|
{
|
|
addToExistingSelection = 1;
|
|
}
|
|
else if ( mouseEvent->ShiftDown() )
|
|
{
|
|
if ( selection.size() > 0 && !prop->IsCategory() )
|
|
addToExistingSelection = 2;
|
|
else
|
|
addToExistingSelection = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( addToExistingSelection == 1 )
|
|
{
|
|
// Add/remove one
|
|
if ( !alreadySelected )
|
|
{
|
|
res = DoAddToSelection(prop, selFlags);
|
|
}
|
|
else if ( GetSelectedProperties().size() > 1 )
|
|
{
|
|
res = DoRemoveFromSelection(prop, selFlags);
|
|
}
|
|
}
|
|
else if ( addToExistingSelection == 2 )
|
|
{
|
|
// Add this, and all in between
|
|
|
|
// Find top selected property
|
|
wxPGProperty* topSelProp = selection[0];
|
|
int topSelPropY = topSelProp->GetY();
|
|
for ( unsigned int i=1; i<selection.size(); i++ )
|
|
{
|
|
wxPGProperty* p = selection[i];
|
|
int y = p->GetY();
|
|
if ( y < topSelPropY )
|
|
{
|
|
topSelProp = p;
|
|
topSelPropY = y;
|
|
}
|
|
}
|
|
|
|
wxPGProperty* startFrom;
|
|
wxPGProperty* stopAt;
|
|
|
|
if ( prop->GetY() <= topSelPropY )
|
|
{
|
|
// Property is above selection (or same)
|
|
startFrom = prop;
|
|
stopAt = topSelProp;
|
|
}
|
|
else
|
|
{
|
|
// Property is below selection
|
|
startFrom = topSelProp;
|
|
stopAt = prop;
|
|
}
|
|
|
|
// Iterate through properties in-between, and select them
|
|
wxPropertyGridIterator it;
|
|
|
|
for ( it = GetIterator(wxPG_ITERATE_VISIBLE, startFrom);
|
|
!it.AtEnd();
|
|
it++ )
|
|
{
|
|
wxPGProperty* p = *it;
|
|
|
|
if ( !p->IsCategory() &&
|
|
!m_pState->DoIsPropertySelected(p) )
|
|
{
|
|
DoAddToSelection(p, selFlags);
|
|
}
|
|
|
|
if ( p == stopAt )
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
res = DoSelectAndEdit(prop, colIndex, selFlags);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoSetSelection( const wxArrayPGProperty& newSelection,
|
|
int selFlags )
|
|
{
|
|
if ( newSelection.size() > 0 )
|
|
{
|
|
if ( !DoSelectProperty(newSelection[0], selFlags) )
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DoClearSelection(false, selFlags);
|
|
}
|
|
|
|
for ( unsigned int i = 1; i < newSelection.size(); i++ )
|
|
{
|
|
DoAddToSelection(newSelection[i], selFlags);
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::MakeColumnEditable( unsigned int column,
|
|
bool editable )
|
|
{
|
|
wxASSERT( column != 1 );
|
|
|
|
wxArrayInt& cols = m_pState->m_editableColumns;
|
|
|
|
if ( editable )
|
|
{
|
|
cols.push_back(column);
|
|
}
|
|
else
|
|
{
|
|
for ( int i = cols.size() - 1; i > 0; i-- )
|
|
{
|
|
if ( cols[i] == (int)column )
|
|
cols.erase( cols.begin() + i );
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoBeginLabelEdit( unsigned int colIndex,
|
|
int selFlags )
|
|
{
|
|
wxPGProperty* selected = GetSelection();
|
|
wxCHECK_RET(selected, wxT("No property selected"));
|
|
wxCHECK_RET(colIndex != 1, wxT("Do not use this for column 1"));
|
|
|
|
if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) )
|
|
{
|
|
if ( SendEvent( wxEVT_PG_LABEL_EDIT_BEGIN,
|
|
selected, NULL, 0,
|
|
colIndex ) )
|
|
return;
|
|
}
|
|
|
|
wxString text;
|
|
const wxPGCell* cell = NULL;
|
|
if ( selected->HasCell(colIndex) )
|
|
{
|
|
cell = &selected->GetCell(colIndex);
|
|
if ( !cell->HasText() && colIndex == 0 )
|
|
text = selected->GetLabel();
|
|
}
|
|
|
|
if ( !cell )
|
|
{
|
|
if ( colIndex == 0 )
|
|
text = selected->GetLabel();
|
|
else
|
|
cell = &selected->GetOrCreateCell(colIndex);
|
|
}
|
|
|
|
if ( cell && cell->HasText() )
|
|
text = cell->GetText();
|
|
|
|
DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE); // send event
|
|
|
|
m_selColumn = colIndex;
|
|
|
|
wxRect r = GetEditorWidgetRect(selected, m_selColumn);
|
|
|
|
wxWindow* tc = GenerateEditorTextCtrl(r.GetPosition(),
|
|
r.GetSize(),
|
|
text,
|
|
NULL,
|
|
wxTE_PROCESS_ENTER,
|
|
0,
|
|
colIndex);
|
|
|
|
wxWindowID id = tc->GetId();
|
|
tc->Connect(id, wxEVT_TEXT_ENTER,
|
|
wxCommandEventHandler(wxPropertyGrid::OnLabelEditorEnterPress),
|
|
NULL, this);
|
|
tc->Connect(id, wxEVT_KEY_DOWN,
|
|
wxKeyEventHandler(wxPropertyGrid::OnLabelEditorKeyPress),
|
|
NULL, this);
|
|
|
|
tc->SetFocus();
|
|
|
|
m_labelEditor = wxStaticCast(tc, wxTextCtrl);
|
|
m_labelEditorProperty = selected;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void
|
|
wxPropertyGrid::OnLabelEditorEnterPress( wxCommandEvent& WXUNUSED(event) )
|
|
{
|
|
DoEndLabelEdit(true);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnLabelEditorKeyPress( wxKeyEvent& event )
|
|
{
|
|
int keycode = event.GetKeyCode();
|
|
|
|
if ( keycode == WXK_ESCAPE )
|
|
{
|
|
DoEndLabelEdit(false);
|
|
}
|
|
else
|
|
{
|
|
HandleKeyEvent(event, true);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoEndLabelEdit( bool commit, int selFlags )
|
|
{
|
|
if ( !m_labelEditor )
|
|
return;
|
|
|
|
wxPGProperty* prop = m_labelEditorProperty;
|
|
wxASSERT(prop);
|
|
|
|
if ( commit )
|
|
{
|
|
if ( !(selFlags & wxPG_SEL_DONT_SEND_EVENT) )
|
|
{
|
|
// wxPG_SEL_NOVALIDATE is passed correctly in selFlags
|
|
if ( SendEvent( wxEVT_PG_LABEL_EDIT_ENDING,
|
|
prop, NULL, selFlags,
|
|
m_selColumn ) )
|
|
return;
|
|
}
|
|
|
|
wxString text = m_labelEditor->GetValue();
|
|
wxPGCell* cell = NULL;
|
|
if ( prop->HasCell(m_selColumn) )
|
|
{
|
|
cell = &prop->GetCell(m_selColumn);
|
|
}
|
|
else
|
|
{
|
|
if ( m_selColumn == 0 )
|
|
prop->SetLabel(text);
|
|
else
|
|
cell = &prop->GetOrCreateCell(m_selColumn);
|
|
}
|
|
|
|
if ( cell )
|
|
cell->SetText(text);
|
|
}
|
|
|
|
m_selColumn = 1;
|
|
int wasFocused = m_iFlags & wxPG_FL_FOCUSED;
|
|
|
|
DestroyEditorWnd(m_labelEditor);
|
|
|
|
m_labelEditor = NULL;
|
|
m_labelEditorProperty = NULL;
|
|
|
|
// Fix focus (needed at least on wxGTK)
|
|
if ( wasFocused )
|
|
SetFocusOnCanvas();
|
|
|
|
DrawItem(prop);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetExtraStyle( long exStyle )
|
|
{
|
|
if ( exStyle & wxPG_EX_ENABLE_TLP_TRACKING )
|
|
OnTLPChanging(::wxGetTopLevelParent(this));
|
|
else
|
|
OnTLPChanging(NULL);
|
|
|
|
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 ( !this->IsDoubleBuffered() )
|
|
{
|
|
exStyle &= ~(wxPG_EX_NATIVE_DOUBLE_BUFFERING);
|
|
}
|
|
else
|
|
{
|
|
wxDELETE(m_doubleBuffer);
|
|
}
|
|
}
|
|
|
|
wxControl::SetExtraStyle( exStyle );
|
|
|
|
if ( exStyle & wxPG_EX_INIT_NOCAT )
|
|
m_pState->InitNonCatMode();
|
|
|
|
if ( exStyle & wxPG_EX_HELP_AS_TOOLTIPS )
|
|
m_windowStyle |= wxPG_TOOLTIPS;
|
|
|
|
// Set global style
|
|
wxPGGlobalVars->m_extraStyle = exStyle;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// returns the best acceptable minimal size
|
|
wxSize wxPropertyGrid::DoGetBestSize() const
|
|
{
|
|
int lineHeight = wxMax(15, m_lineHeight);
|
|
|
|
// don't make the grid too tall (limit height to 10 items) but don't
|
|
// make it too small neither
|
|
int numLines = wxMin
|
|
(
|
|
wxMax(m_pState->m_properties->GetChildCount(), 3),
|
|
10
|
|
);
|
|
|
|
wxClientDC dc(const_cast<wxPropertyGrid *>(this));
|
|
int width = m_marginWidth;
|
|
for ( unsigned int i = 0; i < m_pState->m_colWidths.size(); i++ )
|
|
{
|
|
width += m_pState->GetColumnFitWidth(dc, m_pState->DoGetRoot(), i, true);
|
|
}
|
|
|
|
const wxSize sz = wxSize(width, lineHeight*numLines + 40);
|
|
|
|
CacheBestSize(sz);
|
|
return sz;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnTLPChanging( wxWindow* newTLP )
|
|
{
|
|
if ( newTLP == m_tlp )
|
|
return;
|
|
|
|
wxLongLong currentTime = ::wxGetLocalTimeMillis();
|
|
|
|
//
|
|
// Parent changed so let's redetermine and re-hook the
|
|
// correct top-level window.
|
|
if ( m_tlp )
|
|
{
|
|
m_tlp->Disconnect( wxEVT_CLOSE_WINDOW,
|
|
wxCloseEventHandler(wxPropertyGrid::OnTLPClose),
|
|
NULL, this );
|
|
m_tlpClosed = m_tlp;
|
|
m_tlpClosedTime = currentTime;
|
|
}
|
|
|
|
if ( newTLP )
|
|
{
|
|
// Only accept new tlp if same one was not just dismissed.
|
|
if ( newTLP != m_tlpClosed ||
|
|
m_tlpClosedTime+250 < currentTime )
|
|
{
|
|
newTLP->Connect( wxEVT_CLOSE_WINDOW,
|
|
wxCloseEventHandler(wxPropertyGrid::OnTLPClose),
|
|
NULL, this );
|
|
m_tlpClosed = NULL;
|
|
}
|
|
else
|
|
{
|
|
newTLP = NULL;
|
|
}
|
|
}
|
|
|
|
m_tlp = newTLP;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnTLPClose( wxCloseEvent& event )
|
|
{
|
|
// ClearSelection forces value validation/commit.
|
|
if ( event.CanVeto() && !DoClearSelection() )
|
|
{
|
|
event.Veto();
|
|
return;
|
|
}
|
|
|
|
// Ok, it can close, set tlp pointer to NULL. Some other event
|
|
// handler can of course veto the close, but our OnIdle() should
|
|
// then be able to regain the tlp pointer.
|
|
OnTLPChanging(NULL);
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::Reparent( wxWindowBase *newParent )
|
|
{
|
|
OnTLPChanging((wxWindow*)newParent);
|
|
|
|
bool res = wxControl::Reparent(newParent);
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid Font and Colour Methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::CalculateFontAndBitmapStuff( int vspacing )
|
|
{
|
|
int x = 0, y = 0;
|
|
|
|
m_captionFont = wxControl::GetFont();
|
|
|
|
GetTextExtent(wxS("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(wxFONTWEIGHT_BOLD);
|
|
GetTextExtent(wxS("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;
|
|
|
|
if ( m_pState )
|
|
m_pState->CalculateFontAndBitmapStuff(vspacing);
|
|
|
|
if ( m_iFlags & wxPG_FL_INITIALIZED )
|
|
RecalculateVirtualSize();
|
|
|
|
InvalidateBestSize();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnSysColourChanged( wxSysColourChangedEvent &WXUNUSED(event) )
|
|
{
|
|
if ((m_iFlags & wxPG_FL_INITIALIZED)!=0) {
|
|
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()
|
|
{
|
|
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;
|
|
m_categoryDefaultCell.GetData()->SetBgCol(m_colCapBack);
|
|
}
|
|
|
|
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;
|
|
m_categoryDefaultCell.GetData()->SetFgCol(capForeCol);
|
|
}
|
|
|
|
if ( !(m_coloursCustomized & 0x0008) )
|
|
{
|
|
wxColour bgCol = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
|
|
m_colPropBack = bgCol;
|
|
m_propertyDefaultCell.GetData()->SetBgCol(bgCol);
|
|
if ( !m_unspecifiedAppearance.GetBgCol().IsOk() )
|
|
m_unspecifiedAppearance.SetBgCol(bgCol);
|
|
}
|
|
|
|
if ( !(m_coloursCustomized & 0x0010) )
|
|
{
|
|
wxColour fgCol = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
|
|
m_colPropFore = fgCol;
|
|
m_propertyDefaultCell.GetData()->SetFgCol(fgCol);
|
|
if ( !m_unspecifiedAppearance.GetFgCol().IsOk() )
|
|
m_unspecifiedAppearance.SetFgCol(fgCol);
|
|
}
|
|
|
|
if ( !(m_coloursCustomized & 0x0020) )
|
|
m_colSelBack = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
|
|
|
|
if ( !(m_coloursCustomized & 0x0040) )
|
|
m_colSelFore = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT );
|
|
|
|
if ( !(m_coloursCustomized & 0x0080) )
|
|
m_colLine = m_colCapBack;
|
|
|
|
if ( !(m_coloursCustomized & 0x0100) )
|
|
m_colDisPropFore = m_colCapFore;
|
|
|
|
m_colEmptySpace = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::ResetColours()
|
|
{
|
|
m_coloursCustomized = 0;
|
|
|
|
RegainColours();
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::SetFont( const wxFont& font )
|
|
{
|
|
// Must disable active editor.
|
|
DoClearSelection();
|
|
|
|
bool res = wxControl::SetFont( font );
|
|
if ( res && GetParent()) // may not have been Create()ed yet if SetFont called from SetWindowVariant
|
|
{
|
|
CalculateFontAndBitmapStuff( m_vspacing );
|
|
Refresh();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetLineColour( const wxColour& col )
|
|
{
|
|
m_colLine = col;
|
|
m_coloursCustomized |= 0x80;
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetMarginColour( const wxColour& col )
|
|
{
|
|
m_colMargin = col;
|
|
m_coloursCustomized |= 0x01;
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetCellBackgroundColour( const wxColour& col )
|
|
{
|
|
m_colPropBack = col;
|
|
m_coloursCustomized |= 0x08;
|
|
|
|
m_propertyDefaultCell.GetData()->SetBgCol(col);
|
|
m_unspecifiedAppearance.SetBgCol(col);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetCellTextColour( const wxColour& col )
|
|
{
|
|
m_colPropFore = col;
|
|
m_coloursCustomized |= 0x10;
|
|
|
|
m_propertyDefaultCell.GetData()->SetFgCol(col);
|
|
m_unspecifiedAppearance.SetFgCol(col);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetEmptySpaceColour( const wxColour& col )
|
|
{
|
|
m_colEmptySpace = col;
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetCellDisabledTextColour( const wxColour& col )
|
|
{
|
|
m_colDisPropFore = col;
|
|
m_coloursCustomized |= 0x100;
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetSelectionBackgroundColour( const wxColour& col )
|
|
{
|
|
m_colSelBack = col;
|
|
m_coloursCustomized |= 0x20;
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetSelectionTextColour( const wxColour& col )
|
|
{
|
|
m_colSelFore = col;
|
|
m_coloursCustomized |= 0x40;
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetCaptionBackgroundColour( const wxColour& col )
|
|
{
|
|
m_colCapBack = col;
|
|
m_coloursCustomized |= 0x02;
|
|
|
|
m_categoryDefaultCell.GetData()->SetBgCol(col);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetCaptionTextColour( const wxColour& col )
|
|
{
|
|
m_colCapFore = col;
|
|
m_coloursCustomized |= 0x04;
|
|
|
|
m_categoryDefaultCell.GetData()->SetFgCol(col);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid property adding and removal
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::PrepareAfterItemsAdded()
|
|
{
|
|
if ( !m_pState || !m_pState->m_itemsAdded ) return;
|
|
|
|
m_pState->m_itemsAdded = false;
|
|
|
|
if ( m_windowStyle & wxPG_AUTO_SORT )
|
|
Sort(wxPG_SORT_TOP_LEVEL_ONLY);
|
|
|
|
RecalculateVirtualSize();
|
|
|
|
// Fix editor position
|
|
CorrectEditorWidgetPosY();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid property operations
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::EnsureVisible( wxPGPropArg id )
|
|
{
|
|
wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false)
|
|
|
|
Update();
|
|
|
|
bool changed = false;
|
|
|
|
// Is it inside collapsed section?
|
|
if ( !p->IsVisible() )
|
|
{
|
|
// expand parents
|
|
wxPGProperty* parent = p->GetParent();
|
|
wxPGProperty* grandparent = parent->GetParent();
|
|
|
|
if ( grandparent && grandparent != m_pState->m_properties )
|
|
Expand( grandparent );
|
|
|
|
Expand( parent );
|
|
changed = true;
|
|
}
|
|
|
|
// Need to scroll?
|
|
int vx, vy;
|
|
GetViewStart(&vx,&vy);
|
|
vy*=wxPG_PIXELS_PER_UNIT;
|
|
|
|
int y = p->GetY();
|
|
|
|
if ( y < vy )
|
|
{
|
|
Scroll(vx, y/wxPG_PIXELS_PER_UNIT );
|
|
m_iFlags |= wxPG_FL_SCROLLED;
|
|
changed = true;
|
|
}
|
|
else if ( (y+m_lineHeight) > (vy+m_height) )
|
|
{
|
|
Scroll(vx, (y-m_height+(m_lineHeight*2))/wxPG_PIXELS_PER_UNIT );
|
|
m_iFlags |= wxPG_FL_SCROLLED;
|
|
changed = true;
|
|
}
|
|
|
|
if ( changed )
|
|
DrawItems( p, p );
|
|
|
|
return changed;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid helper methods called by properties
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Control font changer helper.
|
|
void wxPropertyGrid::SetCurControlBoldFont()
|
|
{
|
|
wxWindow* editor = GetEditorControl();
|
|
editor->SetFont( m_captionFont );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPoint wxPropertyGrid::GetGoodEditorDialogPosition( wxPGProperty* p,
|
|
const wxSize& sz )
|
|
{
|
|
#if wxPG_SMALL_SCREEN
|
|
// On small-screen devices, always show dialogs with default position and size.
|
|
return wxDefaultPosition;
|
|
#else
|
|
int splitterX = GetSplitterPosition();
|
|
int x = splitterX;
|
|
int y = p->GetY();
|
|
|
|
wxCHECK_MSG( y >= 0, wxPoint(-1,-1), wxT("invalid y?") );
|
|
|
|
ImprovedClientToScreen( &x, &y );
|
|
|
|
int sw = wxSystemSettings::GetMetric( ::wxSYS_SCREEN_X );
|
|
int sh = wxSystemSettings::GetMetric( ::wxSYS_SCREEN_Y );
|
|
|
|
int new_x;
|
|
int new_y;
|
|
|
|
if ( x > (sw/2) )
|
|
// left
|
|
new_x = x + (m_width-splitterX) - sz.x;
|
|
else
|
|
// right
|
|
new_x = x;
|
|
|
|
if ( y > (sh/2) )
|
|
// above
|
|
new_y = y - sz.y;
|
|
else
|
|
// below
|
|
new_y = y + m_lineHeight;
|
|
|
|
return wxPoint(new_x,new_y);
|
|
#endif
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxString& wxPropertyGrid::ExpandEscapeSequences( wxString& dst_str, wxString& src_str )
|
|
{
|
|
if ( src_str.empty() )
|
|
{
|
|
dst_str = src_str;
|
|
return src_str;
|
|
}
|
|
|
|
bool prev_is_slash = false;
|
|
|
|
wxString::const_iterator i = src_str.begin();
|
|
|
|
dst_str.clear();
|
|
|
|
for ( ; i != src_str.end(); ++i )
|
|
{
|
|
wxUniChar a = *i;
|
|
|
|
if ( a != wxS('\\') )
|
|
{
|
|
if ( !prev_is_slash )
|
|
{
|
|
dst_str << a;
|
|
}
|
|
else
|
|
{
|
|
if ( a == wxS('n') )
|
|
{
|
|
#ifdef __WXMSW__
|
|
dst_str << wxS('\n');
|
|
#else
|
|
dst_str << wxS('\n');
|
|
#endif
|
|
}
|
|
else if ( a == wxS('t') )
|
|
dst_str << wxS('\t');
|
|
else
|
|
dst_str << a;
|
|
}
|
|
prev_is_slash = false;
|
|
}
|
|
else
|
|
{
|
|
if ( prev_is_slash )
|
|
{
|
|
dst_str << wxS('\\');
|
|
prev_is_slash = false;
|
|
}
|
|
else
|
|
{
|
|
prev_is_slash = true;
|
|
}
|
|
}
|
|
}
|
|
return dst_str;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxString& wxPropertyGrid::CreateEscapeSequences( wxString& dst_str, wxString& src_str )
|
|
{
|
|
if ( src_str.empty() )
|
|
{
|
|
dst_str = src_str;
|
|
return src_str;
|
|
}
|
|
|
|
wxString::const_iterator i = src_str.begin();
|
|
wxUniChar prev_a = wxS('\0');
|
|
|
|
dst_str.clear();
|
|
|
|
for ( ; i != src_str.end(); ++i )
|
|
{
|
|
wxChar a = *i;
|
|
|
|
if ( a >= wxS(' ') )
|
|
{
|
|
// This surely is not something that requires an escape sequence.
|
|
dst_str << a;
|
|
}
|
|
else
|
|
{
|
|
// This might need...
|
|
if ( a == wxS('\r') )
|
|
{
|
|
// DOS style line end.
|
|
// Already taken care below
|
|
}
|
|
else if ( a == wxS('\n') )
|
|
// UNIX style line end.
|
|
dst_str << wxS("\\n");
|
|
else if ( a == wxS('\t') )
|
|
// Tab.
|
|
dst_str << wxS('\t');
|
|
else
|
|
{
|
|
//wxLogDebug(wxT("WARNING: Could not create escape sequence for character #%i"),(int)a);
|
|
dst_str << a;
|
|
}
|
|
}
|
|
|
|
prev_a = a;
|
|
}
|
|
return dst_str;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGProperty* wxPropertyGrid::DoGetItemAtY( int y ) const
|
|
{
|
|
// Outside?
|
|
if ( y < 0 )
|
|
return NULL;
|
|
|
|
unsigned int a = 0;
|
|
return m_pState->m_properties->GetItemAtY(y, m_lineHeight, &a);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid graphics related methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnPaint( wxPaintEvent& WXUNUSED(event) )
|
|
{
|
|
wxPaintDC dc(this);
|
|
PrepareDC(dc);
|
|
|
|
// Don't paint after destruction has begun
|
|
if ( !HasInternalFlag(wxPG_FL_INITIALIZED) )
|
|
return;
|
|
|
|
// 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;
|
|
|
|
// FIXME: This is just a workaround for a bug that causes splitters not
|
|
// to paint when other windows are being dragged over the grid.
|
|
r.x = 0;
|
|
r.width = GetClientSize().x;
|
|
|
|
r.y = vy;
|
|
r.height = GetClientSize().y;
|
|
|
|
// Repaint this rectangle
|
|
DrawItems( dc, r.y, r.y + r.height, &r );
|
|
|
|
// We assume that the size set when grid is shown
|
|
// is what is desired.
|
|
SetInternalFlag(wxPG_FL_GOOD_SIZE_SET);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DrawExpanderButton( wxDC& dc, const wxRect& rect,
|
|
wxPGProperty* property ) const
|
|
{
|
|
// Prepare rectangle to be used
|
|
wxRect r(rect);
|
|
r.x += m_gutterWidth; r.y += m_buttonSpacingY;
|
|
r.width = m_iconWidth; r.height = m_iconHeight;
|
|
|
|
#if (wxPG_USE_RENDERER_NATIVE)
|
|
//
|
|
#elif wxPG_ICON_WIDTH
|
|
// Drawing expand/collapse button manually
|
|
dc.SetPen(m_colPropFore);
|
|
if ( property->IsCategory() )
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
else
|
|
dc.SetBrush(m_colPropBack);
|
|
|
|
dc.DrawRectangle( r );
|
|
int _y = r.y+(m_iconWidth/2);
|
|
dc.DrawLine(r.x+2,_y,r.x+m_iconWidth-2,_y);
|
|
#else
|
|
wxBitmap* bmp;
|
|
#endif
|
|
|
|
if ( property->IsExpanded() )
|
|
{
|
|
// wxRenderer functions are non-mutating in nature, so it
|
|
// should be safe to cast "const wxPropertyGrid*" to "wxWindow*".
|
|
// Hopefully this does not cause problems.
|
|
#if (wxPG_USE_RENDERER_NATIVE)
|
|
wxRendererNative::Get().DrawTreeItemButton(
|
|
(wxWindow*)this,
|
|
dc,
|
|
r,
|
|
wxCONTROL_EXPANDED
|
|
);
|
|
#elif wxPG_ICON_WIDTH
|
|
//
|
|
#else
|
|
bmp = m_collbmp;
|
|
#endif
|
|
|
|
}
|
|
else
|
|
{
|
|
#if (wxPG_USE_RENDERER_NATIVE)
|
|
wxRendererNative::Get().DrawTreeItemButton(
|
|
(wxWindow*)this,
|
|
dc,
|
|
r,
|
|
0
|
|
);
|
|
#elif wxPG_ICON_WIDTH
|
|
int _x = r.x+(m_iconWidth/2);
|
|
dc.DrawLine(_x,r.y+2,_x,r.y+m_iconWidth-2);
|
|
#else
|
|
bmp = m_expandbmp;
|
|
#endif
|
|
}
|
|
|
|
#if (wxPG_USE_RENDERER_NATIVE)
|
|
//
|
|
#elif wxPG_ICON_WIDTH
|
|
//
|
|
#else
|
|
dc.DrawBitmap( *bmp, r.x, r.y, true );
|
|
#endif
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
//
|
|
// This is the one called by OnPaint event handler and others.
|
|
// topy and bottomy are already unscrolled (ie. physical)
|
|
//
|
|
void wxPropertyGrid::DrawItems( wxDC& dc,
|
|
unsigned int topItemY,
|
|
unsigned int bottomItemY,
|
|
const wxRect* itemsRect )
|
|
{
|
|
if ( IsFrozen() ||
|
|
m_height < 1 ||
|
|
bottomItemY < topItemY ||
|
|
!m_pState )
|
|
return;
|
|
|
|
m_pState->EnsureVirtualHeight();
|
|
|
|
wxRect tempItemsRect;
|
|
if ( !itemsRect )
|
|
{
|
|
tempItemsRect = wxRect(0, topItemY,
|
|
m_pState->m_width,
|
|
bottomItemY);
|
|
itemsRect = &tempItemsRect;
|
|
}
|
|
|
|
int vx, vy;
|
|
GetViewStart(&vx, &vy);
|
|
vx *= wxPG_PIXELS_PER_UNIT;
|
|
vy *= wxPG_PIXELS_PER_UNIT;
|
|
|
|
// itemRect is in virtual grid space
|
|
wxRect drawRect(itemsRect->x - vx,
|
|
itemsRect->y - vy,
|
|
itemsRect->width,
|
|
itemsRect->height);
|
|
|
|
// items added check
|
|
if ( m_pState->m_itemsAdded ) PrepareAfterItemsAdded();
|
|
|
|
int paintFinishY = 0;
|
|
|
|
if ( m_pState->m_properties->GetChildCount() > 0 )
|
|
{
|
|
wxDC* dcPtr = &dc;
|
|
bool isBuffered = false;
|
|
|
|
wxMemoryDC* bufferDC = NULL;
|
|
|
|
if ( !(GetExtraStyle() & wxPG_EX_NATIVE_DOUBLE_BUFFERING) )
|
|
{
|
|
if ( !m_doubleBuffer )
|
|
{
|
|
paintFinishY = itemsRect->y;
|
|
dcPtr = NULL;
|
|
}
|
|
else
|
|
{
|
|
bufferDC = new wxMemoryDC();
|
|
// Use the same layout direction as the window DC uses
|
|
// to ensure that the text is rendered correctly.
|
|
bufferDC->SetLayoutDirection(dc.GetLayoutDirection());
|
|
|
|
// If nothing was changed, then just copy from double-buffer
|
|
bufferDC->SelectObject( *m_doubleBuffer );
|
|
dcPtr = bufferDC;
|
|
|
|
isBuffered = true;
|
|
}
|
|
}
|
|
|
|
if ( dcPtr )
|
|
{
|
|
// paintFinishY and drawBottomY are in buffer/physical space
|
|
paintFinishY = DoDrawItems( *dcPtr, itemsRect, isBuffered );
|
|
int drawBottomY = itemsRect->y + itemsRect->height - vy;
|
|
|
|
// Clear area beyond last painted property
|
|
if ( paintFinishY < drawBottomY )
|
|
{
|
|
dcPtr->SetPen(m_colEmptySpace);
|
|
dcPtr->SetBrush(m_colEmptySpace);
|
|
dcPtr->DrawRectangle(0, paintFinishY,
|
|
m_width,
|
|
drawBottomY );
|
|
}
|
|
}
|
|
|
|
if ( bufferDC )
|
|
{
|
|
dc.Blit( drawRect.x, drawRect.y, drawRect.width,
|
|
drawRect.height,
|
|
bufferDC, 0, 0, wxCOPY );
|
|
delete bufferDC;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just clear the area
|
|
dc.SetPen(m_colEmptySpace);
|
|
dc.SetBrush(m_colEmptySpace);
|
|
dc.DrawRectangle(drawRect);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
int wxPropertyGrid::DoDrawItems( wxDC& dc,
|
|
const wxRect* itemsRect,
|
|
bool isBuffered ) const
|
|
{
|
|
const wxPGProperty* firstItem;
|
|
const wxPGProperty* lastItem;
|
|
|
|
firstItem = DoGetItemAtY(itemsRect->y);
|
|
lastItem = DoGetItemAtY(itemsRect->y+itemsRect->height-1);
|
|
|
|
if ( !lastItem )
|
|
lastItem = GetLastItem( wxPG_ITERATE_VISIBLE );
|
|
|
|
if ( IsFrozen() || m_height < 1 || firstItem == NULL )
|
|
return itemsRect->y;
|
|
|
|
wxCHECK_MSG( !m_pState->m_itemsAdded, itemsRect->y,
|
|
"no items added" );
|
|
wxASSERT( m_pState->m_properties->GetChildCount() );
|
|
|
|
int lh = m_lineHeight;
|
|
|
|
int firstItemTopY;
|
|
int lastItemBottomY;
|
|
|
|
firstItemTopY = itemsRect->y;
|
|
lastItemBottomY = itemsRect->y + itemsRect->height;
|
|
|
|
// Align y coordinates to item boundaries
|
|
firstItemTopY -= firstItemTopY % lh;
|
|
lastItemBottomY += lh - (lastItemBottomY % lh);
|
|
lastItemBottomY -= 1;
|
|
|
|
// Entire range outside scrolled, visible area?
|
|
if ( firstItemTopY >= (int)m_pState->GetVirtualHeight() ||
|
|
lastItemBottomY <= 0 )
|
|
return itemsRect->y;
|
|
|
|
wxCHECK_MSG( firstItemTopY < lastItemBottomY,
|
|
itemsRect->y,
|
|
"invalid y values" );
|
|
|
|
/*
|
|
wxLogDebug(" -> DoDrawItems ( \"%s\" -> \"%s\"
|
|
"height=%i (ch=%i), itemsRect = 0x%lX )",
|
|
firstItem->GetLabel().c_str(),
|
|
lastItem->GetLabel().c_str(),
|
|
(int)(lastItemBottomY - firstItemTopY),
|
|
(int)m_height,
|
|
(unsigned long)&itemsRect );
|
|
*/
|
|
|
|
wxRect r;
|
|
|
|
long windowStyle = m_windowStyle;
|
|
|
|
int xRelMod = 0;
|
|
|
|
//
|
|
// For now, do some manual calculation for double buffering
|
|
// - buffer's y = 0, so align itemsRect and coordinates to that
|
|
//
|
|
// TODO: In future use wxAutoBufferedPaintDC (for example)
|
|
//
|
|
int yRelMod = 0;
|
|
|
|
wxRect cr2;
|
|
|
|
if ( isBuffered )
|
|
{
|
|
xRelMod = itemsRect->x;
|
|
yRelMod = itemsRect->y;
|
|
|
|
//
|
|
// itemsRect conversion
|
|
cr2 = *itemsRect;
|
|
cr2.x -= xRelMod;
|
|
cr2.y -= yRelMod;
|
|
itemsRect = &cr2;
|
|
firstItemTopY -= yRelMod;
|
|
lastItemBottomY -= yRelMod;
|
|
}
|
|
|
|
int x = m_marginWidth - xRelMod;
|
|
|
|
wxFont normalFont = GetFont();
|
|
|
|
bool reallyFocused = (m_iFlags & wxPG_FL_FOCUSED) != 0;
|
|
|
|
bool isPgEnabled = IsEnabled();
|
|
|
|
//
|
|
// Prepare some pens and brushes that are often changed to.
|
|
//
|
|
|
|
wxBrush marginBrush(m_colMargin);
|
|
wxPen marginPen(m_colMargin);
|
|
wxBrush capbgbrush(m_colCapBack,wxBRUSHSTYLE_SOLID);
|
|
wxPen linepen(m_colLine,1,wxPENSTYLE_SOLID);
|
|
|
|
wxColour selBackCol;
|
|
if ( isPgEnabled )
|
|
selBackCol = m_colSelBack;
|
|
else
|
|
selBackCol = m_colMargin;
|
|
|
|
// pen that has same colour as text
|
|
wxPen outlinepen(m_colPropFore,1,wxPENSTYLE_SOLID);
|
|
|
|
//
|
|
// Clear margin with background colour
|
|
//
|
|
dc.SetBrush( marginBrush );
|
|
if ( !(windowStyle & wxPG_HIDE_MARGIN) )
|
|
{
|
|
dc.SetPen( *wxTRANSPARENT_PEN );
|
|
dc.DrawRectangle(-1-xRelMod,firstItemTopY-1,x+2,lastItemBottomY-firstItemTopY+2);
|
|
}
|
|
|
|
const wxPGProperty* firstSelected = GetSelection();
|
|
const wxPropertyGridPageState* state = m_pState;
|
|
const wxArrayInt& colWidths = state->m_colWidths;
|
|
|
|
// TODO: Only render columns that are within clipping region.
|
|
|
|
dc.SetFont(normalFont);
|
|
|
|
wxPropertyGridConstIterator it( state, wxPG_ITERATE_VISIBLE, firstItem );
|
|
int endScanBottomY = lastItemBottomY + lh;
|
|
int y = firstItemTopY;
|
|
|
|
//
|
|
// Pregenerate list of visible properties.
|
|
wxArrayPGProperty visPropArray;
|
|
visPropArray.reserve((m_height/m_lineHeight)+6);
|
|
|
|
for ( ; !it.AtEnd(); it.Next() )
|
|
{
|
|
const wxPGProperty* p = *it;
|
|
|
|
if ( !p->HasFlag(wxPG_PROP_HIDDEN) )
|
|
{
|
|
visPropArray.push_back((wxPGProperty*)p);
|
|
|
|
if ( y > endScanBottomY )
|
|
break;
|
|
|
|
y += lh;
|
|
}
|
|
}
|
|
|
|
visPropArray.push_back(NULL);
|
|
|
|
wxPGProperty* nextP = visPropArray[0];
|
|
|
|
int gridWidth = state->m_width;
|
|
|
|
y = firstItemTopY;
|
|
for ( unsigned int arrInd=1;
|
|
nextP && y <= lastItemBottomY;
|
|
arrInd++ )
|
|
{
|
|
wxPGProperty* p = nextP;
|
|
nextP = visPropArray[arrInd];
|
|
|
|
int rowHeight = m_fontHeight+(m_spacingy*2)+1;
|
|
int textMarginHere = x;
|
|
int renderFlags = 0;
|
|
|
|
int greyDepth = m_marginWidth;
|
|
if ( !(windowStyle & wxPG_HIDE_CATEGORIES) )
|
|
greyDepth = (((int)p->m_depthBgCol)-1) * m_subgroup_extramargin + m_marginWidth;
|
|
|
|
int greyDepthX = greyDepth - xRelMod;
|
|
|
|
// Use basic depth if in non-categoric mode and parent is base array.
|
|
if ( !(windowStyle & wxPG_HIDE_CATEGORIES) || p->GetParent() != m_pState->m_properties )
|
|
{
|
|
textMarginHere += ((unsigned int)((p->m_depth-1)*m_subgroup_extramargin));
|
|
}
|
|
|
|
// Paint margin area
|
|
dc.SetBrush(marginBrush);
|
|
dc.SetPen(marginPen);
|
|
dc.DrawRectangle( -xRelMod, y, greyDepth, lh );
|
|
|
|
dc.SetPen( linepen );
|
|
|
|
int y2 = y + lh;
|
|
|
|
#ifdef __WXMSW__
|
|
// Margin Edge
|
|
// Modified by JACS to not draw a margin if wxPG_HIDE_MARGIN is specified, since it
|
|
// looks better, at least under Windows when we have a themed border (the themed-window-specific
|
|
// whitespace between the real border and the propgrid margin exacerbates the double-border look).
|
|
|
|
// Is this or its parent themed?
|
|
bool suppressMarginEdge = (GetWindowStyle() & wxPG_HIDE_MARGIN) &&
|
|
(((GetWindowStyle() & wxBORDER_MASK) == wxBORDER_THEME) ||
|
|
(((GetWindowStyle() & wxBORDER_MASK) == wxBORDER_NONE) && ((GetParent()->GetWindowStyle() & wxBORDER_MASK) == wxBORDER_THEME)));
|
|
#else
|
|
bool suppressMarginEdge = false;
|
|
#endif
|
|
if (!suppressMarginEdge)
|
|
dc.DrawLine( greyDepthX, y, greyDepthX, y2 );
|
|
else
|
|
{
|
|
// Blank out the margin edge
|
|
dc.SetPen(wxPen(GetBackgroundColour()));
|
|
dc.DrawLine( greyDepthX, y, greyDepthX, y2 );
|
|
dc.SetPen( linepen );
|
|
}
|
|
|
|
// Splitters
|
|
unsigned int si;
|
|
int sx = x;
|
|
|
|
for ( si=0; si<colWidths.size(); si++ )
|
|
{
|
|
sx += colWidths[si];
|
|
dc.DrawLine( sx, y, sx, y2 );
|
|
}
|
|
|
|
// Horizontal Line, below
|
|
// (not if both this and next is category caption)
|
|
if ( p->IsCategory() &&
|
|
nextP && nextP->IsCategory() )
|
|
dc.SetPen(m_colCapBack);
|
|
|
|
dc.DrawLine( greyDepthX, y2-1, gridWidth-xRelMod, y2-1 );
|
|
|
|
//
|
|
// Need to override row colours?
|
|
wxColour rowFgCol;
|
|
wxColour rowBgCol;
|
|
|
|
bool isSelected = state->DoIsPropertySelected(p);
|
|
|
|
if ( !isSelected )
|
|
{
|
|
// Disabled may get different colour.
|
|
if ( !p->IsEnabled() )
|
|
{
|
|
renderFlags |= wxPGCellRenderer::Disabled |
|
|
wxPGCellRenderer::DontUseCellFgCol;
|
|
rowFgCol = m_colDisPropFore;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
renderFlags |= wxPGCellRenderer::Selected;
|
|
|
|
if ( !p->IsCategory() )
|
|
{
|
|
renderFlags |= wxPGCellRenderer::DontUseCellFgCol |
|
|
wxPGCellRenderer::DontUseCellBgCol;
|
|
|
|
if ( reallyFocused && p == firstSelected )
|
|
{
|
|
rowFgCol = m_colSelFore;
|
|
rowBgCol = selBackCol;
|
|
}
|
|
else if ( isPgEnabled )
|
|
{
|
|
rowFgCol = m_colPropFore;
|
|
if ( p == firstSelected )
|
|
rowBgCol = m_colMargin;
|
|
else
|
|
rowBgCol = selBackCol;
|
|
}
|
|
else
|
|
{
|
|
rowFgCol = m_colDisPropFore;
|
|
rowBgCol = selBackCol;
|
|
}
|
|
}
|
|
}
|
|
|
|
wxBrush rowBgBrush;
|
|
|
|
if ( rowBgCol.IsOk() )
|
|
rowBgBrush = wxBrush(rowBgCol);
|
|
|
|
if ( HasInternalFlag(wxPG_FL_CELL_OVERRIDES_SEL) )
|
|
renderFlags = renderFlags & ~wxPGCellRenderer::DontUseCellColours;
|
|
|
|
//
|
|
// Fill additional margin area with background colour of first cell
|
|
if ( greyDepthX < textMarginHere )
|
|
{
|
|
if ( !(renderFlags & wxPGCellRenderer::DontUseCellBgCol) )
|
|
{
|
|
wxPGCell& cell = p->GetCell(0);
|
|
rowBgCol = cell.GetBgCol();
|
|
rowBgBrush = wxBrush(rowBgCol);
|
|
}
|
|
dc.SetBrush(rowBgBrush);
|
|
dc.SetPen(rowBgCol);
|
|
dc.DrawRectangle(greyDepthX+1, y,
|
|
textMarginHere-greyDepthX, lh-1);
|
|
}
|
|
|
|
bool fontChanged = false;
|
|
|
|
// Expander button rectangle
|
|
wxRect butRect( ((p->m_depth - 1) * m_subgroup_extramargin) - xRelMod,
|
|
y,
|
|
m_marginWidth,
|
|
lh );
|
|
|
|
// Default cell rect fill the entire row
|
|
wxRect cellRect(greyDepthX, y,
|
|
gridWidth - greyDepth + 2, rowHeight-1 );
|
|
|
|
bool isCategory = p->IsCategory();
|
|
|
|
if ( isCategory )
|
|
{
|
|
dc.SetFont(m_captionFont);
|
|
fontChanged = true;
|
|
|
|
if ( renderFlags & wxPGCellRenderer::DontUseCellBgCol )
|
|
{
|
|
dc.SetBrush(rowBgBrush);
|
|
dc.SetPen(rowBgCol);
|
|
}
|
|
|
|
if ( renderFlags & wxPGCellRenderer::DontUseCellFgCol )
|
|
{
|
|
dc.SetTextForeground(rowFgCol);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fine tune button rectangle to actually fit the cell
|
|
if ( butRect.x > 0 )
|
|
butRect.x += IN_CELL_EXPANDER_BUTTON_X_ADJUST;
|
|
|
|
if ( p->m_flags & wxPG_PROP_MODIFIED &&
|
|
(windowStyle & wxPG_BOLD_MODIFIED) )
|
|
{
|
|
dc.SetFont(m_captionFont);
|
|
fontChanged = true;
|
|
}
|
|
|
|
// Magic fine-tuning for non-category rows
|
|
cellRect.x += 1;
|
|
}
|
|
|
|
int firstCellWidth = colWidths[0] - (greyDepthX - m_marginWidth);
|
|
int firstCellX = cellRect.x;
|
|
|
|
// Calculate cellRect.x for the last cell
|
|
unsigned int ci = 0;
|
|
int cellX = x + 1;
|
|
for ( ci=0; ci<colWidths.size(); ci++ )
|
|
cellX += colWidths[ci];
|
|
cellRect.x = cellX;
|
|
|
|
// Draw cells from back to front so that we can easily tell if the
|
|
// cell on the right was empty from text
|
|
bool prevFilled = true;
|
|
ci = colWidths.size();
|
|
do
|
|
{
|
|
ci--;
|
|
|
|
int textXAdd = 0;
|
|
|
|
if ( ci == 0 )
|
|
{
|
|
textXAdd = textMarginHere - greyDepthX;
|
|
cellRect.width = firstCellWidth;
|
|
cellRect.x = firstCellX;
|
|
}
|
|
else
|
|
{
|
|
int colWidth = colWidths[ci];
|
|
cellRect.width = colWidth;
|
|
cellRect.x -= colWidth;
|
|
}
|
|
|
|
// Merge with column to the right?
|
|
if ( !prevFilled && isCategory )
|
|
{
|
|
cellRect.width += colWidths[ci+1];
|
|
}
|
|
|
|
if ( !isCategory )
|
|
cellRect.width -= 1;
|
|
|
|
wxWindow* cellEditor = NULL;
|
|
int cellRenderFlags = renderFlags;
|
|
|
|
// Tree Item Button (must be drawn before clipping is set up)
|
|
if ( ci == 0 && !HasFlag(wxPG_HIDE_MARGIN) && p->HasVisibleChildren() )
|
|
DrawExpanderButton( dc, butRect, p );
|
|
|
|
// Background
|
|
if ( isSelected && (ci == 1 || ci == m_selColumn) )
|
|
{
|
|
if ( p == firstSelected )
|
|
{
|
|
if ( ci == 1 && m_wndEditor )
|
|
cellEditor = m_wndEditor;
|
|
else if ( ci == m_selColumn && m_labelEditor )
|
|
cellEditor = m_labelEditor;
|
|
}
|
|
|
|
if ( cellEditor )
|
|
{
|
|
wxColour editorBgCol =
|
|
cellEditor->GetBackgroundColour();
|
|
dc.SetBrush(editorBgCol);
|
|
dc.SetPen(editorBgCol);
|
|
dc.SetTextForeground(m_colPropFore);
|
|
dc.DrawRectangle(cellRect);
|
|
|
|
if ( m_dragStatus != 0 ||
|
|
(m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE) )
|
|
cellEditor = NULL;
|
|
}
|
|
else
|
|
{
|
|
dc.SetBrush(m_colPropBack);
|
|
dc.SetPen(m_colPropBack);
|
|
if ( p->IsEnabled() )
|
|
dc.SetTextForeground(m_colPropFore);
|
|
else
|
|
dc.SetTextForeground(m_colDisPropFore);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( renderFlags & wxPGCellRenderer::DontUseCellBgCol )
|
|
{
|
|
dc.SetBrush(rowBgBrush);
|
|
dc.SetPen(rowBgCol);
|
|
}
|
|
|
|
if ( renderFlags & wxPGCellRenderer::DontUseCellFgCol )
|
|
{
|
|
dc.SetTextForeground(rowFgCol);
|
|
}
|
|
}
|
|
|
|
dc.SetClippingRegion(cellRect);
|
|
|
|
cellRect.x += textXAdd;
|
|
cellRect.width -= textXAdd;
|
|
|
|
// Foreground
|
|
if ( !cellEditor )
|
|
{
|
|
wxPGCellRenderer* renderer;
|
|
int cmnVal = p->GetCommonValue();
|
|
if ( cmnVal == -1 || ci != 1 )
|
|
{
|
|
renderer = p->GetCellRenderer(ci);
|
|
prevFilled = renderer->Render(dc, cellRect, this,
|
|
p, ci, -1,
|
|
cellRenderFlags );
|
|
}
|
|
else
|
|
{
|
|
renderer = GetCommonValue(cmnVal)->GetRenderer();
|
|
prevFilled = renderer->Render(dc, cellRect, this,
|
|
p, ci, -1,
|
|
cellRenderFlags );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
prevFilled = true;
|
|
}
|
|
|
|
dc.DestroyClippingRegion(); // Is this really necessary?
|
|
}
|
|
while ( ci > 0 );
|
|
|
|
if ( fontChanged )
|
|
dc.SetFont(normalFont);
|
|
|
|
y += rowHeight;
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxRect wxPropertyGrid::GetPropertyRect( const wxPGProperty* p1, const wxPGProperty* p2 ) const
|
|
{
|
|
wxRect r;
|
|
|
|
if ( m_width < 10 || m_height < 10 ||
|
|
!m_pState->m_properties->GetChildCount() ||
|
|
p1 == NULL )
|
|
return wxRect(0,0,0,0);
|
|
|
|
int vy = 0;
|
|
|
|
//
|
|
// Return rect which encloses the given property range
|
|
// (in logical grid coordinates)
|
|
//
|
|
|
|
int visTop = p1->GetY();
|
|
int visBottom;
|
|
if ( p2 )
|
|
visBottom = p2->GetY() + m_lineHeight;
|
|
else
|
|
visBottom = m_height + visTop;
|
|
|
|
// If seleced property is inside the range, we'll extend the range to include
|
|
// control's size.
|
|
wxPGProperty* selected = GetSelection();
|
|
if ( selected )
|
|
{
|
|
int selectedY = selected->GetY();
|
|
if ( selectedY >= visTop && selectedY < visBottom )
|
|
{
|
|
wxWindow* editor = GetEditorControl();
|
|
if ( editor )
|
|
{
|
|
int visBottom2 = selectedY + editor->GetSize().y;
|
|
if ( visBottom2 > visBottom )
|
|
visBottom = visBottom2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return wxRect(0,visTop-vy,m_pState->m_width,visBottom-visTop);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DrawItems( const wxPGProperty* p1, const wxPGProperty* p2 )
|
|
{
|
|
if ( IsFrozen() )
|
|
return;
|
|
|
|
if ( m_pState->m_itemsAdded )
|
|
PrepareAfterItemsAdded();
|
|
|
|
wxRect r = GetPropertyRect(p1, p2);
|
|
if ( r.width > 0 )
|
|
{
|
|
// Convert rectangle from logical grid coordinates to physical ones
|
|
int vx, vy;
|
|
GetViewStart(&vx, &vy);
|
|
vx *= wxPG_PIXELS_PER_UNIT;
|
|
vy *= wxPG_PIXELS_PER_UNIT;
|
|
r.x -= vx;
|
|
r.y -= vy;
|
|
RefreshRect(r);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::RefreshProperty( wxPGProperty* p )
|
|
{
|
|
if ( m_pState->DoIsPropertySelected(p) || p->IsChildSelected(true) )
|
|
{
|
|
// NB: We must copy the selection.
|
|
wxArrayPGProperty selection = m_pState->m_selection;
|
|
DoSetSelection(selection, wxPG_SEL_FORCE);
|
|
}
|
|
|
|
DrawItemAndChildren(p);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DrawItemAndValueRelated( wxPGProperty* p )
|
|
{
|
|
if ( IsFrozen() )
|
|
return;
|
|
|
|
// Draw item, children, and parent too, if it is not category
|
|
wxPGProperty* parent = p->GetParent();
|
|
|
|
while ( parent &&
|
|
!parent->IsCategory() &&
|
|
parent->GetParent() )
|
|
{
|
|
DrawItem(parent);
|
|
parent = parent->GetParent();
|
|
}
|
|
|
|
DrawItemAndChildren(p);
|
|
}
|
|
|
|
void wxPropertyGrid::DrawItemAndChildren( wxPGProperty* p )
|
|
{
|
|
wxCHECK_RET( p, wxT("invalid property id") );
|
|
|
|
// Do not draw if in non-visible page
|
|
if ( p->GetParentState() != m_pState )
|
|
return;
|
|
|
|
// do not draw a single item if multiple pending
|
|
if ( m_pState->m_itemsAdded || IsFrozen() )
|
|
return;
|
|
|
|
// Update child control.
|
|
wxPGProperty* selected = GetSelection();
|
|
if ( selected && selected->GetParent() == p )
|
|
RefreshEditor();
|
|
|
|
const wxPGProperty* lastDrawn = p->GetLastVisibleSubItem();
|
|
|
|
DrawItems(p, lastDrawn);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::Refresh( bool WXUNUSED(eraseBackground),
|
|
const wxRect *rect )
|
|
{
|
|
PrepareAfterItemsAdded();
|
|
|
|
wxWindow::Refresh(false, rect);
|
|
|
|
#if wxPG_REFRESH_CONTROLS
|
|
// I think this really helps only GTK+1.2
|
|
if ( m_wndEditor ) m_wndEditor->Refresh();
|
|
if ( m_wndEditor2 ) m_wndEditor2->Refresh();
|
|
#endif
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid global operations
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::Clear()
|
|
{
|
|
m_pState->DoClear();
|
|
|
|
m_propHover = NULL;
|
|
|
|
m_prevVY = 0;
|
|
|
|
RecalculateVirtualSize();
|
|
|
|
// Need to clear some area at the end
|
|
if ( !IsFrozen() )
|
|
RefreshRect(wxRect(0, 0, m_width, m_height));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::EnableCategories( bool enable )
|
|
{
|
|
DoClearSelection();
|
|
|
|
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 ( !IsFrozen() )
|
|
{
|
|
if ( m_windowStyle & wxPG_AUTO_SORT )
|
|
{
|
|
m_pState->m_itemsAdded = true; // force
|
|
PrepareAfterItemsAdded();
|
|
}
|
|
}
|
|
else
|
|
m_pState->m_itemsAdded = true;
|
|
|
|
// No need for RecalculateVirtualSize() here - it is already called in
|
|
// wxPropertyGridPageState method above.
|
|
|
|
Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SwitchState( wxPropertyGridPageState* pNewState )
|
|
{
|
|
wxASSERT( pNewState );
|
|
wxASSERT( pNewState->GetGrid() );
|
|
|
|
if ( pNewState == m_pState )
|
|
return;
|
|
|
|
wxArrayPGProperty oldSelection = m_pState->m_selection;
|
|
|
|
// Call ClearSelection() instead of DoClearSelection()
|
|
// so that selection clear events are not sent.
|
|
ClearSelection();
|
|
|
|
m_pState->m_selection = oldSelection;
|
|
|
|
bool orig_mode = m_pState->IsInNonCatMode();
|
|
bool new_state_mode = pNewState->IsInNonCatMode();
|
|
|
|
m_pState = pNewState;
|
|
|
|
// Validate width
|
|
int pgWidth = GetClientSize().x;
|
|
if ( HasVirtualWidth() )
|
|
{
|
|
int minWidth = pgWidth;
|
|
if ( pNewState->m_width < minWidth )
|
|
{
|
|
pNewState->m_width = minWidth;
|
|
pNewState->CheckColumnWidths();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Just in case, fully re-center splitter
|
|
//if ( HasFlag( wxPG_SPLITTER_AUTO_CENTER ) )
|
|
// pNewState->m_fSplitterX = -1.0;
|
|
|
|
pNewState->OnClientWidthChange(pgWidth,
|
|
pgWidth - pNewState->m_width);
|
|
}
|
|
|
|
m_propHover = 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 ( !IsFrozen() )
|
|
{
|
|
// Refresh, if not frozen.
|
|
m_pState->PrepareAfterItemsAdded();
|
|
|
|
// Reselect (Use SetSelection() instead of Do-variant so that
|
|
// events won't be sent).
|
|
SetSelection(m_pState->m_selection);
|
|
|
|
RecalculateVirtualSize(0);
|
|
Refresh();
|
|
}
|
|
else
|
|
m_pState->m_itemsAdded = true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Call to SetSplitterPosition will always disable splitter auto-centering
|
|
// if parent window is shown.
|
|
void wxPropertyGrid::DoSetSplitterPosition( int newxpos,
|
|
int splitterIndex,
|
|
int flags )
|
|
{
|
|
if ( ( newxpos < wxPG_DRAG_MARGIN ) )
|
|
return;
|
|
|
|
wxPropertyGridPageState* state = m_pState;
|
|
|
|
if ( flags & wxPG_SPLITTER_FROM_EVENT )
|
|
state->m_dontCenterSplitter = true;
|
|
|
|
state->DoSetSplitterPosition(newxpos, splitterIndex, flags);
|
|
|
|
if ( flags & wxPG_SPLITTER_REFRESH )
|
|
{
|
|
if ( GetSelection() )
|
|
CorrectEditorWidgetSizeX();
|
|
|
|
Refresh();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::ResetColumnSizes( bool enableAutoResizing )
|
|
{
|
|
wxPropertyGridPageState* state = m_pState;
|
|
if ( state )
|
|
state->ResetColumnSizes(0);
|
|
|
|
if ( enableAutoResizing && HasFlag(wxPG_SPLITTER_AUTO_CENTER) )
|
|
m_pState->m_dontCenterSplitter = false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::CenterSplitter( bool enableAutoResizing )
|
|
{
|
|
SetSplitterPosition( m_width/2 );
|
|
if ( enableAutoResizing && HasFlag(wxPG_SPLITTER_AUTO_CENTER) )
|
|
m_pState->m_dontCenterSplitter = false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid item iteration (GetNextProperty etc.) methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Returns nearest paint visible property (such that will be painted unless
|
|
// window is scrolled or resized). If given property is paint visible, then
|
|
// it itself will be returned
|
|
wxPGProperty* wxPropertyGrid::GetNearestPaintVisible( wxPGProperty* p ) const
|
|
{
|
|
int vx,vy1;// Top left corner of client
|
|
GetViewStart(&vx,&vy1);
|
|
vy1 *= wxPG_PIXELS_PER_UNIT;
|
|
|
|
int vy2 = vy1 + m_height;
|
|
int propY = p->GetY2(m_lineHeight);
|
|
|
|
if ( (propY + m_lineHeight) < vy1 )
|
|
{
|
|
// Too high
|
|
return DoGetItemAtY( vy1 );
|
|
}
|
|
else if ( propY > vy2 )
|
|
{
|
|
// Too low
|
|
return DoGetItemAtY( vy2 );
|
|
}
|
|
|
|
// Itself paint visible
|
|
return p;
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Methods related to change in value, value modification and sending events
|
|
// -----------------------------------------------------------------------
|
|
|
|
// commits any changes in editor of selected property
|
|
// return true if validation did not fail
|
|
// flags are same as with DoSelectProperty
|
|
bool wxPropertyGrid::CommitChangesFromEditor( wxUint32 flags )
|
|
{
|
|
// Committing already?
|
|
if ( m_inCommitChangesFromEditor )
|
|
return true;
|
|
|
|
// Don't do this if already processing editor event. It might
|
|
// induce recursive dialogs and crap like that.
|
|
if ( m_iFlags & wxPG_FL_IN_HANDLECUSTOMEDITOREVENT )
|
|
{
|
|
if ( m_inDoPropertyChanged )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wxPGProperty* selected = GetSelection();
|
|
|
|
if ( m_wndEditor &&
|
|
IsEditorsValueModified() &&
|
|
(m_iFlags & wxPG_FL_INITIALIZED) &&
|
|
selected )
|
|
{
|
|
m_inCommitChangesFromEditor = true;
|
|
|
|
wxVariant variant(selected->GetValueRef());
|
|
bool valueIsPending = false;
|
|
|
|
// JACS - necessary to avoid new focus being found spuriously within OnIdle
|
|
// due to another window getting focus
|
|
wxWindow* oldFocus = m_curFocused;
|
|
|
|
bool validationFailure = false;
|
|
bool forceSuccess = (flags & (wxPG_SEL_NOVALIDATE|wxPG_SEL_FORCE)) ? true : false;
|
|
|
|
m_chgInfo_changedProperty = NULL;
|
|
|
|
// If truly modified, schedule value as pending.
|
|
if ( selected->GetEditorClass()->
|
|
GetValueFromControl( variant,
|
|
selected,
|
|
GetEditorControl() ) )
|
|
{
|
|
if ( DoEditorValidate() &&
|
|
PerformValidation(selected, variant) )
|
|
{
|
|
valueIsPending = true;
|
|
}
|
|
else
|
|
{
|
|
validationFailure = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
EditorsValueWasNotModified();
|
|
}
|
|
|
|
m_inCommitChangesFromEditor = false;
|
|
|
|
bool res = true;
|
|
|
|
if ( validationFailure && !forceSuccess )
|
|
{
|
|
if (oldFocus)
|
|
{
|
|
oldFocus->SetFocus();
|
|
m_curFocused = oldFocus;
|
|
}
|
|
|
|
res = OnValidationFailure(selected, variant);
|
|
|
|
// Now prevent further validation failure messages
|
|
if ( res )
|
|
{
|
|
EditorsValueWasNotModified();
|
|
OnValidationFailureReset(selected);
|
|
}
|
|
}
|
|
else if ( valueIsPending )
|
|
{
|
|
DoPropertyChanged( selected, flags );
|
|
EditorsValueWasNotModified();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::PerformValidation( wxPGProperty* p, wxVariant& pendingValue,
|
|
int flags )
|
|
{
|
|
//
|
|
// Runs all validation functionality.
|
|
// Returns true if value passes all tests.
|
|
//
|
|
|
|
m_validationInfo.m_failureBehavior = m_permanentValidationFailureBehavior;
|
|
m_validationInfo.m_isFailing = true;
|
|
|
|
//
|
|
// Variant list a special value that cannot be validated
|
|
// by normal means.
|
|
if ( pendingValue.GetType() != wxPG_VARIANT_TYPE_LIST )
|
|
{
|
|
if ( !p->ValidateValue(pendingValue, m_validationInfo) )
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Adapt list to child values, if necessary
|
|
wxVariant* pPendingValue = &pendingValue;
|
|
wxVariant* pList = NULL;
|
|
|
|
// If parent has wxPG_PROP_AGGREGATE flag, or uses composite
|
|
// string value, then we need treat as it was changed instead
|
|
// (or, in addition, as is the case with composite string parent).
|
|
// This includes creating list variant for child values.
|
|
|
|
wxPGProperty* pwc = p->GetParent();
|
|
wxPGProperty* changedProperty = p;
|
|
wxPGProperty* baseChangedProperty = changedProperty;
|
|
wxVariant bcpPendingList;
|
|
|
|
wxVariant listValue = pendingValue;
|
|
listValue.SetName(p->GetBaseName());
|
|
|
|
while ( pwc &&
|
|
(pwc->HasFlag(wxPG_PROP_AGGREGATE) || pwc->HasFlag(wxPG_PROP_COMPOSED_VALUE)) )
|
|
{
|
|
wxVariantList tempList;
|
|
wxVariant lv(tempList, pwc->GetBaseName());
|
|
lv.Append(listValue);
|
|
listValue = lv;
|
|
pPendingValue = &listValue;
|
|
|
|
if ( pwc->HasFlag(wxPG_PROP_AGGREGATE) )
|
|
{
|
|
baseChangedProperty = pwc;
|
|
bcpPendingList = lv;
|
|
}
|
|
|
|
changedProperty = pwc;
|
|
pwc = pwc->GetParent();
|
|
}
|
|
|
|
wxVariant value;
|
|
wxPGProperty* evtChangingProperty = changedProperty;
|
|
|
|
if ( pPendingValue->GetType() != wxPG_VARIANT_TYPE_LIST )
|
|
{
|
|
value = *pPendingValue;
|
|
}
|
|
else
|
|
{
|
|
// Convert list to child values
|
|
pList = pPendingValue;
|
|
changedProperty->AdaptListToValue( *pPendingValue, &value );
|
|
}
|
|
|
|
wxVariant evtChangingValue = value;
|
|
|
|
if ( flags & SendEvtChanging )
|
|
{
|
|
// FIXME: After proper ValueToString()s added, remove
|
|
// this. It is just a temporary fix, as evt_changing
|
|
// will simply not work for wxPG_PROP_COMPOSED_VALUE
|
|
// (unless it is selected, and textctrl editor is open).
|
|
if ( changedProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) )
|
|
{
|
|
evtChangingProperty = baseChangedProperty;
|
|
if ( evtChangingProperty != p )
|
|
{
|
|
evtChangingProperty->AdaptListToValue( bcpPendingList, &evtChangingValue );
|
|
}
|
|
else
|
|
{
|
|
evtChangingValue = pendingValue;
|
|
}
|
|
}
|
|
|
|
if ( evtChangingProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) )
|
|
{
|
|
if ( changedProperty == GetSelection() )
|
|
{
|
|
wxWindow* editor = GetEditorControl();
|
|
wxASSERT( wxDynamicCast(editor, wxTextCtrl) );
|
|
evtChangingValue = wxStaticCast(editor, wxTextCtrl)->GetValue();
|
|
}
|
|
else
|
|
{
|
|
wxLogDebug(wxT("WARNING: wxEVT_PG_CHANGING is about to happen with old value."));
|
|
}
|
|
}
|
|
}
|
|
|
|
wxASSERT( m_chgInfo_changedProperty == NULL );
|
|
m_chgInfo_changedProperty = changedProperty;
|
|
m_chgInfo_baseChangedProperty = baseChangedProperty;
|
|
m_chgInfo_pendingValue = value;
|
|
|
|
if ( pList )
|
|
m_chgInfo_valueList = *pList;
|
|
else
|
|
m_chgInfo_valueList.MakeNull();
|
|
|
|
// If changedProperty is not property which value was edited,
|
|
// then call wxPGProperty::ValidateValue() for that as well.
|
|
if ( p != changedProperty && value.GetType() != wxPG_VARIANT_TYPE_LIST )
|
|
{
|
|
if ( !changedProperty->ValidateValue(value, m_validationInfo) )
|
|
return false;
|
|
}
|
|
|
|
if ( flags & SendEvtChanging )
|
|
{
|
|
// SendEvent returns true if event was vetoed
|
|
if ( SendEvent( wxEVT_PG_CHANGING, evtChangingProperty,
|
|
&evtChangingValue ) )
|
|
return false;
|
|
}
|
|
|
|
if ( flags & IsStandaloneValidation )
|
|
{
|
|
// If called in 'generic' context, we need to reset
|
|
// m_chgInfo_changedProperty and write back translated value.
|
|
m_chgInfo_changedProperty = NULL;
|
|
pendingValue = value;
|
|
}
|
|
|
|
m_validationInfo.m_isFailing = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
#if wxUSE_STATUSBAR
|
|
wxStatusBar* wxPropertyGrid::GetStatusBar()
|
|
{
|
|
wxWindow* topWnd = ::wxGetTopLevelParent(this);
|
|
if ( wxDynamicCast(topWnd, wxFrame) )
|
|
{
|
|
wxFrame* pFrame = wxStaticCast(topWnd, wxFrame);
|
|
if ( pFrame )
|
|
return pFrame->GetStatusBar();
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoShowPropertyError( wxPGProperty* WXUNUSED(property), const wxString& msg )
|
|
{
|
|
if ( msg.empty() )
|
|
return;
|
|
|
|
#if wxUSE_STATUSBAR
|
|
if ( !wxPGGlobalVars->m_offline )
|
|
{
|
|
wxStatusBar* pStatusBar = GetStatusBar();
|
|
if ( pStatusBar )
|
|
{
|
|
pStatusBar->SetStatusText(msg);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
::wxMessageBox(msg, _("Property Error"));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoHidePropertyError( wxPGProperty* WXUNUSED(property) )
|
|
{
|
|
#if wxUSE_STATUSBAR
|
|
if ( !wxPGGlobalVars->m_offline )
|
|
{
|
|
wxStatusBar* pStatusBar = GetStatusBar();
|
|
if ( pStatusBar )
|
|
{
|
|
pStatusBar->SetStatusText(wxEmptyString);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::OnValidationFailure( wxPGProperty* property,
|
|
wxVariant& invalidValue )
|
|
{
|
|
if ( m_inOnValidationFailure )
|
|
return true;
|
|
|
|
m_inOnValidationFailure = true;
|
|
wxON_BLOCK_EXIT_SET(m_inOnValidationFailure, false);
|
|
|
|
wxWindow* editor = GetEditorControl();
|
|
int vfb = m_validationInfo.m_failureBehavior;
|
|
|
|
if ( m_inDoSelectProperty )
|
|
{
|
|
// When property selection is being changed, do not display any
|
|
// messages, if some were already shown for this property.
|
|
if ( property->HasFlag(wxPG_PROP_INVALID_VALUE) )
|
|
{
|
|
m_validationInfo.m_failureBehavior =
|
|
vfb & ~(wxPG_VFB_SHOW_MESSAGE |
|
|
wxPG_VFB_SHOW_MESSAGEBOX |
|
|
wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR);
|
|
}
|
|
}
|
|
|
|
// First call property's handler
|
|
property->OnValidationFailure(invalidValue);
|
|
|
|
bool res = DoOnValidationFailure(property, invalidValue);
|
|
|
|
//
|
|
// For non-wxTextCtrl editors, we do need to revert the value
|
|
if ( !wxDynamicCast(editor, wxTextCtrl) &&
|
|
property == GetSelection() )
|
|
{
|
|
property->GetEditorClass()->UpdateControl(property, editor);
|
|
}
|
|
|
|
property->SetFlag(wxPG_PROP_INVALID_VALUE);
|
|
|
|
return res;
|
|
}
|
|
|
|
bool wxPropertyGrid::DoOnValidationFailure( wxPGProperty* property, wxVariant& WXUNUSED(invalidValue) )
|
|
{
|
|
int vfb = m_validationInfo.m_failureBehavior;
|
|
|
|
if ( vfb & wxPG_VFB_BEEP )
|
|
::wxBell();
|
|
|
|
if ( (vfb & wxPG_VFB_MARK_CELL) &&
|
|
!property->HasFlag(wxPG_PROP_INVALID_VALUE) )
|
|
{
|
|
unsigned int colCount = m_pState->GetColumnCount();
|
|
|
|
// We need backup marked property's cells
|
|
m_propCellsBackup = property->m_cells;
|
|
|
|
wxColour vfbFg = *wxWHITE;
|
|
wxColour vfbBg = *wxRED;
|
|
|
|
property->EnsureCells(colCount);
|
|
|
|
for ( unsigned int i=0; i<colCount; i++ )
|
|
{
|
|
wxPGCell& cell = property->m_cells[i];
|
|
cell.SetFgCol(vfbFg);
|
|
cell.SetBgCol(vfbBg);
|
|
}
|
|
|
|
DrawItemAndChildren(property);
|
|
|
|
if ( property == GetSelection() )
|
|
{
|
|
SetInternalFlag(wxPG_FL_CELL_OVERRIDES_SEL);
|
|
|
|
wxWindow* editor = GetEditorControl();
|
|
if ( editor )
|
|
{
|
|
editor->SetForegroundColour(vfbFg);
|
|
editor->SetBackgroundColour(vfbBg);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( vfb & (wxPG_VFB_SHOW_MESSAGE |
|
|
wxPG_VFB_SHOW_MESSAGEBOX |
|
|
wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR) )
|
|
{
|
|
wxString msg = m_validationInfo.m_failureMessage;
|
|
|
|
if ( msg.empty() )
|
|
msg = _("You have entered invalid value. Press ESC to cancel editing.");
|
|
|
|
#if wxUSE_STATUSBAR
|
|
if ( vfb & wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR )
|
|
{
|
|
if ( !wxPGGlobalVars->m_offline )
|
|
{
|
|
wxStatusBar* pStatusBar = GetStatusBar();
|
|
if ( pStatusBar )
|
|
pStatusBar->SetStatusText(msg);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( vfb & wxPG_VFB_SHOW_MESSAGE )
|
|
DoShowPropertyError(property, msg);
|
|
|
|
if ( vfb & wxPG_VFB_SHOW_MESSAGEBOX )
|
|
::wxMessageBox(msg, _("Property Error"));
|
|
}
|
|
|
|
return (vfb & wxPG_VFB_STAY_IN_PROPERTY) ? false : true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::DoOnValidationFailureReset( wxPGProperty* property )
|
|
{
|
|
int vfb = m_validationInfo.m_failureBehavior;
|
|
|
|
if ( vfb & wxPG_VFB_MARK_CELL )
|
|
{
|
|
// Revert cells
|
|
property->m_cells = m_propCellsBackup;
|
|
|
|
ClearInternalFlag(wxPG_FL_CELL_OVERRIDES_SEL);
|
|
|
|
if ( property == GetSelection() && GetEditorControl() )
|
|
{
|
|
// Calling this will recreate the control, thus resetting its colour
|
|
RefreshProperty(property);
|
|
}
|
|
else
|
|
{
|
|
DrawItemAndChildren(property);
|
|
}
|
|
}
|
|
|
|
#if wxUSE_STATUSBAR
|
|
if ( vfb & wxPG_VFB_SHOW_MESSAGE_ON_STATUSBAR )
|
|
{
|
|
if ( !wxPGGlobalVars->m_offline )
|
|
{
|
|
wxStatusBar* pStatusBar = GetStatusBar();
|
|
if ( pStatusBar )
|
|
pStatusBar->SetStatusText(wxEmptyString);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( vfb & wxPG_VFB_SHOW_MESSAGE )
|
|
{
|
|
DoHidePropertyError(property);
|
|
}
|
|
|
|
m_validationInfo.m_isFailing = false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// flags are same as with DoSelectProperty
|
|
bool wxPropertyGrid::DoPropertyChanged( wxPGProperty* p, unsigned int selFlags )
|
|
{
|
|
if ( m_inDoPropertyChanged )
|
|
return true;
|
|
|
|
m_inDoPropertyChanged = true;
|
|
wxON_BLOCK_EXIT_SET(m_inDoPropertyChanged, false);
|
|
|
|
wxPGProperty* selected = GetSelection();
|
|
|
|
m_pState->m_anyModified = true;
|
|
|
|
// Maybe need to update control
|
|
wxASSERT( m_chgInfo_changedProperty != NULL );
|
|
|
|
// These values were calculated in PerformValidation()
|
|
wxPGProperty* changedProperty = m_chgInfo_changedProperty;
|
|
wxVariant value = m_chgInfo_pendingValue;
|
|
|
|
// If property's value is being changed, assume it is valid
|
|
OnValidationFailureReset(selected);
|
|
|
|
wxPGProperty* topPaintedProperty = changedProperty;
|
|
|
|
while ( !topPaintedProperty->IsCategory() &&
|
|
!topPaintedProperty->IsRoot() )
|
|
{
|
|
topPaintedProperty = topPaintedProperty->GetParent();
|
|
}
|
|
|
|
changedProperty->SetValue(value, &m_chgInfo_valueList, wxPG_SETVAL_BY_USER);
|
|
|
|
// NB: Call GetEditorControl() as late as possible, because OnSetValue()
|
|
// and perhaps other user-defined virtual functions may change it.
|
|
wxWindow* editor = GetEditorControl();
|
|
|
|
// Set as Modified (not if dragging just began)
|
|
if ( !(p->m_flags & wxPG_PROP_MODIFIED) )
|
|
{
|
|
p->m_flags |= wxPG_PROP_MODIFIED;
|
|
if ( p == selected && (m_windowStyle & wxPG_BOLD_MODIFIED) )
|
|
{
|
|
if ( editor )
|
|
SetCurControlBoldFont();
|
|
}
|
|
}
|
|
|
|
wxPGProperty* pwc;
|
|
|
|
// Propagate updates to parent(s)
|
|
pwc = p;
|
|
wxPGProperty* prevPwc = NULL;
|
|
|
|
while ( prevPwc != topPaintedProperty )
|
|
{
|
|
pwc->m_flags |= wxPG_PROP_MODIFIED;
|
|
|
|
if ( pwc == selected && (m_windowStyle & wxPG_BOLD_MODIFIED) )
|
|
{
|
|
if ( editor )
|
|
SetCurControlBoldFont();
|
|
}
|
|
|
|
prevPwc = pwc;
|
|
pwc = pwc->GetParent();
|
|
}
|
|
|
|
// Draw the actual property
|
|
DrawItemAndChildren( topPaintedProperty );
|
|
|
|
//
|
|
// If value was set by wxPGProperty::OnEvent, then update the editor
|
|
// control.
|
|
if ( selFlags & wxPG_SEL_DIALOGVAL )
|
|
{
|
|
RefreshEditor();
|
|
}
|
|
else
|
|
{
|
|
#if wxPG_REFRESH_CONTROLS
|
|
if ( m_wndEditor ) m_wndEditor->Refresh();
|
|
if ( m_wndEditor2 ) m_wndEditor2->Refresh();
|
|
#endif
|
|
}
|
|
|
|
// Sanity check
|
|
wxASSERT( !changedProperty->GetParent()->HasFlag(wxPG_PROP_AGGREGATE) );
|
|
|
|
// If top parent has composite string value, then send to child parents,
|
|
// starting from baseChangedProperty.
|
|
if ( changedProperty->HasFlag(wxPG_PROP_COMPOSED_VALUE) )
|
|
{
|
|
pwc = m_chgInfo_baseChangedProperty;
|
|
|
|
while ( pwc != changedProperty )
|
|
{
|
|
SendEvent( wxEVT_PG_CHANGED, pwc, NULL );
|
|
pwc = pwc->GetParent();
|
|
}
|
|
}
|
|
|
|
SendEvent( wxEVT_PG_CHANGED, changedProperty, NULL );
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::ChangePropertyValue( wxPGPropArg id, wxVariant newValue )
|
|
{
|
|
wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false)
|
|
|
|
m_chgInfo_changedProperty = NULL;
|
|
|
|
if ( PerformValidation(p, newValue) )
|
|
{
|
|
DoPropertyChanged(p);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
OnValidationFailure(p, newValue);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxVariant wxPropertyGrid::GetUncommittedPropertyValue()
|
|
{
|
|
wxPGProperty* prop = GetSelectedProperty();
|
|
|
|
if ( !prop )
|
|
return wxNullVariant;
|
|
|
|
wxTextCtrl* tc = GetEditorTextCtrl();
|
|
wxVariant value = prop->GetValue();
|
|
|
|
if ( !tc || !IsEditorsValueModified() )
|
|
return value;
|
|
|
|
if ( !prop->StringToValue(value, tc->GetValue()) )
|
|
return value;
|
|
|
|
if ( !PerformValidation(prop, value, IsStandaloneValidation) )
|
|
return prop->GetValue();
|
|
|
|
return value;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Runs wxValidator for the selected property
|
|
bool wxPropertyGrid::DoEditorValidate()
|
|
{
|
|
#if wxUSE_VALIDATORS
|
|
wxRecursionGuard guard(m_validatingEditor);
|
|
if ( guard.IsInside() )
|
|
return false;
|
|
|
|
wxPGProperty* selected = GetSelection();
|
|
if ( selected )
|
|
{
|
|
wxWindow* wnd = GetEditorControl();
|
|
|
|
wxValidator* validator = selected->GetValidator();
|
|
if ( validator && wnd )
|
|
{
|
|
validator->SetWindow(wnd);
|
|
if ( !validator->Validate(this) )
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::HandleCustomEditorEvent( wxEvent &event )
|
|
{
|
|
//
|
|
// NB: We should return true if the event was recognized as
|
|
// a dedicated wxPropertyGrid event, and as such was
|
|
// either properly handled or ignored.
|
|
//
|
|
|
|
// It is possible that this handler receives event even before
|
|
// the control has been properly initialized. Let's skip the
|
|
// event handling in that case.
|
|
if ( !m_pState )
|
|
return false;
|
|
|
|
// Don't care about the event if it originated from the
|
|
// 'label editor'. In this function we only care about the
|
|
// property value editor.
|
|
if ( m_labelEditor && event.GetId() == m_labelEditor->GetId() )
|
|
{
|
|
event.Skip();
|
|
return true;
|
|
}
|
|
|
|
wxPGProperty* selected = GetSelection();
|
|
|
|
// Somehow, event is handled after property has been deselected.
|
|
// Possibly, but very rare.
|
|
if ( !selected ||
|
|
selected->HasFlag(wxPG_PROP_BEING_DELETED) ||
|
|
m_inOnValidationFailure ||
|
|
// Also don't handle editor event if wxEVT_PG_CHANGED or
|
|
// similar is currently doing something (showing a
|
|
// message box, for instance).
|
|
m_processedEvent )
|
|
return true;
|
|
|
|
if ( m_iFlags & wxPG_FL_IN_HANDLECUSTOMEDITOREVENT )
|
|
return true;
|
|
|
|
wxVariant pendingValue(selected->GetValueRef());
|
|
wxWindow* wnd = GetEditorControl();
|
|
wxWindow* editorWnd = wxDynamicCast(event.GetEventObject(), wxWindow);
|
|
int selFlags = 0;
|
|
bool wasUnspecified = selected->IsValueUnspecified();
|
|
int usesAutoUnspecified = selected->UsesAutoUnspecified();
|
|
bool valueIsPending = false;
|
|
|
|
m_chgInfo_changedProperty = NULL;
|
|
|
|
m_iFlags &= ~wxPG_FL_VALUE_CHANGE_IN_EVENT;
|
|
|
|
//
|
|
// Filter out excess wxTextCtrl modified events
|
|
if ( event.GetEventType() == wxEVT_TEXT && wnd )
|
|
{
|
|
if ( wxDynamicCast(wnd, wxTextCtrl) )
|
|
{
|
|
wxTextCtrl* tc = (wxTextCtrl*) wnd;
|
|
|
|
wxString newTcValue = tc->GetValue();
|
|
if ( m_prevTcValue == newTcValue )
|
|
return true;
|
|
m_prevTcValue = newTcValue;
|
|
}
|
|
else if ( wxDynamicCast(wnd, wxComboCtrl) )
|
|
{
|
|
// In some cases we might stumble unintentionally on
|
|
// wxComboCtrl's embedded wxTextCtrl's events. Let's
|
|
// avoid them.
|
|
if ( wxDynamicCast(editorWnd, wxTextCtrl) )
|
|
return false;
|
|
|
|
wxComboCtrl* cc = (wxComboCtrl*) wnd;
|
|
|
|
wxString newTcValue = cc->GetTextCtrl()->GetValue();
|
|
if ( m_prevTcValue == newTcValue )
|
|
return true;
|
|
m_prevTcValue = newTcValue;
|
|
}
|
|
}
|
|
|
|
SetInternalFlag(wxPG_FL_IN_HANDLECUSTOMEDITOREVENT);
|
|
|
|
bool validationFailure = false;
|
|
bool buttonWasHandled = false;
|
|
bool result = false;
|
|
|
|
//
|
|
// Try common button handling
|
|
if ( m_wndEditor2 && event.GetEventType() == wxEVT_BUTTON )
|
|
{
|
|
wxPGEditorDialogAdapter* adapter = selected->GetEditorDialog();
|
|
|
|
if ( adapter )
|
|
{
|
|
buttonWasHandled = true;
|
|
// Store as res2, as previously (and still currently alternatively)
|
|
// dialogs can be shown by handling wxEVT_BUTTON
|
|
// in wxPGProperty::OnEvent().
|
|
adapter->ShowDialog( this, selected );
|
|
delete adapter;
|
|
}
|
|
}
|
|
|
|
if ( !buttonWasHandled )
|
|
{
|
|
if ( wnd || m_wndEditor2 )
|
|
{
|
|
// First call editor class' event handler.
|
|
const wxPGEditor* editor = selected->GetEditorClass();
|
|
|
|
if ( editor->OnEvent( this, selected, editorWnd, event ) )
|
|
{
|
|
result = true;
|
|
|
|
// If changes, validate them
|
|
if ( DoEditorValidate() )
|
|
{
|
|
if ( editor->GetValueFromControl( pendingValue,
|
|
selected,
|
|
wnd ) )
|
|
valueIsPending = true;
|
|
|
|
// Mark value always as pending if validation is currently
|
|
// failing and value was not unspecified
|
|
if ( !valueIsPending &&
|
|
!pendingValue.IsNull() &&
|
|
m_validationInfo.m_isFailing )
|
|
valueIsPending = true;
|
|
}
|
|
else
|
|
{
|
|
validationFailure = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then the property's custom handler (must be always called, unless
|
|
// validation failed).
|
|
if ( !validationFailure )
|
|
buttonWasHandled = selected->OnEvent( this, editorWnd, event );
|
|
}
|
|
|
|
// SetValueInEvent(), as called in one of the functions referred above
|
|
// overrides editor's value.
|
|
if ( m_iFlags & wxPG_FL_VALUE_CHANGE_IN_EVENT )
|
|
{
|
|
valueIsPending = true;
|
|
pendingValue = m_changeInEventValue;
|
|
selFlags |= wxPG_SEL_DIALOGVAL;
|
|
}
|
|
|
|
if ( !validationFailure && valueIsPending )
|
|
if ( !PerformValidation(selected, pendingValue) )
|
|
validationFailure = true;
|
|
|
|
if ( validationFailure)
|
|
{
|
|
OnValidationFailure(selected, pendingValue);
|
|
}
|
|
else if ( valueIsPending )
|
|
{
|
|
selFlags |= ( !wasUnspecified && selected->IsValueUnspecified() && usesAutoUnspecified ) ? wxPG_SEL_SETUNSPEC : 0;
|
|
|
|
DoPropertyChanged(selected, selFlags);
|
|
EditorsValueWasNotModified();
|
|
|
|
// Regardless of editor type, unfocus editor on
|
|
// text-editing related enter press.
|
|
if ( event.GetEventType() == wxEVT_TEXT_ENTER )
|
|
{
|
|
SetFocusOnCanvas();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No value after all
|
|
|
|
// Regardless of editor type, unfocus editor on
|
|
// text-editing related enter press.
|
|
if ( event.GetEventType() == wxEVT_TEXT_ENTER )
|
|
{
|
|
SetFocusOnCanvas();
|
|
}
|
|
|
|
// Let unhandled button click events go to the parent
|
|
if ( !buttonWasHandled && event.GetEventType() == wxEVT_BUTTON )
|
|
{
|
|
result = true;
|
|
wxCommandEvent evt(wxEVT_BUTTON,GetId());
|
|
GetEventHandler()->AddPendingEvent(evt);
|
|
}
|
|
}
|
|
|
|
ClearInternalFlag(wxPG_FL_IN_HANDLECUSTOMEDITOREVENT);
|
|
|
|
return result;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid editor control helper methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxRect wxPropertyGrid::GetEditorWidgetRect( wxPGProperty* p, int column ) const
|
|
{
|
|
int itemy = p->GetY2(m_lineHeight);
|
|
int splitterX = m_pState->DoGetSplitterPosition(column-1);
|
|
int colEnd = splitterX + m_pState->m_colWidths[column];
|
|
int imageOffset = 0;
|
|
|
|
int vx, vy; // Top left corner of client
|
|
GetViewStart(&vx, &vy);
|
|
vy *= wxPG_PIXELS_PER_UNIT;
|
|
|
|
if ( column == 1 )
|
|
{
|
|
// TODO: If custom image detection changes from current, change this.
|
|
if ( m_iFlags & wxPG_FL_CUR_USES_CUSTOM_IMAGE )
|
|
{
|
|
//m_iFlags |= wxPG_FL_CUR_USES_CUSTOM_IMAGE;
|
|
int iw = p->OnMeasureImage().x;
|
|
if ( iw < 1 )
|
|
iw = wxPG_CUSTOM_IMAGE_WIDTH;
|
|
imageOffset = p->GetImageOffset(iw);
|
|
}
|
|
}
|
|
else if ( column == 0 )
|
|
{
|
|
splitterX += (p->m_depth - 1) * m_subgroup_extramargin;
|
|
}
|
|
|
|
return wxRect
|
|
(
|
|
splitterX+imageOffset+wxPG_XBEFOREWIDGET+wxPG_CONTROL_MARGIN+1,
|
|
itemy-vy,
|
|
colEnd-splitterX-wxPG_XBEFOREWIDGET-wxPG_CONTROL_MARGIN-imageOffset-1,
|
|
m_lineHeight-1
|
|
);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxRect wxPropertyGrid::GetImageRect( wxPGProperty* p, int item ) const
|
|
{
|
|
wxSize sz = GetImageSize(p, item);
|
|
return wxRect(wxPG_CONTROL_MARGIN + wxCC_CUSTOM_IMAGE_MARGIN1,
|
|
wxPG_CUSTOM_IMAGE_SPACINGY,
|
|
sz.x,
|
|
sz.y);
|
|
}
|
|
|
|
// return size of custom paint image
|
|
wxSize wxPropertyGrid::GetImageSize( wxPGProperty* p, int item ) const
|
|
{
|
|
// If called with NULL property, then return default image
|
|
// size for properties that use image.
|
|
if ( !p )
|
|
return wxSize(wxPG_CUSTOM_IMAGE_WIDTH,wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight));
|
|
|
|
wxSize cis = p->OnMeasureImage(item);
|
|
|
|
int choiceCount = p->m_choices.GetCount();
|
|
int comVals = p->GetDisplayedCommonValueCount();
|
|
if ( item >= choiceCount && comVals > 0 )
|
|
{
|
|
unsigned int cvi = item-choiceCount;
|
|
cis = GetCommonValue(cvi)->GetRenderer()->GetImageSize(NULL, 1, cvi);
|
|
}
|
|
else if ( item >= 0 && choiceCount == 0 )
|
|
return wxSize(0, 0);
|
|
|
|
if ( cis.x < 0 )
|
|
{
|
|
if ( cis.x <= -1 )
|
|
cis.x = wxPG_CUSTOM_IMAGE_WIDTH;
|
|
}
|
|
if ( cis.y <= 0 )
|
|
{
|
|
if ( cis.y >= -1 )
|
|
cis.y = wxPG_STD_CUST_IMAGE_HEIGHT(m_lineHeight);
|
|
else
|
|
cis.y = -cis.y;
|
|
}
|
|
return cis;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// takes scrolling into account
|
|
void wxPropertyGrid::ImprovedClientToScreen( int* px, int* py )
|
|
{
|
|
int vx, vy;
|
|
GetViewStart(&vx,&vy);
|
|
vy*=wxPG_PIXELS_PER_UNIT;
|
|
vx*=wxPG_PIXELS_PER_UNIT;
|
|
*px -= vx;
|
|
*py -= vy;
|
|
ClientToScreen( px, py );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridHitTestResult wxPropertyGrid::HitTest( const wxPoint& pt ) const
|
|
{
|
|
wxPoint pt2;
|
|
GetViewStart(&pt2.x,&pt2.y);
|
|
pt2.x *= wxPG_PIXELS_PER_UNIT;
|
|
pt2.y *= wxPG_PIXELS_PER_UNIT;
|
|
pt2.x += pt.x;
|
|
pt2.y += pt.y;
|
|
|
|
return m_pState->HitTest(pt2);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// 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 );
|
|
|
|
m_curcursor = type;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxString
|
|
wxPropertyGrid::GetUnspecifiedValueText( int argFlags ) const
|
|
{
|
|
const wxPGCell& ua = GetUnspecifiedValueAppearance();
|
|
|
|
if ( ua.HasText() &&
|
|
!(argFlags & wxPG_FULL_VALUE) &&
|
|
!(argFlags & wxPG_EDITABLE_VALUE) )
|
|
return ua.GetText();
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid property selection, editor creation
|
|
// -----------------------------------------------------------------------
|
|
|
|
//
|
|
// This class forwards events from property editor controls to wxPropertyGrid.
|
|
class wxPropertyGridEditorEventForwarder : public wxEvtHandler
|
|
{
|
|
public:
|
|
wxPropertyGridEditorEventForwarder( wxPropertyGrid* propGrid )
|
|
: wxEvtHandler(), m_propGrid(propGrid)
|
|
{
|
|
}
|
|
|
|
virtual ~wxPropertyGridEditorEventForwarder()
|
|
{
|
|
}
|
|
|
|
private:
|
|
bool ProcessEvent( wxEvent& event ) wxOVERRIDE
|
|
{
|
|
// Always skip
|
|
event.Skip();
|
|
|
|
m_propGrid->HandleCustomEditorEvent(event);
|
|
|
|
//
|
|
// NB: We should return true if the event was recognized as
|
|
// a dedicated wxPropertyGrid event, and as such was
|
|
// either properly handled or ignored.
|
|
//
|
|
if ( m_propGrid->IsMainButtonEvent(event) )
|
|
return true;
|
|
|
|
//
|
|
// NB: On wxMSW, a wxTextCtrl with wxTE_PROCESS_ENTER
|
|
// may beep annoyingly if that event is skipped
|
|
// and passed to parent event handler.
|
|
if ( event.GetEventType() == wxEVT_TEXT_ENTER )
|
|
return true;
|
|
|
|
return wxEvtHandler::ProcessEvent(event);
|
|
}
|
|
|
|
wxPropertyGrid* m_propGrid;
|
|
};
|
|
|
|
// Setups event handling for child control
|
|
void wxPropertyGrid::SetupChildEventHandling( wxWindow* argWnd )
|
|
{
|
|
wxWindowID id = argWnd->GetId();
|
|
|
|
if ( argWnd == m_wndEditor )
|
|
{
|
|
argWnd->Connect(id, wxEVT_MOTION,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseMoveChild),
|
|
NULL, this);
|
|
argWnd->Connect(id, wxEVT_LEFT_UP,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseUpChild),
|
|
NULL, this);
|
|
argWnd->Connect(id, wxEVT_LEFT_DOWN,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseClickChild),
|
|
NULL, this);
|
|
argWnd->Connect(id, wxEVT_RIGHT_UP,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseRightClickChild),
|
|
NULL, this);
|
|
argWnd->Connect(id, wxEVT_ENTER_WINDOW,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseEntry),
|
|
NULL, this);
|
|
argWnd->Connect(id, wxEVT_LEAVE_WINDOW,
|
|
wxMouseEventHandler(wxPropertyGrid::OnMouseEntry),
|
|
NULL, this);
|
|
}
|
|
|
|
wxPropertyGridEditorEventForwarder* forwarder;
|
|
forwarder = new wxPropertyGridEditorEventForwarder(this);
|
|
argWnd->PushEventHandler(forwarder);
|
|
|
|
argWnd->Connect(id, wxEVT_KEY_DOWN,
|
|
wxCharEventHandler(wxPropertyGrid::OnChildKeyDown),
|
|
NULL, this);
|
|
}
|
|
|
|
void wxPropertyGrid::DeletePendingObjects()
|
|
{
|
|
#if !WXWIN_COMPATIBILITY_3_0
|
|
// Delete pending property editors and their event handlers.
|
|
while ( !m_deletedEditorObjects.empty() )
|
|
{
|
|
wxObject* obj = m_deletedEditorObjects.back();
|
|
m_deletedEditorObjects.pop_back();
|
|
|
|
delete obj;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void wxPropertyGrid::DestroyEditorWnd( wxWindow* wnd )
|
|
{
|
|
if ( !wnd )
|
|
return;
|
|
|
|
wnd->Hide();
|
|
|
|
// Do not free editors immediately (for sake of processing events)
|
|
#if WXWIN_COMPATIBILITY_3_0
|
|
wxPendingDelete.Append(wnd);
|
|
#else
|
|
m_deletedEditorObjects.push_back(wnd);
|
|
#endif
|
|
}
|
|
|
|
void wxPropertyGrid::FreeEditors()
|
|
{
|
|
//
|
|
// Return focus back to canvas from children (this is required at least for
|
|
// GTK+, which, unlike Windows, clears focus when control is destroyed
|
|
// instead of moving it to closest parent).
|
|
SetFocusOnCanvas();
|
|
|
|
// Do not free editors immediately if processing events
|
|
if ( m_wndEditor2 )
|
|
{
|
|
wxEvtHandler* handler = m_wndEditor2->PopEventHandler(false);
|
|
m_wndEditor2->Hide();
|
|
#if WXWIN_COMPATIBILITY_3_0
|
|
wxPendingDelete.Append( handler );
|
|
#else
|
|
m_deletedEditorObjects.push_back(handler);
|
|
#endif
|
|
DestroyEditorWnd(m_wndEditor2);
|
|
m_wndEditor2 = NULL;
|
|
}
|
|
|
|
if ( m_wndEditor )
|
|
{
|
|
wxEvtHandler* handler = m_wndEditor->PopEventHandler(false);
|
|
m_wndEditor->Hide();
|
|
#if WXWIN_COMPATIBILITY_3_0
|
|
wxPendingDelete.Append( handler );
|
|
#else
|
|
m_deletedEditorObjects.push_back(handler);
|
|
#endif
|
|
DestroyEditorWnd(m_wndEditor);
|
|
m_wndEditor = NULL;
|
|
}
|
|
}
|
|
|
|
// Call with NULL to de-select property
|
|
bool wxPropertyGrid::DoSelectProperty( wxPGProperty* p, unsigned int flags )
|
|
{
|
|
/*
|
|
if (p)
|
|
{
|
|
wxLogDebug(wxT("SelectProperty( %s (%s[%i]) )"),p->m_label.c_str(),
|
|
p->m_parent->m_label.c_str(),p->GetIndexInParent());
|
|
}
|
|
else
|
|
{
|
|
wxLogDebug(wxT("SelectProperty( NULL, -1 )"));
|
|
}
|
|
*/
|
|
|
|
if ( m_inDoSelectProperty )
|
|
return true;
|
|
|
|
m_inDoSelectProperty = true;
|
|
wxON_BLOCK_EXIT_SET(m_inDoSelectProperty, false);
|
|
|
|
if ( !m_pState )
|
|
return false;
|
|
|
|
wxArrayPGProperty prevSelection = m_pState->m_selection;
|
|
wxPGProperty* prevFirstSel;
|
|
|
|
if ( prevSelection.size() > 0 )
|
|
prevFirstSel = prevSelection[0];
|
|
else
|
|
prevFirstSel = NULL;
|
|
|
|
if ( prevFirstSel && prevFirstSel->HasFlag(wxPG_PROP_BEING_DELETED) )
|
|
prevFirstSel = NULL;
|
|
|
|
// Always send event, as this is indirect call
|
|
DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE);
|
|
|
|
/*
|
|
if ( prevFirstSel )
|
|
wxPrintf( "Selected %s\n", prevFirstSel->GetClassInfo()->GetClassName() );
|
|
else
|
|
wxPrintf( "None selected\n" );
|
|
|
|
if (p)
|
|
wxPrintf( "P = %s\n", p->GetClassInfo()->GetClassName() );
|
|
else
|
|
wxPrintf( "P = NULL\n" );
|
|
*/
|
|
|
|
wxWindow* primaryCtrl = NULL;
|
|
|
|
// If we are frozen, then just set the values.
|
|
if ( IsFrozen() )
|
|
{
|
|
m_iFlags &= ~(wxPG_FL_ABNORMAL_EDITOR);
|
|
m_editorFocused = 0;
|
|
m_pState->DoSetSelection(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 = NULL;
|
|
}
|
|
else
|
|
{
|
|
// Is it the same?
|
|
if ( prevFirstSel == p &&
|
|
prevSelection.size() <= 1 &&
|
|
!(flags & wxPG_SEL_FORCE) )
|
|
{
|
|
// Only set focus if not deselecting
|
|
if ( p )
|
|
{
|
|
if ( flags & wxPG_SEL_FOCUS )
|
|
{
|
|
if ( m_wndEditor )
|
|
{
|
|
m_wndEditor->SetFocus();
|
|
m_editorFocused = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetFocusOnCanvas();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// First, deactivate previous
|
|
if ( prevFirstSel )
|
|
{
|
|
// Must double-check if this is an selected in case of forceswitch
|
|
if ( p != prevFirstSel )
|
|
{
|
|
if ( !CommitChangesFromEditor(flags) )
|
|
{
|
|
// Validation has failed, so we can't exit the previous editor
|
|
//::wxMessageBox(_("Please correct the value or press ESC to cancel the edit."),
|
|
// _("Invalid Value"),wxOK|wxICON_ERROR);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// This should be called after CommitChangesFromEditor(), so that
|
|
// OnValidationFailure() still has information on property's
|
|
// validation state.
|
|
OnValidationFailureReset(prevFirstSel);
|
|
|
|
FreeEditors();
|
|
|
|
m_iFlags &= ~(wxPG_FL_ABNORMAL_EDITOR);
|
|
EditorsValueWasNotModified();
|
|
}
|
|
|
|
SetInternalFlag(wxPG_FL_IN_SELECT_PROPERTY);
|
|
|
|
m_pState->DoSetSelection(p);
|
|
|
|
// Redraw unselected
|
|
for ( unsigned int i=0; i<prevSelection.size(); i++ )
|
|
{
|
|
DrawItem(prevSelection[i]);
|
|
}
|
|
|
|
//
|
|
// Then, activate the one given.
|
|
if ( p )
|
|
{
|
|
int propY = p->GetY2(m_lineHeight);
|
|
|
|
int splitterX = GetSplitterPosition();
|
|
m_editorFocused = 0;
|
|
m_iFlags |= wxPG_FL_PRIMARY_FILLS_ENTIRE;
|
|
|
|
wxASSERT( m_wndEditor == NULL );
|
|
|
|
//
|
|
// Only create editor for non-disabled non-caption
|
|
if ( !p->IsCategory() && !(p->m_flags & wxPG_PROP_DISABLED) )
|
|
{
|
|
// do this for non-caption items
|
|
|
|
m_selColumn = 1;
|
|
|
|
// 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, m_selColumn);
|
|
wxPoint goodPos = grect.GetPosition();
|
|
|
|
// Editor appearance can now be considered clear
|
|
m_editorAppearance.SetEmptyData();
|
|
|
|
const wxPGEditor* editor = p->GetEditorClass();
|
|
wxCHECK_MSG(editor, false,
|
|
wxT("NULL editor class not allowed"));
|
|
|
|
m_iFlags &= ~wxPG_FL_FIXED_WIDTH_EDITOR;
|
|
|
|
wxPGWindowList wndList =
|
|
editor->CreateControls(this,
|
|
p,
|
|
goodPos,
|
|
grect.GetSize());
|
|
|
|
m_wndEditor = wndList.m_primary;
|
|
m_wndEditor2 = wndList.m_secondary;
|
|
primaryCtrl = GetEditorControl();
|
|
|
|
//
|
|
// Essentially, primaryCtrl == m_wndEditor
|
|
//
|
|
|
|
// NOTE: It is allowed for m_wndEditor to be NULL - in this
|
|
// case value is drawn as normal, and m_wndEditor2 is
|
|
// assumed to be a right-aligned button that triggers
|
|
// a separate editorCtrl window.
|
|
|
|
if ( m_wndEditor )
|
|
{
|
|
wxASSERT_MSG( m_wndEditor->GetParent() == GetPanel(),
|
|
"CreateControls must use result of "
|
|
"wxPropertyGrid::GetPanel() as parent "
|
|
"of controls." );
|
|
|
|
// Set validator, if any
|
|
#if wxUSE_VALIDATORS
|
|
wxValidator* validator = p->GetValidator();
|
|
if ( validator )
|
|
primaryCtrl->SetValidator(*validator);
|
|
#endif
|
|
|
|
if ( m_wndEditor->GetSize().y > (m_lineHeight+6) )
|
|
m_iFlags |= wxPG_FL_ABNORMAL_EDITOR;
|
|
|
|
// If it has modified status, use bold font
|
|
// (must be done before capturing m_ctrlXAdjust)
|
|
if ( (p->m_flags & wxPG_PROP_MODIFIED) &&
|
|
(m_windowStyle & wxPG_BOLD_MODIFIED) )
|
|
SetCurControlBoldFont();
|
|
|
|
// Store x relative to splitter (we'll need it).
|
|
m_ctrlXAdjust = m_wndEditor->GetPosition().x - splitterX;
|
|
|
|
// Check if background clear is not necessary
|
|
wxPoint pos = m_wndEditor->GetPosition();
|
|
if ( pos.x > (splitterX+1) || pos.y > propY )
|
|
{
|
|
m_iFlags &= ~(wxPG_FL_PRIMARY_FILLS_ENTIRE);
|
|
}
|
|
|
|
m_wndEditor->SetSizeHints(3, 3);
|
|
|
|
SetupChildEventHandling(primaryCtrl);
|
|
|
|
// Focus and select all (wxTextCtrl, wxComboBox etc)
|
|
if ( flags & wxPG_SEL_FOCUS )
|
|
{
|
|
primaryCtrl->SetFocus();
|
|
|
|
p->GetEditorClass()->OnFocus(p, primaryCtrl);
|
|
}
|
|
else
|
|
{
|
|
if ( p->IsValueUnspecified() )
|
|
SetEditorAppearance(m_unspecifiedAppearance,
|
|
true);
|
|
}
|
|
}
|
|
|
|
if ( m_wndEditor2 )
|
|
{
|
|
wxASSERT_MSG( m_wndEditor2->GetParent() == GetPanel(),
|
|
"CreateControls must use result of "
|
|
"wxPropertyGrid::GetPanel() as parent "
|
|
"of controls." );
|
|
|
|
// Get proper id for wndSecondary
|
|
m_wndSecId = m_wndEditor2->GetId();
|
|
wxWindowList children = m_wndEditor2->GetChildren();
|
|
wxWindowList::iterator node = children.begin();
|
|
if ( node != children.end() )
|
|
m_wndSecId = ((wxWindow*)*node)->GetId();
|
|
|
|
m_wndEditor2->SetSizeHints(3,3);
|
|
|
|
m_wndEditor2->Show();
|
|
|
|
SetupChildEventHandling(m_wndEditor2);
|
|
|
|
// If no primary editor, focus to button to allow
|
|
// it to interprete ENTER etc.
|
|
// NOTE: Due to problems focusing away from it, this
|
|
// has been disabled.
|
|
/*
|
|
if ( (flags & wxPG_SEL_FOCUS) && !m_wndEditor )
|
|
m_wndEditor2->SetFocus();
|
|
*/
|
|
}
|
|
|
|
if ( flags & wxPG_SEL_FOCUS )
|
|
m_editorFocused = 1;
|
|
|
|
}
|
|
else
|
|
{
|
|
// Make sure focus is in grid canvas (important for wxGTK,
|
|
// at least)
|
|
SetFocusOnCanvas();
|
|
}
|
|
|
|
EditorsValueWasNotModified();
|
|
|
|
// If it's inside collapsed section, expand parent, scroll, etc.
|
|
// Also, if it was partially visible, scroll it into view.
|
|
if ( !(flags & wxPG_SEL_NONVISIBLE) )
|
|
EnsureVisible( p );
|
|
|
|
if ( m_wndEditor )
|
|
{
|
|
m_wndEditor->Show(true);
|
|
}
|
|
|
|
if ( !(flags & wxPG_SEL_NO_REFRESH) )
|
|
DrawItem(p);
|
|
}
|
|
else
|
|
{
|
|
// Make sure focus is in grid canvas
|
|
SetFocusOnCanvas();
|
|
}
|
|
|
|
ClearInternalFlag(wxPG_FL_IN_SELECT_PROPERTY);
|
|
}
|
|
|
|
const wxString* pHelpString = NULL;
|
|
|
|
if ( p )
|
|
pHelpString = &p->GetHelpString();
|
|
|
|
if ( !(GetExtraStyle() & wxPG_EX_HELP_AS_TOOLTIPS) )
|
|
{
|
|
#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).
|
|
//
|
|
wxStatusBar* statusbar = GetStatusBar();
|
|
if ( statusbar )
|
|
{
|
|
if ( pHelpString && !pHelpString->empty() )
|
|
{
|
|
// Set help box text.
|
|
statusbar->SetStatusText( *pHelpString );
|
|
m_iFlags |= wxPG_FL_STRING_IN_STATUSBAR;
|
|
}
|
|
else if ( 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
|
|
}
|
|
else
|
|
{
|
|
#if wxPG_SUPPORT_TOOLTIPS
|
|
//
|
|
// Show help as a tool tip on the editor control.
|
|
//
|
|
if ( pHelpString && !pHelpString->empty() &&
|
|
primaryCtrl )
|
|
{
|
|
primaryCtrl->SetToolTip(*pHelpString);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// call wx event handler (here so that it also occurs on deselection)
|
|
if ( !(flags & wxPG_SEL_DONT_SEND_EVENT) )
|
|
SendEvent( wxEVT_PG_SELECTED, p, NULL );
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::UnfocusEditor()
|
|
{
|
|
wxPGProperty* selected = GetSelection();
|
|
|
|
if ( !selected || !m_wndEditor || IsFrozen() )
|
|
return true;
|
|
|
|
if ( !CommitChangesFromEditor(0) )
|
|
return false;
|
|
|
|
SetFocusOnCanvas();
|
|
DrawItem(selected);
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::RefreshEditor()
|
|
{
|
|
wxPGProperty* p = GetSelection();
|
|
if ( !p )
|
|
return;
|
|
|
|
wxWindow* wnd = GetEditorControl();
|
|
if ( !wnd )
|
|
return;
|
|
|
|
// Set editor font boldness - must do this before
|
|
// calling UpdateControl().
|
|
if ( HasFlag(wxPG_BOLD_MODIFIED) )
|
|
{
|
|
if ( p->HasFlag(wxPG_PROP_MODIFIED) )
|
|
wnd->SetFont(GetCaptionFont());
|
|
else
|
|
wnd->SetFont(GetFont());
|
|
}
|
|
|
|
const wxPGEditor* editorClass = p->GetEditorClass();
|
|
|
|
editorClass->UpdateControl(p, wnd);
|
|
|
|
if ( p->IsValueUnspecified() )
|
|
SetEditorAppearance(m_unspecifiedAppearance, true);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::SelectProperty( wxPGPropArg id, bool focus )
|
|
{
|
|
wxPG_PROP_ARG_CALL_PROLOG_RETVAL(false)
|
|
|
|
int flags = wxPG_SEL_DONT_SEND_EVENT;
|
|
if ( focus )
|
|
flags |= wxPG_SEL_FOCUS;
|
|
|
|
return DoSelectProperty(p, flags);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid expand/collapse state
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoCollapse( wxPGProperty* p, bool sendEvents )
|
|
{
|
|
wxPGProperty* pwc = wxStaticCast(p, wxPGProperty);
|
|
wxPGProperty* selected = GetSelection();
|
|
|
|
// If active editor was inside collapsed section, then disable it
|
|
if ( selected && selected->IsSomeParent(p) )
|
|
{
|
|
DoClearSelection();
|
|
}
|
|
|
|
// Store dont-center-splitter flag 'cause we need to temporarily set it
|
|
bool prevDontCenterSplitter = m_pState->m_dontCenterSplitter;
|
|
m_pState->m_dontCenterSplitter = true;
|
|
|
|
bool res = m_pState->DoCollapse(pwc);
|
|
|
|
if ( res )
|
|
{
|
|
if ( sendEvents )
|
|
SendEvent( wxEVT_PG_ITEM_COLLAPSED, p );
|
|
|
|
RecalculateVirtualSize();
|
|
Refresh();
|
|
}
|
|
|
|
m_pState->m_dontCenterSplitter = prevDontCenterSplitter;
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoExpand( wxPGProperty* p, bool sendEvents )
|
|
{
|
|
wxCHECK_MSG( p, false, wxT("invalid property id") );
|
|
|
|
wxPGProperty* pwc = (wxPGProperty*)p;
|
|
|
|
// Store dont-center-splitter flag 'cause we need to temporarily set it
|
|
bool prevDontCenterSplitter = m_pState->m_dontCenterSplitter;
|
|
m_pState->m_dontCenterSplitter = true;
|
|
|
|
bool res = m_pState->DoExpand(pwc);
|
|
|
|
if ( res )
|
|
{
|
|
if ( sendEvents )
|
|
SendEvent( wxEVT_PG_ITEM_EXPANDED, p );
|
|
|
|
RecalculateVirtualSize();
|
|
Refresh();
|
|
}
|
|
|
|
m_pState->m_dontCenterSplitter = prevDontCenterSplitter;
|
|
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::DoHideProperty( wxPGProperty* p, bool hide, int flags )
|
|
{
|
|
if ( IsFrozen() )
|
|
return m_pState->DoHideProperty(p, hide, flags);
|
|
|
|
wxArrayPGProperty selection = m_pState->m_selection; // Must use a copy
|
|
int selRemoveCount = 0;
|
|
for ( unsigned int i=0; i<selection.size(); i++ )
|
|
{
|
|
wxPGProperty* selected = selection[i];
|
|
if ( selected == p || selected->IsSomeParent(p) )
|
|
{
|
|
if ( !DoRemoveFromSelection(p, flags) )
|
|
return false;
|
|
selRemoveCount += 1;
|
|
}
|
|
}
|
|
|
|
m_pState->DoHideProperty(p, hide, flags);
|
|
|
|
RecalculateVirtualSize();
|
|
Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid size related methods
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::RecalculateVirtualSize( int forceXPos )
|
|
{
|
|
// Don't check for !HasInternalFlag(wxPG_FL_INITIALIZED) here. Otherwise
|
|
// virtual size calculation may go wrong.
|
|
if ( HasInternalFlag(wxPG_FL_RECALCULATING_VIRTUAL_SIZE) ||
|
|
IsFrozen() ||
|
|
!m_pState )
|
|
return;
|
|
|
|
//
|
|
// If virtual height was changed, then recalculate editor control position(s)
|
|
if ( m_pState->m_vhCalcPending )
|
|
CorrectEditorWidgetPosY();
|
|
|
|
m_pState->EnsureVirtualHeight();
|
|
|
|
wxASSERT_LEVEL_2_MSG(
|
|
m_pState->GetVirtualHeight() == m_pState->GetActualVirtualHeight(),
|
|
"VirtualHeight and ActualVirtualHeight should match"
|
|
);
|
|
|
|
m_iFlags |= wxPG_FL_RECALCULATING_VIRTUAL_SIZE;
|
|
|
|
int x = m_pState->m_width;
|
|
int y = m_pState->m_virtualHeight;
|
|
|
|
int width, height;
|
|
GetClientSize(&width,&height);
|
|
|
|
// Now adjust virtual size.
|
|
SetVirtualSize(x, y);
|
|
|
|
int xAmount = 0;
|
|
int xPos = 0;
|
|
|
|
//
|
|
// Adjust scrollbars
|
|
if ( HasVirtualWidth() )
|
|
{
|
|
xAmount = x/wxPG_PIXELS_PER_UNIT;
|
|
xPos = GetScrollPos( wxHORIZONTAL );
|
|
}
|
|
|
|
if ( forceXPos != -1 )
|
|
xPos = forceXPos;
|
|
// xPos too high?
|
|
else if ( xPos > (xAmount-(width/wxPG_PIXELS_PER_UNIT)) )
|
|
xPos = 0;
|
|
|
|
int yAmount = y / wxPG_PIXELS_PER_UNIT;
|
|
int yPos = GetScrollPos( wxVERTICAL );
|
|
|
|
SetScrollbars( wxPG_PIXELS_PER_UNIT, wxPG_PIXELS_PER_UNIT,
|
|
xAmount, yAmount, xPos, yPos, true );
|
|
|
|
// This may be needed in addition to calling SetScrollbars()
|
|
// when class inherits from wxScrollHelper instead of
|
|
// actual wxScrolled<T>.
|
|
AdjustScrollbars();
|
|
|
|
// Must re-get size now
|
|
GetClientSize(&width,&height);
|
|
|
|
if ( !HasVirtualWidth() )
|
|
{
|
|
m_pState->SetVirtualWidth(width);
|
|
}
|
|
|
|
m_width = width;
|
|
m_height = height;
|
|
|
|
m_pState->CheckColumnWidths();
|
|
|
|
if ( GetSelection() )
|
|
CorrectEditorWidgetSizeX();
|
|
|
|
m_iFlags &= ~wxPG_FL_RECALCULATING_VIRTUAL_SIZE;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnResize( wxSizeEvent& event )
|
|
{
|
|
if ( !(m_iFlags & wxPG_FL_INITIALIZED) )
|
|
return;
|
|
|
|
int width, height;
|
|
GetClientSize(&width, &height);
|
|
|
|
m_width = width;
|
|
m_height = height;
|
|
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pState->OnClientWidthChange( width, event.GetSize().x - m_ncWidth, true );
|
|
m_ncWidth = event.GetSize().x;
|
|
|
|
if ( !IsFrozen() )
|
|
{
|
|
if ( m_pState->m_itemsAdded )
|
|
PrepareAfterItemsAdded();
|
|
else
|
|
// Without this, virtual size (atleast under wxGTK) will be skewed
|
|
RecalculateVirtualSize();
|
|
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::SetVirtualWidth( int width )
|
|
{
|
|
if ( width == -1 )
|
|
{
|
|
// Disable virtual width
|
|
width = GetClientSize().x;
|
|
ClearInternalFlag(wxPG_FL_HAS_VIRTUAL_WIDTH);
|
|
}
|
|
else
|
|
{
|
|
// Enable virtual width
|
|
SetInternalFlag(wxPG_FL_HAS_VIRTUAL_WIDTH);
|
|
}
|
|
m_pState->SetVirtualWidth( width );
|
|
}
|
|
|
|
void wxPropertyGrid::SetFocusOnCanvas()
|
|
{
|
|
// To prevent wxPropertyGrid from stealing focus from other controls,
|
|
// only move focus to the grid if it was already in one if its child
|
|
// controls.
|
|
wxWindow* focus = wxWindow::FindFocus();
|
|
if ( focus )
|
|
{
|
|
wxWindow* parent = focus->GetParent();
|
|
while ( parent )
|
|
{
|
|
if ( parent == this )
|
|
{
|
|
SetFocus();
|
|
break;
|
|
}
|
|
parent = parent->GetParent();
|
|
}
|
|
}
|
|
|
|
m_editorFocused = 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid mouse event handling
|
|
// -----------------------------------------------------------------------
|
|
|
|
// selFlags uses same values DoSelectProperty's flags
|
|
// Returns true if event was vetoed.
|
|
bool wxPropertyGrid::SendEvent( int eventType, wxPGProperty* p,
|
|
wxVariant* pValue,
|
|
unsigned int selFlags,
|
|
unsigned int column )
|
|
{
|
|
// selFlags should have wxPG_SEL_NOVALIDATE if event is not
|
|
// vetoable.
|
|
|
|
// Send property grid event of specific type and with specific property
|
|
wxPropertyGridEvent evt( eventType, m_eventObject->GetId() );
|
|
evt.SetPropertyGrid(this);
|
|
evt.SetEventObject(m_eventObject);
|
|
evt.SetProperty(p);
|
|
evt.SetColumn(column);
|
|
if ( eventType == wxEVT_PG_CHANGING )
|
|
{
|
|
wxASSERT( pValue );
|
|
evt.SetCanVeto(true);
|
|
m_validationInfo.m_pValue = pValue;
|
|
evt.SetupValidationInfo();
|
|
}
|
|
else
|
|
{
|
|
if ( p )
|
|
evt.SetPropertyValue(p->GetValue());
|
|
|
|
if ( !(selFlags & wxPG_SEL_NOVALIDATE) )
|
|
evt.SetCanVeto(true);
|
|
}
|
|
|
|
wxPropertyGridEvent* prevProcessedEvent = m_processedEvent;
|
|
m_processedEvent = &evt;
|
|
m_eventObject->HandleWindowEvent(evt);
|
|
m_processedEvent = prevProcessedEvent;
|
|
|
|
return evt.WasVetoed();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Return false if should be skipped
|
|
bool wxPropertyGrid::HandleMouseClick( int x, unsigned int y, wxMouseEvent &event )
|
|
{
|
|
bool res = true;
|
|
|
|
// Need to set focus?
|
|
if ( !(m_iFlags & wxPG_FL_FOCUSED) )
|
|
{
|
|
SetFocusOnCanvas();
|
|
}
|
|
|
|
wxPropertyGridPageState* state = m_pState;
|
|
int splitterHit;
|
|
int splitterHitOffset;
|
|
int columnHit = state->HitTestH( x, &splitterHit, &splitterHitOffset );
|
|
|
|
wxPGProperty* p = DoGetItemAtY(y);
|
|
|
|
if ( p )
|
|
{
|
|
int depth = (int)p->GetDepth() - 1;
|
|
|
|
int marginEnds = m_marginWidth + ( depth * m_subgroup_extramargin );
|
|
|
|
if ( x >= marginEnds )
|
|
{
|
|
// Outside margin.
|
|
|
|
if ( p->IsCategory() )
|
|
{
|
|
// This is category.
|
|
wxPropertyCategory* pwc = (wxPropertyCategory*)p;
|
|
|
|
int textX = m_marginWidth + ((unsigned int)((pwc->m_depth-1)*m_subgroup_extramargin));
|
|
|
|
// Expand, collapse, activate etc. if click on text or left of splitter.
|
|
if ( x >= textX
|
|
&&
|
|
( x < (textX+pwc->GetTextExtent(this, m_captionFont)+(wxPG_CAPRECTXMARGIN*2)) ||
|
|
columnHit == 0
|
|
)
|
|
)
|
|
{
|
|
if ( !AddToSelectionFromInputEvent( p,
|
|
columnHit,
|
|
&event ) )
|
|
return res;
|
|
|
|
// On double-click, expand/collapse.
|
|
if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) )
|
|
{
|
|
if ( pwc->IsExpanded() ) DoCollapse( p, true );
|
|
else DoExpand( p, true );
|
|
}
|
|
}
|
|
}
|
|
else if ( splitterHit == -1 )
|
|
{
|
|
// Click on value.
|
|
unsigned int selFlag = 0;
|
|
if ( columnHit == 1 )
|
|
{
|
|
m_iFlags |= wxPG_FL_ACTIVATION_BY_CLICK;
|
|
selFlag = wxPG_SEL_FOCUS;
|
|
}
|
|
if ( !AddToSelectionFromInputEvent( p,
|
|
columnHit,
|
|
&event,
|
|
selFlag ) )
|
|
return res;
|
|
|
|
m_iFlags &= ~(wxPG_FL_ACTIVATION_BY_CLICK);
|
|
|
|
if ( p->GetChildCount() && !p->IsCategory() )
|
|
// On double-click, expand/collapse.
|
|
if ( event.ButtonDClick() && !(m_windowStyle & wxPG_HIDE_MARGIN) )
|
|
{
|
|
wxPGProperty* pwc = (wxPGProperty*)p;
|
|
if ( pwc->IsExpanded() ) DoCollapse( p, true );
|
|
else DoExpand( p, true );
|
|
}
|
|
|
|
// Do not Skip() the event after selection has been made.
|
|
// Otherwise default event handling behaviour kicks in
|
|
// and may revert focus back to the main canvas.
|
|
res = true;
|
|
}
|
|
else
|
|
{
|
|
// click on splitter
|
|
if ( !(m_windowStyle & wxPG_STATIC_SPLITTER) )
|
|
{
|
|
if ( event.GetEventType() == wxEVT_LEFT_DCLICK )
|
|
{
|
|
// Double-clicking the splitter causes auto-centering
|
|
if ( m_pState->GetColumnCount() <= 2 )
|
|
{
|
|
ResetColumnSizes( true );
|
|
|
|
SendEvent(wxEVT_PG_COL_DRAGGING,
|
|
m_propHover,
|
|
NULL,
|
|
wxPG_SEL_NOVALIDATE,
|
|
(unsigned int)m_draggedSplitter);
|
|
}
|
|
}
|
|
else if ( m_dragStatus == 0 )
|
|
{
|
|
//
|
|
// Begin draggin the splitter
|
|
//
|
|
|
|
// send event
|
|
DoEndLabelEdit(true, wxPG_SEL_NOVALIDATE);
|
|
|
|
// Allow application to veto dragging
|
|
if ( !SendEvent(wxEVT_PG_COL_BEGIN_DRAG,
|
|
p, NULL, 0,
|
|
(unsigned int)splitterHit) )
|
|
{
|
|
if ( m_wndEditor )
|
|
{
|
|
// Changes must be committed here or the
|
|
// value won't be drawn correctly
|
|
if ( !CommitChangesFromEditor() )
|
|
return res;
|
|
|
|
m_wndEditor->Show ( false );
|
|
}
|
|
|
|
if ( !(m_iFlags & wxPG_FL_MOUSE_CAPTURED) )
|
|
{
|
|
CaptureMouse();
|
|
m_iFlags |= wxPG_FL_MOUSE_CAPTURED;
|
|
}
|
|
|
|
m_dragStatus = 1;
|
|
m_draggedSplitter = splitterHit;
|
|
m_dragOffset = splitterHitOffset;
|
|
|
|
#if wxPG_REFRESH_CONTROLS
|
|
// Fixes button disappearance bug
|
|
if ( m_wndEditor2 )
|
|
m_wndEditor2->Show ( false );
|
|
#endif
|
|
|
|
m_startingSplitterX = x - splitterHitOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Click on margin.
|
|
if ( p->GetChildCount() )
|
|
{
|
|
int nx = x + m_marginWidth - marginEnds; // Normalize x.
|
|
|
|
// Fine tune cell button x
|
|
if ( !p->IsCategory() )
|
|
nx -= IN_CELL_EXPANDER_BUTTON_X_ADJUST;
|
|
|
|
if ( (nx >= m_gutterWidth && nx < (m_gutterWidth+m_iconWidth)) )
|
|
{
|
|
int y2 = y % m_lineHeight;
|
|
if ( (y2 >= m_buttonSpacingY && y2 < (m_buttonSpacingY+m_iconHeight)) )
|
|
{
|
|
// On click on expander button, expand/collapse
|
|
if ( ((wxPGProperty*)p)->IsExpanded() )
|
|
DoCollapse( p, true );
|
|
else
|
|
DoExpand( p, true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::HandleMouseRightClick( int WXUNUSED(x),
|
|
unsigned int WXUNUSED(y),
|
|
wxMouseEvent& event )
|
|
{
|
|
if ( m_propHover )
|
|
{
|
|
// Select property here as well
|
|
wxPGProperty* p = m_propHover;
|
|
AddToSelectionFromInputEvent(p, m_colHover, &event);
|
|
|
|
// Send right click event.
|
|
SendEvent( wxEVT_PG_RIGHT_CLICK, p );
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::HandleMouseDoubleClick( int WXUNUSED(x),
|
|
unsigned int WXUNUSED(y),
|
|
wxMouseEvent& event )
|
|
{
|
|
if ( m_propHover )
|
|
{
|
|
// Select property here as well
|
|
wxPGProperty* p = m_propHover;
|
|
|
|
AddToSelectionFromInputEvent(p, m_colHover, &event);
|
|
|
|
// Send double-click event.
|
|
SendEvent( wxEVT_PG_DOUBLE_CLICK, m_propHover );
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Return false if should be skipped
|
|
bool wxPropertyGrid::HandleMouseMove( int x, unsigned int y,
|
|
wxMouseEvent &event )
|
|
{
|
|
// Safety check (needed because mouse capturing may
|
|
// otherwise freeze the control)
|
|
if ( m_dragStatus > 0 && !event.Dragging() )
|
|
{
|
|
HandleMouseUp(x, y, event);
|
|
}
|
|
|
|
wxPropertyGridPageState* state = m_pState;
|
|
int splitterHit;
|
|
int splitterHitOffset;
|
|
int columnHit = state->HitTestH( x, &splitterHit, &splitterHitOffset );
|
|
int splitterX = x - splitterHitOffset;
|
|
|
|
m_colHover = columnHit;
|
|
|
|
if ( m_dragStatus > 0 )
|
|
{
|
|
if ( x > (m_marginWidth + wxPG_DRAG_MARGIN) &&
|
|
x < (m_pState->m_width - wxPG_DRAG_MARGIN) )
|
|
{
|
|
|
|
int newSplitterX = x - m_dragOffset;
|
|
|
|
// Splitter redraw required?
|
|
if ( newSplitterX != splitterX )
|
|
{
|
|
// Move everything
|
|
DoSetSplitterPosition(newSplitterX,
|
|
m_draggedSplitter,
|
|
wxPG_SPLITTER_REFRESH |
|
|
wxPG_SPLITTER_FROM_EVENT);
|
|
|
|
SendEvent(wxEVT_PG_COL_DRAGGING,
|
|
m_propHover,
|
|
NULL,
|
|
wxPG_SEL_NOVALIDATE,
|
|
(unsigned int)m_draggedSplitter);
|
|
}
|
|
|
|
m_dragStatus = 2;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
|
|
int ih = m_lineHeight;
|
|
int sy = y;
|
|
|
|
#if wxPG_SUPPORT_TOOLTIPS
|
|
wxPGProperty* prevHover = m_propHover;
|
|
unsigned char prevSide = m_mouseSide;
|
|
#endif
|
|
int curPropHoverY = y - (y % ih);
|
|
|
|
// On which item it hovers
|
|
if ( !m_propHover
|
|
||
|
|
( sy < m_propHoverY || sy >= (m_propHoverY+ih) )
|
|
)
|
|
{
|
|
// Mouse moves on another property
|
|
|
|
m_propHover = DoGetItemAtY(y);
|
|
m_propHoverY = curPropHoverY;
|
|
|
|
// Send hover event
|
|
SendEvent( wxEVT_PG_HIGHLIGHTED, m_propHover );
|
|
}
|
|
|
|
#if wxPG_SUPPORT_TOOLTIPS
|
|
// Store which side we are on
|
|
m_mouseSide = 0;
|
|
if ( columnHit == 1 )
|
|
m_mouseSide = 2;
|
|
else if ( columnHit == 0 )
|
|
m_mouseSide = 1;
|
|
|
|
//
|
|
// If tooltips are enabled, show label or value as a tip
|
|
// in case it doesn't otherwise show in full length.
|
|
//
|
|
if ( m_windowStyle & wxPG_TOOLTIPS )
|
|
{
|
|
if ( m_propHover != prevHover || prevSide != m_mouseSide )
|
|
{
|
|
if ( m_propHover && !m_propHover->IsCategory() )
|
|
{
|
|
|
|
if ( GetExtraStyle() & wxPG_EX_HELP_AS_TOOLTIPS )
|
|
{
|
|
// Show help string as a tooltip
|
|
wxString tipString = m_propHover->GetHelpString();
|
|
|
|
SetToolTip(tipString);
|
|
}
|
|
else
|
|
{
|
|
// Show cropped value string as a tooltip
|
|
wxString tipString;
|
|
int space = 0;
|
|
|
|
if ( m_mouseSide == 1 )
|
|
{
|
|
tipString = m_propHover->m_label;
|
|
space = splitterX-m_marginWidth-3;
|
|
}
|
|
else if ( m_mouseSide == 2 )
|
|
{
|
|
tipString = m_propHover->GetDisplayedString();
|
|
|
|
space = m_width - splitterX;
|
|
if ( m_propHover->m_flags & wxPG_PROP_CUSTOMIMAGE )
|
|
space -= wxPG_CUSTOM_IMAGE_WIDTH +
|
|
wxCC_CUSTOM_IMAGE_MARGIN1 +
|
|
wxCC_CUSTOM_IMAGE_MARGIN2;
|
|
}
|
|
|
|
if ( space )
|
|
{
|
|
int tw, th;
|
|
GetTextExtent( tipString, &tw, &th, 0, 0 );
|
|
if ( tw > space )
|
|
SetToolTip( tipString );
|
|
}
|
|
else
|
|
{
|
|
SetToolTip( m_emptyString );
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetToolTip( m_emptyString );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ( splitterHit == -1 ||
|
|
!m_propHover ||
|
|
HasFlag(wxPG_STATIC_SPLITTER) )
|
|
{
|
|
// hovering on something else
|
|
if ( m_curcursor != wxCURSOR_ARROW )
|
|
CustomSetCursor( wxCURSOR_ARROW );
|
|
}
|
|
else
|
|
{
|
|
// Do not allow splitter cursor on caption items.
|
|
// (also not if we were dragging and its started
|
|
// outside the splitter region)
|
|
|
|
if ( !m_propHover->IsCategory() &&
|
|
!event.Dragging() )
|
|
{
|
|
|
|
// hovering on splitter
|
|
|
|
// NB: Condition disabled since MouseLeave event (from the
|
|
// editor control) cannot be reliably detected.
|
|
//if ( m_curcursor != wxCURSOR_SIZEWE )
|
|
CustomSetCursor( wxCURSOR_SIZEWE, true );
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// hovering on something else
|
|
if ( m_curcursor != wxCURSOR_ARROW )
|
|
CustomSetCursor( wxCURSOR_ARROW );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Multi select by dragging
|
|
//
|
|
if ( (GetExtraStyle() & wxPG_EX_MULTIPLE_SELECTION) &&
|
|
event.LeftIsDown() &&
|
|
m_propHover &&
|
|
GetSelection() &&
|
|
columnHit != 1 &&
|
|
!state->DoIsPropertySelected(m_propHover) )
|
|
{
|
|
// Additional requirement is that the hovered property
|
|
// is adjacent to edges of selection.
|
|
const wxArrayPGProperty& selection = GetSelectedProperties();
|
|
|
|
// Since categories cannot be selected along with 'other'
|
|
// properties, exclude them from iterator flags.
|
|
int iterFlags = wxPG_ITERATE_VISIBLE & (~wxPG_PROP_CATEGORY);
|
|
|
|
for ( int i=(selection.size()-1); i>=0; i-- )
|
|
{
|
|
// TODO: This could be optimized by keeping track of
|
|
// which properties are at the edges of selection.
|
|
wxPGProperty* selProp = selection[i];
|
|
if ( state->ArePropertiesAdjacent(m_propHover, selProp,
|
|
iterFlags) )
|
|
{
|
|
DoAddToSelection(m_propHover);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Also handles Leaving event
|
|
bool wxPropertyGrid::HandleMouseUp( int x, unsigned int WXUNUSED(y),
|
|
wxMouseEvent &WXUNUSED(event) )
|
|
{
|
|
wxPropertyGridPageState* state = m_pState;
|
|
bool res = false;
|
|
|
|
int splitterHit;
|
|
int splitterHitOffset;
|
|
state->HitTestH( x, &splitterHit, &splitterHitOffset );
|
|
|
|
// No event type check - basically calling this method should
|
|
// just stop dragging.
|
|
// Left up after dragged?
|
|
if ( m_dragStatus >= 1 )
|
|
{
|
|
//
|
|
// End Splitter Dragging
|
|
//
|
|
// DO NOT ENABLE FOLLOWING LINE!
|
|
// (it is only here as a reminder to not to do it)
|
|
//splitterX = x;
|
|
|
|
SendEvent(wxEVT_PG_COL_END_DRAG,
|
|
m_propHover,
|
|
NULL,
|
|
wxPG_SEL_NOVALIDATE,
|
|
(unsigned int)m_draggedSplitter);
|
|
|
|
// Disable splitter auto-centering (but only if moved any -
|
|
// otherwise we end up disabling auto-center even after a
|
|
// recentering double-click).
|
|
int posDiff = abs(m_startingSplitterX -
|
|
GetSplitterPosition(m_draggedSplitter));
|
|
|
|
if ( posDiff > 1 )
|
|
state->m_dontCenterSplitter = true;
|
|
|
|
// This is necessary to return cursor
|
|
if ( m_iFlags & wxPG_FL_MOUSE_CAPTURED )
|
|
{
|
|
ReleaseMouse();
|
|
m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED);
|
|
}
|
|
|
|
// Set back the default cursor, if necessary
|
|
if ( splitterHit == -1 ||
|
|
!m_propHover )
|
|
{
|
|
CustomSetCursor( wxCURSOR_ARROW );
|
|
}
|
|
|
|
m_dragStatus = 0;
|
|
|
|
// Control background needs to be cleared
|
|
wxPGProperty* selected = GetSelection();
|
|
if ( !(m_iFlags & wxPG_FL_PRIMARY_FILLS_ENTIRE) && selected )
|
|
DrawItem( selected );
|
|
|
|
if ( m_wndEditor )
|
|
{
|
|
m_wndEditor->Show ( true );
|
|
}
|
|
|
|
#if wxPG_REFRESH_CONTROLS
|
|
// Fixes button disappearance bug
|
|
if ( m_wndEditor2 )
|
|
m_wndEditor2->Show ( true );
|
|
#endif
|
|
|
|
// This clears the focus.
|
|
m_editorFocused = 0;
|
|
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::OnMouseCommon( wxMouseEvent& event, int* px, int* py )
|
|
{
|
|
int splitterX = GetSplitterPosition();
|
|
|
|
int ux, uy;
|
|
CalcUnscrolledPosition( event.m_x, event.m_y, &ux, &uy );
|
|
|
|
wxWindow* wnd = GetEditorControl();
|
|
|
|
// Hide popup on clicks
|
|
if ( event.GetEventType() != wxEVT_MOTION )
|
|
if ( wxDynamicCast(wnd, wxOwnerDrawnComboBox) )
|
|
{
|
|
((wxOwnerDrawnComboBox*)wnd)->HidePopup();
|
|
}
|
|
|
|
wxRect r;
|
|
if ( wnd )
|
|
r = wnd->GetRect();
|
|
if ( wnd == NULL || m_dragStatus ||
|
|
(
|
|
ux <= (splitterX + wxPG_SPLITTERX_DETECTMARGIN2) ||
|
|
ux >= (r.x+r.width) ||
|
|
event.m_y < r.y ||
|
|
event.m_y >= (r.y+r.height)
|
|
)
|
|
)
|
|
{
|
|
*px = ux;
|
|
*py = uy;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor ( wxCURSOR_ARROW );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnMouseClick( wxMouseEvent &event )
|
|
{
|
|
int x, y;
|
|
if ( OnMouseCommon( event, &x, &y ) )
|
|
{
|
|
if ( !HandleMouseClick(x, y, event) )
|
|
event.Skip();
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
|
|
// Do not Skip() event here - OnMouseClick() call above
|
|
// should have already taken care of it.
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
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 ) )
|
|
{
|
|
if ( !HandleMouseUp(x, y, event) )
|
|
event.Skip();
|
|
}
|
|
else
|
|
{
|
|
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.
|
|
|
|
if ( event.Entering() )
|
|
{
|
|
if ( !(m_iFlags & wxPG_FL_MOUSE_INSIDE) )
|
|
{
|
|
// TODO: Fix this (detect parent and only do
|
|
// cursor trick if it is a manager).
|
|
wxASSERT( GetParent() );
|
|
GetParent()->SetCursor(wxNullCursor);
|
|
|
|
m_iFlags |= wxPG_FL_MOUSE_INSIDE;
|
|
}
|
|
else
|
|
GetParent()->SetCursor(wxNullCursor);
|
|
}
|
|
else if ( event.Leaving() )
|
|
{
|
|
// Without this, wxSpinCtrl editor will sometimes have wrong cursor
|
|
SetCursor( wxNullCursor );
|
|
|
|
// Get real cursor position
|
|
wxPoint pt = ScreenToClient(::wxGetMousePosition());
|
|
|
|
if ( ( pt.x <= 0 || pt.y <= 0 || pt.x >= m_width || pt.y >= m_height ) )
|
|
{
|
|
{
|
|
if ( (m_iFlags & wxPG_FL_MOUSE_INSIDE) )
|
|
{
|
|
m_iFlags &= ~(wxPG_FL_MOUSE_INSIDE);
|
|
}
|
|
|
|
if ( m_dragStatus )
|
|
wxPropertyGrid::HandleMouseUp ( -1, 10000, event );
|
|
}
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Common code used by various OnMouseXXXChild methods.
|
|
bool wxPropertyGrid::OnMouseChildCommon( wxMouseEvent &event, int* px, int *py )
|
|
{
|
|
wxWindow* topCtrlWnd = (wxWindow*)event.GetEventObject();
|
|
wxASSERT( topCtrlWnd );
|
|
int x, y;
|
|
event.GetPosition(&x,&y);
|
|
|
|
int splitterX = GetSplitterPosition();
|
|
|
|
wxRect r = topCtrlWnd->GetRect();
|
|
if ( !m_dragStatus &&
|
|
x > (splitterX-r.x+wxPG_SPLITTERX_DETECTMARGIN2) &&
|
|
y >= 0 && y < r.height \
|
|
)
|
|
{
|
|
if ( m_curcursor != wxCURSOR_ARROW ) CustomSetCursor ( wxCURSOR_ARROW );
|
|
event.Skip();
|
|
}
|
|
else
|
|
{
|
|
CalcUnscrolledPosition( event.m_x + r.x, event.m_y + r.y, \
|
|
px, py );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void wxPropertyGrid::OnMouseClickChild( wxMouseEvent &event )
|
|
{
|
|
int x,y;
|
|
if ( OnMouseChildCommon(event,&x,&y) )
|
|
{
|
|
bool res = HandleMouseClick(x,y,event);
|
|
if ( !res ) event.Skip();
|
|
}
|
|
}
|
|
|
|
void wxPropertyGrid::OnMouseRightClickChild( wxMouseEvent &event )
|
|
{
|
|
int x,y;
|
|
wxASSERT( m_wndEditor );
|
|
// These coords may not be exact (about +-2),
|
|
// but that should not matter (right click is about item, not position).
|
|
wxPoint pt = m_wndEditor->GetPosition();
|
|
CalcUnscrolledPosition( event.m_x + pt.x, event.m_y + pt.y, &x, &y );
|
|
|
|
// FIXME: Used to set m_propHover to selection here. Was it really
|
|
// necessary?
|
|
|
|
bool res = HandleMouseRightClick(x,y,event);
|
|
if ( !res ) event.Skip();
|
|
}
|
|
|
|
void wxPropertyGrid::OnMouseMoveChild( wxMouseEvent &event )
|
|
{
|
|
int x,y;
|
|
if ( OnMouseChildCommon(event,&x,&y) )
|
|
{
|
|
bool res = HandleMouseMove(x,y,event);
|
|
if ( !res ) event.Skip();
|
|
}
|
|
}
|
|
|
|
void wxPropertyGrid::OnMouseUpChild( wxMouseEvent &event )
|
|
{
|
|
int x,y;
|
|
if ( OnMouseChildCommon(event,&x,&y) )
|
|
{
|
|
bool res = HandleMouseUp(x,y,event);
|
|
if ( !res ) event.Skip();
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid keyboard event handling
|
|
// -----------------------------------------------------------------------
|
|
|
|
int wxPropertyGrid::KeyEventToActions(wxKeyEvent &event, int* pSecond) const
|
|
{
|
|
// Translates wxKeyEvent to wxPG_ACTION_XXX
|
|
|
|
int keycode = event.GetKeyCode();
|
|
int modifiers = event.GetModifiers();
|
|
|
|
wxASSERT( !(modifiers&~(0xFFFF)) );
|
|
|
|
int hashMapKey = (keycode & 0xFFFF) | ((modifiers & 0xFFFF) << 16);
|
|
|
|
wxPGHashMapI2I::const_iterator it = m_actionTriggers.find(hashMapKey);
|
|
|
|
if ( it == m_actionTriggers.end() )
|
|
return 0;
|
|
|
|
if ( pSecond )
|
|
{
|
|
int second = (it->second>>16) & 0xFFFF;
|
|
*pSecond = second;
|
|
}
|
|
|
|
return (it->second & 0xFFFF);
|
|
}
|
|
|
|
void wxPropertyGrid::AddActionTrigger( int action, int keycode, int modifiers )
|
|
{
|
|
wxASSERT( !(modifiers&~(0xFFFF)) );
|
|
|
|
int hashMapKey = (keycode & 0xFFFF) | ((modifiers & 0xFFFF) << 16);
|
|
|
|
wxPGHashMapI2I::iterator it = m_actionTriggers.find(hashMapKey);
|
|
|
|
if ( it != m_actionTriggers.end() )
|
|
{
|
|
// This key combination is already used
|
|
|
|
// Can add secondary?
|
|
wxASSERT_MSG( !(it->second&~(0xFFFF)),
|
|
wxT("You can only add up to two separate actions per key combination.") );
|
|
|
|
action = it->second | (action<<16);
|
|
}
|
|
|
|
m_actionTriggers[hashMapKey] = action;
|
|
}
|
|
|
|
void wxPropertyGrid::ClearActionTriggers( int action )
|
|
{
|
|
wxPGHashMapI2I::iterator it;
|
|
bool didSomething;
|
|
|
|
do
|
|
{
|
|
didSomething = false;
|
|
|
|
for ( it = m_actionTriggers.begin();
|
|
it != m_actionTriggers.end();
|
|
it++ )
|
|
{
|
|
if ( it->second == action )
|
|
{
|
|
m_actionTriggers.erase(it);
|
|
didSomething = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while ( didSomething );
|
|
}
|
|
|
|
void wxPropertyGrid::HandleKeyEvent( wxKeyEvent &event, bool fromChild )
|
|
{
|
|
//
|
|
// Handles key event when editor control is not focused.
|
|
//
|
|
|
|
wxCHECK2(!IsFrozen(), return);
|
|
|
|
// Travelsal between items, collapsing/expanding, etc.
|
|
wxPGProperty* selected = GetSelection();
|
|
int keycode = event.GetKeyCode();
|
|
bool editorFocused = IsEditorFocused();
|
|
|
|
if ( keycode == WXK_TAB )
|
|
{
|
|
#if defined(__WXGTK__)
|
|
wxWindow* mainControl;
|
|
|
|
if ( HasInternalFlag(wxPG_FL_IN_MANAGER) )
|
|
mainControl = GetParent();
|
|
else
|
|
mainControl = this;
|
|
#endif
|
|
|
|
if ( !event.ShiftDown() )
|
|
{
|
|
if ( !editorFocused && m_wndEditor )
|
|
{
|
|
DoSelectProperty( selected, wxPG_SEL_FOCUS );
|
|
}
|
|
else
|
|
{
|
|
// Tab traversal workaround for platforms on which
|
|
// wxWindow::Navigate() may navigate into first child
|
|
// instead of next sibling. Does not work perfectly
|
|
// in every scenario (for instance, when property grid
|
|
// is either first or last control).
|
|
#if defined(__WXGTK__)
|
|
wxWindow* sibling = mainControl->GetNextSibling();
|
|
if ( sibling )
|
|
sibling->SetFocusFromKbd();
|
|
#else
|
|
Navigate(wxNavigationKeyEvent::IsForward);
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( editorFocused )
|
|
{
|
|
UnfocusEditor();
|
|
}
|
|
else
|
|
{
|
|
#if defined(__WXGTK__)
|
|
wxWindow* sibling = mainControl->GetPrevSibling();
|
|
if ( sibling )
|
|
sibling->SetFocusFromKbd();
|
|
#else
|
|
Navigate(wxNavigationKeyEvent::IsBackward);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Ignore Alt and Control when they are down alone
|
|
if ( keycode == WXK_ALT ||
|
|
keycode == WXK_CONTROL )
|
|
{
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
int secondAction;
|
|
int action = KeyEventToActions(event, &secondAction);
|
|
|
|
if ( editorFocused && action == wxPG_ACTION_CANCEL_EDIT )
|
|
{
|
|
//
|
|
// Esc cancels any changes
|
|
if ( IsEditorsValueModified() )
|
|
{
|
|
EditorsValueWasNotModified();
|
|
|
|
// Update the control as well
|
|
selected->GetEditorClass()->
|
|
SetControlStringValue( selected,
|
|
GetEditorControl(),
|
|
selected->GetDisplayedString() );
|
|
}
|
|
|
|
OnValidationFailureReset(selected);
|
|
|
|
UnfocusEditor();
|
|
return;
|
|
}
|
|
|
|
// Except for TAB, ESC, and any keys specifically dedicated to
|
|
// wxPropertyGrid itself, handle child control events in child control.
|
|
if ( fromChild &&
|
|
wxPGFindInVector(m_dedicatedKeys, keycode) == wxNOT_FOUND )
|
|
{
|
|
// Only propagate event if it had modifiers
|
|
if ( !event.HasModifiers() )
|
|
{
|
|
event.StopPropagation();
|
|
}
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
bool wasHandled = false;
|
|
|
|
if ( selected )
|
|
{
|
|
// Show dialog?
|
|
if ( ButtonTriggerKeyTest(action, event) )
|
|
return;
|
|
|
|
wxPGProperty* p = selected;
|
|
|
|
if ( action == wxPG_ACTION_EDIT && !editorFocused )
|
|
{
|
|
DoSelectProperty( p, wxPG_SEL_FOCUS );
|
|
wasHandled = true;
|
|
}
|
|
|
|
// Travel and expand/collapse
|
|
int selectDir = -2;
|
|
|
|
if ( p->GetChildCount() )
|
|
{
|
|
if ( action == wxPG_ACTION_COLLAPSE_PROPERTY || secondAction == wxPG_ACTION_COLLAPSE_PROPERTY )
|
|
{
|
|
if ( (m_windowStyle & wxPG_HIDE_MARGIN) || DoCollapse(p, true) )
|
|
wasHandled = true;
|
|
}
|
|
else if ( action == wxPG_ACTION_EXPAND_PROPERTY || secondAction == wxPG_ACTION_EXPAND_PROPERTY )
|
|
{
|
|
if ( (m_windowStyle & wxPG_HIDE_MARGIN) || DoExpand(p, true) )
|
|
wasHandled = true;
|
|
}
|
|
}
|
|
|
|
if ( !wasHandled )
|
|
{
|
|
if ( action == wxPG_ACTION_PREV_PROPERTY || secondAction == wxPG_ACTION_PREV_PROPERTY )
|
|
{
|
|
selectDir = -1;
|
|
}
|
|
else if ( action == wxPG_ACTION_NEXT_PROPERTY || secondAction == wxPG_ACTION_NEXT_PROPERTY )
|
|
{
|
|
selectDir = 1;
|
|
}
|
|
}
|
|
|
|
if ( selectDir >= -1 )
|
|
{
|
|
p = wxPropertyGridIterator::OneStep( m_pState, wxPG_ITERATE_VISIBLE, p, selectDir );
|
|
if ( p )
|
|
{
|
|
int selFlags = 0;
|
|
int reopenLabelEditorCol = -1;
|
|
|
|
if ( editorFocused )
|
|
{
|
|
// If editor was focused, then make the next editor
|
|
// focused as well
|
|
selFlags |= wxPG_SEL_FOCUS;
|
|
}
|
|
else
|
|
{
|
|
// Also maintain the same label editor focus state
|
|
if ( m_labelEditor )
|
|
reopenLabelEditorCol = m_selColumn;
|
|
}
|
|
|
|
DoSelectProperty(p, selFlags);
|
|
|
|
if ( reopenLabelEditorCol >= 0 )
|
|
DoBeginLabelEdit(reopenLabelEditorCol);
|
|
}
|
|
wasHandled = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If nothing was selected, select the first item now
|
|
// (or navigate out of tab).
|
|
if ( action != wxPG_ACTION_CANCEL_EDIT && secondAction != wxPG_ACTION_CANCEL_EDIT )
|
|
{
|
|
wxPGProperty* p = wxPropertyGridInterface::GetFirst();
|
|
if ( p ) DoSelectProperty(p);
|
|
wasHandled = true;
|
|
}
|
|
}
|
|
|
|
if ( !wasHandled )
|
|
event.Skip();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnKey( wxKeyEvent &event )
|
|
{
|
|
// If there was editor open and focused, then this event should not
|
|
// really be processed here.
|
|
if ( IsEditorFocused() )
|
|
{
|
|
// However, if event had modifiers, it is probably still best
|
|
// to skip it.
|
|
if ( event.HasModifiers() )
|
|
event.Skip();
|
|
else
|
|
event.StopPropagation();
|
|
return;
|
|
}
|
|
|
|
HandleKeyEvent(event, false);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGrid::ButtonTriggerKeyTest( int action, wxKeyEvent& event )
|
|
{
|
|
if ( action == -1 )
|
|
{
|
|
int secondAction;
|
|
action = KeyEventToActions(event, &secondAction);
|
|
}
|
|
|
|
// Does the keycode trigger button?
|
|
if ( action == wxPG_ACTION_PRESS_BUTTON &&
|
|
m_wndEditor2 )
|
|
{
|
|
wxCommandEvent evt(wxEVT_BUTTON, m_wndEditor2->GetId());
|
|
GetEventHandler()->AddPendingEvent(evt);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnChildKeyDown( wxKeyEvent &event )
|
|
{
|
|
HandleKeyEvent(event, true);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGrid miscellaneous event handling
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnIdle( wxIdleEvent& WXUNUSED(event) )
|
|
{
|
|
// Skip fake idle events generated e.g. by calling
|
|
// wxYield from within event handler.
|
|
if ( m_processedEvent )
|
|
return;
|
|
|
|
//
|
|
// Check if the focus is in this control or one of its children
|
|
wxWindow* newFocused = wxWindow::FindFocus();
|
|
|
|
if ( newFocused != m_curFocused )
|
|
HandleFocusChange( newFocused );
|
|
|
|
//
|
|
// Check if top-level parent has changed
|
|
if ( GetExtraStyle() & wxPG_EX_ENABLE_TLP_TRACKING )
|
|
{
|
|
wxWindow* tlp = ::wxGetTopLevelParent(this);
|
|
if ( tlp != m_tlp )
|
|
OnTLPChanging(tlp);
|
|
}
|
|
|
|
// Delete pending property editors and their event handlers.
|
|
DeletePendingObjects();
|
|
|
|
//
|
|
// Resolve pending property removals
|
|
// In order to determine whether deletion/removal
|
|
// was done we need to track the size of the list
|
|
// before and after the operation.
|
|
// (Note that lists are changed at every operation.)
|
|
size_t cntAfter = m_deletedProperties.size();
|
|
while ( cntAfter > 0 )
|
|
{
|
|
size_t cntBefore = cntAfter;
|
|
|
|
DeleteProperty(m_deletedProperties[0]);
|
|
|
|
cntAfter = m_deletedProperties.size();
|
|
wxASSERT_MSG( cntAfter <= cntBefore,
|
|
wxT("Increased number of pending items after deletion") );
|
|
// Break if deletion was not done
|
|
if ( cntAfter >= cntBefore )
|
|
break;
|
|
}
|
|
cntAfter = m_removedProperties.size();
|
|
while ( cntAfter > 0 )
|
|
{
|
|
size_t cntBefore = cntAfter;
|
|
|
|
RemoveProperty(m_removedProperties[0]);
|
|
|
|
cntAfter = m_removedProperties.size();
|
|
wxASSERT_MSG( cntAfter <= cntBefore,
|
|
wxT("Increased number of pending items after removal") );
|
|
// Break if removal was not done
|
|
if ( cntAfter >= cntBefore )
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool wxPropertyGrid::IsEditorFocused() const
|
|
{
|
|
wxWindow* focus = wxWindow::FindFocus();
|
|
|
|
if ( focus == m_wndEditor || focus == m_wndEditor2 ||
|
|
focus == GetEditorControl() )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Called by focus event handlers. newFocused is the window that becomes focused.
|
|
void wxPropertyGrid::HandleFocusChange( wxWindow* newFocused )
|
|
{
|
|
//
|
|
// Never allow focus to be changed when handling editor event.
|
|
// Especially because they may be displaing a dialog which
|
|
// could cause all kinds of weird (native) focus changes.
|
|
if ( HasInternalFlag(wxPG_FL_IN_HANDLECUSTOMEDITOREVENT) )
|
|
return;
|
|
|
|
unsigned int oldFlags = m_iFlags;
|
|
bool wasEditorFocused = false;
|
|
wxWindow* wndEditor = m_wndEditor;
|
|
|
|
m_iFlags &= ~(wxPG_FL_FOCUSED);
|
|
|
|
wxWindow* parent = newFocused;
|
|
|
|
// This must be one of nextFocus' parents.
|
|
while ( parent )
|
|
{
|
|
if ( parent == wndEditor )
|
|
{
|
|
wasEditorFocused = true;
|
|
}
|
|
// Use m_eventObject, which is either wxPropertyGrid or
|
|
// wxPropertyGridManager, as appropriate.
|
|
else if ( parent == m_eventObject )
|
|
{
|
|
m_iFlags |= wxPG_FL_FOCUSED;
|
|
break;
|
|
}
|
|
parent = parent->GetParent();
|
|
}
|
|
|
|
// Notify editor control when it receives a focus
|
|
if ( wasEditorFocused && m_curFocused != newFocused )
|
|
{
|
|
wxPGProperty* p = GetSelection();
|
|
if ( p )
|
|
{
|
|
const wxPGEditor* editor = p->GetEditorClass();
|
|
ResetEditorAppearance();
|
|
editor->OnFocus(p, GetEditorControl());
|
|
}
|
|
}
|
|
|
|
m_curFocused = newFocused;
|
|
|
|
if ( (m_iFlags & wxPG_FL_FOCUSED) !=
|
|
(oldFlags & wxPG_FL_FOCUSED) )
|
|
{
|
|
if ( !(m_iFlags & wxPG_FL_FOCUSED) )
|
|
{
|
|
// 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());
|
|
|
|
}
|
|
*/
|
|
}
|
|
|
|
// Redraw selected
|
|
wxPGProperty* selected = GetSelection();
|
|
if ( selected && (m_iFlags & wxPG_FL_INITIALIZED) )
|
|
DrawItem( selected );
|
|
}
|
|
}
|
|
|
|
void wxPropertyGrid::OnFocusEvent( wxFocusEvent& event )
|
|
{
|
|
if ( event.GetEventType() == wxEVT_SET_FOCUS )
|
|
HandleFocusChange((wxWindow*)event.GetEventObject());
|
|
// Line changed to "else" when applying wxPropertyGrid patch #1675902
|
|
//else if ( event.GetWindow() )
|
|
else
|
|
HandleFocusChange(event.GetWindow());
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGrid::OnChildFocusEvent( wxChildFocusEvent& event )
|
|
{
|
|
HandleFocusChange((wxWindow*)event.GetEventObject());
|
|
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 )
|
|
{
|
|
m_iFlags &= ~(wxPG_FL_MOUSE_CAPTURED);
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Property editor related functions
|
|
// -----------------------------------------------------------------------
|
|
|
|
// noDefCheck = true prevents infinite recursion.
|
|
wxPGEditor* wxPropertyGrid::DoRegisterEditorClass( wxPGEditor* editorClass,
|
|
const wxString& editorName,
|
|
bool noDefCheck )
|
|
{
|
|
wxASSERT( editorClass );
|
|
|
|
if ( !noDefCheck && wxPGGlobalVars->m_mapEditorClasses.empty() )
|
|
RegisterDefaultEditors();
|
|
|
|
wxString name = editorName;
|
|
if ( name.empty() )
|
|
name = editorClass->GetName();
|
|
|
|
// Existing editor under this name?
|
|
wxPGHashMapS2P::iterator vt_it = wxPGGlobalVars->m_mapEditorClasses.find(name);
|
|
|
|
if ( vt_it != wxPGGlobalVars->m_mapEditorClasses.end() )
|
|
{
|
|
// If this name was already used, try class name.
|
|
name = editorClass->GetClassInfo()->GetClassName();
|
|
vt_it = wxPGGlobalVars->m_mapEditorClasses.find(name);
|
|
}
|
|
|
|
wxCHECK_MSG( vt_it == wxPGGlobalVars->m_mapEditorClasses.end(),
|
|
(wxPGEditor*) vt_it->second,
|
|
"Editor with given name was already registered" );
|
|
|
|
wxPGGlobalVars->m_mapEditorClasses[name] = (void*)editorClass;
|
|
|
|
return editorClass;
|
|
}
|
|
|
|
// Use this in RegisterDefaultEditors.
|
|
#define wxPGRegisterDefaultEditorClass(EDITOR) \
|
|
if ( wxPGEditor_##EDITOR == NULL ) \
|
|
{ \
|
|
wxPGEditor_##EDITOR = wxPropertyGrid::RegisterEditorClass( \
|
|
new wxPG##EDITOR##Editor, true ); \
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPGStringTokenizer
|
|
// Needed to handle C-style string lists (e.g. "str1" "str2")
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGStringTokenizer::wxPGStringTokenizer( const wxString& str, wxChar delimeter )
|
|
: m_str(&str), m_curPos(str.begin()), m_delimeter(delimeter)
|
|
{
|
|
}
|
|
|
|
wxPGStringTokenizer::~wxPGStringTokenizer()
|
|
{
|
|
}
|
|
|
|
bool wxPGStringTokenizer::HasMoreTokens()
|
|
{
|
|
const wxString& str = *m_str;
|
|
|
|
wxString::const_iterator i = m_curPos;
|
|
|
|
wxUniChar delim = m_delimeter;
|
|
wxUniChar a;
|
|
wxUniChar prev_a = wxT('\0');
|
|
|
|
bool inToken = false;
|
|
|
|
while ( i != str.end() )
|
|
{
|
|
a = *i;
|
|
|
|
if ( !inToken )
|
|
{
|
|
if ( a == delim )
|
|
{
|
|
inToken = true;
|
|
m_readyToken.clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( prev_a != wxT('\\') )
|
|
{
|
|
if ( a != delim )
|
|
{
|
|
if ( a != wxT('\\') )
|
|
m_readyToken << a;
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
m_curPos = i;
|
|
return true;
|
|
}
|
|
prev_a = a;
|
|
}
|
|
else
|
|
{
|
|
m_readyToken << a;
|
|
prev_a = wxT('\0');
|
|
}
|
|
}
|
|
++i;
|
|
}
|
|
|
|
m_curPos = str.end();
|
|
|
|
if ( inToken )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wxString wxPGStringTokenizer::GetNextToken()
|
|
{
|
|
return m_readyToken;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPGChoiceEntry
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGChoiceEntry::wxPGChoiceEntry()
|
|
: wxPGCell(), m_value(wxPG_INVALID_VALUE)
|
|
{
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPGChoicesData
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGChoicesData::wxPGChoicesData()
|
|
{
|
|
}
|
|
|
|
wxPGChoicesData::~wxPGChoicesData()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void wxPGChoicesData::Clear()
|
|
{
|
|
m_items.clear();
|
|
}
|
|
|
|
void wxPGChoicesData::CopyDataFrom( wxPGChoicesData* data )
|
|
{
|
|
wxASSERT( m_items.size() == 0 );
|
|
|
|
m_items = data->m_items;
|
|
}
|
|
|
|
wxPGChoiceEntry& wxPGChoicesData::Insert( int index,
|
|
const wxPGChoiceEntry& item )
|
|
{
|
|
wxVector<wxPGChoiceEntry>::iterator it;
|
|
if ( index == -1 )
|
|
{
|
|
it = m_items.end();
|
|
index = (int) m_items.size();
|
|
}
|
|
else
|
|
{
|
|
it = m_items.begin() + index;
|
|
}
|
|
|
|
m_items.insert(it, item);
|
|
|
|
wxPGChoiceEntry& ownEntry = m_items[index];
|
|
|
|
// Need to fix value?
|
|
if ( ownEntry.GetValue() == wxPG_INVALID_VALUE )
|
|
ownEntry.SetValue(index);
|
|
|
|
return ownEntry;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGridEvent
|
|
// -----------------------------------------------------------------------
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(wxPropertyGridEvent, wxCommandEvent)
|
|
|
|
|
|
wxDEFINE_EVENT( wxEVT_PG_SELECTED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_CHANGING, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_CHANGED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_HIGHLIGHTED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_RIGHT_CLICK, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_PAGE_CHANGED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_ITEM_EXPANDED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_ITEM_COLLAPSED, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_DOUBLE_CLICK, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_LABEL_EDIT_BEGIN, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_LABEL_EDIT_ENDING, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_COL_BEGIN_DRAG, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_COL_DRAGGING, wxPropertyGridEvent );
|
|
wxDEFINE_EVENT( wxEVT_PG_COL_END_DRAG, wxPropertyGridEvent );
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridEvent::Init()
|
|
{
|
|
m_validationInfo = NULL;
|
|
m_column = 1;
|
|
m_canVeto = false;
|
|
m_wasVetoed = false;
|
|
m_pg = NULL;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridEvent::wxPropertyGridEvent(wxEventType commandType, int id)
|
|
: wxCommandEvent(commandType,id)
|
|
{
|
|
m_property = NULL;
|
|
Init();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridEvent::wxPropertyGridEvent(const wxPropertyGridEvent& event)
|
|
: wxCommandEvent(event)
|
|
{
|
|
m_eventType = event.GetEventType();
|
|
m_eventObject = event.m_eventObject;
|
|
m_pg = event.m_pg;
|
|
OnPropertyGridSet();
|
|
m_property = event.m_property;
|
|
m_validationInfo = event.m_validationInfo;
|
|
m_canVeto = event.m_canVeto;
|
|
m_wasVetoed = event.m_wasVetoed;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridEvent::OnPropertyGridSet()
|
|
{
|
|
if ( !m_pg )
|
|
return;
|
|
|
|
#if wxUSE_THREADS
|
|
wxCriticalSectionLocker(wxPGGlobalVars->m_critSect);
|
|
#endif
|
|
m_pg->m_liveEvents.push_back(this);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridEvent::~wxPropertyGridEvent()
|
|
{
|
|
if ( m_pg )
|
|
{
|
|
#if wxUSE_THREADS
|
|
wxCriticalSectionLocker(wxPGGlobalVars->m_critSect);
|
|
#endif
|
|
|
|
// Use iterate from the back since it is more likely that the event
|
|
// being desroyed is at the end of the array.
|
|
wxVector<wxPropertyGridEvent*>& liveEvents = m_pg->m_liveEvents;
|
|
|
|
for ( int i = liveEvents.size()-1; i >= 0; i-- )
|
|
{
|
|
if ( liveEvents[i] == this )
|
|
{
|
|
liveEvents.erase(liveEvents.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxEvent* wxPropertyGridEvent::Clone() const
|
|
{
|
|
return new wxPropertyGridEvent( *this );
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// wxPropertyGridPopulator
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridPopulator::wxPropertyGridPopulator()
|
|
{
|
|
m_state = NULL;
|
|
m_pg = NULL;
|
|
wxPGGlobalVars->m_offline++;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridPopulator::SetState( wxPropertyGridPageState* state )
|
|
{
|
|
m_state = state;
|
|
m_propHierarchy.clear();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridPopulator::SetGrid( wxPropertyGrid* pg )
|
|
{
|
|
m_pg = pg;
|
|
pg->Freeze();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPropertyGridPopulator::~wxPropertyGridPopulator()
|
|
{
|
|
//
|
|
// Free unused sets of choices
|
|
wxPGHashMapS2P::iterator it;
|
|
|
|
for( it = m_dictIdChoices.begin(); it != m_dictIdChoices.end(); ++it )
|
|
{
|
|
wxPGChoicesData* data = (wxPGChoicesData*) it->second;
|
|
data->DecRef();
|
|
}
|
|
|
|
if ( m_pg )
|
|
{
|
|
m_pg->Thaw();
|
|
m_pg->GetPanel()->Refresh();
|
|
}
|
|
wxPGGlobalVars->m_offline--;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGProperty* wxPropertyGridPopulator::Add( const wxString& propClass,
|
|
const wxString& propLabel,
|
|
const wxString& propName,
|
|
const wxString* propValue,
|
|
wxPGChoices* pChoices )
|
|
{
|
|
wxClassInfo* classInfo = wxClassInfo::FindClass(propClass);
|
|
wxPGProperty* parent = GetCurParent();
|
|
|
|
if ( parent->HasFlag(wxPG_PROP_AGGREGATE) )
|
|
{
|
|
ProcessError(wxString::Format(wxT("new children cannot be added to '%s'"),parent->GetName().c_str()));
|
|
return NULL;
|
|
}
|
|
|
|
if ( !classInfo || !classInfo->IsKindOf(wxCLASSINFO(wxPGProperty)) )
|
|
{
|
|
ProcessError(wxString::Format(wxT("'%s' is not valid property class"),propClass.c_str()));
|
|
return NULL;
|
|
}
|
|
|
|
wxPGProperty* property = (wxPGProperty*) classInfo->CreateObject();
|
|
|
|
property->SetLabel(propLabel);
|
|
property->DoSetName(propName);
|
|
|
|
if ( pChoices && pChoices->IsOk() )
|
|
property->SetChoices(*pChoices);
|
|
|
|
m_state->DoInsert(parent, -1, property);
|
|
|
|
if ( propValue )
|
|
property->SetValueFromString( *propValue, wxPG_FULL_VALUE|
|
|
wxPG_PROGRAMMATIC_VALUE );
|
|
|
|
return property;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridPopulator::AddChildren( wxPGProperty* property )
|
|
{
|
|
m_propHierarchy.push_back(property);
|
|
DoScanForChildren();
|
|
m_propHierarchy.pop_back();
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
wxPGChoices wxPropertyGridPopulator::ParseChoices( const wxString& choicesString,
|
|
const wxString& idString )
|
|
{
|
|
wxPGChoices choices;
|
|
|
|
// Using id?
|
|
if ( choicesString[0] == wxT('@') )
|
|
{
|
|
wxString ids = choicesString.substr(1);
|
|
wxPGHashMapS2P::iterator it = m_dictIdChoices.find(ids);
|
|
if ( it == m_dictIdChoices.end() )
|
|
ProcessError(wxString::Format(wxT("No choices defined for id '%s'"),ids.c_str()));
|
|
else
|
|
choices.AssignData((wxPGChoicesData*)it->second);
|
|
}
|
|
else
|
|
{
|
|
bool found = false;
|
|
if ( !idString.empty() )
|
|
{
|
|
wxPGHashMapS2P::iterator it = m_dictIdChoices.find(idString);
|
|
if ( it != m_dictIdChoices.end() )
|
|
{
|
|
choices.AssignData((wxPGChoicesData*)it->second);
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if ( !found )
|
|
{
|
|
// Parse choices string
|
|
wxString::const_iterator it = choicesString.begin();
|
|
wxString label;
|
|
wxString value;
|
|
int state = 0;
|
|
bool labelValid = false;
|
|
|
|
for ( ; it != choicesString.end(); ++it )
|
|
{
|
|
wxChar c = *it;
|
|
|
|
if ( state != 1 )
|
|
{
|
|
if ( c == wxT('"') )
|
|
{
|
|
if ( labelValid )
|
|
{
|
|
long l;
|
|
if ( !value.ToLong(&l, 0) ) l = wxPG_INVALID_VALUE;
|
|
choices.Add(label, l);
|
|
}
|
|
labelValid = false;
|
|
//wxLogDebug(wxT("%s, %s"),label.c_str(),value.c_str());
|
|
value.clear();
|
|
label.clear();
|
|
state = 1;
|
|
}
|
|
else if ( c == wxT('=') )
|
|
{
|
|
if ( labelValid )
|
|
{
|
|
state = 2;
|
|
}
|
|
}
|
|
else if ( state == 2 && (wxIsalnum(c) || c == wxT('x')) )
|
|
{
|
|
value << c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( c == wxT('"') )
|
|
{
|
|
state = 0;
|
|
labelValid = true;
|
|
}
|
|
else
|
|
label << c;
|
|
}
|
|
}
|
|
|
|
if ( labelValid )
|
|
{
|
|
long l;
|
|
if ( !value.ToLong(&l, 0) ) l = wxPG_INVALID_VALUE;
|
|
choices.Add(label, l);
|
|
}
|
|
|
|
if ( !choices.IsOk() )
|
|
{
|
|
choices.EnsureData();
|
|
}
|
|
|
|
// Assign to id
|
|
if ( !idString.empty() )
|
|
m_dictIdChoices[idString] = choices.GetData();
|
|
}
|
|
}
|
|
|
|
return choices;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGridPopulator::ToLongPCT( const wxString& s, long* pval, long max )
|
|
{
|
|
if ( s.Last() == wxT('%') )
|
|
{
|
|
wxString s2 = s.substr(0,s.length()-1);
|
|
long val;
|
|
if ( s2.ToLong(&val, 10) )
|
|
{
|
|
*pval = (val*max)/100;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return s.ToLong(pval, 10);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
bool wxPropertyGridPopulator::AddAttribute( const wxString& name,
|
|
const wxString& type,
|
|
const wxString& value )
|
|
{
|
|
int l = m_propHierarchy.size();
|
|
if ( !l )
|
|
return false;
|
|
|
|
wxPGProperty* p = m_propHierarchy[l-1];
|
|
wxString valuel = value.Lower();
|
|
wxVariant variant;
|
|
|
|
if ( type.empty() )
|
|
{
|
|
long v;
|
|
|
|
// Auto-detect type
|
|
if ( valuel == wxT("true") || valuel == wxT("yes") || valuel == wxT("1") )
|
|
variant = true;
|
|
else if ( valuel == wxT("false") || valuel == wxT("no") || valuel == wxT("0") )
|
|
variant = false;
|
|
else if ( value.ToLong(&v, 0) )
|
|
variant = v;
|
|
else
|
|
variant = value;
|
|
}
|
|
else
|
|
{
|
|
if ( type == wxT("string") )
|
|
{
|
|
variant = value;
|
|
}
|
|
else if ( type == wxT("int") )
|
|
{
|
|
long v = 0;
|
|
value.ToLong(&v, 0);
|
|
variant = v;
|
|
}
|
|
else if ( type == wxT("bool") )
|
|
{
|
|
if ( valuel == wxT("true") || valuel == wxT("yes") || valuel == wxT("1") )
|
|
variant = true;
|
|
else
|
|
variant = false;
|
|
}
|
|
else
|
|
{
|
|
ProcessError(wxString::Format(wxT("Invalid attribute type '%s'"),type.c_str()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
p->SetAttribute( name, variant );
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
void wxPropertyGridPopulator::ProcessError( const wxString& msg )
|
|
{
|
|
wxLogError(_("Error in resource: %s"),msg.c_str());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
#endif // wxUSE_PROPGRID
|