Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 15813 was 15813, checked in by jzenisek, 7 years ago

#2839: improved HiveAdmin interactions:

  • collaps groups with slaves only
  • update scheduleView only if currently visible
  • made schedule scrollable for non-admins
  • changed selection backcolor
  • fixed add/delete project permission bug for non-admins
File size: 18.3 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    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
46    private readonly Color selectedBackColor = Color.DodgerBlue;
47    private readonly Color selectedForeColor = Color.White;
48
49    private Project selectedProject = null;
50    public Project SelectedProject {
51      get { return selectedProject; }
52      set { if (selectedProject != value) ChangeSelectedProject(value); }
53    }
54
55    private readonly object locker = new object();
56
57    public new IItemList<Project> Content {
58      get { return (IItemList<Project>)base.Content; }
59      set { base.Content = value; }
60    }
61
62    public ProjectsView() {
63      InitializeComponent();
64
65      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagGreen);
66      projectsTreeView.ImageList.Images.Add(VSImageLibrary.FlagRed);
67
68      HiveAdminClient.Instance.Refreshing += HiveAdminClient_Instance_Refreshing;
69      HiveAdminClient.Instance.Refreshed += HiveAdminClient_Instance_Refreshed;
70      AccessClient.Instance.Refreshing += AccessClient_Instance_Refreshing;
71      AccessClient.Instance.Refreshed += AccessClient_Instance_Refreshed;
72    }
73
74    #region Overrides
75    protected override void OnClosing(FormClosingEventArgs e) {
76      AccessClient.Instance.Refreshed -= AccessClient_Instance_Refreshed;
77      AccessClient.Instance.Refreshing -= AccessClient_Instance_Refreshing;
78      HiveAdminClient.Instance.Refreshed -= HiveAdminClient_Instance_Refreshed;
79      HiveAdminClient.Instance.Refreshing -= HiveAdminClient_Instance_Refreshing;
80      base.OnClosing(e);
81    }
82
83    protected override void RegisterContentEvents() {
84      base.RegisterContentEvents();
85      Content.ItemsAdded += Content_ItemsAdded;
86      Content.ItemsRemoved += Content_ItemsRemoved;
87    }
88
89    protected override void DeregisterContentEvents() {
90      Content.ItemsRemoved -= Content_ItemsRemoved;
91      Content.ItemsAdded -= Content_ItemsAdded;
92      base.DeregisterContentEvents();
93    }
94
95    protected override void OnContentChanged() {
96      base.OnContentChanged();
97      if (Content == null) {
98        projectsTreeView.Nodes.Clear();
99        projectView.Content = null;
100        projectPermissionsView.Content = null;
101        projectResourcesView.Content = null;
102      } else {
103        BuildProjectTree(Content);
104      }
105    }
106
107    protected override void SetEnabledStateOfControls() {
108      base.SetEnabledStateOfControls();
109      bool enabled = Content != null && !Locked && !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 (Content.Any(x => x.ParentProjectId == selectedProject.Id)) {
228        MessageBox.Show(
229          "Only empty projects can be deleted.",
230          "HeuristicLab Hive Administrator",
231          MessageBoxButtons.OK,
232          MessageBoxIcon.Error);
233        return;
234      }
235
236      var result = MessageBox.Show(
237        "Do you really want to delete " + selectedProject.Name + "?",
238        "HeuristicLab Hive Administrator",
239        MessageBoxButtons.YesNo,
240        MessageBoxIcon.Question);
241
242      if (result == DialogResult.Yes) {
243        await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
244          action: () => {
245            RemoveProject(selectedProject); 
246          },
247          finallyCallback: () => {
248            removeButton.Enabled = true;
249          });
250      }
251    }
252
253    private async void saveProjectButton_Click(object sender, EventArgs e) {
254      lock (locker) {
255        if (!saveProjectButton.Enabled) return;
256        saveProjectButton.Enabled = false;
257      }
258
259      await SecurityExceptionUtil.TryAsyncAndReportSecurityExceptions(
260        action: () => {
261          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
262          foreach (var project in projectsToSave)
263            project.Store();
264          UpdateProjects();
265        },
266        finallyCallback: () => saveProjectButton.Enabled = true);
267
268      OnContentChanged();
269    }
270
271    private void projectsTreeView_MouseDown(object sender, MouseEventArgs e) {
272      var node = projectsTreeView.GetNodeAt(e.Location);
273      if(node != null) ChangeSelectedProjectNode(node);
274    }
275
276    private void projectsTreeView_BeforeSelect(object sender, TreeViewCancelEventArgs e) {
277      e.Cancel = true;
278    }
279
280    private void projectsTreeView_DragDrop(object sender, DragEventArgs e) {
281      if (e.Effect == DragDropEffects.None) return;
282
283      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
284      if (sourceNode == null) return;
285      var sourceProject = ((Project)sourceNode.Tag);
286      if (sourceProject == null) return;
287
288      var treeView = (TreeView)sender;
289      if (sourceNode.TreeView != treeView) return;
290
291      var targetPoint = treeView.PointToClient(new Point(e.X, e.Y));
292      var targetNode = treeView.GetNodeAt(targetPoint);
293
294      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
295
296      if (!HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
297        MessageBox.Show(
298          "You cannot drag projects to this project.",
299          "HeuristicLab Hive Administrator",
300          MessageBoxButtons.OK,
301          MessageBoxIcon.Information);
302        return;
303      }
304
305      if (sourceNode.Parent == null) {
306        treeView.Nodes.Remove(sourceNode);
307      } else {
308        sourceNode.Parent.Nodes.Remove(sourceNode);
309        sourceProject.ParentProjectId = null;
310      }
311
312      if (targetNode == null) {
313        treeView.Nodes.Add(sourceNode);
314      } else if(targetProject.Id != Guid.Empty) {
315        targetNode.Nodes.Add(sourceNode);
316        sourceProject.ParentProjectId = targetProject.Id;
317      }
318
319      SelectedProject = sourceProject;
320      OnContentChanged();
321    }
322
323    private void projectsTreeView_ItemDrag(object sender, ItemDragEventArgs e) {
324      var sourceNode = (TreeNode)e.Item;
325      if (IsAuthorized((Project)sourceNode.Tag))
326        DoDragDrop(sourceNode, DragDropEffects.All);
327    }
328
329    private void projectsTreeView_DragEnterOver(object sender, DragEventArgs e) {
330      e.Effect = DragDropEffects.Move;
331
332      var sourceNode = (TreeNode)e.Data.GetData(typeof(TreeNode));
333      var sourceProject = ((Project)sourceNode.Tag);
334
335      var targetPoint = projectsTreeView.PointToClient(new Point(e.X, e.Y));
336      var targetNode = projectsTreeView.GetNodeAt(targetPoint);
337      var targetProject = (targetNode != null) ? (Project)targetNode.Tag : null;
338
339      if ((!IsAdmin() && (targetNode == null || targetProject == null))
340      || sourceNode == null
341      || sourceProject == null
342      || sourceNode == targetNode
343      || !HiveAdminClient.Instance.CheckParentChange(sourceProject, targetProject)) {
344        e.Effect = DragDropEffects.None;
345      }
346    }
347
348    private void projectsTreeView_QueryContinueDrag(object sender, QueryContinueDragEventArgs e) {
349      e.Action = DragAction.Continue;
350    }
351    #endregion
352
353    #region Helpers
354    private void BuildProjectTree(IEnumerable<Project> projects) {
355      projectsTreeView.Nodes.Clear();
356      if (!projects.Any()) return;
357
358      var mainProjects = new HashSet<Project>(projects.Where(x => x.ParentProjectId == null));
359      var parentedMainProjects = new HashSet<Project>(projects
360        .Where(x => x.ParentProjectId.HasValue
361        && !projects.Select(y => y.Id).Contains(x.ParentProjectId.Value)));
362      mainProjects.UnionWith(parentedMainProjects);
363      var subProbjects = new HashSet<Project>(projects.Except(mainProjects));
364
365      var stack = new Stack<Project>(mainProjects.OrderByDescending(x => x.Name));
366      if (selectedProject != null) SelectedProject = projects.Where(x => x.Id == selectedProject.Id).FirstOrDefault();
367      bool nodeSelected = false;
368
369      TreeNode currentNode = null;
370      Project currentProject = null;
371
372      while (stack.Any()) {
373        var newProject = stack.Pop();
374        var newNode = new TreeNode(newProject.Name) { Tag = newProject };
375        StyleTreeNode(newNode, newProject);
376     
377        if (selectedProject == null) {
378          SelectedProject = newProject;
379        }
380        if (newProject.Id == selectedProject.Id && !nodeSelected) {
381          newNode.BackColor = selectedBackColor;
382          newNode.ForeColor = selectedForeColor;
383          newNode.Text += SELECTED_TAG;
384          nodeSelected = true;
385        }
386
387        // search for parent node of newNode and save in currentNode
388        // necessary since newNodes (stack top items) might be siblings
389        // or grand..grandparents of previous node (currentNode)
390        while (currentNode != null && newProject.ParentProjectId != currentProject.Id) {
391          currentNode = currentNode.Parent;
392          currentProject = currentNode == null ? null : (Project)currentNode.Tag;
393        }
394
395        if (currentNode == null) {
396          projectsTreeView.Nodes.Add(newNode);
397          newNode.ImageIndex = greenFlagImageIndex;
398        } else {
399          currentNode.Nodes.Add(newNode);
400          newNode.ImageIndex = redFlagImageIndex;
401        }
402
403        newNode.SelectedImageIndex = newNode.ImageIndex;
404
405        var childProjects = subProbjects.Where(x => x.ParentProjectId == newProject.Id);
406        if (childProjects.Any()) {
407          foreach (var project in childProjects.OrderByDescending(x => x.Name)) {
408            subProbjects.Remove(project);
409            stack.Push(project);
410          }
411          currentNode = newNode;
412          currentProject = newProject;
413        }
414      }
415
416      projectsTreeView.ExpandAll();
417    }
418
419    private void StyleTreeNode(TreeNode n, Project p) {
420      n.Text = p.Name;
421      n.BackColor = Color.Transparent;
422      n.ForeColor = Color.Black;
423
424      if (p.Id == Guid.Empty) {
425        n.Text += NOT_STORED_TAG;
426      } else if (p.Modified) {
427        n.Text += CHANGES_NOT_STORED_TAG;
428      }
429    }
430
431    private void ResetTreeNodes(TreeNodeCollection nodes) {
432      foreach (TreeNode n in nodes) {
433        StyleTreeNode(n, (Project)n.Tag);
434        if (n.Nodes.Count > 0) {
435          ResetTreeNodes(n.Nodes);
436        }
437      }
438    }
439
440    private void ChangeSelectedProject(Project project) {
441      projectView.Content = project;
442      projectPermissionsView.Content = project;
443      projectResourcesView.Content = project;
444
445      bool projectIsNew = project != null && project.Id == Guid.Empty;
446      bool locked = project == null || projectIsNew;
447      bool deleteEnabled = project != null && (
448        projectIsNew
449        || HiveAdminClient.Instance.CheckOwnershipOfParentProject(project, UserInformation.Instance.User.Id)
450        || !HiveAdminClient.Instance.ProjectDescendants[project.Id].Any());
451
452      addButton.Enabled = !locked;
453      removeButton.Enabled = deleteEnabled;
454      projectPermissionsView.Locked = locked;
455      projectResourcesView.Locked = locked;
456      selectedProject = project;
457    }
458
459    private void ChangeSelectedProjectNode(TreeNode projectNode) {
460      if (projectNode == null) return;
461      SelectedProject = (Project)projectNode.Tag;
462      ResetTreeNodes(projectsTreeView.Nodes);
463      projectNode.BackColor = selectedBackColor;
464      projectNode.ForeColor = selectedForeColor;
465      projectNode.Text += SELECTED_TAG;
466    }
467
468    private void UpdateProjects() {
469      try {
470        HiveAdminClient.Instance.Refresh();
471        Content = HiveAdminClient.Instance.Projects;
472      } catch (AnonymousUserException) {
473        ShowHiveInformationDialog();
474      }
475    }
476
477    private void RemoveProject(Project project) {
478      if (project == null) return;
479
480      try {
481        if (project.Id != Guid.Empty) {
482          var projectsToSave = Content.Where(x => x.Id == Guid.Empty || x.Modified);
483          foreach (var p in projectsToSave)
484            p.Store();
485          HiveAdminClient.Delete(project);
486          UpdateProjects();
487        } else {
488          Content.Remove(project);
489        }
490      } catch (AnonymousUserException) {
491        ShowHiveInformationDialog();
492      }
493    }
494
495    private bool IsAuthorized(Project project) {
496      return project != null && UserInformation.Instance.UserExists;
497    }
498
499    private bool IsAdmin() {
500      return HiveRoles.CheckAdminUserPermissions();
501    }
502
503    private void ShowHiveInformationDialog() {
504      if (InvokeRequired) Invoke((Action)ShowHiveInformationDialog);
505      else {
506        using (HiveInformationDialog dialog = new HiveInformationDialog()) {
507          dialog.ShowDialog(this);
508        }
509      }
510    }
511    #endregion
512  }
513}
Note: See TracBrowser for help on using the repository browser.