//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "stdafx.h" #include "vgui_controls/TreeView.h" #include "vgui_controls/ListPanel.h" #include "vgui_controls/SectionedListPanel.h" #include "vgui_controls/ComboBox.h" #include "vgui_controls/PropertySheet.h" #include "vgui_controls/PropertyPage.h" #include using namespace vgui; extern IP4* p4; // list of all tree view icons enum { IMAGE_FOLDER = 1, IMAGE_OPENFOLDER, IMAGE_FILE, }; //----------------------------------------------------------------------------- // Purpose: Handles file view //----------------------------------------------------------------------------- class CFileTreeView : public TreeView { DECLARE_CLASS_SIMPLE( CFileTreeView, TreeView ); public: CFileTreeView(Panel *parent, const char *name) : BaseClass(parent, name) { } // override to incremental request and show p4 directories virtual void GenerateChildrenOfNode(int itemIndex) { KeyValues *pkv = GetItemData(itemIndex); if (!pkv->GetInt("dir")) return; const char *pFilePath = pkv->GetString("path", ""); if (!pFilePath[0]) return; surface()->SetCursor(dc_waitarrow); // get the list of files CUtlVector &files = p4->GetFileList(pFilePath); // generate children // add all the items for (int i = 0; i < files.Count(); i++) { if (files[i].m_bDeleted) continue; KeyValues *kv = new KeyValues("node", "text", p4->String( files[i].m_sName ) ); if (files[i].m_bDir) { kv->SetInt("expand", 1); kv->SetInt("dir", 1); kv->SetInt("image", IMAGE_FOLDER); } else { kv->SetInt("image", IMAGE_FILE); } // get the files path char szPath[MAX_PATH]; if (files[i].m_bDir) { Q_snprintf(szPath, sizeof(szPath), "%s/%s/%%%%1", p4->String( files[i].m_sPath ), p4->String( files[i].m_sName ) ); } else { Q_snprintf(szPath, sizeof(szPath), "%s/%s", p4->String( files[i].m_sPath ), p4->String( files[i].m_sName )); } // translate the files path from a depot path into a local path char szLocalPath[MAX_PATH]; p4->GetLocalFilePath(szLocalPath, szPath, sizeof(szLocalPath)); kv->SetString("path", szLocalPath); // now change the items text to match the local paths file name... char *pLocalName = Q_strrchr(szLocalPath, '\\'); if (pLocalName) { *pLocalName = 0; ++pLocalName; } kv->SetString("text", pLocalName); int itemID = AddItem(kv, itemIndex); // mark out of date files in red if (files[i].m_iHaveRevision < files[i].m_iHeadRevision) { SetItemFgColor(itemID, Color(255, 0, 0, 255)); } } } // setup a context menu whenever a directory is clicked on virtual void GenerateContextMenu( int itemIndex, int x, int y ) { KeyValues *pkv = GetItemData(itemIndex); const char *pFilePath = pkv->GetString("path", ""); if (!pFilePath[0]) return; Menu *pContext = new Menu(this, "FileContext"); if (pkv->GetInt("dir")) { pContext->AddMenuItem("Cloak folder", new KeyValues("CloakFolder", "item", itemIndex), GetParent(), NULL); } else { pContext->AddMenuItem("Open for edit", new KeyValues("EditFile", "item", itemIndex), GetParent(), NULL); pContext->AddMenuItem("Open for delete", new KeyValues("DeleteFile", "item", itemIndex), GetParent(), NULL); } // show the context menu pContext->SetPos(x, y); pContext->SetVisible(true); } // setup a smaller font virtual void ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); SetFont( pScheme->GetFont("DefaultSmall") ); } }; //----------------------------------------------------------------------------- // Purpose: Revision list //----------------------------------------------------------------------------- class CSmallTextListPanel : public ListPanel { DECLARE_CLASS_SIMPLE( CSmallTextListPanel, ListPanel ); public: CSmallTextListPanel(Panel *parent, const char *name) : BaseClass(parent, name) { } virtual void ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); SetFont( pScheme->GetFont("DefaultSmall") ); } }; //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CVP4Dialog::CVP4Dialog() : BaseClass(NULL, "vp4dialog"), m_Images(false) { SetSize(1024, 768); SetTitle("VP4", true); // clientspec selection m_pClientCombo = new ComboBox(this, "ClientCombo", 16, false); // file browser tree controls m_pFileTree = new CFileTreeView(this, "FileTree"); // build our list of images m_Images.AddImage(scheme()->GetImage("resource/icon_folder", false)); m_Images.AddImage(scheme()->GetImage("resource/icon_folder_selected", false)); m_Images.AddImage(scheme()->GetImage("resource/icon_file", false)); m_pFileTree->SetImageList(&m_Images, false); // property sheet - revisions, changes, etc. m_pViewsSheet = new PropertySheet(this, "ViewsSheet"); // changes m_pChangesPage = new PropertyPage(m_pViewsSheet, "ChangesPage"); m_pViewsSheet->AddPage(m_pChangesPage, "Changes"); m_pChangesList = new SectionedListPanel(m_pChangesPage, "ChangesList"); // layout changes int x, y, wide, tall; m_pChangesPage->GetBounds(x, y, wide, tall); m_pChangesList->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 6, 6, -12, -12 ); // revisions m_pRevisionsPage = new PropertyPage(m_pViewsSheet, "RevisionsPage"); m_pViewsSheet->AddPage(m_pRevisionsPage, "History"); m_pRevisionList = new CSmallTextListPanel(m_pRevisionsPage, "RevisionList"); m_pRevisionList->SetEmptyListText("No file or directory currently selected."); m_pRevisionList->AddColumnHeader(0, "change", "change", 52, ListPanel::COLUMN_HIDDEN); m_pRevisionList->AddColumnHeader(1, "date", "date", 52, 0); m_pRevisionList->AddColumnHeader(2, "time", "time", 52, ListPanel::COLUMN_HIDDEN); m_pRevisionList->AddColumnHeader(3, "user", "user", 64, 0); m_pRevisionList->AddColumnHeader(4, "description", "description", 32, ListPanel::COLUMN_RESIZEWITHWINDOW); m_pRevisionList->SetAllowUserModificationOfColumns(true); // layout revisions m_pRevisionsPage->GetBounds(x, y, wide, tall); m_pRevisionList->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 6, 6, -12, -12 ); LoadControlSettingsAndUserConfig("//PLATFORM/resource/vp4dialog.res"); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CVP4Dialog::~CVP4Dialog() { } //----------------------------------------------------------------------------- // Purpose: stops app on close //----------------------------------------------------------------------------- void CVP4Dialog::OnClose() { BaseClass::OnClose(); ivgui()->Stop(); } //----------------------------------------------------------------------------- // Purpose: called to open //----------------------------------------------------------------------------- void CVP4Dialog::Activate() { BaseClass::Activate(); char szTitle[256]; Q_snprintf(szTitle, sizeof(szTitle), "VP4 - %s", p4->String( p4->GetActiveClient().m_sUser )); SetTitle(szTitle, true); RefreshFileList(); RefreshClientList(); RefreshChangesList(); p4->GetClientList(); } //----------------------------------------------------------------------------- // Purpose: Refreshes the active file list //----------------------------------------------------------------------------- void CVP4Dialog::RefreshFileList() { m_pFileTree->RemoveAll(); m_pFileTree->SetFgColor(Color(216, 222, 211, 255)); // add the base node KeyValues *pkv = new KeyValues("root"); pkv->SetString("text", p4->String( p4->GetActiveClient().m_sLocalRoot )); pkv->SetString("path", p4->String( p4->GetActiveClient().m_sLocalRoot )); pkv->SetInt("dir", 1); int iRoot = m_pFileTree->AddItem(pkv, m_pFileTree->GetRootItemIndex()); m_pFileTree->ExpandItem(iRoot, true); } //----------------------------------------------------------------------------- // Purpose: utility function used in qsort //----------------------------------------------------------------------------- int __cdecl IntSortFunc(const void *elem1, const void *elem2) { if ( *((int *)elem1) < *((int *)elem2) ) return -1; else if ( *((int *)elem1) > *((int *)elem2) ) return 1; return 0; } //----------------------------------------------------------------------------- // Purpose: rebuilds the list of changes //----------------------------------------------------------------------------- void CVP4Dialog::RefreshChangesList() { m_pChangesList->RemoveAll(); m_pChangesList->RemoveAllSections(); CUtlVector files; p4->GetOpenedFileList( files ); CUtlVector sections; // find out all the changelists for (int i = 0; i < files.Count(); i++) { if (!sections.IsValidIndex(sections.Find(files[i].m_iChangelist))) { // add a new section sections.AddToTail(files[i].m_iChangelist); } } // sort the changelists qsort(sections.Base(), sections.Count(), sizeof(int), &IntSortFunc); // add the changeslists {for (int i = 0; i < sections.Count(); i++) { m_pChangesList->AddSection(sections[i], ""); char szChangelistName[256]; if (sections[i] > 0) { Q_snprintf(szChangelistName, sizeof(szChangelistName), "CHANGE: %d", sections[i]); } else { Q_snprintf(szChangelistName, sizeof(szChangelistName), "CHANGE: DEFAULT"); } m_pChangesList->AddColumnToSection(sections[i], "file", szChangelistName, SectionedListPanel::COLUMN_BRIGHT, 512); }} // add the files to the changelists {for (int i = 0; i < files.Count(); i++) { char szFile[_MAX_PATH]; Q_snprintf(szFile, sizeof(szFile), "%s/%s", p4->String( files[i].m_sPath ) + p4->GetDepotRootLength(), p4->String( files[i].m_sName )); KeyValues *pkv = new KeyValues("node", "file", szFile); m_pChangesList->AddItem(files[i].m_iChangelist, pkv); }} } //----------------------------------------------------------------------------- // Purpose: Refreshes the client selection combo //----------------------------------------------------------------------------- void CVP4Dialog::RefreshClientList() { m_pClientCombo->RemoveAll(); CUtlVector &clients = p4->GetClientList(); P4Client_t &activeClient = p4->GetActiveClient(); // go through all clients for (int i = 0; i < clients.Count(); i++) { // only add clients that are defined for this machine (host) if (clients[i].m_sHost != activeClient.m_sHost) continue; // add in the new item char szText[256]; Q_snprintf(szText, sizeof(szText), "%s %s", p4->String( clients[i].m_sName ), p4->String( clients[i].m_sLocalRoot )); m_pClientCombo->AddItem(szText, new KeyValues("client", "client", p4->String( clients[i].m_sName ))); } m_pClientCombo->SetText( p4->String( activeClient.m_sName )); if (m_pClientCombo->GetItemCount() > 1) { m_pClientCombo->SetEnabled(true); } else { m_pClientCombo->SetEnabled(false); } } //----------------------------------------------------------------------------- // Purpose: refreshes dialog on text changing //----------------------------------------------------------------------------- void CVP4Dialog::OnTextChanged() { KeyValues *pkv = m_pClientCombo->GetActiveItemUserData(); if (!pkv) return; // set the new client p4->SetActiveClient(pkv->GetString("client")); // refresh the UI Activate(); m_pRevisionList->RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Cloaks a folder //----------------------------------------------------------------------------- void CVP4Dialog::CloakFolder(int iItem) { KeyValues *pkv = m_pFileTree->GetItemData(iItem); if (!pkv) return; // change the clientspec to remove the folder p4->RemovePathFromActiveClientspec(pkv->GetString("path")); // remove the folder from the tree view m_pFileTree->RemoveItem(0-iItem, false); m_pFileTree->InvalidateLayout(); m_pFileTree->Repaint(); } //----------------------------------------------------------------------------- // Purpose: open for edit //----------------------------------------------------------------------------- void CVP4Dialog::OpenFileForEdit(int iItem) { KeyValues *pkv = m_pFileTree->GetItemData(iItem); if (!pkv) return; p4->OpenFileForEdit(pkv->GetString("path")); RefreshChangesList(); } //----------------------------------------------------------------------------- // Purpose: open for delete //----------------------------------------------------------------------------- void CVP4Dialog::OpenFileForDelete(int iItem) { KeyValues *pkv = m_pFileTree->GetItemData(iItem); if (!pkv) return; p4->OpenFileForDelete(pkv->GetString("path")); RefreshChangesList(); } //----------------------------------------------------------------------------- // Purpose: updates revision view on a file being selected //----------------------------------------------------------------------------- void CVP4Dialog::OnFileSelected() { m_pRevisionList->RemoveAll(); // only update if reviews page is visible if (!m_pRevisionsPage->IsVisible()) return; // update list int iItem = m_pFileTree->GetFirstSelectedItem(); if (iItem < 0) { m_pRevisionList->SetEmptyListText("No file or directory currently selected."); return; } surface()->SetCursor(dc_waitarrow); m_pRevisionList->SetEmptyListText("There is no revision history for the selected file."); KeyValues *pkv = m_pFileTree->GetItemData(iItem); CUtlVector &revisions = p4->GetRevisionList(pkv->GetString("path"), pkv->GetInt("dir")); // add all the revisions for (int i = 0; i < revisions.Count(); i++) { KeyValues *pkv = new KeyValues("node"); pkv->SetString("user", p4->String( revisions[i].m_sUser )); pkv->SetInt("change", revisions[i].m_iChange); char szDate[256]; Q_snprintf(szDate, sizeof(szDate), "%d/%d/%d", revisions[i].m_nYear, revisions[i].m_nMonth, revisions[i].m_nDay); pkv->SetString("date", szDate); char szTime[256]; Q_snprintf(szTime, sizeof(szTime), "%d:%d:%d", revisions[i].m_nHour, revisions[i].m_nMinute, revisions[i].m_nSecond); pkv->SetString("time", szTime); // take just the first line of the description char *pDesc = revisions[i].m_Description.GetForModify(); // move to the first non-whitespace while (*pDesc && (isspace(*pDesc) || iscntrl(*pDesc))) { ++pDesc; } char szShortDescription[256]; Q_strncpy(szShortDescription, pDesc, sizeof(szShortDescription)); // truncate to last terminator char *pTerm = szShortDescription; while (*pTerm && !iscntrl(*pTerm)) { ++pTerm; } *pTerm = 0; pkv->SetString("description", szShortDescription); m_pRevisionList->AddItem(pkv, 0, false, false); } }