source: branches/2839_HiveProjectManagement/HeuristicLab.Clients.Hive.Administrator/3.3/Views/ProjectsView.cs @ 15767

Last change on this file since 15767 was 15767, checked in by jzenisek, 3 years ago

#2839: fixed cyclic-relation bug in projects- and resources-view by preventing parents from being dragged onto descendants

File size: 18.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2017 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
43    private readonly Color changedColor = Color.FromArgb(255, 87, 191, 193); // #57bfc1
44    private readonly Color selectedColor = Color.FromArgb(255, 240, 194, 59); // #f0c23b
45    private Project selectedProject = null;
46
47    private readonly object locker = new object();
48
49    public new IItemList<Project> Content {
50      get { return (IItemList<Project>)base.Content; }
51      set { base.Content = value; }
52    }
53
54    public ProjectsView() {
55      InitializeComponent();
56
57      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagGreen);
58      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagRed);
59
60      HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing;
61      HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed;
62      AccessClient.Instance.Refreshing += AccessClient_Instance_Refreshing;
63      AccessClient.Instance.Refreshed += AccessClient_Instance_Refreshed;
64    }
65
66    #region Overrides
67    protected override void OnClosing(FormClosingEventArgs e) {
68      AccessClient.Instance.Refreshed -= AccessClient_Instance_Refreshed;
69      AccessClient.Instance.Refreshing -= AccessClient_Instance_Refreshing;
70      HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed;
71      HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing;
72      base.OnClosing(e);
73    }
74
75    protected override void RegisterContentEvents() {
76      base.RegisterContentEvents();
77      Content.ItemsAdded += Content_ItemsAdded;
78      Content.ItemsRemoved += Content_ItemsRemoved;
79    }
80
81    protected override void DeregisterContentEvents() {
82      Content.ItemsRemoved -= Content_ItemsRemoved;
83      Content.ItemsAdded -= Content_ItemsAdded;
84      base.DeregisterContentEvents();
85    }
86
87    protected override void OnContentChanged() {
88      base.OnContentChanged();
89      if (Content == null) {
90        projectsTreeView.Nodes.Clear();
91        projectView.Content = null;
92        projectPermissionsView.Content = null;
93        projectResourcesView.Content = null;
94      } else {
95        var top = BuildProjectTree(Content);
96        projectView.Content = top;
97        projectPermissionsView.Content = top;
98        projectResourcesView.Content = top;
99
100        if(top != null && top.Id == Guid.Empty) {
101          projectPermissionsView.Locked = true;
102          projectResourcesView.Locked = true;
103        }
104      }
105    }
106
107    protected override void SetEnabledStateOfControls() {
108      base.SetEnabledStateOfControls();
109      bool enabled = Content != null && !ReadOnly;
110      refreshButton.Enabled = enabled;
111      addButton.Enabled = enabled;
112      removeButton.Enabled = enabled;
113      saveProjectButton.Enabled = enabled;
114      projectView.Enabled = enabled;
115      projectPermissionsView.Enabled = enabled;
116      projectResourcesView.Enabled = enabled;
117    }
118    #endregion
119
120    #region Event Handlers
121    private void Content_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
122      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsAdded, sender, e);
123      else {
124        OnContentChanged();
125      }
126    }
127
128    private void Content_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<Project>> e) {
129      if (InvokeRequired) Invoke((Action<object, CollectionItemsChangedEventArgs<IndexedItem<Project>>>)Content_ItemsRemoved, sender, e);
130      else {
131        OnContentChanged();
132      }
133    }
134
135    private void ProjectViewContent_PropertyChanged(object sender, PropertyChangedEventArgs e) {
136      if (InvokeRequired) Invoke((Action<object, PropertyChangedEventArgs>)ProjectViewContent_PropertyChanged, sender, e);
137      else {
138        OnContentChanged();
139      }
140    }
141
142    private void HiveAdminClient_Instance_Refreshing(object sender, EventArgs e) {
143      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshing, sender, e);
144      else {
145        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
146        mainForm.AddOperationProgressToView(this, "Refreshing ...");
147        SetEnabledStateOfControls();
148      }
149    }
150
151    private void HiveAdminClient_Instance_Refreshed(object sender, EventArgs e) {
152      if (InvokeRequired) Invoke((Action<object, EventArgs>)HiveAdminClient_Instance_Refreshed, sender, e);
153      else {
154        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
155        mainForm.RemoveOperationProgressFromView(this);
156        SetEnabledStateOfControls();
157      }
158    }
159
160    private void AccessClient_Instance_Refreshing(object sender, EventArgs e) {
161      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshing, sender, e);
162      else {
163        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
164        mainForm.AddOperationProgressToView(this, "Refreshing ...");
165        SetEnabledStateOfControls();
166      }
167    }
168
169    private void AccessClient_Instance_Refreshed(object sender, EventArgs e) {
170      if (InvokeRequired) Invoke((Action<object, EventArgs>)AccessClient_Instance_Refreshed, sender, e);
171      else {
172        var mainForm = MainFormManager.GetMainForm<MainForm.WindowsForms.MainForm>();
173        mainForm.RemoveOperationProgressFromView(this);
174        SetEnabledStateOfControls();
175      }
176    }
177
178    private async void ProjectsView_Load(object sender, EventArgs e) {
179      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
180        action: () => UpdateProjects());
181    }
182
183    private async void refreshButton_Click(object sender, EventArgs e) {
184      lock (locker) {
185        if (!refreshButton.Enabled) return;
186        refreshButton.Enabled = false;
187      }
188
189      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
190        action: () => UpdateProjects(),
191        finallyCallback: () => {
192          refreshButton.Enabled = true;
193        });
194    }
195
196    private void addButton_Click(object sender, EventArgs e) {
197      Guid? parentProjectId = null;
198
199      if (selectedProject != null && selectedProject.Id == Guid.Empty) {
200        MessageBox.Show(
201          "You cannot add a project to a not yet stored project.",
202          "HeuristicLab Hive Administrator",
203          MessageBoxButtons.OK,
204          MessageBoxIcon.Information);
205        return;
206      }
207
208      if (selectedProject != null) parentProjectId = selectedProject.Id;
209      var project = new Project {
210        Name = "New Project",
211        OwnerUserId = UserInformation.Instance.User.Id,
212        ParentProjectId = parentProjectId
213      };
214
215      selectedProject = project;
216      Content.Add(project);
217    }
218
219    private async void removeButton_Click(object sender, EventArgs e) {
220      if (selectedProject == null) return;
221
222      lock (locker) {
223        if (!removeButton.Enabled) return;
224        removeButton.Enabled = false;
225      }
226
227      if(!IsAdmin()
228        && HiveAdminClient.Instance.CheckOwnershipOfParentProject(selectedProject,
229          UserInformation.Instance.User.Id)) {
230        MessageBox.Show(
231          "Only subprojects of owned projects can be deleted.",
232          "HeuristicLab Hive Administrator",
233          MessageBoxButtons.OK,
234          MessageBoxIcon.Error);
235        return;
236      }
237
238      if (!IsAdmin() && Content.Any(x => x.ParentProjectId == selectedProject.Id)) {
239        MessageBox.Show(
240          "Only empty projects can be deleted.",
241          "HeuristicLab Hive Administrator",
242          MessageBoxButtons.OK,
243          MessageBoxIcon.Error);
244        return;
245      }
246
247      var result = MessageBox.Show(
248        "Do you really want to delete " + selectedProject.Name + "?",
249        "HeuristicLab Hive Administrator",
250        MessageBoxButtons.YesNo,
251        MessageBoxIcon.Question);
252
253      if (result == DialogResult.Yes) {
254        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
255          action: () => RemoveProject(selectedProject),
256          finallyCallback: () => {
257            removeButton.Enabled = true;
258          });
259      }
260    }
261
262    private async void saveProjectButton_Click(object sender, EventArgs e) {
263      lock (locker) {
264        if (!saveProjectButton.Enabled) return;
265        saveProjectButton.Enabled = false;
266      }
267
268      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
269        action: () => {
270          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
271          foreach (var project in projectsToSave)
272            project.Store();
273          UpdateProjects();
274        },
275        finallyCallback: () => saveProjectButton.Enabled = true);
276
277      OnContentChanged();
278    }
279
280    private void projectsTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
281      ChangeSelectedProjectNode(e.Node);
282
283      //if (projectView.Content != null)
284      //  projectView.Content.PropertyChanged -= ProjectViewContent_PropertyChanged;
285
286      projectView.Content = selectedProject;
287      projectPermissionsView.Content = selectedProject;
288      projectResourcesView.Content = selectedProject;
289
290      //if (selectedProject != null)
291      //  selectedProject.PropertyChanged += ProjectViewContent_PropertyChanged;
292    }
293
294    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
295      if (e.Effect == DragDropEffects.None) return;
296
297      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
298      if (sourceNode == null) return;
299      var sourceProject = ((Project)sourceNode.Tag);
300      if (sourceProject == null) return;
301
302      var treeView = (TreeView)sender;
303      if (sourceNode.TreeView != treeView) return;
304
305      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
306      var targetNode = treeView.GetNodeAt(targetPoint);
307
308      if (sourceNode == targetNode) return;
309      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
310
311      if(targetProject != null && targetProject.Id == Guid.Empty) {
312        MessageBox.Show(
313          "You cannot drag projects to a not yet stored project.",
314          "HeuristicLab Hive Administrator",
315          MessageBoxButtons.OK,
316          MessageBoxIcon.Information);
317        return;
318      }
319      if(!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
320        MessageBox.Show(
321          "You cannot drag to this project.",
322          "HeuristicLab Hive Administrator",
323          MessageBoxButtons.OK,
324          MessageBoxIcon.Information);
325      return;
326      }
327
328      if (sourceNode.Parent == null) {
329        treeView.Nodes.Remove(sourceNode);
330      } else {
331        sourceNode.Parent.Nodes.Remove(sourceNode);
332        sourceProject.ParentProjectId = null;
333      }
334
335      if (targetNode == null) {
336        treeView.Nodes.Add(sourceNode);
337      } else if(targetProject.Id != Guid.Empty) {
338        targetNode.Nodes.Add(sourceNode);
339        sourceProject.ParentProjectId = targetProject.Id;
340      }
341
342      selectedProject = sourceProject;
343      OnContentChanged();
344    }
345
346    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
347      var sourceNode = (TreeNode)e.Item;
348      if (IsAuthorized((Project)sourceNode.Tag))
349        DoDragDrop(sourceNode, DragDropEffects.All);
350    }
351
352    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
353      e.Effect = DragDropEffects.Move;
354
355      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
356      var sourceProject = ((Project)sourceNode.Tag);
357
358      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
359      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
360      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
361
362      if ((!IsAdmin() && (targetNode == null || targetProject == null))
363      || sourceNode == null
364      || sourceProject == null
365      || sourceNode == targetNode
366      || (targetProject != null && targetProject.Id == Guid.Empty)
367      || (targetProject != null && targetProject.Id == sourceProject.ParentProjectId)
368      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
369        e.Effect = DragDropEffects.None;
370      }
371    }
372
373    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
374      e.Action = DragAction.Continue;
375    }
376    #endregion
377
378    #region Helpers
379    private void ResetTreeNodes(TreeNodeCollection nodes, Color c1, Color c2, bool resetText) {
380      foreach (TreeNode n in nodes) {
381        var pro = ((Project)n.Tag);
382        if (n.BackColor.Equals(c1)) {
383          n.BackColor = c2;
384        }
385        if (resetText) {
386          n.Text = pro.Name;
387
388          if (pro.Id == Guid.Empty) {
389            n.Text += " [not stored]";
390          } else if (pro.Modified) {
391            n.Text += " [changes not stored]";
392          }
393        }
394        if (n.Nodes.Count > 0) {
395          ResetTreeNodes(n.Nodes, c1, c2, resetText);
396        }
397      }
398    }
399
400    private void ChangeSelectedProjectNode(TreeNode projectNode) {
401      selectedProject = (Project)projectNode.Tag;
402      ResetTreeNodes(projectsTreeView.Nodes, selectedColor, Color.Transparent, true);
403      projectNode.BackColor = selectedColor;
404      projectNode.Text += " [selected]";
405    }
406
407    private Project BuildProjectTree(IEnumerable<Project> projects) {
408      projectsTreeView.Nodes.Clear();
409      if (!projects.Any()) return null;
410
411      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
412      var parentedMainProjects = new HashSet<Project>(projects
413        .Where(x => x.ParentProjectId.HasValue
414        && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
415            mainProjects.UnionWith(parentedMainProjects);
416      var subProbjects = new HashSet<Project>(projects.Except(mainProjects));
417
418      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
419      if (selectedProject != null) selectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
420      bool nodeSelected = false;
421
422      TreeNode currentNode = null;
423      Project currentProject = null;
424     
425      while (stack.Any()) {
426        var newProject = stack.Pop();
427        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
428
429        if (newProject.Id == Guid.Empty) {
430          newNode.Text += " [not stored]";
431        } else if(newProject.Modified) {
432          newNode.Text += " [changes not stored]";
433        }
434        if (selectedProject == null) {
435          selectedProject = newProject;
436        }
437        if (newProject.Id == selectedProject.Id && !nodeSelected) {
438          newNode.BackColor = selectedColor;
439          newNode.Text += " [selected]";
440          nodeSelected = true;
441        }
442
443        // search for parent node of newNode and save in currentNode
444        // necessary since newNodes (stack top items) might be siblings
445        // or grand..grandparents of previous node (currentNode)
446        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
447          currentNode = currentNode.Parent;
448          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
449        }
450
451        if (currentNode == null) {
452          projectsTreeView.Nodes.Add(newNode);
453          newNode.ImageIndex = greenFlagImageIndex;
454        } else {
455          currentNode.Nodes.Add(newNode);
456          newNode.ImageIndex = redFlagImageIndex;
457        }
458
459        newNode.SelectedImageIndex = newNode.ImageIndex;
460
461        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
462        if (childProjects.Any()) {
463          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
464            subProbjects.Remove(project);
465            stack.Push(project);
466          }
467          currentNode = newNode;
468          currentProject = newProject;
469        }
470      }
471
472      projectsTreeView.ExpandAll();
473
474      return selectedProject;
475    }
476
477    private void UpdateProjects() {
478      try {
479        HiveAdminClient.Instance.Refresh();
480        Content = HiveAdminClient.Instance.Projects;
481      } catch (AnonymousUserException) {
482        ShowHiveInformationDialog();
483      }
484    }
485
486    private void RemoveProject(Project project) {
487      if (project == null || project.Id == Guid.Empty) return;
488
489      try {
490        HiveAdminClient.Delete(project);
491        Content.Remove(selectedProject);
492      } catch (AnonymousUserException) {
493        ShowHiveInformationDialog();
494      }
495    }
496
497    private bool IsAuthorized(Project project) {
498      return project != null && UserInformation.Instance.UserExists;
499          //&& (project.OwnerUserId == UserInformation.Instance.User.Id || HiveRoles.CheckAdminUserPermissions());
500    }
501
502    private bool IsAdmin() {
503      return HiveRoles.CheckAdminUserPermissions();
504    }
505
506    private void ShowHiveInformationDialog() {
507      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
508      else {
509        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
510          dialog.ShowDialog(this);
511        }
512      }
513    }
514    #endregion
515  }
516}
Note: See TracBrowser for help on using the repository browser.