//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Dialog for selecting game configurations // //=====================================================================================// #include "cbase.h" #include #include #include #include #include #include #include #include #include #include "vgui_bitmappanel.h" #include #include "imageutils.h" #include "bsp_utils.h" #include "icommandline.h" #include "publish_file_dialog.h" #include "workshop/ugc_utils.h" #ifdef TF_CLIENT_DLL #include "../server/tf/workshop/maps_workshop.h" #endif // TF_CLIENT_DLL #include "steam/steam_api.h" // memdbgon must be the last include file in a .cpp file!!! #include static CUtlString g_MapFilename; static CUtlString g_PreviewFilename; ConVar publish_file_last_dir( "publish_file_last_dir", "", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD ); CFilePublishDialog *g_pSteamFilePublishDialog = NULL; #define WORKSHOP_TEMP_UPLOAD_DIR "workshop/upload" class CPrepareFileThread : public CThread { public: CPrepareFileThread( const char *pszInputFile, const char *pszOutputFile ) : m_strInput( pszInputFile ) , m_strOutput( pszOutputFile ) {} // Return 0 for success virtual int Run() { if ( V_strcasecmp( V_GetFileExtension( m_strInput.Get() ), "bsp" ) == 0 ) { return BSP_SyncRepack( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1; } else { // Just copy file to prepared location return engine->CopyLocalFile( m_strInput.Get(), m_strOutput.Get() ) ? 0 : 1; } } private: CUtlString m_strInput; CUtlString m_strOutput; }; //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CFilePublishDialog::CFilePublishDialog( Panel *parent, const char *name, PublishedFileDetails_t *pDetails ) : BaseClass( parent, name ) { m_pPrepareFileThread = NULL; g_pSteamFilePublishDialog = this; // These must be supplied m_bValidFile = false; m_bValidJpeg = false; vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); // Save this for later if ( pDetails != NULL ) { m_FileDetails = *pDetails; m_bAddingNewFile = false; g_MapFilename = m_FileDetails.lpszFilename; m_nFileID = m_FileDetails.publishedFileDetails.m_nPublishedFileId; } else { // Clear it out m_FileDetails.lpszFilename = NULL; m_FileDetails.publishedFileDetails.m_eVisibility = k_ERemoteStoragePublishedFileVisibilityPublic; m_FileDetails.publishedFileDetails.m_hFile = k_UGCHandleInvalid; m_FileDetails.publishedFileDetails.m_hPreviewFile = k_UGCHandleInvalid; m_FileDetails.publishedFileDetails.m_nConsumerAppID = k_uAppIdInvalid; m_FileDetails.publishedFileDetails.m_nCreatorAppID = k_uAppIdInvalid; m_FileDetails.publishedFileDetails.m_nPublishedFileId = 0; // FIXME: Need a real "invalid" value m_FileDetails.publishedFileDetails.m_rtimeCreated = 0; m_FileDetails.publishedFileDetails.m_rtimeUpdated = 0; m_FileDetails.publishedFileDetails.m_ulSteamIDOwner = 0; // FIXME: Need a real "invalid" value memset( m_FileDetails.publishedFileDetails.m_rgchDescription, 0, k_cchPublishedDocumentDescriptionMax ); memset( m_FileDetails.publishedFileDetails.m_rgchTitle, 0, k_cchPublishedDocumentTitleMax ); m_bAddingNewFile = true; g_MapFilename = ""; m_nFileID = k_PublishedFileIdInvalid; } m_nFileDetailsChanges = 0; m_fileOpenMode = FILEOPEN_NONE; // Setup our image panel m_pCroppedTextureImagePanel = new CBitmapPanel( this, "PreviewImage" ); m_pCroppedTextureImagePanel->SetSize( DesiredPreviewWidth(), DesiredPreviewHeight() ); m_pCroppedTextureImagePanel->SetVisible( true ); m_pStatusBox = NULL; // Start downloading our preview image m_bPreviewDownloadPending = false; DownloadPreviewImage(); } //----------------------------------------------------------------------------- // Destructor //----------------------------------------------------------------------------- CFilePublishDialog::~CFilePublishDialog() { //delete m_pConfigCombo; g_pSteamFilePublishDialog = NULL; // We should be in a modal dialog when this is running, not closable Assert( !m_pPrepareFileThread ); if ( m_pPrepareFileThread ) { m_pPrepareFileThread->Stop(); delete m_pPrepareFileThread; m_pPrepareFileThread = NULL; } } void CFilePublishDialog::ErrorMessage( ErrorCode_t errorCode, KeyValues *pkvTokens ) { switch ( errorCode ) { case kFailedToPublishFile: ErrorMessage( "Failed to publish file!" ); break; case kFailedToUpdateFile: ErrorMessage( "Failed to update file!" ); break; case kFailedToPrepareFile: ErrorMessage( "Failed to prepare file!" ); break; case kSteamCloudNotAvailable: ErrorMessage( "Steam Cloud is not available." ); break; case kSteamExceededCloudQuota: ErrorMessage( "Exceed Steam Cloud quota." ); break; case kFailedToWriteToSteamCloud: ErrorMessage( "Failed to write to Steam cloud!" ); break; case kFileNotFound: ErrorMessage( "File not found!" ); break; case kNeedTitleAndDescription: ErrorMessage( "Need to have a title and description!" ); break; case kFailedFileValidation: ErrorMessage( "File failed to validate!" ); break; case kFailedUserModifiedFile: ErrorMessage( "File was manually modified after verifying process" ); break; case kInvalidMapName: ErrorMessage( "Invalid name for map. Map names must be lowercase and of the form cp_foo.bsp." ); break; default: Assert( false ); // Unhandled enum value break; } if ( pkvTokens ) { pkvTokens->deleteThis(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::ErrorMessage( const char *lpszText ) { vgui::MessageBox *pBox = new vgui::MessageBox( "", lpszText, this ); pBox->SetPaintBorderEnabled( false ); pBox->SetPaintBackgroundEnabled( true ); pBox->SetBgColor( Color(0,0,0,255) ); pBox->DoModal(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CFilePublishDialog::GetStatusString( StatusCode_t statusCode ) { switch ( statusCode ) { case kPublishing: return "Publishing, please wait..."; break; case kUpdating: return "Publishing, please wait..."; break; } return ""; } //----------------------------------------------------------------------------- // Purpose: Show our modal status window to cover asynchronous tasks // TODO: Pull this out into a more generalized solution across dialogs //----------------------------------------------------------------------------- void CFilePublishDialog::ShowStatusWindow( StatusCode_t statusCode ) { // Throw up our status box if ( m_pStatusBox ) { m_pStatusBox->CloseModal(); m_pStatusBox = NULL; // FIXME: Does this clear up the memory? } const char *lpszText = GetStatusString( statusCode ); // Pop a message to the user so they know to wait m_pStatusBox = new vgui::MessageBox( "", lpszText, this ); m_pStatusBox->SetPaintBorderEnabled( false ); m_pStatusBox->SetPaintBackgroundEnabled( true ); m_pStatusBox->SetBgColor( Color(0,0,0,255) ); m_pStatusBox->SetOKButtonVisible( false ); m_pStatusBox->DoModal(); } //----------------------------------------------------------------------------- // Purpose: Hide our modal status window // TODO: Pull this out into a more generalized solution across dialogs //----------------------------------------------------------------------------- void CFilePublishDialog::HideStatusWindow( void ) { m_pStatusBox->CloseModal(); m_pStatusBox = NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::DownloadPreviewImage( void ) { // TODO: We need a generic "no image" image if ( m_bAddingNewFile ) return; // Start off our download char szTargetFilename[MAX_PATH]; V_snprintf( szTargetFilename, sizeof(szTargetFilename), "%llu_thumb.jpg", m_FileDetails.publishedFileDetails.m_nPublishedFileId ); m_UGCPreviewFileRequest.StartDownload( m_FileDetails.publishedFileDetails.m_hPreviewFile, "downloads", szTargetFilename ); m_bPreviewDownloadPending = true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::OnTick( void ) { BaseClass::OnTick(); if ( m_pPrepareFileThread ) { if ( !m_pPrepareFileThread->IsAlive() ) { // Finished, trigger handler int result = m_pPrepareFileThread->GetResult(); delete m_pPrepareFileThread; m_pPrepareFileThread = NULL; OnFilePrepared( result == 0 ); } } if ( m_bPreviewDownloadPending ) { UGCFileRequestStatus_t ugcStatus = m_UGCPreviewFileRequest.Update(); switch ( ugcStatus ) { case UGCFILEREQUEST_ERROR: Warning("An error occurred while attempting to download a file from the UGC server!\n"); m_bPreviewDownloadPending = false; break; case UGCFILEREQUEST_FINISHED: // Update our image preview char szLocalFilename[MAX_PATH]; m_UGCPreviewFileRequest.GetLocalFileName( szLocalFilename, sizeof(szLocalFilename) ); char szLocalPath[ _MAX_PATH ]; g_pFullFileSystem->GetLocalPath( szLocalFilename, szLocalPath, sizeof(szLocalPath) ); SetPreviewImage( szLocalPath ); m_bPreviewDownloadPending = false; break; default: // Working, continue to wait... return; break; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::SetPreviewImage( const char *lpszFilename ) { if ( lpszFilename == NULL ) return; // Retain this g_PreviewFilename = lpszFilename; m_bValidJpeg = false; ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( lpszFilename, m_imgSource ); if ( nErrorCode != CE_SUCCESS ) { } else { m_bValidJpeg = true; PerformSquarize(); m_pCroppedTextureImagePanel->SetBitmap( GetPreviewBitmap() ); } // Update the state of our publish button SetPublishButtonState(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::PerformSquarize() { if ( !BForceSquarePreviewImage() ) return; const Bitmap_t *pResizeSrc = &m_imgSource; if ( !IsSourceImageSquare() ) { // Select the smaller dimension as the size int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() ); // Crop it. // Yeah, the crop and resize could be done all in one step. // And...I don't care. int x0 = ( m_imgSource.Width() - nSize ) / 2; int y0 = ( m_imgSource.Height() - nSize ) / 2; m_imgTemp.Crop( x0, y0, nSize, nSize, &m_imgSource ); pResizeSrc = &m_imgTemp; } // resize ImgUtl_ResizeBitmap( m_imgSquare, DesiredPreviewWidth(), DesiredPreviewHeight(), pResizeSrc ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Bitmap_t &CFilePublishDialog::GetPreviewBitmap() { return BForceSquarePreviewImage() ? m_imgSquare : m_imgSource; } //----------------------------------------------------------------------------- // Purpose: Setup our edit fields with the appropriate information //----------------------------------------------------------------------------- void CFilePublishDialog::PopulateEditFields( void ) { m_pFileTitle->SetText( m_FileDetails.publishedFileDetails.m_rgchTitle ); m_pFileDescription->SetText( m_FileDetails.publishedFileDetails.m_rgchDescription ); if ( m_FileDetails.lpszFilename && !FStrEq( m_FileDetails.lpszFilename, "" ) ) { char szShortName[ MAX_PATH ]; Q_FileBase( m_FileDetails.lpszFilename, szShortName, sizeof(szShortName) ); const char *szExt = Q_GetFileExtension( m_FileDetails.lpszFilename ); Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) ); m_pFilename->SetText( szShortName ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( GetResFile() ); m_pFileTitle = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileTitle" ) ); if ( m_pFileTitle ) { m_pFileTitle->AddActionSignalTarget( this ); } m_pFileDescription = dynamic_cast< vgui::TextEntry * >( FindChildByName( "FileDesc" ) ); if ( m_pFileDescription ) { m_pFileDescription->SetMultiline( true ); m_pFileDescription->SetCatchEnterKey( true ); m_pFileDescription->SetVerticalScrollbar( true ); } m_pFilename = dynamic_cast< vgui::Label * >( FindChildByName( "SourceFile" ) ); if ( !g_MapFilename.IsEmpty() ) { m_pFilename->SetText( g_MapFilename ); } m_pPublishButton = dynamic_cast< vgui::Button * >( FindChildByName( "ButtonPublish" ) ); // If we're updating, change the context of the button if ( !m_bAddingNewFile ) { m_pPublishButton->SetText( "Update" ); m_pPublishButton->SetCommand( "Update" ); } // Setup our initial state for the edit fields PopulateEditFields(); SetPublishButtonState(); } //----------------------------------------------------------------------------- // Purpose: Helper to build thumbnail name //----------------------------------------------------------------------------- void CFilePublishDialog::GetPreviewFilename( char *szOut, size_t outLen ) { char szMapShortName[MAX_PATH]; Q_FileBase( g_MapFilename, szMapShortName, sizeof(szMapShortName) ); Q_snprintf( szOut, outLen, "%s_thumb.jpg", szMapShortName ); } //----------------------------------------------------------------------------- // Purpose: Callback when our create item has completed. Need to do initial update. //----------------------------------------------------------------------------- void CFilePublishDialog::Steam_OnCreateItem( CreateItemResult_t *pResult, bool bError ) { bError = bError || pResult->m_eResult != k_EResultOK; m_nFileID = pResult->m_nPublishedFileId; if ( bError ) { HideStatusWindow(); ErrorMessage( kFailedToPublishFile ); if ( m_nFileID != k_PublishedFileIdInvalid ) { // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. // Once this is fixed in steam, this call should probably be moved steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); m_nFileID = k_PublishedFileIdInvalid; } } else { StartPrepareFile(); } } //----------------------------------------------------------------------------- // Purpose: Callback from our map compression thread finishing //----------------------------------------------------------------------------- void CFilePublishDialog::OnFilePrepared( bool bSucceeded ) { if ( bSucceeded ) { // Move on to final publishing bSucceeded = UpdateFileInternal(); } if ( bSucceeded ) { // Done, waiting on file publish callback return; } // Failure // This is after OnCreateItem for new files, so cleanup the incomplete item on failure from either the compress or // kicking off the update. if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid ) { // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. // Once this is fixed in steam, this call should probably be moved steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); m_nFileID = k_PublishedFileIdInvalid; } HideStatusWindow(); ErrorMessage( kFailedToUpdateFile ); } //----------------------------------------------------------------------------- // Purpose: Callback when our publish call has completed //----------------------------------------------------------------------------- void CFilePublishDialog::Steam_OnPublishFile( SubmitItemUpdateResult_t *pResult, bool bError ) { // Remove prepared map char szPreparedMap[MAX_PATH] = { 0 }; V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), szPreparedMap, sizeof( szPreparedMap ) ); g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID ); // Remove local thumbnail CUtlBuffer bufData; char szPreviewFilename[MAX_PATH]; GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) ); g_pFullFileSystem->RemoveFile( szPreviewFilename, UGC_PATHID ); HideStatusWindow(); if ( bError || pResult->m_eResult != k_EResultOK ) { if ( m_bAddingNewFile && m_nFileID != k_PublishedFileIdInvalid ) { // TODO ISteamUGC is conspicuously missing a delete call, but shares IDs with SteamRemoteStorage. // Once this is fixed in steam, this call should probably be moved steamapicontext->SteamRemoteStorage()->DeletePublishedFile( m_nFileID ); m_nFileID = k_PublishedFileIdInvalid; } ErrorMessage( kFailedToPublishFile ); } else { EUniverse universe = GetUniverse(); switch ( universe ) { case k_EUniversePublic: steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); break; case k_EUniverseBeta: steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://beta.steamcommunity.com/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); break; case k_EUniverseDev: steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( CFmtStrMax( "http://localhost/community/sharedfiles/filedetails/?id=%llu&requirelogin=true", m_nFileID ) ); break; } // Tell our parent what happened KeyValues *pkvActionSignal = new KeyValues( "ChangedFile" ); pkvActionSignal->SetUint64( "nPublishedFileID", m_nFileID ); PostActionSignal( pkvActionSignal ); // Close down the window CloseModal(); } } //----------------------------------------------------------------------------- // Purpose: Share the file with Steam Cloud and return the handle for later usage //----------------------------------------------------------------------------- bool CFilePublishDialog::PublishFile() { // Must be a valid file ErrorCode_t errorCode = ValidateFile( g_MapFilename ); #ifdef TF_CLIENT_DLL const char *pExt = V_GetFileExtension( g_MapFilename ); if ( errorCode == kNoError && pExt && V_strcmp( pExt, "bsp" ) == 0 ) { if ( !CTFMapsWorkshop::IsValidOriginalFileNameForMap( CUtlString( V_GetFileName( g_MapFilename ) ) ) ) { errorCode = kInvalidMapName; } } #endif if ( errorCode != kNoError ) { ErrorMessage( errorCode ); return false; } ShowStatusWindow( kPublishing ); EWorkshopFileType eFileType = WorkshipFileTypeForFile( g_MapFilename ); // Create file on UGC SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->CreateItem( GetTargetAppID(), eFileType ); // Set the callback m_callbackCreateItem.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnCreateItem ); return true; } //----------------------------------------------------------------------------- // Purpose: Kick off the map compression thread //----------------------------------------------------------------------------- void CFilePublishDialog::StartPrepareFile( void ) { // Ensure temp dir exists g_pFullFileSystem->CreateDirHierarchy( WORKSHOP_TEMP_UPLOAD_DIR, UGC_PATHID ); char szOutPath[MAX_PATH] = { 0 }; V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), szOutPath, sizeof( szOutPath ) ); // Ensure this file isn't leftover in output dir g_pFullFileSystem->RemoveFile( szOutPath, UGC_PATHID ); // Start thread Assert( !m_pPrepareFileThread ); m_pPrepareFileThread = new CPrepareFileThread( g_MapFilename, szOutPath ); m_pPrepareFileThread->Start(); } //----------------------------------------------------------------------------- // Purpose: Parse commands coming in from the VGUI dialog //----------------------------------------------------------------------------- void CFilePublishDialog::SetPublishButtonState( void ) { if ( m_bAddingNewFile ) { if ( m_bValidFile && m_bValidJpeg ) { m_pPublishButton->SetEnabled( true ); } else { m_pPublishButton->SetEnabled( false ); } } else // Updating a previous entry { // m_pPublishButton->SetEnabled( (m_nFileDetailsChanges!=0) ); m_pPublishButton->SetEnabled( true ); // For now, always allow it. Worst case it's a no-op } } //----------------------------------------------------------------------------- // Purpose: Parse commands coming in from the VGUI dialog //----------------------------------------------------------------------------- bool CFilePublishDialog::UpdateFile( void ) { // We should have been created for an existing file or published already, both of which set our ID. Assert( m_nFileID != k_PublishedFileIdInvalid ); ShowStatusWindow( kUpdating ); if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) { StartPrepareFile(); } else { // Not updating map, go straight to update step if ( !UpdateFileInternal() ) { HideStatusWindow(); ErrorMessage( kFailedToUpdateFile ); } return false; } return true; } //----------------------------------------------------------------------------- // Purpose: Update a file, used by the create and update pathways to fill in a UGC item //----------------------------------------------------------------------------- bool CFilePublishDialog::UpdateFileInternal() { ISteamUGC *pUGC = steamapicontext->SteamUGC(); UGCUpdateHandle_t hItem = pUGC->StartItemUpdate( GetTargetAppID(), m_nFileID ); if ( hItem == k_UGCUpdateHandleInvalid ) { UGCWarning( "StartItemUpdate failed\n" ); return false; } bool bError = false; // create thumbnail CUtlBuffer bufData; char szPreviewFilename[MAX_PATH]; GetPreviewFilename( szPreviewFilename, sizeof( szPreviewFilename ) ); if ( !bError && ImgUtl_SaveBitmapToBuffer( bufData, GetPreviewBitmap(), kImageFileFormat_JPG ) == CE_SUCCESS ) { bError = !g_pFullFileSystem->WriteFile( szPreviewFilename, UGC_PATHID, bufData ); // Get full path to give steam g_pFullFileSystem->RelativePathToFullPath( szPreviewFilename, UGC_PATHID, szPreviewFilename, sizeof( szPreviewFilename ) ); } else { bError = true; } // Get the compressed map out of the upload directory char szPreparedMap[MAX_PATH] = { 0 }; char szFullPreparedPath[MAX_PATH] = { 0 }; if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) { V_ComposeFileName( WORKSHOP_TEMP_UPLOAD_DIR, V_GetFileName( g_MapFilename ), szPreparedMap, sizeof( szPreparedMap ) ); g_pFullFileSystem->RelativePathToFullPath( szPreparedMap, UGC_PATHID, szFullPreparedPath, sizeof( szFullPreparedPath ) ); bError |= !*szFullPreparedPath; } if ( !bError ) { // Set title char szTitle[k_cchPublishedDocumentTitleMax]; m_pFileTitle->GetText( szTitle, sizeof(szTitle) ); Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle ); bError |= !pUGC->SetItemTitle( hItem, szTitle ); // Set descriptor char szDesc[k_cchPublishedDocumentDescriptionMax]; m_pFileDescription->GetText( szDesc, sizeof(szDesc) ); Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc ); bError |= !pUGC->SetItemDescription( hItem, szDesc ); // Set thumbnail if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_PREVIEW ) { bError |= !pUGC->SetItemPreview( hItem, szPreviewFilename ); } // Set file if ( m_bAddingNewFile || m_nFileDetailsChanges & PFILE_FIELD_FILE ) { if ( *szFullPreparedPath ) { bError |= !pUGC->SetItemContent( hItem, szFullPreparedPath ); // Metadata for our files is just the original filename, since they are currently all single files bError |= !pUGC->SetItemMetadata( hItem, V_GetFileName( g_MapFilename.Get() ) ); } else { UGCWarning( "Prepared map does not appear to exist\n" ); bError = true; } } // Tags SteamParamStringArray_t strArray; PopulateTags( strArray ); bError |= !pUGC->SetItemTags( hItem, &strArray ); // Visibility bError |= !pUGC->SetItemVisibility( hItem, k_ERemoteStoragePublishedFileVisibilityPublic ); } else { bError = true; } if ( !bError ) { SteamAPICall_t hSteamAPICall = steamapicontext->SteamUGC()->SubmitItemUpdate( hItem, NULL ); m_callbackPublishFile.Set( hSteamAPICall, this, &CFilePublishDialog::Steam_OnPublishFile ); return true; } // Failed, cleanup prepared map g_pFullFileSystem->RemoveFile( szPreparedMap, UGC_PATHID ); return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CFilePublishDialog::PerformLayout() { BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- // Purpose: Parse commands coming in from the VGUI dialog //----------------------------------------------------------------------------- void CFilePublishDialog::OnCommand( const char *command ) { if ( Q_stricmp( command, "Publish" ) == 0 ) { // Verify they've filled everything out properly bool bHasTitle = ( m_pFileTitle->GetTextLength() > 0 ); bool bHasDesc = ( m_pFileDescription->GetTextLength() > 0 ); if ( !bHasTitle || !bHasDesc ) { ErrorMessage( kNeedTitleAndDescription ); return; } // Get our title char szTitle[k_cchPublishedDocumentTitleMax]; m_pFileTitle->GetText( szTitle, sizeof(szTitle) ); Q_AggressiveStripPrecedingAndTrailingWhitespace( szTitle ); // Get our descriptor char szDesc[k_cchPublishedDocumentDescriptionMax]; m_pFileDescription->GetText( szDesc, sizeof(szDesc) ); Q_AggressiveStripPrecedingAndTrailingWhitespace( szDesc ); bHasTitle = Q_strlen( szTitle ) != 0; bHasDesc = Q_strlen( szDesc ) != 0; if ( !bHasTitle || !bHasDesc ) { ErrorMessage( kNeedTitleAndDescription ); return; } PublishFile(); } else if ( Q_stricmp( command, "Update" ) == 0 ) { UpdateFile(); } else if ( Q_stricmp( command, "MainFileMaps" ) == 0 ) { m_fileOpenMode = FILEOPEN_MAIN_FILE; // Create a new dialog vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_MAP ), GetFileTypeDescriptions( IMPORT_FILTER_MAP ), true ); if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) { pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); } char textBuffer[1024]; m_pFilename->GetText( textBuffer, sizeof( textBuffer ) ); char szFilePath[MAX_PATH]; g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) ); strcat( szFilePath, "/" ); strcat( szFilePath, textBuffer ); // Get the currently set dir and use that as the start // pDlg->ExpandTreeToPath( szFilePath ); pDlg->MoveToCenterOfScreen(); pDlg->AddActionSignalTarget( this ); pDlg->SetDeleteSelfOnClose( true ); pDlg->DoModal(); pDlg->Activate(); } else if ( Q_stricmp( command, "MainFileOther" ) == 0 ) { m_fileOpenMode = FILEOPEN_MAIN_FILE; // Create a new dialog vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); pDlg->AddFilter( GetFileTypes( IMPORT_FILTER_OTHER ), GetFileTypeDescriptions( IMPORT_FILTER_OTHER ), true ); if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) { pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); } char textBuffer[1024]; m_pFilename->GetText( textBuffer, sizeof( textBuffer ) ); char szFilePath[MAX_PATH]; g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof( szFilePath ) ); strcat( szFilePath, "/" ); strcat( szFilePath, textBuffer ); // Get the currently set dir and use that as the start // pDlg->ExpandTreeToPath( szFilePath ); pDlg->MoveToCenterOfScreen(); pDlg->AddActionSignalTarget( this ); pDlg->SetDeleteSelfOnClose( true ); pDlg->DoModal(); pDlg->Activate(); } else if ( Q_stricmp( command, "PreviewBrowse" ) == 0 ) { m_fileOpenMode = FILEOPEN_PREVIEW; // Create a new dialog vgui::FileOpenDialog *pDlg = new vgui::FileOpenDialog( NULL, "Select File", true ); pDlg->AddFilter( GetPreviewFileTypes(), GetPreviewFileTypeDescriptions(), true ); if ( !FStrEq( publish_file_last_dir.GetString(), "" ) ) { pDlg->SetStartDirectory( publish_file_last_dir.GetString() ); } char szFilePath[MAX_PATH]; g_pFullFileSystem->GetCurrentDirectory( szFilePath, sizeof(szFilePath) ); strcat( szFilePath, "/" ); strcat( szFilePath, g_PreviewFilename ); // Get the currently set dir and use that as the start // pDlg->ExpandTreeToPath( szFilePath ); pDlg->MoveToCenterOfScreen(); pDlg->AddActionSignalTarget( this ); pDlg->SetDeleteSelfOnClose( true ); pDlg->DoModal(); pDlg->Activate(); } else { BaseClass::OnCommand( command ); } } //----------------------------------------------------------------------------- // Purpose: Take a filename, shorten it for display but retain the full path internally //----------------------------------------------------------------------------- CFilePublishDialog::ErrorCode_t CFilePublishDialog::ValidateFile( const char *lpszFilename ) { return kNoError; } //----------------------------------------------------------------------------- // Purpose: Take a filename, shorten it for display but retain the full path internally //----------------------------------------------------------------------------- void CFilePublishDialog::SetFile( const char *lpszFilename, bool bImported ) { // Must be a valid file ErrorCode_t errorCode = ValidateFile( lpszFilename ); if ( errorCode != kNoError ) { ErrorMessage( errorCode ); return; } m_bValidFile = true; g_MapFilename = lpszFilename; char szShortName[ MAX_PATH ]; Q_FileBase( g_MapFilename, szShortName, sizeof(szShortName) ); const char *szExt = Q_GetFileExtension( lpszFilename ); Q_SetExtension( szShortName, CFmtStr( ".%s", szExt ).Access(), sizeof(szShortName ) ); m_pFilename->SetText( szShortName ); // Notify of the change m_nFileDetailsChanges |= PFILE_FIELD_FILE; SetPublishButtonState(); } //----------------------------------------------------------------------------- // Purpose: Notify us that the directory dialog has returned a new entry //----------------------------------------------------------------------------- void CFilePublishDialog::OnFileSelected( const char *fullPath ) { char basepath[ MAX_PATH ]; Q_ExtractFilePath( fullPath, basepath, sizeof( basepath ) ); publish_file_last_dir.SetValue( basepath ); if ( m_fileOpenMode == FILEOPEN_MAIN_FILE ) { SetFile( fullPath ); } else if ( m_fileOpenMode == FILEOPEN_PREVIEW ) { // Notify of the change m_nFileDetailsChanges |= PFILE_FIELD_PREVIEW; SetPreviewImage( fullPath ); } }