Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Clients.Hive.Administrator/3.3/Views/ProjectsView.cs @ 16773

Last change on this file since 16773 was 16565, checked in by gkronber, 6 years ago

#2520: merged changes from PersistenceOverhaul branch (r16451:16564) into trunk

File size: 20.7 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.ComponentModel;
25using System.Drawing;
26using System.Linq;
27using System.Windows.Forms;
28using HeuristicLab.Clients.Access;
29using HeuristicLab.Clients.Hive.Views;
30using HeuristicLab.Collections;
31using HeuristicLab.Common.Resources;
32using HeuristicLab.Core;
33using HeuristicLab.Core.Views;
34using HeuristicLab.MainForm;
35
36namespace HeuristicLab.Clients.Hive.Administrator.Views {
37  [View("Projects View")]
38  [Content(typeof(IItemList<Project>), false)]
39  public partial class ProjectsView : ItemView, IDisposable {
40    private const int greenFlagImageIndex = 0;
41    private const int redFlagImageIndex = 1;
42    private const string SELECTED_TAG = ""; // " [selected]";
43    private const string NOT_STORED_TAG = "*"; // " [not stored]";
44    private const string CHANGES_NOT_STORED_TAG = "*"; // " [changes not stored]";
45    private const string INACTIVE_TAG = " [inactive]";
46
47    private readonly Color selectedBackColor = Color.DodgerBlue;
48    private readonly Color selectedForeColor = Color.White;
49    private readonly Color grayTextColor = SystemColors.GrayText;
50
51    private Project selectedProject = null;
52    public Project SelectedProject {
53      get { return selectedProject; }
54      set { if (selectedProject != value) ChangeSelectedProject(value); }
55    }
56
57    private readonly object locker = new object();
58    private bool refreshingInternal = false;
59    private bool refreshingExternal = false;
60
61    public new IItemList<Project> Content {
62      get { return (IItemList<Project>)base.Content; }
63      set { base.Content = value; }
64    }
65
66    public ProjectsView() {
67      InitializeComponent();
68
69      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagGreen);
70      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagRed);
71
72      HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing;
73      HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed;
74    }
75
76    #region Overrides
77    protected override void RegisterContentEvents() {
78      base.RegisterContentEvents();
79      Content.ItemsAdded += Content_ItemsAdded;
80      Content.ItemsRemoved += Content_ItemsRemoved;
81    }
82
83    protected override void DeregisterContentEvents() {
84      Content.ItemsRemoved -= Content_ItemsRemoved;
85      Content.ItemsAdded -= Content_ItemsAdded;
86      base.DeregisterContentEvents();
87    }
88
89    protected override void OnContentChanged() {
90      base.OnContentChanged();
91      if (Content == null) {
92        projectsTreeView.Nodes.Clear();
93        projectView.Content = null;
94        projectPermissionsView.Content = null;
95        projectResourcesView.Content = null;
96        projectJobsView.Content = null;
97        selectedProject = null;
98      } else {
99        BuildProjectTree(Content);
100      }
101      SetEnabledStateOfControls();
102    }
103
104    protected override void SetEnabledStateOfControls() {
105      base.SetEnabledStateOfControls();
106
107      bool locked = Content == null || Locked || ReadOnly;
108      bool parentOwner = selectedProject != null && HiveAdminClient.Instance.CheckOwnershipOfParentProject(selectedProject, UserInformation.Instance.User.Id);
109      bool selectedProjectDisabled = selectedProject == null
110                                     || selectedProject != null && selectedProject.Id == Guid.Empty;
111
112      bool selectedProjectHasJobs =
113        !selectedProjectDisabled && HiveAdminClient.Instance.Jobs.ContainsKey(selectedProject.Id)
114                                 && HiveAdminClient.Instance.Jobs[selectedProject.Id] != null
115                                 && HiveAdminClient.Instance.Jobs[selectedProject.Id].Any();
116
117      bool addLocked = locked
118                       || (selectedProject == null && !IsAdmin())
119                       || (selectedProject != null && selectedProject.Id == Guid.Empty)
120                       || (selectedProject != null && !IsAdmin() && !parentOwner && selectedProject.OwnerUserId != UserInformation.Instance.User.Id)
121                       || (selectedProject != null && (DateTime.Now < selectedProject.StartDate || DateTime.Now > selectedProject.EndDate));
122
123      bool deleteLocked = locked
124                          || !Content.Any()
125                          || selectedProject == null
126                          || Content.Any(x => x.ParentProjectId == selectedProject.Id)
127                          || selectedProjectHasJobs
128                          || (!IsAdmin() && !parentOwner);
129
130      bool saveLocked = locked
131                        || !Content.Any()
132                        || selectedProject == null
133                        || (!IsAdmin() && !parentOwner && selectedProject.OwnerUserId != UserInformation.Instance.User.Id);
134
135
136      addButton.Enabled = !addLocked;
137      removeButton.Enabled = !deleteLocked;
138      saveProjectButton.Enabled = !saveLocked;
139
140      projectView.Enabled = !locked;
141      projectPermissionsView.Enabled = !locked && !selectedProjectDisabled;
142      projectResourcesView.Enabled = !locked && !selectedProjectDisabled;
143      projectJobsView.Enabled = !locked && !selectedProjectDisabled;
144
145      projectView.Locked = locked;
146      projectPermissionsView.Locked = locked || selectedProjectDisabled;
147      projectResourcesView.Locked = locked || selectedProjectDisabled;
148      projectJobsView.Locked = locked || selectedProjectDisabled;
149    }
150    #endregion
151
152    #region Event Handlers
153    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
154      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsAdded, sender, e);
155      else {
156        OnContentChanged();
157      }
158    }
159
160    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
161      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsRemoved, sender, e);
162      else {
163        OnContentChanged();
164      }
165    }
166
167    private void ProjectViewContent_PropertyChanged(object sender, PropertyChangedEventArgs e) {
168      if (InvokeRequired) Invoke((Action<object, PropertyChangedEventArgs>)ProjectViewContent_PropertyChanged, sender, e);
169      else {
170        OnContentChanged();
171      }
172    }
173
174    private void HiveAdminClient_Instance_Refreshing(object sender, EventArgs e) {
175      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshing, sender, e);
176      else {
177        lock (locker) {
178          if (refreshingExternal) return;
179          if (!refreshingInternal) refreshingExternal = true;
180        }
181
182        Progress.Show(this, "Refreshing ...", ProgressMode.Indeterminate);
183        SetEnabledStateOfControls();
184      }
185    }
186
187    private void HiveAdminClient_Instance_Refreshed(object sender, EventArgs e) {
188      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshed, sender, e);
189      else {
190        if (refreshingExternal) refreshingExternal = false;
191        Content = HiveAdminClient.Instance.Projects;
192
193        Progress.Hide(this);
194        SetEnabledStateOfControls();
195      }
196    }
197
198    private async void ProjectsView_Load(object sender, EventArgs e) {
199      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
200        action: () => UpdateProjects());
201    }
202
203    private void ProjectsView_Disposed(object sender, EventArgs e) {
204      HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed;
205      HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing;
206    }
207
208    private async void refreshButton_Click(object sender, EventArgs e) {
209      lock (locker) {
210        if (!refreshButton.Enabled) return;
211        refreshButton.Enabled = false;
212      }
213
214      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
215        action: () => UpdateProjects(),
216        finallyCallback: () => {
217          refreshButton.Enabled = true;
218        });
219    }
220
221    private void addButton_Click(object sender, EventArgs e) {
222
223      if (selectedProject == null && !IsAdmin()) {
224        MessageBox.Show(
225          "You are not allowed to add a root project - please select a parent project.",
226          "HeuristicLab Hive Administrator",
227          MessageBoxButtons.OK,
228          MessageBoxIcon.Information);
229        return;
230      }
231
232      if (selectedProject != null && selectedProject.Id == Guid.Empty) {
233        MessageBox.Show(
234          "You cannot add a project to a not yet stored project.",
235          "HeuristicLab Hive Administrator",
236          MessageBoxButtons.OK,
237          MessageBoxIcon.Information);
238        return;
239      }
240
241      var project = new Project {
242        Name = "New Project",
243        OwnerUserId = UserInformation.Instance.User.Id,
244      };
245      if (selectedProject != null) {
246        project.ParentProjectId = selectedProject.Id;
247        project.EndDate = selectedProject.EndDate;
248      }
249
250      SelectedProject = project;
251      Content.Add(project);
252    }
253
254    private async void removeButton_Click(object sender, EventArgs e) {
255      if (selectedProject == null) return;
256
257      lock (locker) {
258        // for details go to ChangeSelectedProject(..)
259        if (!removeButton.Enabled) return;
260        removeButton.Enabled = false;
261      }
262
263      var result = MessageBox.Show(
264        "Do you really want to delete " + selectedProject.Name + "?",
265        "HeuristicLab Hive Administrator",
266        MessageBoxButtons.YesNo,
267        MessageBoxIcon.Question);
268
269      if (result == DialogResult.Yes) {
270        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
271          action: () => {
272            RemoveProject(selectedProject);
273          });
274      }
275      SetEnabledStateOfControls();
276    }
277
278    private async void saveProjectButton_Click(object sender, EventArgs e) {
279      lock (locker) {
280        if (!saveProjectButton.Enabled) return;
281        saveProjectButton.Enabled = false;
282      }
283
284      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
285        action: () => {
286          //if (selectedProject != null && selectedProject.Id == Guid.Empty)
287          //  SelectedProject = HiveAdminClient.Instance.GetAvailableProjectAncestors(selectedProject.Id).LastOrDefault();
288          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
289          foreach (var project in projectsToSave)
290            project.Store();
291
292          UpdateProjects();
293        },
294        finallyCallback: () => saveProjectButton.Enabled = true);
295
296      OnContentChanged();
297    }
298
299    private void projectsTreeView_MouseDown(object sender, MouseEventArgs e) {
300      var node = projectsTreeView.GetNodeAt(e.Location);
301      if (node == null) return;
302      var p = (Project)node.Tag;
303      if (!HiveAdminClient.Instance.DisabledParentProjects.Contains(p)) ChangeSelectedProjectNode(node);
304    }
305
306    private void projectsTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) {
307      e.Cancel = true;
308    }
309
310    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
311      if (e.Effect == DragDropEffects.None) return;
312
313      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
314      if (sourceNode == null) return;
315      var sourceProject = ((Project)sourceNode.Tag);
316      if (sourceProject == null) return;
317
318      var treeView = (TreeView)sender;
319      if (sourceNode.TreeView != treeView) return;
320
321      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
322      var targetNode = treeView.GetNodeAt(targetPoint);
323
324      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
325
326      if (!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
327        MessageBox.Show(
328          "You cannot drag projects to this project.",
329          "HeuristicLab Hive Administrator",
330          MessageBoxButtons.OK,
331          MessageBoxIcon.Information);
332        return;
333      }
334
335      if (sourceNode.Parent == null) {
336        treeView.Nodes.Remove(sourceNode);
337      } else {
338        sourceNode.Parent.Nodes.Remove(sourceNode);
339        sourceProject.ParentProjectId = null;
340      }
341
342      if (targetNode == null) {
343        treeView.Nodes.Add(sourceNode);
344      } else if (targetProject.Id != Guid.Empty) {
345        targetNode.Nodes.Add(sourceNode);
346        sourceProject.ParentProjectId = targetProject.Id;
347      }
348
349      SelectedProject = sourceProject;
350      OnContentChanged();
351    }
352
353    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
354      var sourceNode = (TreeNode)e.Item;
355      if (IsAuthorized((Project)sourceNode.Tag))
356        DoDragDrop(sourceNode, DragDropEffects.All);
357    }
358
359    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
360      e.Effect = DragDropEffects.Move;
361
362      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
363      var sourceProject = ((Project)sourceNode.Tag);
364
365      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
366      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
367      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
368
369      if ((!IsAdmin() && (targetNode == null || targetProject == null))
370      || sourceNode == null
371      || sourceProject == null
372      || sourceNode == targetNode
373      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
374        e.Effect = DragDropEffects.None;
375      }
376    }
377
378    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
379      e.Action = DragAction.Continue;
380    }
381    #endregion
382
383    #region Helpers
384    private void BuildProjectTree(IEnumerable<Project> projects) {
385      projectsTreeView.Nodes.Clear();
386      if (!projects.Any()) return;
387
388      var disabledParentProjects = HiveAdminClient.Instance.DisabledParentProjects;
389      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
390      //var parentedMainProjects = new HashSet<Project>(projects
391      //  .Where(x => x.ParentProjectId.HasValue
392      //  && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
393      //mainProjects.UnionWith(parentedMainProjects);
394      var mainDisabledParentProjects = new HashSet<Project>(disabledParentProjects.Where(x => x.ParentProjectId == null || x.ParentProjectId == Guid.Empty));
395      mainProjects.UnionWith(mainDisabledParentProjects);
396      var subProbjects = new HashSet<Project>(projects.Union(disabledParentProjects).Except(mainProjects));
397
398      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
399      if (selectedProject != null) SelectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
400      bool nodeSelected = false;
401
402      TreeNode currentNode = null;
403      Project currentProject = null;
404
405      while (stack.Any()) {
406        var newProject = stack.Pop();
407        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
408        StyleTreeNode(newNode, newProject);
409
410        if (selectedProject == null && !disabledParentProjects.Contains(newProject)) {
411          SelectedProject = newProject;
412        }
413        if (!nodeSelected && selectedProject != null && selectedProject.Id == newProject.Id) {
414          newNode.BackColor = selectedBackColor;
415          newNode.ForeColor = selectedForeColor;
416          newNode.Text += SELECTED_TAG;
417          nodeSelected = true;
418        }
419
420        // search for parent node of newNode and save in currentNode
421        // necessary since newNodes (stack top items) might be siblings
422        // or grand..grandparents of previous node (currentNode)
423        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
424          currentNode = currentNode.Parent;
425          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
426        }
427
428        if (currentNode == null) {
429          projectsTreeView.Nodes.Add(newNode);
430          newNode.ImageIndex = greenFlagImageIndex;
431        } else {
432          currentNode.Nodes.Add(newNode);
433          newNode.ImageIndex = redFlagImageIndex;
434        }
435
436        newNode.SelectedImageIndex = newNode.ImageIndex;
437
438        if (disabledParentProjects.Contains(newProject)) {
439          newNode.Checked = false;
440          newNode.ForeColor = grayTextColor;
441        }
442
443        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
444        if (childProjects.Any()) {
445          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
446            subProbjects.Remove(project);
447            stack.Push(project);
448          }
449          currentNode = newNode;
450          currentProject = newProject;
451        }
452      }
453
454      projectsTreeView.ExpandAll();
455    }
456
457    private void StyleTreeNode(TreeNode n, Project p) {
458      n.Text = p.Name;
459      n.BackColor = Color.Transparent;
460      n.ForeColor = Color.Black;
461
462      if (HiveAdminClient.Instance.DisabledParentProjects.Select(x => x.Id).Contains(p.Id)) {
463        n.ForeColor = grayTextColor;
464      } else {
465        if (p.Id == Guid.Empty) {
466          n.Text += NOT_STORED_TAG;
467        } else if (p.Modified) {
468          n.Text += CHANGES_NOT_STORED_TAG;
469        }
470
471        if (!IsActive(p)) {
472          n.ForeColor = grayTextColor;
473          n.Text += INACTIVE_TAG;
474        }
475      }
476    }
477
478    private void ResetTreeNodes(TreeNodeCollection nodes) {
479      foreach (TreeNode n in nodes) {
480        StyleTreeNode(n, (Project)n.Tag);
481        if (n.Nodes.Count > 0) {
482          ResetTreeNodes(n.Nodes);
483        }
484      }
485    }
486
487    private void ChangeSelectedProject(Project project) {
488      projectView.Content = project;
489      projectPermissionsView.Content = project;
490      projectResourcesView.Content = project;
491      projectJobsView.Content = project;
492      selectedProject = project;
493      SetEnabledStateOfControls();
494    }
495
496    private void ChangeSelectedProjectNode(TreeNode projectNode) {
497      if (projectNode == null) return;
498      SelectedProject = (Project)projectNode.Tag;
499      ResetTreeNodes(projectsTreeView.Nodes);
500      projectNode.BackColor = selectedBackColor;
501      projectNode.ForeColor = selectedForeColor;
502      projectNode.Text += SELECTED_TAG;
503    }
504
505    private void UpdateProjects() {
506      lock (locker) {
507        if (refreshingInternal || refreshingExternal) return;
508        refreshingInternal = true;
509      }
510
511      try {
512        HiveAdminClient.Instance.Refresh();
513      } catch (AnonymousUserException) {
514        ShowHiveInformationDialog();
515      } finally {
516        refreshingInternal = false;
517      }
518    }
519
520    private void RemoveProject(Project project) {
521      if (project == null) return;
522
523      try {
524        if (project.Id != Guid.Empty) {
525          SelectedProject = HiveAdminClient.Instance.GetAvailableProjectAncestors(project.Id).LastOrDefault();
526          HiveAdminClient.Delete(project);
527          UpdateProjects();
528        } else {
529          SelectedProject = Content.FirstOrDefault(x => x.Id == project.ParentProjectId);
530          Content.Remove(project);
531        }
532      } catch (AnonymousUserException) {
533        ShowHiveInformationDialog();
534      }
535    }
536
537    private bool IsActive(Project project) {
538      return DateTime.Now >= project.StartDate
539             && (project.EndDate == null
540                 || DateTime.Now < project.EndDate.Value);
541    }
542
543    private bool IsAuthorized(Project project) {
544      return project != null && UserInformation.Instance.UserExists;
545    }
546
547    private bool IsAdmin() {
548      return HiveRoles.CheckAdminUserPermissions();
549    }
550
551    private void ShowHiveInformationDialog() {
552      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
553      else {
554        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
555          dialog.ShowDialog(this);
556        }
557      }
558    }
559    #endregion
560  }
561}
Note: See TracBrowser for help on using the repository browser.