Ejemplo n.º 1
0
        /// <summary>
        /// Tries to get every mod in the Dictionary, which can be installed
        /// It also transforms the Recommender list to a string
        /// </summary>
        /// <param name="mods"></param>
        /// <returns></returns>
        private Dictionary <CkanModule, string> GetShowableMods(Dictionary <string, List <string> > mods)
        {
            Dictionary <CkanModule, string> modules = new Dictionary <CkanModule, string>();

            var opts = new RelationshipResolverOptions
            {
                with_all_suggests              = false,
                with_recommends                = false,
                with_suggests                  = false,
                without_enforce_consistency    = false,
                without_toomanyprovides_kraken = true
            };

            foreach (var pair in mods)
            {
                CkanModule module;

                try
                {
                    var resolver = new RelationshipResolver(new List <string> {
                        pair.Key
                    }, opts,
                                                            RegistryManager.Instance(manager.CurrentInstance).registry, CurrentInstance.VersionCriteria());
                    if (!resolver.ModList().Any())
                    {
                        continue;
                    }

                    module = RegistryManager.Instance(manager.CurrentInstance)
                             .registry.LatestAvailable(pair.Key, CurrentInstance.VersionCriteria());
                }
                catch
                {
                    continue;
                }

                if (module == null)
                {
                    continue;
                }
                modules.Add(module, String.Join(",", pair.Value.ToArray()));
            }
            return(modules);
        }
Ejemplo n.º 2
0
        private void _UpdateModsList(IEnumerable <ModChange> mc, Dictionary <string, bool> old_modules = null)
        {
            log.Info("Updating the mod list");

            ResetProgress();
            tabController.RenameTab("WaitTabPage", "Loading modules");
            ShowWaitDialog(false);
            tabController.SetTabLock(true);
            Util.Invoke(this, SwitchEnabledState);
            ClearLog();

            AddLogMessage("Loading registry...");
            KspVersionCriteria versionCriteria = CurrentInstance.VersionCriteria();
            IRegistryQuerier   registry        = RegistryManager.Instance(CurrentInstance).registry;

            AddLogMessage("Loading installed modules...");
            var gui_mods = new HashSet <GUIMod>();

            gui_mods.UnionWith(
                registry.InstalledModules
                .Select(instMod => new GUIMod(instMod, registry, versionCriteria))
                );
            AddLogMessage("Loading available modules...");
            gui_mods.UnionWith(
                registry.Available(versionCriteria)
                .Select(m => new GUIMod(m, registry, versionCriteria))
                );
            AddLogMessage("Loading incompatible modules...");
            gui_mods.UnionWith(
                registry.Incompatible(versionCriteria)
                .Select(m => new GUIMod(m, registry, versionCriteria, true))
                );

            if (mc != null)
            {
                AddLogMessage("Restoring change set...");
                foreach (ModChange change in mc)
                {
                    // Propagate IsInstallChecked and IsUpgradeChecked to the next generation
                    gui_mods.FirstOrDefault(
                        mod => mod.Identifier == change.Mod.Identifier
                        )?.SetRequestedChange(change.ChangeType);
                }
            }

            AddLogMessage("Preserving new flags...");
            if (old_modules != null)
            {
                foreach (GUIMod gm in gui_mods)
                {
                    bool oldIncompat;
                    if (old_modules.TryGetValue(gm.Identifier, out oldIncompat))
                    {
                        // Found it; check if newly compatible
                        if (!gm.IsIncompatible && oldIncompat)
                        {
                            gm.IsNew = true;
                        }
                    }
                    else
                    {
                        // Newly indexed, show regardless of compatibility
                        gm.IsNew = true;
                    }
                }
            }
            else
            {
                // Copy the new mod flag from the old list.
                var old_new_mods = new HashSet <GUIMod>(
                    mainModList.Modules.Where(m => m.IsNew));
                foreach (var gui_mod in gui_mods.Where(m => old_new_mods.Contains(m)))
                {
                    gui_mod.IsNew = true;
                }
            }

            AddLogMessage("Populating mod list...");
            // Update our mod listing
            mainModList.ConstructModList(gui_mods.ToList(), mc, configuration.HideEpochs, configuration.HideV);
            mainModList.Modules = new ReadOnlyCollection <GUIMod>(
                mainModList.full_list_of_mod_rows.Values.Select(row => row.Tag as GUIMod).ToList());

            AddLogMessage("Updating filters...");

            var has_any_updates      = gui_mods.Any(mod => mod.HasUpdate);
            var has_any_installed    = gui_mods.Any(mod => mod.IsInstalled);
            var has_any_replacements = gui_mods.Any(mod => mod.IsInstalled && mod.HasReplacement);

            //TODO Consider using smart enumeration pattern so stuff like this is easier
            Util.Invoke(menuStrip2, () =>
            {
                FilterToolButton.DropDownItems[0].Text = String.Format("Compatible ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.Compatible));
                FilterToolButton.DropDownItems[1].Text = String.Format("Installed ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.Installed));
                FilterToolButton.DropDownItems[2].Text = String.Format("Upgradeable ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.InstalledUpdateAvailable));
                FilterToolButton.DropDownItems[3].Text = String.Format("Replaceable ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.Replaceable));
                FilterToolButton.DropDownItems[4].Text = String.Format("Cached ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.Cached));
                FilterToolButton.DropDownItems[5].Text = String.Format("Newly compatible ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.NewInRepository));
                FilterToolButton.DropDownItems[6].Text = String.Format("Not installed ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.NotInstalled));
                FilterToolButton.DropDownItems[7].Text = String.Format("Incompatible ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.Incompatible));
                FilterToolButton.DropDownItems[8].Text = String.Format("All ({0})",
                                                                       mainModList.CountModsByFilter(GUIModFilter.All));

                UpdateAllToolButton.Enabled = has_any_updates;
            });

            UpdateFilters(this);

            // Hide update and replacement columns if not needed.
            // Write it to the configuration, else they are hidden agian after a filter change.
            // After the update / replacement, they are hidden again.
            ModList.Columns["UpdateCol"].Visible     = has_any_updates;
            ModList.Columns["AutoInstalled"].Visible = has_any_installed && !configuration.HiddenColumnNames.Contains("AutoInstalled");
            ModList.Columns["ReplaceCol"].Visible    = has_any_replacements;

            AddLogMessage("Updating tray...");
            UpdateTrayInfo();

            HideWaitDialog(true);
            tabController.HideTab("WaitTabPage");
            tabController.SetTabLock(false);
            Util.Invoke(this, SwitchEnabledState);
            Util.Invoke(this, () => Main.Instance.ModList.Focus());
        }
Ejemplo n.º 3
0
        // this probably needs to be refactored
        private void InstallMods(object sender, DoWorkEventArgs e)
        {
            installCanceled = false;
            Wait.ClearLog();

            var opts = (KeyValuePair <ModChanges, RelationshipResolverOptions>)e.Argument;

            RegistryManager registry_manager = RegistryManager.Instance(manager.CurrentInstance);
            Registry        registry         = registry_manager.registry;
            ModuleInstaller installer        = ModuleInstaller.GetInstance(CurrentInstance, Manager.Cache, currentUser);

            // Avoid accumulating multiple event handlers
            installer.onReportModInstalled -= OnModInstalled;
            installer.onReportModInstalled += OnModInstalled;
            // setup progress callback

            // this will be the final list of mods we want to install
            HashSet <CkanModule> toInstall = new HashSet <CkanModule>();
            var toUninstall = new HashSet <CkanModule>();
            var toUpgrade   = new HashSet <CkanModule>();

            // First compose sets of what the user wants installed, upgraded, and removed.
            foreach (ModChange change in opts.Key)
            {
                switch (change.ChangeType)
                {
                case GUIModChangeType.Remove:
                    toUninstall.Add(change.Mod);
                    break;

                case GUIModChangeType.Update:
                    toUpgrade.Add(change is ModUpgrade mu
                            ? mu.targetMod
                            : change.Mod);
                    break;

                case GUIModChangeType.Install:
                    toInstall.Add(change.Mod);
                    break;

                case GUIModChangeType.Replace:
                    ModuleReplacement repl = registry.GetReplacement(change.Mod, CurrentInstance.VersionCriteria());
                    if (repl != null)
                    {
                        toUninstall.Add(repl.ToReplace);
                        toInstall.Add(repl.ReplaceWith);
                    }
                    break;
                }
            }

            // Prompt for recommendations and suggestions, if any
            if (installer.FindRecommendations(
                    opts.Key.Where(ch => ch.ChangeType == GUIModChangeType.Install)
                    .Select(ch => ch.Mod)
                    .ToHashSet(),
                    toInstall,
                    registry,
                    out Dictionary <CkanModule, Tuple <bool, List <string> > > recommendations,
                    out Dictionary <CkanModule, List <string> > suggestions,
                    out Dictionary <CkanModule, HashSet <string> > supporters
                    ))
            {
                tabController.ShowTab("ChooseRecommendedModsTabPage", 3);
                ChooseRecommendedMods.LoadRecommendations(
                    registry, toInstall, toUninstall,
                    CurrentInstance.VersionCriteria(), Manager.Cache,
                    recommendations, suggestions, supporters);
                tabController.SetTabLock(true);
                var result = ChooseRecommendedMods.Wait();
                if (result == null)
                {
                    installCanceled = true;
                }
                else
                {
                    toInstall.UnionWith(result);
                }
                tabController.SetTabLock(false);
                tabController.HideTab("ChooseRecommendedModsTabPage");
            }

            if (installCanceled)
            {
                tabController.ShowTab("ManageModsTabPage");
                e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                return;
            }

            // Now let's make all our changes.
            Util.Invoke(this, () =>
            {
                // Need to be on the GUI thread to get the translated string
                tabController.RenameTab("WaitTabPage", Properties.Resources.MainInstallWaitTitle);
            });
            ShowWaitDialog();
            tabController.SetTabLock(true);

            IDownloader downloader = new NetAsyncModulesDownloader(currentUser, Manager.Cache);

            downloader.Progress    += Wait.SetModuleProgress;
            downloader.AllComplete += Wait.DownloadsComplete;
            cancelCallback          = () =>
            {
                downloader.CancelDownload();
                installCanceled = true;
            };

            HashSet <string> possibleConfigOnlyDirs = null;

            // checks if all actions were successfull
            bool processSuccessful       = false;
            bool resolvedAllProvidedMods = false;

            // uninstall/installs/upgrades until every list is empty
            // if the queue is NOT empty, resolvedAllProvidedMods is set to false until the action is done
            while (!resolvedAllProvidedMods)
            {
                try
                {
                    e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                    if (toUninstall.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.UninstallList(toUninstall.Select(m => m.identifier),
                                                    ref possibleConfigOnlyDirs, registry_manager, false, toInstall);
                            processSuccessful = true;
                        }
                    }
                    if (toUpgrade.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.Upgrade(toUpgrade, downloader, ref possibleConfigOnlyDirs, registry_manager, true, true, false);
                            processSuccessful = true;
                        }
                    }
                    if (toInstall.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.InstallList(toInstall, opts.Value, registry_manager, ref possibleConfigOnlyDirs, downloader, false);
                            processSuccessful = true;
                        }
                    }

                    HandlePossibleConfigOnlyDirs(registry, possibleConfigOnlyDirs);

                    e.Result = new KeyValuePair <bool, ModChanges>(processSuccessful, opts.Key);
                    if (installCanceled)
                    {
                        return;
                    }
                    resolvedAllProvidedMods = true;
                }
                catch (TooManyModsProvideKraken k)
                {
                    // Prompt user to choose which mod to use
                    tabController.ShowTab("ChooseProvidedModsTabPage", 3);
                    ChooseProvidedMods.LoadProviders(k.requested, k.modules, Manager.Cache);
                    tabController.SetTabLock(true);
                    CkanModule chosen = ChooseProvidedMods.Wait();
                    // Close the selection prompt
                    tabController.SetTabLock(false);
                    tabController.HideTab("ChooseProvidedModsTabPage");
                    if (chosen != null)
                    {
                        // User picked a mod, queue it up for installation
                        toInstall.Add(chosen);
                        // DON'T return so we can loop around and try the above InstallList call again
                        tabController.ShowTab("WaitTabPage");
                    }
                    else
                    {
                        // User cancelled, get out
                        tabController.ShowTab("ManageModsTabPage");
                        e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                        return;
                    }
                }
            }
        }
Ejemplo n.º 4
0
        // this probably needs to be refactored
        private void InstallMods(object sender, DoWorkEventArgs e)
        {
            installCanceled = false;
            ClearLog();

            var opts = (KeyValuePair <ModChanges, RelationshipResolverOptions>)e.Argument;

            IRegistryQuerier registry  = RegistryManager.Instance(manager.CurrentInstance).registry;
            ModuleInstaller  installer = ModuleInstaller.GetInstance(CurrentInstance, Manager.Cache, GUI.user);

            // Avoid accumulating multiple event handlers
            installer.onReportModInstalled -= OnModInstalled;
            installer.onReportModInstalled += OnModInstalled;
            // setup progress callback

            // this will be the final list of mods we want to install
            HashSet <CkanModule> toInstall = new HashSet <CkanModule>();
            var toUninstall = new HashSet <string>();
            var toUpgrade   = new HashSet <string>();

            // First compose sets of what the user wants installed, upgraded, and removed.
            foreach (ModChange change in opts.Key)
            {
                switch (change.ChangeType)
                {
                case GUIModChangeType.Remove:
                    toUninstall.Add(change.Mod.identifier);
                    break;

                case GUIModChangeType.Update:
                    toUpgrade.Add(change.Mod.identifier);
                    break;

                case GUIModChangeType.Install:
                    toInstall.Add(change.Mod);
                    break;

                case GUIModChangeType.Replace:
                    ModuleReplacement repl = registry.GetReplacement(change.Mod, CurrentInstance.VersionCriteria());
                    if (repl != null)
                    {
                        toUninstall.Add(repl.ToReplace.identifier);
                        toInstall.Add(repl.ReplaceWith);
                    }
                    break;
                }
            }

            // Prompt for recommendations and suggestions, if any
            var recRows = getRecSugRows(
                opts.Key.Where(ch => ch.ChangeType == GUIModChangeType.Install)
                .Select(ch => ch.Mod),
                registry, toInstall
                );

            if (recRows.Any())
            {
                ShowRecSugDialog(recRows, toInstall);
            }

            tabController.HideTab("ChooseRecommendedModsTabPage");

            if (installCanceled)
            {
                tabController.ShowTab("ManageModsTabPage");
                e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                return;
            }

            // Now let's make all our changes.
            tabController.RenameTab("WaitTabPage", Properties.Resources.MainInstallWaitTitle);
            ShowWaitDialog();
            tabController.SetTabLock(true);

            IDownloader downloader = new NetAsyncModulesDownloader(GUI.user, Manager.Cache);

            cancelCallback = () =>
            {
                downloader.CancelDownload();
                installCanceled = true;
            };

            // checks if all actions were successfull
            bool processSuccessful       = false;
            bool resolvedAllProvidedMods = false;

            // uninstall/installs/upgrades until every list is empty
            // if the queue is NOT empty, resolvedAllProvidedMods is set to false until the action is done
            while (!resolvedAllProvidedMods)
            {
                try
                {
                    e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                    if (toUninstall.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.UninstallList(toUninstall, false, toInstall.Select(m => m.identifier));
                            processSuccessful = true;
                        }
                    }
                    if (toUpgrade.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.Upgrade(toUpgrade, downloader);
                            processSuccessful = true;
                        }
                    }
                    if (toInstall.Count > 0)
                    {
                        processSuccessful = false;
                        if (!installCanceled)
                        {
                            installer.InstallList(toInstall, opts.Value, downloader, false);
                            processSuccessful = true;
                        }
                    }
                    e.Result = new KeyValuePair <bool, ModChanges>(processSuccessful, opts.Key);
                    if (installCanceled)
                    {
                        return;
                    }
                    resolvedAllProvidedMods = true;
                }
                catch (TooManyModsProvideKraken k)
                {
                    // Prompt user to choose which mod to use
                    CkanModule chosen = TooManyModsProvideCore(k).Result;
                    // Close the selection prompt
                    Util.Invoke(this, () =>
                    {
                        tabController.ShowTab("WaitTabPage");
                        tabController.HideTab("ChooseProvidedModsTabPage");
                    });
                    if (chosen != null)
                    {
                        // User picked a mod, queue it up for installation
                        toInstall.Add(chosen);
                        // DON'T return so we can loop around and try the above InstallList call again
                    }
                    else
                    {
                        // User cancelled, get out
                        tabController.ShowTab("ManageModsTabPage");
                        e.Result = new KeyValuePair <bool, ModChanges>(false, opts.Key);
                        return;
                    }
                }
                catch (DependencyNotSatisfiedKraken ex)
                {
                    GUI.user.RaiseMessage(Properties.Resources.MainInstallDepNotSatisfied, ex.parent, ex.module);
                    return;
                }
                catch (ModuleNotFoundKraken ex)
                {
                    GUI.user.RaiseMessage(Properties.Resources.MainInstallNotFound, ex.module);
                    return;
                }
                catch (BadMetadataKraken ex)
                {
                    GUI.user.RaiseMessage(Properties.Resources.MainInstallBadMetadata, ex.module, ex.Message);
                    return;
                }
                catch (FileExistsKraken ex)
                {
                    if (ex.owningModule != null)
                    {
                        GUI.user.RaiseMessage(
                            Properties.Resources.MainInstallFileExists,
                            ex.filename, ex.installingModule, ex.owningModule,
                            Meta.GetVersion()
                            );
                    }
                    else
                    {
                        GUI.user.RaiseMessage(
                            Properties.Resources.MainInstallUnownedFileExists,
                            ex.installingModule, ex.filename
                            );
                    }
                    GUI.user.RaiseMessage(Properties.Resources.MainInstallGameDataReverted);
                    return;
                }
                catch (InconsistentKraken ex)
                {
                    // The prettiest Kraken formats itself for us.
                    GUI.user.RaiseMessage(ex.InconsistenciesPretty);
                    return;
                }
                catch (CancelledActionKraken)
                {
                    return;
                }
                catch (MissingCertificateKraken kraken)
                {
                    // Another very pretty kraken.
                    GUI.user.RaiseMessage(kraken.ToString());
                    return;
                }
                catch (DownloadThrottledKraken kraken)
                {
                    string msg = kraken.ToString();
                    GUI.user.RaiseMessage(msg);
                    if (YesNoDialog(string.Format(Properties.Resources.MainInstallOpenSettingsPrompt, msg),
                                    Properties.Resources.MainInstallOpenSettings,
                                    Properties.Resources.MainInstallNo))
                    {
                        // Launch the URL describing this host's throttling practices, if any
                        if (kraken.infoUrl != null)
                        {
                            Process.Start(new ProcessStartInfo()
                            {
                                UseShellExecute = true,
                                FileName        = kraken.infoUrl.ToString()
                            });
                        }
                        // Now pretend they clicked the menu option for the settings
                        Enabled = false;
                        settingsDialog.ShowDialog();
                        Enabled = true;
                    }
                    return;
                }
                catch (ModuleDownloadErrorsKraken kraken)
                {
                    GUI.user.RaiseMessage(kraken.ToString());
                    GUI.user.RaiseError(kraken.ToString());
                    return;
                }
                catch (DirectoryNotFoundKraken kraken)
                {
                    GUI.user.RaiseMessage("\r\n{0}", kraken.Message);
                    return;
                }
                catch (DllNotFoundException)
                {
                    if (GUI.user.RaiseYesNoDialog(Properties.Resources.MainInstallLibCurlMissing))
                    {
                        Process.Start(new ProcessStartInfo()
                        {
                            UseShellExecute = true,
                            FileName        = "https://github.com/KSP-CKAN/CKAN/wiki/libcurl"
                        });
                    }
                    throw;
                }
            }
        }
Ejemplo n.º 5
0
        private void _UpdateModsList(bool repo_updated, List <ModChange> mc)
        {
            log.Info("Updating the mod list");

            KspVersionCriteria versionCriteria = CurrentInstance.VersionCriteria();
            IRegistryQuerier   registry        = RegistryManager.Instance(CurrentInstance).registry;

            var gui_mods = new HashSet <GUIMod>();

            gui_mods.UnionWith(
                registry.InstalledModules
                .Select(instMod => new GUIMod(instMod, registry, versionCriteria))
                );
            gui_mods.UnionWith(
                registry.Available(versionCriteria)
                .Select(m => new GUIMod(m, registry, versionCriteria))
                );
            gui_mods.UnionWith(
                registry.Incompatible(versionCriteria)
                .Select(m => new GUIMod(m, registry, versionCriteria, true))
                );

            var old_modules = mainModList.Modules.ToDictionary(m => m, m => m.IsIncompatible);

            if (repo_updated)
            {
                foreach (GUIMod gm in gui_mods)
                {
                    bool oldIncompat;
                    if (old_modules.TryGetValue(gm, out oldIncompat))
                    {
                        // Found it; check if newly compatible
                        if (!gm.IsIncompatible && oldIncompat)
                        {
                            gm.IsNew = true;
                        }
                    }
                    else
                    {
                        // Newly indexed, show regardless of compatibility
                        gm.IsNew = true;
                    }
                }
            }
            else
            {
                //Copy the new mod flag from the old list.
                var old_new_mods = new HashSet <GUIMod>(old_modules.Keys.Where(m => m.IsNew));
                foreach (var gui_mod in gui_mods.Where(m => old_new_mods.Contains(m)))
                {
                    gui_mod.IsNew = true;
                }
            }

            // Update our mod listing. If we're doing a repo update, then we don't refresh
            // all (in case the user has selected changes they wish to apply).
            mainModList.ConstructModList(gui_mods.ToList(), mc, !repo_updated, configuration.HideEpochs, configuration.HideV);
            mainModList.Modules = new ReadOnlyCollection <GUIMod>(
                mainModList.full_list_of_mod_rows.Values.Select(row => row.Tag as GUIMod).ToList());

            //TODO Consider using smart enumeration pattern so stuff like this is easier
            FilterToolButton.DropDownItems[0].Text = String.Format("Compatible ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.Compatible));
            FilterToolButton.DropDownItems[1].Text = String.Format("Installed ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.Installed));
            FilterToolButton.DropDownItems[2].Text = String.Format("Upgradeable ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.InstalledUpdateAvailable));
            FilterToolButton.DropDownItems[3].Text = String.Format("Cached ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.Cached));
            FilterToolButton.DropDownItems[4].Text = String.Format("Newly compatible ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.NewInRepository));
            FilterToolButton.DropDownItems[5].Text = String.Format("Not installed ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.NotInstalled));
            FilterToolButton.DropDownItems[6].Text = String.Format("Incompatible ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.Incompatible));
            FilterToolButton.DropDownItems[7].Text = String.Format("All ({0})",
                                                                   mainModList.CountModsByFilter(GUIModFilter.All));
            var has_any_updates = gui_mods.Any(mod => mod.HasUpdate);

            UpdateAllToolButton.Enabled = has_any_updates;
            UpdateFilters(this);
        }