• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

HicServices / RDMP / 6237307473

19 Sep 2023 04:02PM UTC coverage: 57.015% (-0.4%) from 57.44%
6237307473

push

github

web-flow
Feature/rc4 (#1570)

* Syntax tidying
* Dependency updates
* Event handling singletons (ThrowImmediately and co)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: James A Sutherland <>
Co-authored-by: James Friel <jfriel001@dundee.ac.uk>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

10734 of 20259 branches covered (0.0%)

Branch coverage included in aggregate %.

5922 of 5922 new or added lines in 565 files covered. (100.0%)

30687 of 52390 relevant lines covered (58.57%)

7361.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Application/ResearchDataManagementPlatform/WindowManagement/WindowManager.cs
1
// Copyright (c) The University of Dundee 2018-2019
2
// This file is part of the Research Data Management Platform (RDMP).
3
// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4
// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5
// You should have received a copy of the GNU General Public License along with RDMP. If not, see <https://www.gnu.org/licenses/>.
6

7
using System;
8
using System.Collections.Generic;
9
using System.Linq;
10
using Rdmp.Core;
11
using Rdmp.Core.CommandExecution;
12
using Rdmp.Core.Curation.Data.Dashboarding;
13
using Rdmp.Core.Icons.IconProvision;
14
using Rdmp.Core.MapsDirectlyToDatabaseTable;
15
using Rdmp.Core.Repositories;
16
using Rdmp.Core.ReusableLibraryCode.Checks;
17
using Rdmp.UI;
18
using Rdmp.UI.Collections;
19
using Rdmp.UI.Refreshing;
20
using Rdmp.UI.SimpleDialogs;
21
using Rdmp.UI.SingleControlForms;
22
using Rdmp.UI.TestsAndSetup;
23
using Rdmp.UI.TestsAndSetup.ServicePropogation;
24
using Rdmp.UI.Theme;
25
using ResearchDataManagementPlatform.WindowManagement.ContentWindowTracking.Persistence;
26
using ResearchDataManagementPlatform.WindowManagement.Events;
27
using ResearchDataManagementPlatform.WindowManagement.HomePane;
28
using SixLabors.ImageSharp;
29
using SixLabors.ImageSharp.PixelFormats;
30
using WeifenLuo.WinFormsUI.Docking;
31
using Image = SixLabors.ImageSharp.Image;
32

33
namespace ResearchDataManagementPlatform.WindowManagement;
34

35
/// <summary>
36
/// Handles creating and tracking the main RDMPCollectionUIs tree views
37
/// </summary>
38
public class WindowManager
39
{
40
    private readonly Dictionary<RDMPCollection, PersistableToolboxDockContent> _visibleToolboxes = new();
×
41
    private readonly List<RDMPSingleControlTab> _trackedWindows = new();
×
42
    private readonly List<DockContent> _trackedAdhocWindows = new();
×
43

44
    public NavigationTrack<INavigation> Navigation { get; private set; }
×
45
    public event TabChangedHandler TabChanged;
46

47
    private readonly DockPanel _mainDockPanel;
48

49
    public RDMPMainForm MainForm { get; set; }
×
50

51
    /// <summary>
52
    /// The location finder for the Catalogue and optionally Data Export databases
53
    /// </summary>
54
    public IRDMPPlatformRepositoryServiceLocator RepositoryLocator { get; set; }
×
55

56
    public ActivateItems ActivateItems;
57
    private readonly WindowFactory _windowFactory;
58

59
    public event RDMPCollectionCreatedEventHandler CollectionCreated;
60

61
    private HomeUI _home;
62
    private DockContent _homeContent;
63

64
    public WindowManager(ITheme theme, RDMPMainForm mainForm, RefreshBus refreshBus, DockPanel mainDockPanel,
×
65
        IRDMPPlatformRepositoryServiceLocator repositoryLocator, ICheckNotifier globalErrorCheckNotifier)
×
66
    {
67
        _windowFactory = new WindowFactory(repositoryLocator, this);
×
68
        ActivateItems = new ActivateItems(theme, refreshBus, mainDockPanel, repositoryLocator, _windowFactory, this,
×
69
            globalErrorCheckNotifier);
×
70

71
        GlobalExceptionHandler.Instance.Handler = e =>
×
72
            globalErrorCheckNotifier.OnCheckPerformed(new CheckEventArgs(e.Message, CheckResult.Fail, e));
×
73

74
        _mainDockPanel = mainDockPanel;
×
75

76
        MainForm = mainForm;
×
77
        RepositoryLocator = repositoryLocator;
×
78

79
        Navigation = new NavigationTrack<INavigation>(c => c.IsAlive, c => c.Activate(ActivateItems));
×
80
        mainDockPanel.ActiveDocumentChanged += mainDockPanel_ActiveDocumentChanged;
×
81
        ActivateItems.Emphasise += RecordEmphasis;
×
82
    }
×
83

84
    private void RecordEmphasis(object sender, EmphasiseEventArgs args)
85
    {
86
        if (args.Request.ObjectToEmphasise is IMapsDirectlyToDatabaseTable m)
×
87
            Navigation.Append(new CollectionNavigation(m));
×
88
    }
×
89

90
    /// <summary>
91
    /// Creates a new instance of the given RDMPCollectionUI specified by the Enum collectionToCreate at the specified dock position
92
    /// </summary>
93
    /// <param name="collectionToCreate"></param>
94
    /// <param name="position"></param>
95
    /// <returns></returns>
96
    public PersistableToolboxDockContent Create(RDMPCollection collectionToCreate,
97
        DockState position = DockState.DockLeft)
98
    {
99
        PersistableToolboxDockContent toReturn;
100
        RDMPCollectionUI collection;
101

102
        switch (collectionToCreate)
103
        {
104
            case RDMPCollection.Catalogue:
105
                collection = new CatalogueCollectionUI();
×
106
                toReturn = Show(RDMPCollection.Catalogue, collection, "Catalogues",
×
107
                    Image.Load<Rgba32>(CatalogueIcons.Catalogue));
×
108
                break;
×
109

110
            case RDMPCollection.DataLoad:
111
                collection = new LoadMetadataCollectionUI();
×
112
                toReturn = Show(RDMPCollection.DataLoad, collection, "Load Configurations",
×
113
                    Image.Load<Rgba32>(CatalogueIcons.LoadMetadata));
×
114
                break;
×
115

116
            case RDMPCollection.Tables:
117
                collection = new TableInfoCollectionUI();
×
118
                toReturn = Show(RDMPCollection.Tables, collection, "Tables",
×
119
                    Image.Load<Rgba32>(CatalogueIcons.TableInfo));
×
120
                break;
×
121

122
            case RDMPCollection.DataExport:
123
                if (RepositoryLocator.DataExportRepository == null)
×
124
                {
125
                    WideMessageBox.Show("Data export database unavailable",
×
126
                        "Cannot create DataExport Toolbox because DataExportRepository has not been set/created yet");
×
127
                    return null;
×
128
                }
129

130
                collection = new DataExportCollectionUI();
×
131
                toReturn = Show(RDMPCollection.DataExport, collection, "Projects",
×
132
                    Image.Load<Rgba32>(CatalogueIcons.Project));
×
133
                break;
×
134

135
            case RDMPCollection.Cohort:
136
                collection = new CohortIdentificationCollectionUI();
×
137
                toReturn = Show(RDMPCollection.Cohort, collection, "Cohort Builder",
×
138
                    Image.Load<Rgba32>(CatalogueIcons.CohortIdentificationConfiguration));
×
139
                break;
×
140
            case RDMPCollection.SavedCohorts:
141
                collection = new SavedCohortsCollectionUI();
×
142
                toReturn = Show(RDMPCollection.SavedCohorts, collection, "Saved Cohorts",
×
143
                    Image.Load<Rgba32>(CatalogueIcons.AllCohortsNode));
×
144
                break;
×
145
            case RDMPCollection.Favourites:
146
                collection = new FavouritesCollectionUI();
×
147
                toReturn = Show(RDMPCollection.Favourites, collection, "Favourites",
×
148
                    Image.Load<Rgba32>(CatalogueIcons.Favourite));
×
149
                break;
×
150

151
            default: throw new ArgumentOutOfRangeException(nameof(collectionToCreate));
×
152
        }
153

154
        toReturn.DockState = position;
×
155

156
        collection.SetItemActivator(ActivateItems);
×
157

158
        CollectionCreated?.Invoke(this, new RDMPCollectionCreatedEventHandlerArgs(collectionToCreate));
×
159

160
        collection.CommonTreeFunctionality.Tree.SelectionChanged += (s, e) =>
×
161
        {
×
162
            if (collection.CommonTreeFunctionality.Tree.SelectedObject is IMapsDirectlyToDatabaseTable im)
×
163
                Navigation.Append(new CollectionNavigation(im));
×
164
        };
×
165

166
        return toReturn;
×
167
    }
168

169

170
    private PersistableToolboxDockContent Show(RDMPCollection collection, RDMPCollectionUI control, string label,
171
        Image<Rgba32> image)
172
    {
173
        var content =
×
174
            _windowFactory.Create(ActivateItems, control, label, image,
×
175
                collection); //these are collections so are not tracked with a window tracker.
×
176
        content.Closed += (s, e) => content_Closed(collection);
×
177

178
        _visibleToolboxes.Add(collection, content);
×
179
        content.Show(_mainDockPanel, DockState.DockLeft);
×
180

181
        return content;
×
182
    }
183

184
    private void content_Closed(RDMPCollection collection)
185
    {
186
        //no longer visible
187
        _visibleToolboxes.Remove(collection);
×
188
    }
×
189

190
    /// <summary>
191
    /// Closes the specified RDMPCollectionUI (must be open - use IsVisible to check this)
192
    /// </summary>
193
    /// <param name="collection"></param>
194
    public void Destroy(RDMPCollection collection)
195
    {
196
        _visibleToolboxes[collection].Close();
×
197
    }
×
198

199
    /// <summary>
200
    /// Brings the specified collection to the front (must already be visible)
201
    /// </summary>
202
    /// <param name="collection"></param>
203
    public void Pop(RDMPCollection collection)
204
    {
205
        if (!_visibleToolboxes.TryGetValue(collection, out var content)) return;
×
206
        content.DockState = content.DockState switch
×
207
        {
×
208
            DockState.DockLeftAutoHide => DockState.DockLeft,
×
209
            DockState.DockRightAutoHide => DockState.DockRight,
×
210
            DockState.DockTopAutoHide => DockState.DockTop,
×
211
            DockState.DockBottomAutoHide => DockState.DockBottom,
×
212
            _ => _visibleToolboxes[collection].DockState
×
213
        };
×
214

215
        content.Activate();
×
216
    }
×
217

218
    /// <summary>
219
    /// Returns true if the corresponding RDMPCollectionUI is open (even if it is buried under other windows).
220
    /// </summary>
221
    /// <param name="collection"></param>
222
    /// <returns></returns>
223
    public bool IsVisible(RDMPCollection collection) => _visibleToolboxes.ContainsKey(collection);
×
224

225
    public RDMPCollection GetFocusedCollection()
226
    {
227
        return _visibleToolboxes.Where(static t => t.Value.ContainsFocus).Select(static t => t.Key).FirstOrDefault();
×
228
    }
229

230
    internal void OnFormClosing(System.Windows.Forms.FormClosingEventArgs e)
231
    {
232
        foreach (var c in _trackedWindows)
×
233
            if (c.Control is IConsultableBeforeClosing consult)
×
234
            {
235
                consult.ConsultAboutClosing(this, e);
×
236

237
                if (e.Cancel) return;
×
238
            }
239
    }
×
240

241

242
    /// <summary>
243
    /// Attempts to ensure that a compatible RDMPCollectionUI is made visible for the supplied object which must be one of the expected root Tree types of
244
    /// an RDMPCollectionUI.  For example Project is the a root object of DataExportCollectionUI.  If a matching collection is already visible or no collection
245
    /// supports the supplied object as a root object then nothing will happen.  Otherwise the coresponding collection will be shown
246
    /// </summary>
247
    /// <param name="root"></param>
248
    public void ShowCollectionWhichSupportsRootObjectType(object root)
249
    {
250
        var collection = GetCollectionForRootObject(root);
×
251

252
        if (collection == RDMPCollection.None)
×
253
            return;
×
254

255
        if (IsVisible(collection))
×
256
        {
257
            Pop(collection);
×
258
            return;
×
259
        }
260

261
        Create(collection);
×
262
    }
×
263

264
    public RDMPCollection GetCollectionForRootObject(object root)
265
    {
266
        if (FavouritesCollectionUI.IsRootObject(ActivateItems, root))
×
267
            return RDMPCollection.Favourites;
×
268

269
        if (CatalogueCollectionUI.IsRootObject(root))
×
270
            return RDMPCollection.Catalogue;
×
271

272
        if (CohortIdentificationCollectionUI.IsRootObject(root))
×
273
            return RDMPCollection.Cohort;
×
274

275
        if (DataExportCollectionUI.IsRootObject(root))
×
276
            return RDMPCollection.DataExport;
×
277

278
        if (LoadMetadataCollectionUI.IsRootObject(root))
×
279
            return RDMPCollection.DataLoad;
×
280

281
        if (TableInfoCollectionUI.IsRootObject(root))
×
282
            return RDMPCollection.Tables;
×
283

284
        return SavedCohortsCollectionUI.IsRootObject(root) ? RDMPCollection.SavedCohorts : RDMPCollection.None;
×
285
    }
286

287
    /// <summary>
288
    /// Displays the HomeUI tab or brings it to the front if it is already open
289
    /// </summary>
290
    public void PopHome()
291
    {
292
        if (_home == null)
×
293
        {
294
            _home = new HomeUI(ActivateItems);
×
295

296
            _homeContent = _windowFactory.Create(ActivateItems, _home, "Home",
×
297
                Image.Load<Rgba32>(FamFamFamIcons.application_home));
×
298
            _homeContent.Closed += (s, e) => _home = null;
×
299
            _homeContent.Show(_mainDockPanel, DockState.Document);
×
300
        }
301
        else
302
        {
303
            _homeContent.Activate();
×
304
        }
305
    }
×
306

307
    /// <summary>
308
    /// Closes all currently open RDMPCollectionUI tabs
309
    /// </summary>
310
    public void CloseAllToolboxes()
311
    {
312
        foreach (RDMPCollection collection in Enum.GetValues(typeof(RDMPCollection)))
×
313
            if (IsVisible(collection))
×
314
                Destroy(collection);
×
315
    }
×
316

317
    /// <summary>
318
    /// Closes all content window tabs (i.e. anything that isn't an RDMPCollectionUI tab - see CloseAllToolboxes)
319
    /// </summary>
320
    public void CloseAllWindows()
321
    {
322
        CloseAllWindows(null);
×
323
    }
×
324

325

326
    /// <summary>
327
    /// Closes all Tracked windows
328
    /// </summary>
329
    /// <param name="tab"></param>
330
    public void CloseAllWindows(RDMPSingleControlTab tab)
331
    {
332
        if (tab != null)
×
333
        {
334
            CloseAllButThis(tab);
×
335
            tab.Close();
×
336
        }
337
        else
338
        {
339
            foreach (var trackedWindow in _trackedWindows.ToArray())
×
340
                trackedWindow.Close();
×
341

342
            foreach (var adhoc in _trackedAdhocWindows.ToArray())
×
343
                adhoc.Close();
×
344
        }
345
    }
×
346

347
    private void mainDockPanel_ActiveDocumentChanged(object sender, EventArgs e)
348
    {
349
        var newTab = (DockContent)_mainDockPanel.ActiveDocument;
×
350

351
        if (newTab?.ParentForm != null)
×
352
        {
353
            Navigation.Append(new TabNavigation(newTab));
×
354
            newTab.ParentForm.Text = $"{newTab.TabText} - RDMP";
×
355
        }
356

357

358
        TabChanged?.Invoke(sender, newTab);
×
359
    }
×
360

361

362
    /// <summary>
363
    /// Records the fact that a new single object editing tab has been opened.  .
364
    /// </summary>
365
    /// <exception cref="ArgumentOutOfRangeException">Thrown if another instance of the Control Type is already active with the same DatabaseObject</exception>
366
    /// <param name="window"></param>
367
    public void AddWindow(RDMPSingleControlTab window)
368
    {
369
        if (window is PersistableSingleDatabaseObjectDockContent singleObjectUI)
×
370
            if (AlreadyActive(singleObjectUI.Control.GetType(), singleObjectUI.DatabaseObject))
×
371
                throw new ArgumentOutOfRangeException(
×
372
                    $"Cannot create another window for object {singleObjectUI.DatabaseObject} of type {singleObjectUI.Control.GetType()} because there is already a window active for that object/window type");
×
373

374
        _trackedWindows.Add(window);
×
375

376
        window.FormClosed += (s, e) => Remove(window);
×
377
    }
×
378

379
    /// <summary>
380
    /// Records the fact that a new impromptu/adhoc tab has been shown.  These windows are not checked for duplication.
381
    /// </summary>
382
    /// <param name="adhocWindow"></param>
383
    public void AddAdhocWindow(DockContent adhocWindow)
384
    {
385
        _trackedAdhocWindows.Add(adhocWindow);
×
386
        adhocWindow.FormClosed += (s, e) => _trackedAdhocWindows.Remove(adhocWindow);
×
387
    }
×
388

389
    private void Remove(RDMPSingleControlTab window)
390
    {
391
        _trackedWindows.Remove(window);
×
392
    }
×
393

394
    public PersistableSingleDatabaseObjectDockContent GetActiveWindowIfAnyFor(Type windowType,
395
        IMapsDirectlyToDatabaseTable databaseObject)
396
    {
397
        return _trackedWindows.OfType<PersistableSingleDatabaseObjectDockContent>().SingleOrDefault(t =>
×
398
            t.Control.GetType() == windowType && t.DatabaseObject.Equals(databaseObject));
×
399
    }
400

401
    public PersistableObjectCollectionDockContent GetActiveWindowIfAnyFor(Type windowType,
402
        IPersistableObjectCollection collection)
403
    {
404
        return _trackedWindows.OfType<PersistableObjectCollectionDockContent>()
×
405
            .SingleOrDefault(t => t.Control.GetType() == windowType && t.Collection.Equals(collection));
×
406
    }
407

408
    /// <summary>
409
    /// Check whether a given RDMPSingleControlTab is already showing with the given DatabaseObject (e.g. is user currently editing Catalogue bob in CatalogueUI)
410
    /// </summary>
411
    /// <exception cref="ArgumentException"></exception>
412
    /// <param name="windowType">A Type derrived from RDMPSingleControlTab</param>
413
    /// <param name="databaseObject">An instance of an object which matches the windowType</param>
414
    /// <returns></returns>
415
    public bool AlreadyActive(Type windowType, IMapsDirectlyToDatabaseTable databaseObject)
416
    {
417
        return !typeof(IRDMPSingleDatabaseObjectControl).IsAssignableFrom(windowType)
×
418
            ? throw new ArgumentException("windowType must be a Type derrived from RDMPSingleControlTab")
×
419
            : _trackedWindows.OfType<PersistableSingleDatabaseObjectDockContent>().Any(t =>
×
420
                t.Control.GetType() == windowType && t.DatabaseObject.Equals(databaseObject));
×
421
    }
422

423
    /// <summary>
424
    /// Closes all Tracked windows except the specified tab
425
    /// </summary>
426
    public void CloseAllButThis(DockContent content)
427
    {
428
        var trackedWindowsToClose = _trackedWindows.ToArray().Where(t => t != content);
×
429

430
        foreach (var trackedWindow in trackedWindowsToClose)
×
431
            CloseWindowIfInSameScope(trackedWindow, content);
×
432

433
        foreach (var adhoc in _trackedAdhocWindows.ToArray().Where(t => t != content))
×
434
            CloseWindowIfInSameScope(adhoc, content);
×
435
    }
×
436

437
    private static void CloseWindowIfInSameScope(DockContent toClose, DockContent tabInSameScopeOrNull)
438
    {
439
        var parent = tabInSameScopeOrNull?.Parent;
×
440

441
        if (toClose != null && (parent == null || toClose.Parent == parent))
×
442
            toClose.Close();
×
443
    }
×
444

445
    public void CloseCurrentTab()
446
    {
447
        //nothing to close
448
        if (Navigation.Current == null)
×
449
            return;
×
450

451
        Navigation.Suspend();
×
452
        try
453
        {
454
            Navigation.Current.Close();
×
455

456
            Navigation.Current?.Activate(ActivateItems);
×
457
        }
×
458
        finally
459
        {
460
            Navigation.Resume();
×
461
            if (_mainDockPanel.ActiveDocument is DockContent dc)
×
462
                Navigation.Append(new TabNavigation(dc));
×
463
        }
×
464
    }
×
465

466
    /// <summary>
467
    /// Returns all tracked tabs currently open of the Type <typeparamref name="T"/>
468
    /// </summary>
469
    /// <typeparam name="T"></typeparam>
470
    /// <returns></returns>
471
    public IEnumerable<T> GetAllWindows<T>()
472
    {
473
        return _trackedWindows.OfType<RDMPSingleControlTab>().Select(t => t.Control).OfType<T>();
×
474
    }
475
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc