/** * Cleans the lookup tables for the provided item and all children. * @param item The item to start from */ void MaterialTreeView::CleanLookupTrees(HTREEITEM item) { idStr qt = GetQuicktreePath(item); quickTree.Remove(qt); CTreeCtrl& tree = GetTreeCtrl(); //Clean special lookup tables DWORD type = tree.GetItemData(item); if(type == TYPE_FILE) { idStr file = GetMediaPath(item, TYPE_FILE); fileToTree.Remove(file); } else if(type == TYPE_MATERIAL) { idStr name = GetMediaPath(item, TYPE_MATERIAL); materialToTree.Remove(name); } //Clean all my children if(tree.ItemHasChildren(item)) { HTREEITEM childItem = tree.GetChildItem(item); while(childItem != NULL) { CleanLookupTrees(childItem); childItem = tree.GetNextSiblingItem(childItem); } } }
/** * Returns the filename of the provided item. * @param item The item for which to generate the filename * @param out The location the filename will be placed. */ bool MaterialTreeView::GetFileName(HTREEITEM item, idStr& out) { out = ""; CTreeCtrl& tree = GetTreeCtrl(); DWORD type = tree.GetItemData(item); if(type != TYPE_MATERIAL && type != TYPE_MATERIAL_FOLDER && type != TYPE_FILE) return false; if(type == TYPE_FILE) { out = GetMediaPath(item, TYPE_FILE); return true; } HTREEITEM parent = tree.GetParentItem( item ); while ( parent != NULL ) { DWORD parentType = tree.GetItemData(parent); if(parentType == TYPE_FILE) { out = GetMediaPath(parent, TYPE_FILE); return true; } parent = tree.GetParentItem( parent ); } return false; }
/** * Adds a new material. */ void MaterialTreeView::OnAddMaterial() { CTreeCtrl& tree = GetTreeCtrl(); HTREEITEM item = tree.GetSelectedItem(); DWORD itemType = tree.GetItemData(item); //Determine the file HTREEITEM parent = NULL; if(itemType != TYPE_FILE) { parent = tree.GetParentItem(item); while(1) { if(tree.GetItemData(parent) == TYPE_FILE) break; parent = tree.GetParentItem(parent); } } else { parent = item; } idStr filename = GetMediaPath(parent, TYPE_FILE); //Determine the material folder idStr materialFolder = ""; switch(itemType) { case TYPE_MATERIAL: { HTREEITEM parentFolderItem = tree.GetParentItem(item); if(tree.GetItemData(parentFolderItem) == TYPE_MATERIAL_FOLDER) materialFolder = GetMediaPath(parentFolderItem, TYPE_MATERIAL_FOLDER); } break; case TYPE_MATERIAL_FOLDER: materialFolder = GetMediaPath(item, TYPE_MATERIAL_FOLDER); break; case TYPE_FILE: //There is no material folder break; } idStr name; int num = 1; while(1) { if(materialFolder.Length() > 0) { name = va("%s/newmaterial%d", materialFolder.c_str(), num); } else { name = va("newmaterial%d", num); } if(!declManager->FindMaterial(name, false)) break; num++; } materialDocManager->AddMaterial(name.c_str(), filename.c_str()); }
/** * Searches for a material given the supplied search parameters. Returns the tree item where * the item was found or NULL if no material was found. * @param item The tree item from where to start the search. * @param searchData The parameters to use for the search. */ HTREEITEM MaterialTreeView::FindNextMaterial( HTREEITEM item, MaterialSearchData_t *searchData ) { CTreeCtrl &tree = GetTreeCtrl(); DWORD type = tree.GetItemData( item ); if( type == TYPE_MATERIAL ) { //check the tree name first idStr itemName = tree.GetItemText( item ); int findPos = itemName.Find( searchData->searchText, false ); if( findPos != -1 ) { //Todo: Include match whole word return item; } if( !searchData->nameOnly ) { //Check the material idStr materialName = GetMediaPath( item, TYPE_MATERIAL ); if( materialDocManager->FindMaterial( materialName, searchData, false ) ) { return item; } } } else { //Just check the tree name idStr itemName = tree.GetItemText( item ); int findPos = itemName.Find( searchData->searchText, false ); if( findPos != -1 ) { //Todo: Include match whole word return item; } } return NULL; }
/** * Handles all of the little problems associated with renaming a folder. */ void MaterialTreeView::RenameMaterial( HTREEITEM item, const char *originalName ) { CTreeCtrl &tree = GetTreeCtrl(); const idMaterial *material = declManager->FindMaterial( originalName ); MaterialDoc *pMaterial; //pMaterial = materialDocManager->GetInProgressDoc(material); //if(!pMaterial) { pMaterial = materialDocManager->CreateMaterialDoc( const_cast<idMaterial *>( material ) ); //} //Remove our old quick lookup value materialToTree.Remove( originalName ); //Generate the new name idStr materialName; HTREEITEM parent = tree.GetParentItem( item ); DWORD parentType = tree.GetItemData( parent ); if( parentType == TYPE_MATERIAL_FOLDER ) { //Need to include the material folder materialName = GetMediaPath( parent, TYPE_MATERIAL_FOLDER ); materialName += "/"; } materialName += tree.GetItemText( item ); //Add it to our quick lookup materialToTree.Set( materialName, item ); //Finally make the change internalChange = true; pMaterial->SetMaterialName( materialName, false ); internalChange = false; }
/** * Performs a paste operation. */ void MaterialTreeView::OnPaste() { CTreeCtrl &tree = GetTreeCtrl(); HTREEITEM item = tree.GetSelectedItem(); DWORD itemType = tree.GetItemData( item ); //Paste a material if( item && materialDocManager->IsCopyMaterial() && itemType >= TYPE_FILE ) { //Generate the name if( itemType == TYPE_MATERIAL ) { //Backup one if a file is selected item = tree.GetParentItem( item ); itemType = tree.GetItemData( item ); } idStr materialName = ""; if( itemType != TYPE_FILE ) { materialName = GetMediaPath( item, itemType ) + "/"; } idStr copyName = materialDocManager->GetCopyMaterialName(); idStr copyMaterialName; copyName.ExtractFileName( copyMaterialName ); materialName += copyMaterialName; idStr filename; GetFileName( item, filename ); //If the material name already exists add numbers until we don't find it materialName = materialDocManager->GetUniqueMaterialName( materialName ); //Paste materialDocManager->PasteMaterial( materialName, filename ); } }
/** * Changes the selected material when the select tree item changes. */ void MaterialTreeView::OnTvnSelchanged(NMHDR *pNMHDR, LRESULT *pResult) { LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR); if(pNMTreeView->itemNew.hItem) { CTreeCtrl& tree = GetTreeCtrl(); DWORD type = tree.GetItemData(pNMTreeView->itemNew.hItem); if(type == TYPE_MATERIAL) { idStr mediaName = GetMediaPath(pNMTreeView->itemNew.hItem, type); const idMaterial* material = declManager->FindMaterial(mediaName); materialDocManager->SetSelectedMaterial(const_cast<idMaterial*>(material)); } else { materialDocManager->SetSelectedMaterial(NULL); } } else { materialDocManager->SetSelectedMaterial(NULL); } *pResult = 0; }
void CSDLStateLocation::Load( CLocationObject* pLocation ) { m_pLocationObject = pLocation; pEngine->SetCurrentLocation( this ); string sBackgroundImage = pLocation->GetBackgroundImage(); if ( sBackgroundImage.length() > 0 ) { m_pBackground = new CSDLBaseObject; m_pBackground->LoadImageFromFile( GetMediaPath( pEngine->GetGameDir(), sBackgroundImage ) ); // Center the image m_pBackground->SetPosX( 400 - (m_pBackground->GetWidth() / 2) ); m_pBackground->SetPosY( 300 - (m_pBackground->GetHeight() / 2) ); } pLocation->GetItems()->ResetIteration(); CItemObject* pItemObject = 0; for ( unsigned int i = 0; i < pLocation->GetItems()->GetCount(); ++i ) { pItemObject = pLocation->GetItems()->GetNextItem(); CSDLGameItemObject* pGameItemObject = new CSDLGameItemObject; pGameItemObject->SetDescription( pItemObject->GetDescription() ); pGameItemObject->SetPos( pItemObject->GetPosX(), pItemObject->GetPosY() ); pGameItemObject->SetOnLoad( pItemObject->GetOnLoad() ); pGameItemObject->SetOnClick( pItemObject->GetOnClick() ); if ( pItemObject->GetHasImage() == true ) { pGameItemObject->LoadImageFromFile( GetMediaPath( pEngine->GetGameDir(), pItemObject->GetImageName() ) ); } else { pGameItemObject->SetHoverDefaultRect( false ); SDL_Rect stRect; stRect.x = pGameItemObject->GetPosX(); stRect.y = pGameItemObject->GetPosY(); stRect.w = pItemObject->GetWidth(); stRect.h = pItemObject->GetHeight(); pGameItemObject->SetHoverRect( stRect ); } AddObject( pGameItemObject ); pGameItemObject->LoadItem(); // Call the OnLoad-scripting if there is one } }
/** * Makes sure that a rename operation can be performed after a label edit is complete and * performs the folder or material rename. */ void MaterialTreeView::OnTvnEndlabeledit( NMHDR *pNMHDR, LRESULT *pResult ) { LPNMTVDISPINFO pTVDispInfo = reinterpret_cast<LPNMTVDISPINFO>( pNMHDR ); *pResult = 0; if( pTVDispInfo->item.pszText ) { //Convert any edited text to lower case to keep the name canonical idStr newLabel = pTVDispInfo->item.pszText; newLabel.ToLower(); strncpy( pTVDispInfo->item.pszText, newLabel.c_str(), pTVDispInfo->item.cchTextMax ); CTreeCtrl &tree = GetTreeCtrl(); DWORD type = tree.GetItemData( pTVDispInfo->item.hItem ); if( type == TYPE_MATERIAL ) { MaterialDoc *pMaterial = materialDocManager->GetCurrentMaterialDoc(); //Remove our old quick lookup value materialToTree.Remove( pMaterial->name.c_str() ); //Generate the new name idStr material; HTREEITEM parent = tree.GetParentItem( pTVDispInfo->item.hItem ); DWORD parentType = tree.GetItemData( parent ); if( parentType == TYPE_MATERIAL_FOLDER ) { //Need to include the material folder material = GetMediaPath( parent, TYPE_MATERIAL_FOLDER ); material += "/"; } material += pTVDispInfo->item.pszText; if( declManager->FindMaterial( material, false ) ) { //Can't rename because it conflicts with an existing file MessageBox( "Unable to rename material because it conflicts with another material", "Error" ); } else { //Add it to our quick lookup materialToTree.Set( material, pTVDispInfo->item.hItem ); //Finally make the change internalChange = true; pMaterial->SetMaterialName( material ); internalChange = false; renamedFolder = pTVDispInfo->item.hItem; PostMessage( MSG_RENAME_MATERIAL_COMPLETE ); *pResult = 1; } } else if( type == TYPE_MATERIAL_FOLDER ) { //Clean up the quicktree with the current tree before we allow the edit to commit CleanLookupTrees( pTVDispInfo->item.hItem ); //Store some data so the we can make the appropriate changes after the commit renamedFolder = pTVDispInfo->item.hItem; affectedMaterials.Clear(); GetMaterialPaths( renamedFolder, &affectedMaterials ); PostMessage( MSG_RENAME_FOLDER_COMPLETE ); RenameMaterialFolderModifier *mod = new RenameMaterialFolderModifier( materialDocManager, pTVDispInfo->item.pszText, this, pTVDispInfo->item.hItem, tree.GetItemText( pTVDispInfo->item.hItem ) ); materialDocManager->AddMaterialUndoModifier( mod ); *pResult = 1; } } }
/** * Handles the end of a drag copy/move when the user releases the left mouse button. */ void MaterialTreeView::OnLButtonUp( UINT nFlags, CPoint point ) { CTreeCtrl &tree = GetTreeCtrl(); if( bDragging ) { //Release mouse capture ReleaseCapture(); //Delete the drag image dragImage->DragLeave( GetDesktopWindow() ); dragImage->EndDrag(); bDragging = false; delete dragImage; UINT flags; HTREEITEM item = tree.HitTest( point, &flags ); if( item && ( TVHT_ONITEM & flags ) ) { DWORD itemType = tree.GetItemData( item ); if( itemType == TYPE_MATERIAL ) { //Backup one if a file is selected item = tree.GetParentItem( item ); } //Make sure we aren't dragging to the same place HTREEITEM dragItemParent = tree.GetParentItem( dragItem ); if( dragItemParent != item ) { idStr dragFile; GetFileName( dragItem, dragFile ); idStr filename; GetFileName( item, filename ); //Move within a file copy across files if( !dragFile.Icmp( filename ) ) { materialDocManager->CopyMaterial( materialDocManager->GetCurrentMaterialDoc(), true ); } else { materialDocManager->CopyMaterial( materialDocManager->GetCurrentMaterialDoc(), false ); } //Generate the name idStr materialName = GetMediaPath( item, itemType ); idStr copyName = materialDocManager->GetCopyMaterialName(); idStr copyMaterialName; copyName.ExtractFileName( copyMaterialName ); materialName += "/" + copyMaterialName; //If the material name already exists add numbers until we don't find it materialName = materialDocManager->GetUniqueMaterialName( materialName ); //Paste materialDocManager->PasteMaterial( materialName, filename ); } } } CTreeView::OnLButtonUp( nFlags, point ); }
/** * Creates a list of material paths for all materials under the provided item. * @param item The base item for which to generate the list * @param list The list in which the paths will be stored. */ void MaterialTreeView::GetMaterialPaths( HTREEITEM item, idList<MaterialTreeItem_t> *list ) { CTreeCtrl &tree = GetTreeCtrl(); if( tree.ItemHasChildren( item ) ) { HTREEITEM childItem = tree.GetChildItem( item ); while( childItem != NULL ) { DWORD childType = tree.GetItemData( childItem ); if( childType == TYPE_MATERIAL ) { MaterialTreeItem_t mat; mat.materialName = GetMediaPath( childItem, TYPE_MATERIAL ); mat.treeItem = childItem; list->Append( mat ); } else if( childType == TYPE_MATERIAL_FOLDER ) { GetMaterialPaths( childItem, list ); } childItem = tree.GetNextSiblingItem( childItem ); } } }
bool Renderer::Init(const RECT &windowRect, HWND hWnd) { if(!hWnd) return false; this->hWnd = hWnd; this->bbWidth = windowRect.right - windowRect.left; this->bbHeight = windowRect.bottom - windowRect.top; HRESULT result; result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&this->factory); if(FAILED(result)) { OutputDebugString("\nCreateDXGIFactory() failed\n"); return false; } result = this->factory->EnumAdapters(0, (IDXGIAdapter**)&this->adapter); if(FAILED(result)) { OutputDebugString("\nfactory->EnumAdapters() failed\n"); return false; } D3D_FEATURE_LEVEL featureLvl11 = D3D_FEATURE_LEVEL_11_0; DXGI_SWAP_CHAIN_DESC swapChainDescription; ZeroMemory(&swapChainDescription, sizeof(swapChainDescription)); swapChainDescription.BufferCount = 2; swapChainDescription.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //swapChainDescription.Flags = 0; swapChainDescription.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; swapChainDescription.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDescription.BufferDesc.Width = this->bbWidth; swapChainDescription.BufferDesc.Height = this->bbHeight; swapChainDescription.BufferDesc.RefreshRate.Numerator = 60; swapChainDescription.BufferDesc.RefreshRate.Denominator = 1; swapChainDescription.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDescription.Flags = 0; swapChainDescription.OutputWindow = this->hWnd; swapChainDescription.SampleDesc.Count = 1; swapChainDescription.SampleDesc.Quality = 0; swapChainDescription.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; swapChainDescription.Windowed = TRUE; #ifdef _DEBUG result = D3D11CreateDeviceAndSwapChain( nullptr, D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_DEBUG, &featureLvl11, 1, D3D11_SDK_VERSION, &swapChainDescription, (IDXGISwapChain**)&this->swapChain, (ID3D11Device**)&this->device, nullptr, (ID3D11DeviceContext**)&this->context); if(FAILED(result)) { OutputDebugString("\nD3D11CreateDeviceAndSwapChain() failed\n"); return false; } #else result = D3D11CreateDeviceAndSwapChain( nullptr, D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE, nullptr, nullptr, &featureLvl11, 1, D3D11_SDK_VERSION, &swapChainDescription, (IDXGISwapChain**)&this->SwapChain, (ID3D11Device**)&this->Device, nullptr, (ID3D11DeviceContext**)&this->Context); if(FAILED(result)) { return false; } #endif ID3D11Texture2D *bbSurfacePtr = nullptr; result = this->swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&bbSurfacePtr); if(FAILED(result)) { OutputDebugString("\nswapChain->GetBuffer() failed\n"); return false; } result = this->device->CreateRenderTargetView(bbSurfacePtr, nullptr, (ID3D11RenderTargetView**)&this->RTView); if(FAILED(result)) { OutputDebugString("\ndevice->createRenderTargetView failed\n"); return false; } if(!this->createShadersAndInputLayouts()) return false; this->context->OMSetRenderTargets(1, (ID3D11RenderTargetView**)&this->RTView, nullptr); D3D11_VIEWPORT viewport; ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT)); viewport.Height = this->bbHeight; viewport.Width = this->bbWidth; viewport.MaxDepth = 1.0f; viewport.MinDepth = 0.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; this->context->RSSetViewports(1, &viewport); //create a state to disable depth stencil buffer // Clear the second depth stencil state before setting the parameters. D3D11_DEPTH_STENCIL_DESC depthDisabledStencilDesc; ZeroMemory(&depthDisabledStencilDesc, sizeof(depthDisabledStencilDesc)); // Now create a second depth stencil state which turns off the Z buffer for 2D rendering. The only difference is // that DepthEnable is set to false, all other parameters are the same as the other depth stencil state. depthDisabledStencilDesc.DepthEnable = false; depthDisabledStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthDisabledStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthDisabledStencilDesc.StencilEnable = true; depthDisabledStencilDesc.StencilReadMask = 0xFF; depthDisabledStencilDesc.StencilWriteMask = 0xFF; depthDisabledStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthDisabledStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; depthDisabledStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthDisabledStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthDisabledStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Create the state using the device. result = this->device->CreateDepthStencilState(&depthDisabledStencilDesc, &this->depthStencilDisabled); if (FAILED(result)) { return false; } this->context->OMGetDepthStencilState(&this->depthStencilEnabled, NULL); if(!this->createRasterizerStates()) return false; this->viewMat = Matrix::Identity(); if(!this->createCBuffers()) return false; if(!this->createSamplers()) return false; try { this->SBatch = new SpriteBatch(this->context); this->SFont = new SpriteFont(this->device, GetMediaPath(L"arial.spritefont").data()); } catch(std::exception e) { OutputDebugString("\n"); OutputDebugString(e.what()); OutputDebugString("\n"); return false; } return true; }
/** * Adds a string list of materials to the tree creating the proper hierarchy. * @param root The name of the root item or NULL for no root item. * @param list The list of materials. * @param includeFile If true the materials will be sorted by file. */ void MaterialTreeView::AddStrList( const char *root, idStrList *list, bool includeFile ) { CTreeCtrl &treeMedia = GetTreeCtrl(); idStr out, path; HTREEITEM base = NULL; if( root ) { base = treeMedia.GetRootItem(); if( base ) { out = treeMedia.GetItemText( base ); if( stricmp( root, out ) ) { base = NULL; } } if( base == NULL ) { base = treeMedia.InsertItem( root ); treeMedia.SetItemData( base, TYPE_ROOT ); } } HTREEITEM item = base; HTREEITEM add; list->Sort(); int count = list->Num(); idStr last, qt; for( int i = 0; i < count; i++ ) { idStr *strItem = &( *list )[i]; idStr name = strItem->c_str(); idStr filename; bool afterFile = true; if( includeFile ) { int index = name.Find( "|" ); if( index >= 0 ) { afterFile = false; filename = name.Right( name.Length() - index - 1 ); name = name.Left( index ); } } // now break the name down convert to slashes name.BackSlashesToSlashes(); name.Strip( ' ' ); int index; int len = last.Length(); if( len == 0 ) { index = name.Last( '/' ); if( index >= 0 ) { name.Left( index, last ); } } else if( idStr::Icmpn( last, name, len ) == 0 && name.Last( '/' ) <= len ) { name.Right( name.Length() - len - 1, out ); add = treeMedia.InsertItem( out, item ); qt = root; qt += "/"; qt += name; quickTree.Set( qt, add ); treeMedia.SetItemImage( add, IMAGE_MATERIAL, IMAGE_MATERIAL ); treeMedia.SetItemData( add, TYPE_MATERIAL ); //Add the item to a quick lookup table idStr material = GetMediaPath( add, TYPE_MATERIAL ); materialToTree.Set( material, add ); continue; } else { last.Empty(); } index = 0; item = base; path = ""; while( index >= 0 ) { index = name.Find( '/' ); if( index >= 0 ) { HTREEITEM newItem = NULL; HTREEITEM *check = NULL; name.Left( index, out ); path += out; qt = root; qt += "/"; qt += path; if( quickTree.Get( qt, &check ) ) { newItem = *check; } bool thisisfile = false; if( out == filename ) { thisisfile = true; afterFile = true; } if( newItem == NULL ) { newItem = treeMedia.InsertItem( out, item ); qt = root; qt += "/"; qt += path; quickTree.Set( qt, newItem ); if( !afterFile || thisisfile ) { if( thisisfile ) { afterFile = true; treeMedia.SetItemImage( newItem, IMAGE_FILE, IMAGE_FILE ); treeMedia.SetItemData( newItem, TYPE_FILE ); //Add the item to a quick lookup table idStr file = GetMediaPath( newItem, TYPE_FILE ); //common->Printf("Adding fileToTree: %s - %d\n", file.c_str(), newItem); fileToTree.Set( file, newItem ); } else { treeMedia.SetItemImage( newItem, IMAGE_FOLDER, IMAGE_FOLDER ); treeMedia.SetItemData( newItem, TYPE_FOLDER ); } } else { treeMedia.SetItemImage( newItem, IMAGE_MATERIAL_FOLDER, IMAGE_MATERIAL_FOLDER ); treeMedia.SetItemData( newItem, TYPE_MATERIAL_FOLDER ); } } item = newItem; name.Right( name.Length() - index - 1, out ); name = out; path += "/"; } else { add = treeMedia.InsertItem( name, item ); qt = root; qt += "/"; qt += path; qt += name; quickTree.Set( qt, add ); treeMedia.SetItemImage( add, IMAGE_MATERIAL, IMAGE_MATERIAL ); treeMedia.SetItemData( add, TYPE_MATERIAL ); path = ""; //Add the item to a quick lookup table idStr material = GetMediaPath( add, TYPE_MATERIAL ); materialToTree.Set( material, add ); } } } }