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

HicServices / RDMP / 7104759680

05 Dec 2023 06:06PM UTC coverage: 56.383% (+0.05%) from 56.338%
7104759680

push

github

web-flow
Feature/rdmp-113 datasets (#1682)

* Dataset PR

10585 of 20345 branches covered (0.0%)

Branch coverage included in aggregate %.

102 of 300 new or added lines in 18 files covered. (34.0%)

3 existing lines in 3 files now uncovered.

30488 of 52501 relevant lines covered (58.07%)

7275.97 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
            case RDMPCollection.Datasets:
NEW
152
                collection = new DatasetsCollectionUI();
×
NEW
153
                toReturn = Show(RDMPCollection.Datasets, collection, "Datasets",
×
NEW
154
                    Image.Load<Rgba32>(CatalogueIcons.Dataset));
×
NEW
155
                break;
×
156

UNCOV
157
            default: throw new ArgumentOutOfRangeException(nameof(collectionToCreate));
×
158
        }
159

160
        toReturn.DockState = position;
×
161

162
        collection.SetItemActivator(ActivateItems);
×
163

164
        CollectionCreated?.Invoke(this, new RDMPCollectionCreatedEventHandlerArgs(collectionToCreate));
×
165

166
        collection.CommonTreeFunctionality.Tree.SelectionChanged += (s, e) =>
×
167
        {
×
168
            if (collection.CommonTreeFunctionality.Tree.SelectedObject is IMapsDirectlyToDatabaseTable im)
×
169
                Navigation.Append(new CollectionNavigation(im));
×
170
        };
×
171

172
        return toReturn;
×
173
    }
174

175

176
    private PersistableToolboxDockContent Show(RDMPCollection collection, RDMPCollectionUI control, string label,
177
        Image<Rgba32> image)
178
    {
179
        var content =
×
180
            _windowFactory.Create(ActivateItems, control, label, image,
×
181
                collection); //these are collections so are not tracked with a window tracker.
×
182
        content.Closed += (s, e) => content_Closed(collection);
×
183

184
        _visibleToolboxes.Add(collection, content);
×
185
        content.Show(_mainDockPanel, DockState.DockLeft);
×
186

187
        return content;
×
188
    }
189

190
    private void content_Closed(RDMPCollection collection)
191
    {
192
        //no longer visible
193
        _visibleToolboxes.Remove(collection);
×
194
    }
×
195

196
    /// <summary>
197
    /// Closes the specified RDMPCollectionUI (must be open - use IsVisible to check this)
198
    /// </summary>
199
    /// <param name="collection"></param>
200
    public void Destroy(RDMPCollection collection)
201
    {
202
        _visibleToolboxes[collection].Close();
×
203
    }
×
204

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

221
        content.Activate();
×
222
    }
×
223

224
    /// <summary>
225
    /// Returns true if the corresponding RDMPCollectionUI is open (even if it is buried under other windows).
226
    /// </summary>
227
    /// <param name="collection"></param>
228
    /// <returns></returns>
229
    public bool IsVisible(RDMPCollection collection) => _visibleToolboxes.ContainsKey(collection);
×
230

231
    public RDMPCollection GetFocusedCollection()
232
    {
233
        return _visibleToolboxes.Where(static t => t.Value.ContainsFocus).Select(static t => t.Key).FirstOrDefault();
×
234
    }
235

236
    internal void OnFormClosing(System.Windows.Forms.FormClosingEventArgs e)
237
    {
238
        foreach (var c in _trackedWindows)
×
239
            if (c.Control is IConsultableBeforeClosing consult)
×
240
            {
241
                consult.ConsultAboutClosing(this, e);
×
242

243
                if (e.Cancel) return;
×
244
            }
245
    }
×
246

247

248
    /// <summary>
249
    /// 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
250
    /// an RDMPCollectionUI.  For example Project is the a root object of DataExportCollectionUI.  If a matching collection is already visible or no collection
251
    /// supports the supplied object as a root object then nothing will happen.  Otherwise the coresponding collection will be shown
252
    /// </summary>
253
    /// <param name="root"></param>
254
    public void ShowCollectionWhichSupportsRootObjectType(object root)
255
    {
256
        var collection = GetCollectionForRootObject(root);
×
257

258
        if (collection == RDMPCollection.None)
×
259
            return;
×
260

261
        if (IsVisible(collection))
×
262
        {
263
            Pop(collection);
×
264
            return;
×
265
        }
266

267
        Create(collection);
×
268
    }
×
269

270
    public RDMPCollection GetCollectionForRootObject(object root)
271
    {
272
        if (FavouritesCollectionUI.IsRootObject(ActivateItems, root))
×
273
            return RDMPCollection.Favourites;
×
274

275
        if (CatalogueCollectionUI.IsRootObject(root))
×
276
            return RDMPCollection.Catalogue;
×
277

278
        if (CohortIdentificationCollectionUI.IsRootObject(root))
×
279
            return RDMPCollection.Cohort;
×
280

281
        if (DataExportCollectionUI.IsRootObject(root))
×
282
            return RDMPCollection.DataExport;
×
283

284
        if (LoadMetadataCollectionUI.IsRootObject(root))
×
285
            return RDMPCollection.DataLoad;
×
286

287
        if (TableInfoCollectionUI.IsRootObject(root))
×
288
            return RDMPCollection.Tables;
×
289

290
        return SavedCohortsCollectionUI.IsRootObject(root) ? RDMPCollection.SavedCohorts : RDMPCollection.None;
×
291
    }
292

293
    /// <summary>
294
    /// Displays the HomeUI tab or brings it to the front if it is already open
295
    /// </summary>
296
    public void PopHome()
297
    {
298
        if (_home == null)
×
299
        {
300
            _home = new HomeUI(ActivateItems);
×
301

302
            _homeContent = _windowFactory.Create(ActivateItems, _home, "Home",
×
303
                Image.Load<Rgba32>(FamFamFamIcons.application_home));
×
304
            _homeContent.Closed += (s, e) => _home = null;
×
305
            _homeContent.Show(_mainDockPanel, DockState.Document);
×
306
        }
307
        else
308
        {
309
            _homeContent.Activate();
×
310
        }
311
    }
×
312

313
    /// <summary>
314
    /// Closes all currently open RDMPCollectionUI tabs
315
    /// </summary>
316
    public void CloseAllToolboxes()
317
    {
318
        foreach (RDMPCollection collection in Enum.GetValues(typeof(RDMPCollection)))
×
319
            if (IsVisible(collection))
×
320
                Destroy(collection);
×
321
    }
×
322

323
    /// <summary>
324
    /// Closes all content window tabs (i.e. anything that isn't an RDMPCollectionUI tab - see CloseAllToolboxes)
325
    /// </summary>
326
    public void CloseAllWindows()
327
    {
328
        CloseAllWindows(null);
×
329
    }
×
330

331

332
    /// <summary>
333
    /// Closes all Tracked windows
334
    /// </summary>
335
    /// <param name="tab"></param>
336
    public void CloseAllWindows(RDMPSingleControlTab tab)
337
    {
338
        if (tab != null)
×
339
        {
340
            CloseAllButThis(tab);
×
341
            tab.Close();
×
342
        }
343
        else
344
        {
345
            foreach (var trackedWindow in _trackedWindows.ToArray())
×
346
                trackedWindow.Close();
×
347

348
            foreach (var adhoc in _trackedAdhocWindows.ToArray())
×
349
                adhoc.Close();
×
350
        }
351
    }
×
352

353
    private void mainDockPanel_ActiveDocumentChanged(object sender, EventArgs e)
354
    {
355
        var newTab = (DockContent)_mainDockPanel.ActiveDocument;
×
356

357
        if (newTab?.ParentForm != null)
×
358
        {
359
            Navigation.Append(new TabNavigation(newTab));
×
360
            newTab.ParentForm.Text = $"{newTab.TabText} - RDMP";
×
361
        }
362

363

364
        TabChanged?.Invoke(sender, newTab);
×
365
    }
×
366

367

368
    /// <summary>
369
    /// Records the fact that a new single object editing tab has been opened.  .
370
    /// </summary>
371
    /// <exception cref="ArgumentOutOfRangeException">Thrown if another instance of the Control Type is already active with the same DatabaseObject</exception>
372
    /// <param name="window"></param>
373
    public void AddWindow(RDMPSingleControlTab window)
374
    {
375
        if (window is PersistableSingleDatabaseObjectDockContent singleObjectUI)
×
376
            if (AlreadyActive(singleObjectUI.Control.GetType(), singleObjectUI.DatabaseObject))
×
377
                throw new ArgumentOutOfRangeException(
×
378
                    $"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");
×
379

380
        _trackedWindows.Add(window);
×
381

382
        window.FormClosed += (s, e) => Remove(window);
×
383
    }
×
384

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

395
    private void Remove(RDMPSingleControlTab window)
396
    {
397
        _trackedWindows.Remove(window);
×
398
    }
×
399

400
    public PersistableSingleDatabaseObjectDockContent GetActiveWindowIfAnyFor(Type windowType,
401
        IMapsDirectlyToDatabaseTable databaseObject)
402
    {
403
        return _trackedWindows.OfType<PersistableSingleDatabaseObjectDockContent>().SingleOrDefault(t =>
×
404
            t.Control.GetType() == windowType && t.DatabaseObject.Equals(databaseObject));
×
405
    }
406

407
    public PersistableObjectCollectionDockContent GetActiveWindowIfAnyFor(Type windowType,
408
        IPersistableObjectCollection collection)
409
    {
410
        return _trackedWindows.OfType<PersistableObjectCollectionDockContent>()
×
411
            .SingleOrDefault(t => t.Control.GetType() == windowType && t.Collection.Equals(collection));
×
412
    }
413

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

429
    /// <summary>
430
    /// Closes all Tracked windows except the specified tab
431
    /// </summary>
432
    public void CloseAllButThis(DockContent content)
433
    {
434
        var trackedWindowsToClose = _trackedWindows.ToArray().Where(t => t != content);
×
435

436
        foreach (var trackedWindow in trackedWindowsToClose)
×
437
            CloseWindowIfInSameScope(trackedWindow, content);
×
438

439
        foreach (var adhoc in _trackedAdhocWindows.ToArray().Where(t => t != content))
×
440
            CloseWindowIfInSameScope(adhoc, content);
×
441
    }
×
442

443
    private static void CloseWindowIfInSameScope(DockContent toClose, DockContent tabInSameScopeOrNull)
444
    {
445
        var parent = tabInSameScopeOrNull?.Parent;
×
446

447
        if (toClose != null && (parent == null || toClose.Parent == parent))
×
448
            toClose.Close();
×
449
    }
×
450

451
    public void CloseCurrentTab()
452
    {
453
        //nothing to close
454
        if (Navigation.Current == null)
×
455
            return;
×
456

457
        Navigation.Suspend();
×
458
        try
459
        {
460
            Navigation.Current.Close();
×
461

462
            Navigation.Current?.Activate(ActivateItems);
×
463
        }
×
464
        finally
465
        {
466
            Navigation.Resume();
×
467
            if (_mainDockPanel.ActiveDocument is DockContent dc)
×
468
                Navigation.Append(new TabNavigation(dc));
×
469
        }
×
470
    }
×
471

472
    /// <summary>
473
    /// Returns all tracked tabs currently open of the Type <typeparamref name="T"/>
474
    /// </summary>
475
    /// <typeparam name="T"></typeparam>
476
    /// <returns></returns>
477
    public IEnumerable<T> GetAllWindows<T>()
478
    {
479
        return _trackedWindows.OfType<RDMPSingleControlTab>().Select(t => t.Control).OfType<T>();
×
480
    }
481
}
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