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

Last change on this file since 16072 was 16072, checked in by jzenisek, 2 years ago

#2839: worked on HiveProjectMgmt:

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