//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "console/engineAPI.h" #include "platform/platform.h" #include "gui/editor/inspector/field.h" #include "gui/buttons/guiIconButtonCtrl.h" #include "gui/editor/guiInspector.h" #include "core/util/safeDelete.h" #include "gfx/gfxDrawUtil.h" #include "math/mathTypes.h" #include "core/strings/stringUnit.h" IMPLEMENT_CONOBJECT(GuiInspectorField); ConsoleDocClass( GuiInspectorField, "@brief The GuiInspectorField control is a representation of a single abstract " "field for a given ConsoleObject derived object.\n\n" "Editor use only.\n\n" "@internal" ); //----------------------------------------------------------------------------- GuiInspectorField::GuiInspectorField( GuiInspector* inspector, GuiInspectorGroup* parent, AbstractClassRep::Field* field ) : mParent( parent ), mInspector( inspector ), mField( field ), mFieldArrayIndex( NULL ), mEdit( NULL ), mTargetObject(NULL), mUseHeightOverride(false), mHighlighted(false), mHeightOverride(18), mSpecialEditField(false), mVariableName(StringTable->EmptyString()), mCallbackName(StringTable->EmptyString()) { if( field != NULL ) mCaption = field->pFieldname; else mCaption = StringTable->EmptyString(); setCanSave( false ); setBounds(0,0,100,18); if( field != NULL ) _setFieldDocs( field->pFieldDocs ); } //----------------------------------------------------------------------------- GuiInspectorField::GuiInspectorField() : mParent( NULL ), mInspector( NULL ), mField( NULL ), mEdit( NULL ), mCaption( StringTable->EmptyString() ), mFieldArrayIndex( NULL ), mHighlighted( false ), mTargetObject(NULL), mVariableName(StringTable->EmptyString()), mCallbackName(StringTable->EmptyString()), mSpecialEditField(false), mUseHeightOverride(false), mHeightOverride(18) { setCanSave( false ); } //----------------------------------------------------------------------------- GuiInspectorField::~GuiInspectorField() { } //----------------------------------------------------------------------------- void GuiInspectorField::init( GuiInspector *inspector, GuiInspectorGroup *group ) { mInspector = inspector; mParent = group; } //----------------------------------------------------------------------------- bool GuiInspectorField::onAdd() { setInspectorProfile(); if ( !Parent::onAdd() ) return false; if ( !mInspector ) return false; mEdit = constructEditControl(); if ( mEdit == NULL ) return false; S32 fieldHeight = 18; if (mUseHeightOverride) fieldHeight = mHeightOverride; setBounds(0,0,100, fieldHeight); // Add our edit as a child addObject( mEdit ); // Calculate Caption and EditCtrl Rects updateRects(); // Force our editField to set it's value updateValue(); Con::evaluatef("%d.edit = %d;", this->getId(), mEdit->getId()); return true; } //----------------------------------------------------------------------------- bool GuiInspectorField::resize( const Point2I &newPosition, const Point2I &newExtent ) { if ( !Parent::resize( newPosition, newExtent ) ) return false; return updateRects(); } //----------------------------------------------------------------------------- void GuiInspectorField::onRender( Point2I offset, const RectI &updateRect ) { RectI ctrlRect(offset, getExtent()); // Render fillcolor... if ( mProfile->mOpaque ) GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor); // Render caption... if ( mCaption && mCaption[0] ) { // Backup current ClipRect RectI clipBackup = GFX->getClipRect(); RectI clipRect = updateRect; // The rect within this control in which our caption must fit. RectI rect( offset + mCaptionRect.point + mProfile->mTextOffset, mCaptionRect.extent + Point2I(1,1) - Point2I(5,0) ); // Now clipRect is the amount of our caption rect that is actually visible. bool hit = clipRect.intersect( rect ); if ( hit ) { GFX->setClipRect( clipRect ); GFXDrawUtil *drawer = GFX->getDrawUtil(); // Backup modulation color ColorI currColor; drawer->getBitmapModulation( &currColor ); // Draw caption background... if( !isActive() ) GFX->getDrawUtil()->drawRectFill( clipRect, mProfile->mFillColorNA ); else if ( mHighlighted ) GFX->getDrawUtil()->drawRectFill( clipRect, mProfile->mFillColorHL ); // Draw caption text... drawer->setBitmapModulation( !isActive() ? mProfile->mFontColorNA : mHighlighted ? mProfile->mFontColorHL : mProfile->mFontColor ); // Clip text with '...' if too long to fit String clippedText( mCaption ); clipText( clippedText, clipRect.extent.x ); renderJustifiedText( offset + mProfile->mTextOffset, getExtent(), clippedText ); // Restore modulation color drawer->setBitmapModulation( currColor ); // Restore previous ClipRect GFX->setClipRect( clipBackup ); } } // Render Children... renderChildControls(offset, updateRect); // Render border... if ( mProfile->mBorder ) renderBorder(ctrlRect, mProfile); // Render divider... Point2I worldPnt = mEditCtrlRect.point + offset; GFX->getDrawUtil()->drawLine( worldPnt.x - 5, worldPnt.y, worldPnt.x - 5, worldPnt.y + getHeight(), !isActive() ? mProfile->mBorderColorNA : mHighlighted ? mProfile->mBorderColorHL : mProfile->mBorderColor ); } //----------------------------------------------------------------------------- void GuiInspectorField::setFirstResponder( GuiControl *firstResponder ) { Parent::setFirstResponder( firstResponder ); if ( firstResponder == this || firstResponder == mEdit ) { mInspector->setHighlightField( this ); } } //----------------------------------------------------------------------------- void GuiInspectorField::onMouseDown( const GuiEvent &event ) { if ( mCaptionRect.pointInRect( globalToLocalCoord( event.mousePoint ) ) ) { if ( mEdit ) //mEdit->onMouseDown( event ); mInspector->setHighlightField( this ); } else Parent::onMouseDown( event ); } //----------------------------------------------------------------------------- void GuiInspectorField::onRightMouseUp( const GuiEvent &event ) { if ( mCaptionRect.pointInRect( globalToLocalCoord( event.mousePoint ) ) ) Con::executef( mInspector, "onFieldRightClick", getIdString() ); else Parent::onMouseDown( event ); } //----------------------------------------------------------------------------- void GuiInspectorField::setData( const char* data, bool callbacks ) { if (mSpecialEditField) { if (mTargetObject != nullptr && mVariableName != StringTable->EmptyString()) { mTargetObject->setDataField(mVariableName, NULL, data); if (mCallbackName != StringTable->EmptyString()) Con::executef(mInspector, mCallbackName, mVariableName, data, mTargetObject); } else if (mVariableName != StringTable->EmptyString()) { Con::setVariable(mVariableName, data); if (mCallbackName != StringTable->EmptyString()) Con::executef(mInspector, mCallbackName, mVariableName, data); } } if( mField == NULL ) return; if( verifyData( data ) ) { String strData = data; const U32 numTargets = mInspector->getNumInspectObjects(); if( callbacks && numTargets > 1 ) Con::executef( mInspector, "onBeginCompoundEdit" ); for( U32 i = 0; i < numTargets; ++ i ) { //For now, for simplicity's sake, you can only edit the components in a simple edit SimObject* target = NULL; if (numTargets == 1) { target = mTargetObject; if (!target) target = mInspector->getInspectObject(i); } else { target = mInspector->getInspectObject(i); } String oldValue = target->getDataField( mField->pFieldname, mFieldArrayIndex); // For numeric fields, allow input expressions. String newValue = strData; S32 type= mField->type; ConsoleValue evaluationResult; if( type == TypeS8 || type == TypeS32 || type == TypeF32 ) { char buffer[ 2048 ]; expandEscape( buffer, newValue ); evaluationResult = Con::evaluatef("$f = \"%s\"; return ( %s );", oldValue.c_str(), buffer); newValue = evaluationResult.getString(); Con::evaluatef("$f=0;"); } else if( type == TypeS32Vector || type == TypeF32Vector || type == TypeColorI || type == TypeColorF || type == TypePoint2I || type == TypePoint2F || type == TypePoint3F || type == TypePoint4F || type == TypeRectI || type == TypeRectF || type == TypeMatrixPosition || type == TypeMatrixRotation || type == TypeBox3F || type == TypeRectUV || type == TypeRotationF) { //TODO: we should actually take strings into account and not chop things up between quotes U32 numNewUnits = StringUnit::getUnitCount( newValue, " \t\n\r" ); StringBuilder strNew; bool isFirst = true; for( U32 n = 0; n < numNewUnits; ++ n ) { char oldComponentVal[ 1024 ]; StringUnit::getUnit( oldValue, n, " \t\n\r", oldComponentVal, sizeof( oldComponentVal ) ); char newComponentExpr[ 1024 ]; StringUnit::getUnit( newValue, n, " \t\n\r", newComponentExpr, sizeof( newComponentExpr ) ); char buffer[ 2048 ]; expandEscape( buffer, newComponentExpr ); evaluationResult = Con::evaluatef("$f = \"%s\"; $v = \"%s\"; return ( %s );", oldComponentVal, oldValue.c_str(), buffer); Con::evaluatef("$f=0;$v=0;"); if( !isFirst ) strNew.append( ' ' ); strNew.append( evaluationResult.getString() ); isFirst = false; } newValue = strNew.end(); } target->inspectPreApply(); // Fire callback single-object undo. if( callbacks ) Con::executef( mInspector, "onInspectorFieldModified", target->getIdString(), mField->pFieldname, mFieldArrayIndex ? mFieldArrayIndex : "(null)", oldValue.c_str(), newValue.c_str() ); target->setDataField( mField->pFieldname, mFieldArrayIndex, newValue ); // Give the target a chance to validate. target->inspectPostApply(); } if( callbacks && numTargets > 1 ) Con::executef( mInspector, "onEndCompoundEdit" ); } // Force our edit to update updateValue(); } //----------------------------------------------------------------------------- const char* GuiInspectorField::getData( U32 inspectObjectIndex ) { if (!mSpecialEditField) { if (mField == NULL) return ""; if (mTargetObject) return mTargetObject->getDataField(mField->pFieldname, mFieldArrayIndex); return mInspector->getInspectObject(inspectObjectIndex)->getDataField(mField->pFieldname, mFieldArrayIndex); } else { if (mTargetObject != nullptr && mVariableName != StringTable->EmptyString()) { return mTargetObject->getDataField(mVariableName, NULL); } else if (mVariableName != StringTable->EmptyString()) { return Con::getVariable(mVariableName); } else { return ""; } } } //----------------------------------------------------------------------------- void GuiInspectorField::resetData() { if( !mField ) return; SimObject* inspectObject = getInspector()->getInspectObject(); SimObject* tempObject = static_cast< SimObject* >( inspectObject->getClassRep()->create() ); setData( tempObject->getDataField( mField->pFieldname, mFieldArrayIndex ) ); delete tempObject; } //----------------------------------------------------------------------------- void GuiInspectorField::setInspectorField( AbstractClassRep::Field *field, StringTableEntry caption, const char*arrayIndex ) { mField = field; if ( arrayIndex != NULL ) mFieldArrayIndex = StringTable->insert( arrayIndex ); if ( !caption || !caption[0] ) mCaption = getFieldName(); else mCaption = caption; if ( mField != NULL ) _setFieldDocs( mField->pFieldDocs ); } //----------------------------------------------------------------------------- StringTableEntry GuiInspectorField::getRawFieldName() { if( !mField ) return StringTable->EmptyString(); return mField->pFieldname; } //----------------------------------------------------------------------------- StringTableEntry GuiInspectorField::getFieldName() { // Sanity if ( mField == NULL ) return StringTable->EmptyString(); // Array element? if( mFieldArrayIndex != NULL ) { S32 frameTempSize = dStrlen( mField->pFieldname ) + 32; FrameTemp valCopy( frameTempSize ); dSprintf( (char *)valCopy, frameTempSize, "%s[%s]", mField->pFieldname, mFieldArrayIndex ); // Return formatted element return StringTable->insert( valCopy ); } // Plain field name. return mField->pFieldname; } //----------------------------------------------------------------------------- StringTableEntry GuiInspectorField::getFieldType() { if( !mField ) return StringTable->EmptyString(); return ConsoleBaseType::getType( mField->type )->getTypeName(); } //----------------------------------------------------------------------------- GuiControl* GuiInspectorField::constructEditControl() { GuiControl* retCtrl = new GuiTextEditCtrl(); static StringTableEntry sProfile = StringTable->insert( "profile" ); retCtrl->setDataField( sProfile, NULL, "GuiInspectorTextEditProfile" ); _registerEditControl( retCtrl ); char szBuffer[512]; dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); // Suffices to hook on to "validate" as regardless of whether we lose // focus through the user pressing enter or clicking away on another // keyboard control, we will see a validate call. retCtrl->setField("validate", szBuffer ); return retCtrl; } //----------------------------------------------------------------------------- void GuiInspectorField::setInspectorProfile() { GuiControlProfile *profile = NULL; if( mInspector && (mInspector->getNumInspectObjects() > 1) ) { if( !hasSameValueInAllObjects() ) Sim::findObject( "GuiInspectorMultiFieldDifferentProfile", profile ); else Sim::findObject( "GuiInspectorMultiFieldProfile", profile ); } if( !profile ) Sim::findObject( "GuiInspectorFieldProfile", profile ); if( profile ) setControlProfile( profile ); } //----------------------------------------------------------------------------- void GuiInspectorField::setValue( StringTableEntry newValue ) { GuiTextEditCtrl *ctrl = dynamic_cast( mEdit ); if( ctrl != NULL ) ctrl->setText( newValue ); } //----------------------------------------------------------------------------- void GuiInspectorField::setEditControl(GuiControl* editCtrl) { if (mEdit) mEdit->deleteObject(); mEdit = editCtrl; addObject(mEdit); } //----------------------------------------------------------------------------- bool GuiInspectorField::updateRects() { S32 dividerPos, dividerMargin; mInspector->getDivider( dividerPos, dividerMargin ); Point2I fieldExtent = getExtent(); Point2I fieldPos = getPosition(); S32 editWidth = dividerPos - dividerMargin; mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, editWidth, fieldExtent.y - 1 ); mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); if ( !mEdit ) return false; return mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); } //----------------------------------------------------------------------------- void GuiInspectorField::updateValue() { if( mInspector->getNumInspectObjects() > 1 ) { setInspectorProfile(); if( !hasSameValueInAllObjects() ) setValue( StringTable->EmptyString() ); else setValue( getData() ); } else setValue( getData() ); } //----------------------------------------------------------------------------- void GuiInspectorField::setHLEnabled( bool enabled ) { mHighlighted = enabled; if ( mHighlighted ) { if ( mEdit && !mEdit->isFirstResponder() ) { mEdit->setFirstResponder(); GuiTextEditCtrl *edit = dynamic_cast( mEdit ); if ( edit ) { mouseUnlock(); edit->mouseLock(); edit->setCursorPos(0); } } _executeSelectedCallback(); } } //----------------------------------------------------------------------------- bool GuiInspectorField::hasSameValueInAllObjects() { char value1[ 2048 ]; // Get field value from first object. const char* data1 = getData( 0 ); if( data1 ) { dStrncpy( value1, data1, sizeof( value1 ) ); value1[ sizeof( value1 ) - 1 ] = 0; } else value1[ 0 ] = 0; // Check if all other objects have the same value. const U32 numObjects = mInspector->getNumInspectObjects(); for( U32 i = 1; i < numObjects; ++ i ) { const char* value2 = getData( i ); if( !value2 ) value2 = ""; if( String::compare( value1, value2 ) != 0 ) return false; } return true; } //----------------------------------------------------------------------------- void GuiInspectorField::_executeSelectedCallback() { if( mField ) Con::executef( mInspector, "onFieldSelected", mField->pFieldname, ConsoleBaseType::getType(mField->type)->getTypeName(), mFieldDocs.c_str() ); } //----------------------------------------------------------------------------- void GuiInspectorField::_registerEditControl( GuiControl *ctrl ) { char szName[512]; if(mInspector->getInspectObject() != nullptr) dSprintf( szName, 512, "IE_%s_%d_%s_Field", ctrl->getClassName(), mInspector->getInspectObject()->getId(), mCaption); else dSprintf(szName, 512, "IE_%s_%s_Field", ctrl->getClassName(), mCaption); // Register the object ctrl->registerObject( szName ); } //----------------------------------------------------------------------------- void GuiInspectorField::_setFieldDocs( StringTableEntry docs ) { mFieldDocs = String(); if( docs && docs[ 0 ] ) { // Only accept first line of docs for brevity. const char* newline = dStrchr( docs, '\n' ); if( newline ) mFieldDocs = String( docs, newline - docs ); else mFieldDocs = docs; } } void GuiInspectorField::setHeightOverride(bool useOverride, U32 heightOverride) { mUseHeightOverride = useOverride; if (useOverride) mHeightOverride = heightOverride; S32 fieldHeight = 18; if (mUseHeightOverride) fieldHeight = mHeightOverride; RectI bnds = getBounds(); setBounds(bnds.point.x, bnds.point.y, bnds.extent.x, fieldHeight); // Calculate Caption and EditCtrl Rects updateRects(); // Force our editField to set it's value updateValue(); } //============================================================================= // Console Methods. //============================================================================= // MARK: ---- Console Methods ---- //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, getInspector, S32, (), , "() - Return the GuiInspector to which this field belongs." ) { return object->getInspector()->getId(); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, getInspectedFieldName, const char*, (), , "() - Return the name of the field edited by this inspector field." ) { return object->getFieldName(); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, getInspectedFieldType, const char*, (), , "() - Return the type of the field edited by this inspector field." ) { return object->getFieldType(); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, apply, void, ( const char * newValue, bool callbacks ), (true), "( string newValue, bool callbacks=true ) - Set the field's value. Suppress callbacks for undo if callbacks=false." ) { object->setData( newValue, callbacks ); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, applyWithoutUndo, void, (const char * data), , "() - Set field value without recording undo (same as 'apply( value, false )')." ) { object->setData( data, false ); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, getData, const char*, (), , "() - Return the value currently displayed on the field." ) { return object->getData(); } //----------------------------------------------------------------------------- DefineEngineMethod( GuiInspectorField, reset, void, (), , "() - Reset to default value." ) { object->resetData(); } DefineEngineMethod(GuiInspectorField, setCaption, void, (String newCaption),, "() - Reset to default value.") { object->setCaption(StringTable->insert(newCaption.c_str())); } DefineEngineMethod(GuiInspectorField, setHeightOverride, void, (bool useOverride, U32 heightOverride), , "") { object->setHeightOverride(useOverride, heightOverride); } DefineEngineMethod(GuiInspectorField, setEditControl, void, (GuiControl* editCtrl), (nullAsType()), "() - Reset to default value.") { object->setEditControl(editCtrl); }