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

Last change on this file since 16117 was 16117, checked in by jkarder, 12 months ago

#2839: merged [15377-16116/branches/2839_HiveProjectManagement] into trunk

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