/// <summary> /// Initializes a new instance of the <see cref="GamePage"/> class. The given package group and node tree are /// wrapped by the page. /// </summary> /// <param name="packageGroup">The package group which the node tree maps to.</param> /// <param name="nodeTree">The prebuilt node tree to display.</param> /// <param name="version">The Warcraft version that the game page is contextually relevant for.</param> public GamePage(PackageGroup packageGroup, OptimizedNodeTree nodeTree, WarcraftVersion version) { this.TreeModel = new FileTreeModel(nodeTree); this.GameContext = new WarcraftGameContext(version, packageGroup, this.TreeModel); this.TreeAlignment = new Alignment(0.5f, 0.5f, 1.0f, 1.0f) { TopPadding = 1, BottomPadding = 1 }; this.TreeFilter = new TreeModelFilter(new TreeModelAdapter(this.TreeModel), null) { VisibleFunc = TreeModelVisibilityFunc }; this.TreeSorter = new TreeModelSort(this.TreeFilter); this.TreeSorter.SetSortFunc(0, SortGameTreeRow); this.TreeSorter.SetSortColumnId(0, SortType.Descending); this.Tree = new TreeView(this.TreeSorter) { HeadersVisible = true, EnableTreeLines = true }; CellRendererPixbuf nodeIconRenderer = new CellRendererPixbuf { Xalign = 0.0f }; CellRendererText nodeNameRenderer = new CellRendererText { Xalign = 0.0f }; TreeViewColumn column = new TreeViewColumn { Title = "Data Files", Spacing = 4 }; column.PackStart(nodeIconRenderer, false); column.PackStart(nodeNameRenderer, false); column.SetCellDataFunc(nodeIconRenderer, RenderNodeIcon); column.SetCellDataFunc(nodeNameRenderer, RenderNodeName); this.Tree.AppendColumn(column); ScrolledWindow sw = new ScrolledWindow { this.Tree }; this.TreeAlignment.Add(sw); this.Tree.RowActivated += OnRowActivated; this.Tree.ButtonPressEvent += OnButtonPressed; this.Tree.Selection.Changed += OnSelectionChanged; this.TreeContextMenu = new Menu(); // Save item context button this.SaveItem = new ImageMenuItem { UseStock = true, Label = Stock.Save, CanFocus = false, TooltipText = "Save the currently selected item to disk.", UseUnderline = true }; this.SaveItem.Activated += OnSaveItem; this.TreeContextMenu.Add(this.SaveItem); // Export item context button this.ExportItem = new ImageMenuItem("Export") { Image = new Image(Stock.Convert, IconSize.Button), CanFocus = false, TooltipText = "Exports the currently selected item to another format.", }; this.ExportItem.Activated += OnExportItemRequested; this.TreeContextMenu.Add(this.ExportItem); // Open item context button this.OpenItem = new ImageMenuItem { UseStock = true, Label = Stock.Open, CanFocus = false, TooltipText = "Open the currently selected item.", UseUnderline = true }; this.OpenItem.Activated += OnOpenItem; this.TreeContextMenu.Add(this.OpenItem); // Queue for export context button this.QueueForExportItem = new ImageMenuItem("Queue for export") { Image = new Image(Stock.Convert, IconSize.Button), CanFocus = false, TooltipText = "Queues the currently selected item for batch export.", }; this.QueueForExportItem.Activated += OnQueueForExportRequested; this.TreeContextMenu.Add(this.QueueForExportItem); // Separator SeparatorMenuItem separator = new SeparatorMenuItem(); this.TreeContextMenu.Add(separator); // Copy path context button this.CopyPathItem = new ImageMenuItem("Copy path") { Image = new Image(Stock.Copy, IconSize.Button), CanFocus = false, TooltipText = "Copy the path of the currently selected item.", }; this.CopyPathItem.Activated += OnCopyPath; this.TreeContextMenu.Add(this.CopyPathItem); this.TreeAlignment.ShowAll(); }
/// <summary> /// Initializes a new instance of the <see cref="FileTreeModel"/> class. /// </summary> /// <param name="nodeTree">The precomputed node tree to wrap around.</param> public FileTreeModel(OptimizedNodeTree nodeTree) { this.Tree = nodeTree; this.Stamp = new Random().Next(); }
private void AddGamePage(string alias, WarcraftVersion version, PackageGroup group, OptimizedNodeTree nodeTree) { GamePage page = new GamePage(group, nodeTree, version); page.Alias = alias; page.FileLoadRequested += OnFileLoadRequested; page.ExportItemRequested += OnExportItemRequested; page.EnqueueFileExportRequested += OnEnqueueItemRequested; this.GamePages.Add(page); this.GameTabNotebook.AppendPage(page.PageWidget, new Label(page.Alias)); this.GameTabNotebook.SetTabReorderable(page.PageWidget, true); this.GameTabNotebook.ShowAll(); }
/// <summary> /// Attempts to load a game in a specified path, returning a <see cref="PackageGroup"/> object with the /// packages in the path and an <see cref="OptimizedNodeTree"/> with a fully qualified node tree of the /// package group. /// If no packages are found, then this method will return null in both fields. /// </summary> /// <param name="gameAlias">The alias of the game at the path.</param> /// <param name="gamePath">The path to load as a game.</param> /// <param name="ct">A cancellation token.</param> /// <param name="progress">An <see cref="IProgress{GameLoadingProgress}"/> object for progress reporting.</param> /// <returns>A tuple with a package group and a node tree for the requested game.</returns> public async Task <(PackageGroup packageGroup, OptimizedNodeTree nodeTree)> LoadGameAsync( string gameAlias, string gamePath, CancellationToken ct, IProgress <GameLoadingProgress> progress = null) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0.0f, State = GameLoadingState.SettingUp, Alias = gameAlias }); List <string> packagePaths = Directory.EnumerateFiles ( gamePath, "*", SearchOption.AllDirectories ) .Where(p => p.EndsWith(".mpq", StringComparison.InvariantCultureIgnoreCase)) .OrderBy(p => p) .ToList(); if (packagePaths.Count == 0) { return(null, null); } string packageSetHash = GeneratePathSetHash(packagePaths); string packageTreeFilename = $".{packageSetHash}.tree"; string packageTreeFilePath = Path.Combine(gamePath, packageTreeFilename); PackageGroup packageGroup = new PackageGroup(packageSetHash); OptimizedNodeTree nodeTree = null; bool generateTree = true; if (File.Exists(packageTreeFilePath)) { progress?.Report(new GameLoadingProgress { CompletionPercentage = 0, State = GameLoadingState.LoadingNodeTree, Alias = gameAlias }); try { // Load tree nodeTree = new OptimizedNodeTree(packageTreeFilePath); generateTree = false; } catch (NodeTreeNotFoundException) { Log.Error("No file for the node tree found at the given location."); } catch (UnsupportedNodeTreeVersionException) { Log.Info("Unsupported node tree version present. Deleting and regenerating."); File.Delete(packageTreeFilePath); } } if (generateTree) { // Internal counters for progress reporting double completedSteps = 0; double totalSteps = packagePaths.Count * 2; // Load packages List <(string packageName, IPackage package)> packages = new List <(string packageName, IPackage package)>(); foreach (string packagePath in packagePaths) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); try { PackageInteractionHandler package = await PackageInteractionHandler.LoadAsync(packagePath); packages.Add((Path.GetFileNameWithoutExtension(packagePath), package)); } catch (FileLoadException fex) { Log.Warn($"Failed to load archive {Path.GetFileNameWithoutExtension(packagePath)}: {fex.Message}"); } ++completedSteps; } // Load dictionary if neccesary if (this.Dictionary == null) { progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.LoadingDictionary, Alias = gameAlias }); this.Dictionary = await LoadDictionaryAsync(ct); } // Generate node tree MultiPackageNodeTreeBuilder multiBuilder = new MultiPackageNodeTreeBuilder(this.Dictionary); foreach (var packageInfo in packages) { ct.ThrowIfCancellationRequested(); progress?.Report(new GameLoadingProgress { CompletionPercentage = completedSteps / totalSteps, State = GameLoadingState.BuildingNodeTree, Alias = gameAlias }); await multiBuilder.ConsumePackageAsync(packageInfo.packageName, packageInfo.package, ct); packageGroup.AddPackage((PackageInteractionHandler)packageInfo.package); ++completedSteps; } // Build node tree multiBuilder.Build(); // Save it to disk File.WriteAllBytes(packageTreeFilePath, multiBuilder.CreateTree()); nodeTree = new OptimizedNodeTree(packageTreeFilePath); } else { progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.LoadingPackages, Alias = gameAlias }); // Load packages packageGroup = await PackageGroup.LoadAsync(gameAlias, packageSetHash, gamePath, ct, progress); } progress?.Report(new GameLoadingProgress { CompletionPercentage = 1, State = GameLoadingState.Loading, Alias = gameAlias }); return(packageGroup, nodeTree); }