Subversion Repositories SoapBoxCore

Rev

Rev 17 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
8 scott 1
#region "SoapBox.Core License"
2
/// <header module="SoapBox.Core"> 
3
/// Copyright (C) 2009 SoapBox Automation Inc., All Rights Reserved.
4
/// Contact: SoapBox Automation Licencing (license@soapboxautomation.com)
5
/// 
6
/// This file is part of SoapBox Core.
7
/// 
8
/// Commercial Usage
9
/// Licensees holding valid SoapBox Automation Commercial licenses may use  
10
/// this file in accordance with the SoapBox Automation Commercial License
11
/// Agreement provided with the Software or, alternatively, in accordance 
12
/// with the terms contained in a written agreement between you and
13
/// SoapBox Automation Inc.
14
/// 
15
/// GNU Lesser General Public License Usage
16
/// SoapBox Core is free software: you can redistribute it and/or modify 
17
/// it under the terms of the GNU Lesser General Public License
18
/// as published by the Free Software Foundation, either version 3 of the
19
/// License, or (at your option) any later version.
20
/// 
21
/// SoapBox Core is distributed in the hope that it will be useful, 
22
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
23
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
/// GNU Lesser General Public License for more details.
25
/// 
26
/// You should have received a copy of the GNU Lesser General Public License 
27
/// along with SoapBox Core. If not, see <http://www.gnu.org/licenses/>.
28
/// </header>
29
#endregion
30
 
31
using System;
32
using System.Collections.Generic;
33
using System.Linq;
34
using System.Text;
35
using SoapBox.Core;
36
using System.ComponentModel.Composition;
37
using AvalonDock;
38
using System.IO;
39
using System.Collections.ObjectModel;
40
using System.Collections.Specialized;
41
using System.Windows.Input;
42
using System.Windows;
13 scott 43
using System.Xml.Serialization;
16 scott 44
using System.ComponentModel;
45
using SoapBox.Utilities;
8 scott 46
 
47
namespace SoapBox.Core.Layout
48
{
12 scott 49
    [Export(Services.Layout.LayoutManager, typeof(ILayoutManager))]
8 scott 50
    public class LayoutManager : ILayoutManager
51
    {
52
        private DocumentPane m_docPane = new DocumentPane(); // documents are put in here
53
        private ResizingPanel m_resizingPanel = new ResizingPanel(); // pads are docked to this
54
 
55
        public LayoutManager()
56
        {
57
            // Basic configuration:
58
            // DocumentPane 
59
            //   in a ResizingPanel 
60
            //      in a DockingManager
61
            m_docPane.Name = "DocumentPane";
62
            m_resizingPanel.Name = "ResizingPanel";
18 scott 63
            m_resizingPanel.Children.Add(m_docPane);
8 scott 64
            DockManager.Content = m_resizingPanel;
65
 
66
            DockManager.Loaded += new System.Windows.RoutedEventHandler(DockManager_Loaded);
67
            DockManager.LayoutUpdated += new EventHandler(DockManager_LayoutUpdated);
68
        }
69
 
17 scott 70
        [Import(Services.Logging.LoggingService, typeof(ILoggingService))]
71
        private ILoggingService logger { get; set; }
72
 
8 scott 73
        /// <summary>
74
        /// Shows a pad.  If it hasn't been shown before, it shows it
75
        /// docked to the right side.  Otherwise it restores it to the
76
        /// previous place that it was before hiding.  Doesn't work
77
        /// correctly for floating panes (yet).
78
        /// </summary>
79
        /// <param name="pad"></param>
80
        public void ShowPad(IPad pad)
81
        {
82
            if (!m_padLookup.ContainsKey(pad))
83
            {
84
                DockableContent content = new DockableContent();
85
                content.Content = pad;
86
                content.Title = pad.Title;
87
                content.Name = pad.Name;
88
                m_padLookup.Add(pad, content);
89
                DockablePane dp = new DockablePane();
90
                dp.Items.Add(content);
91
                m_resizingPanel.Children.Add(dp);
16 scott 92
                pad.PropertyChanged += new PropertyChangedEventHandler(pad_PropertyChanged);
8 scott 93
                content.GotFocus += new RoutedEventHandler(pad.OnGotFocus);
94
                content.LostFocus += new RoutedEventHandler(pad.OnLostFocus);
95
            }
17 scott 96
            m_padLookup[pad].Show(DockManager);
8 scott 97
        }
98
 
99
        /// <summary>
13 scott 100
        /// Hides the given pad, if it exists
101
        /// </summary>
102
        /// <param name="pad"></param>
103
        public void HidePad(IPad pad)
104
        {
105
            if (m_padLookup.ContainsKey(pad))
106
            {
17 scott 107
                m_padLookup[pad].Hide();
13 scott 108
            }
109
        }
110
 
111
        public void HideAllPads()
112
        {
113
            foreach (var content in m_padLookup.Values)
114
            {
17 scott 115
                content.Hide();
13 scott 116
            }
117
        }
118
 
18 scott 119
        public IDocument ShowDocument(IDocument document, string memento)
120
        {
121
            return ShowDocument(document, memento, true);
122
        }
123
 
13 scott 124
        /// <summary>
8 scott 125
        /// Shows a document.  Puts it in the document pane.
126
        /// </summary>
127
        /// <param name="document"></param>
18 scott 128
        public IDocument ShowDocument(IDocument document, string memento, bool makeActive)
8 scott 129
        {
13 scott 130
            IDocument doc = document.CreateDocument(memento);
131
            if (doc != null)
8 scott 132
            {
13 scott 133
                if (!m_documentLookup.ContainsKey(doc))
134
                {
135
                    DocumentContent content = new DocumentContent();
136
                    content.Content = doc;
137
                    content.Title = doc.Title;
138
                    content.Name = doc.Name;
139
                    m_documentLookup.Add(doc, content);
140
                    m_docPane.Items.Add(content);
16 scott 141
                    // all these event handlers get unsubscribed in the content_Closing method
142
                    doc.PropertyChanged += new PropertyChangedEventHandler(doc_PropertyChanged);
13 scott 143
                    content.Closing += new EventHandler<System.ComponentModel.CancelEventArgs>(doc.OnClosing);
144
                    content.Closed += new EventHandler(content_Closed);
145
                    content.GotFocus += new RoutedEventHandler(doc.OnGotFocus);
146
                    content.LostFocus += new RoutedEventHandler(doc.OnLostFocus);
147
                    document.OnOpened(content, new EventArgs());
148
                }
17 scott 149
                m_documentLookup[doc].Show(DockManager);
18 scott 150
                if (makeActive)
151
                {
152
                    DockManager.ActiveDocument = m_documentLookup[doc];
153
                }
8 scott 154
            }
13 scott 155
            return doc;
8 scott 156
        }
157
 
16 scott 158
        // Keep the VM properties in sync with
159
        // the V properties (backwards data binding?)
160
        void pad_PropertyChanged(object sender, PropertyChangedEventArgs e)
161
        {
162
            var pad = sender as IPad;
163
            if (pad != null)
164
            {
165
                var content = m_padLookup[pad];
166
                if (content != null)
167
                {
168
                    if (e.PropertyName == m_TitleName)
169
                    {
170
                        content.Title = pad.Title;
171
                    }
172
                    else if (e.PropertyName == m_NameName)
173
                    {
174
                        content.Name = pad.Name;
175
                    }
176
                }
177
            }
178
        }
179
 
180
        // Keep the VM properties in sync with
181
        // the V properties (backwards data binding?)
182
        void doc_PropertyChanged(object sender, PropertyChangedEventArgs e)
183
        {
184
            var doc = sender as IDocument;
185
            if (doc != null)
186
            {
187
                var content = m_documentLookup[doc];
188
                if (content != null)
189
                {
190
                    if (e.PropertyName == m_TitleName)
191
                    {
192
                        content.Title = doc.Title;
193
                    }
194
                    else if (e.PropertyName == m_NameName)
195
                    {
196
                        content.Name = doc.Name;
197
                    }
198
                }
199
            }
200
        }
201
        private static string m_TitleName =
202
            NotifyPropertyChangedHelper.GetPropertyName<ILayoutItem>(o => o.Title);
203
        private static string m_NameName =
204
            NotifyPropertyChangedHelper.GetPropertyName<ILayoutItem>(o => o.Name);
205
 
13 scott 206
        /// <summary>
207
        /// Closes the given instance of a document, if it exists
208
        /// </summary>
209
        /// <param name="document"></param>
210
        public void CloseDocument(IDocument document)
211
        {
212
            if (m_documentLookup.ContainsKey(document))
213
            {
214
                m_documentLookup[document].Close();
215
            }
216
        }
217
 
218
        /// <summary>
219
        /// Close all documents, if they are open
220
        /// </summary>
221
        public void CloseAllDocuments()
222
        {
223
            while (m_documentLookup.Count > 0)
224
            {
225
                IDocument doc = m_documentLookup.Keys.First();
226
                m_documentLookup[doc].Close();
227
            }
228
        }
229
 
8 scott 230
        // Handles removing documents from the data structure when closed
231
        void content_Closed(object sender, EventArgs e)
232
        {
233
            DocumentContent content = sender as DocumentContent;
234
            IDocument document = content.Content as IDocument;
16 scott 235
 
236
            document.PropertyChanged -= new PropertyChangedEventHandler(doc_PropertyChanged);
237
            content.Closing -= new EventHandler<System.ComponentModel.CancelEventArgs>(document.OnClosing);
238
            content.Closed -= new EventHandler(content_Closed);
239
            content.GotFocus -= new RoutedEventHandler(document.OnGotFocus);
240
            content.LostFocus -= new RoutedEventHandler(document.OnLostFocus);
241
 
8 scott 242
            m_documentLookup.Remove(document);
243
            document.OnClosed(sender, e);
244
        }
245
 
246
        /// <summary>
247
        /// The View binds to this property
248
        /// </summary>
249
        public DockingManager DockManager
250
        {
251
            get
252
            {
253
                return m_Content;
254
            }
255
        }
256
        private readonly DockingManager m_Content = new DockingManager();
257
 
258
        #region " ILayoutManager Members "
259
 
260
        public event EventHandler Loaded;
17 scott 261
        public event EventHandler Unloading;
8 scott 262
        public event EventHandler LayoutUpdated;
263
 
264
        /// <summary>
265
        /// Pass through the LayoutUpdated Event
266
        /// </summary>
267
        /// <param name="sender"></param>
268
        /// <param name="e"></param>
269
        void DockManager_LayoutUpdated(object sender, EventArgs e)
270
        {
271
            if (LayoutUpdated != null)
272
            {
273
                LayoutUpdated(sender, e);
274
            }
275
        }
276
 
277
        /// <summary>
278
        /// Pass through the Loaded Event
279
        /// </summary>
280
        /// <param name="sender"></param>
281
        /// <param name="e"></param>
282
        void DockManager_Loaded(object sender, System.Windows.RoutedEventArgs e)
283
        {
17 scott 284
            logger.Info("Layout manager firing Loaded event.");
8 scott 285
            if (Loaded != null)
286
            {
287
                Loaded(sender, new EventArgs());
288
            }
289
        }
290
 
291
        /// <summary>
17 scott 292
        /// The workbench is notifying us that we're unloading, so
293
        /// fire the Unloading event
294
        /// </summary>
295
        public void UnloadingWorkbench()
296
        {
297
            logger.Info("Layout manager firing Unloading event.");
298
            if (Unloading != null)
299
            {
300
                Unloading(this, new EventArgs());
301
            }
302
        }
303
 
304
        /// <summary>
8 scott 305
        /// A collection of tool pads, etc., from the workbench
306
        /// </summary>
307
        public ReadOnlyCollection<IPad> Pads
308
        {
309
            get
310
            {
311
                return new ReadOnlyCollection<IPad>(m_padLookup.Keys.ToList());
312
            }
313
        }
314
        private readonly Dictionary<IPad, DockableContent> m_padLookup = new Dictionary<IPad, DockableContent>();
315
 
316
        /// <summary>
317
        /// A collection of documents from the document manager
318
        /// </summary>
319
        public ReadOnlyCollection<IDocument> Documents
320
        {
321
            get
322
            {
323
                return new ReadOnlyCollection<IDocument>(m_documentLookup.Keys.ToList());
324
            }
325
        }
326
        private readonly Dictionary<IDocument, DocumentContent> m_documentLookup = new Dictionary<IDocument, DocumentContent>();
327
 
328
        /// <summary>
329
        /// Returns true if the given pad is visible.
330
        /// </summary>
331
        /// <param name="pad"></param>
332
        /// <returns></returns>
333
        public bool IsVisible(IPad pad)
334
        {
335
            if (m_padLookup.ContainsKey(pad))
336
            {
337
                DockableContent content = m_padLookup[pad];
338
                return (content.State != DockableContentState.Hidden);
339
            }
340
            else
341
            {
342
                return false;
343
            }
344
        }
345
 
346
        /// <summary>
347
        /// Returns true if the given document is visible.
348
        /// </summary>
349
        /// <param name="document"></param>
350
        /// <returns></returns>
351
        public bool IsVisible(IDocument document)
352
        {
353
            if (m_documentLookup.ContainsKey(document))
354
            {
355
                DocumentContent content = m_documentLookup[document];
356
                return content.IsActiveDocument;
357
            }
358
            else
359
            {
360
                return false;
361
            }
362
        }
363
 
13 scott 364
        // Set this to the list of all available pads and docs so that RestoreLayout
365
        // can use them to build new ones as needed.
366
        public void SetAllPadsDocuments(
367
            IEnumerable<Lazy<IPad, IPadMeta>> AllPads,
368
            IEnumerable<Lazy<IDocument, IDocumentMeta>> AllDocuments)
369
        {
370
            allPads = AllPads;
371
            allDocuments = AllDocuments;
372
        }
373
        private IEnumerable<Lazy<IPad, IPadMeta>> allPads = new Collection<Lazy<IPad, IPadMeta>>();
374
        private IEnumerable<Lazy<IDocument, IDocumentMeta>> allDocuments = new Collection<Lazy<IDocument, IDocumentMeta>>();
375
 
8 scott 376
        /// <summary>
377
        /// Call this method to persist the current layout to disk.
378
        /// </summary>
13 scott 379
        public string SaveLayout()
8 scott 380
        {
381
            if (DockManager.IsLoaded)
382
            {
383
                // Save pads
384
                List<string> padNamesList = new List<string>();
385
                foreach (IPad pad in Pads)
386
                {
12 scott 387
                    // We have to save all the pad names that have ever been
388
                    // shown even if they're hidden now or else the layout
389
                    // manager won't remember where they are when shown again.
8 scott 390
                    padNamesList.Add(pad.Name);
391
                }
392
 
393
                string padNames = String.Join(",", padNamesList.ToArray());
394
 
395
                // Save documents
13 scott 396
                DocumentList docNamesList = new DocumentList();
8 scott 397
                foreach (IDocument doc in Documents)
398
                {
13 scott 399
                    docNamesList.AddItem(
400
                        new DocumentItem(doc.Name, doc.Memento)
401
                        );
8 scott 402
                }
403
 
13 scott 404
                XmlSerializer s = new XmlSerializer(typeof(DocumentList));
405
                StringWriter sw = new StringWriter();
406
                s.Serialize(sw, docNamesList);
407
                string docNames = sw.ToString();
408
                sw.Close();
8 scott 409
 
410
                // Save layout
13 scott 411
                StringWriter swLayout = new StringWriter();
412
                DockManager.SaveLayout(swLayout);
413
                string layout = swLayout.ToString();
414
                swLayout.Close();
415
 
416
                // encode it to base 64 so we don't have to worry about control codes
417
                byte[] encbuf;
418
 
419
                encbuf = System.Text.Encoding.Unicode.GetBytes(padNames);
420
                string padNamesEncoded = Convert.ToBase64String(encbuf);
421
 
422
                encbuf = System.Text.Encoding.Unicode.GetBytes(docNames);
423
                string docNamesEncoded = Convert.ToBase64String(encbuf);
424
 
425
                encbuf = System.Text.Encoding.Unicode.GetBytes(layout);
426
                string layoutEncoded = Convert.ToBase64String(encbuf);
427
 
428
                // "The base-64 digits in ascending order from zero are the uppercase 
429
                // characters "A" to "Z", the lowercase characters "a" to "z", 
430
                // the numerals "0" to "9", and the symbols "+" and "/". 
431
                // The valueless character, "=", is used for trailing padding."
432
                return padNamesEncoded + "." + docNamesEncoded + "." + layoutEncoded;
8 scott 433
            }
13 scott 434
            else
435
            {
436
                throw new InvalidOperationException("The DockManager isn't loaded yet.");
437
            }
8 scott 438
        }
439
 
440
        /// <summary>
441
        /// Call this method to restore the existing layout from disk.
442
        /// </summary>
443
        /// <param name="pads">A collection of all possible Pads</param>
444
        /// <param name="docs">A collection of all possible Documents</param>
13 scott 445
        public void RestoreLayout(string blob)
8 scott 446
        {
13 scott 447
            if (blob == null)
8 scott 448
            {
13 scott 449
                throw new ArgumentNullException("blob");
450
            }
451
            if (blob == string.Empty)
452
            {
453
                return;
454
            }
455
 
456
            // Decode the blob (base64)
457
            char[] charSeparators = new char[] { '.' };
458
            string[] sections = blob.Split(charSeparators, 3, StringSplitOptions.None);
459
 
460
            if (sections.Length != 3)
461
            {
462
                throw new ArgumentOutOfRangeException("blob");
463
            }
464
 
465
            byte[] decbuff;
466
 
467
            decbuff = Convert.FromBase64String(sections[0]);
468
            string padNames = System.Text.Encoding.Unicode.GetString(decbuff);
469
 
470
            decbuff = Convert.FromBase64String(sections[1]);
471
            string docNames = System.Text.Encoding.Unicode.GetString(decbuff);
472
 
473
            decbuff = Convert.FromBase64String(sections[2]);
474
            string layout = System.Text.Encoding.Unicode.GetString(decbuff);
475
 
476
            // PADS
477
            // In case someone tried to show a pad or document before we were loaded
478
            Collection<IPad> oldPads = new Collection<IPad>();
479
            foreach (IPad p in Pads)
480
            {
481
                oldPads.Add(p);
482
            }
483
            Collection<IDocument> oldDocuments = new Collection<IDocument>();
484
            foreach (IDocument d in Documents)
485
            {
486
                oldDocuments.Add(d);
487
            }
488
 
489
            List<string> padNamesList = padNames.Split(new char[] { ',' }).ToList();
490
            foreach (var p in allPads)
491
            {
492
                if (padNamesList.Contains(p.Metadata.Name))
8 scott 493
                {
13 scott 494
                    ShowPad(p.Value);
8 scott 495
                }
496
            }
497
 
13 scott 498
            // DOCUMENTS
499
            DocumentList newList;
500
            XmlSerializer s = new XmlSerializer(typeof(DocumentList));
501
            TextReader r = new StringReader(docNames);
502
            try
8 scott 503
            {
13 scott 504
                newList = (DocumentList)s.Deserialize(r);
505
            }
506
            catch (InvalidOperationException)
507
            {
508
                newList = null;
509
            }
510
            finally
511
            {
512
                r.Close();
513
            }
514
            if (newList != null)
515
            {
516
                Dictionary<string, Collection<string>> docNamesList = new Dictionary<string, Collection<string>>();
517
                foreach (DocumentItem item in newList.Items)
8 scott 518
                {
13 scott 519
                    if (!docNamesList.ContainsKey(item.name))
8 scott 520
                    {
13 scott 521
                        docNamesList.Add(item.name, new Collection<string>());
8 scott 522
                    }
13 scott 523
                    docNamesList[item.name].Add(item.memento);
8 scott 524
                }
13 scott 525
                foreach (var d in allDocuments)
526
                {
527
                    if (docNamesList.ContainsKey(d.Metadata.Name))
528
                    {
529
                        foreach (var memento in docNamesList[d.Metadata.Name])
530
                        {
531
                            ShowDocument(d.Value, memento);
532
                        }
533
                    }
534
                }
8 scott 535
            }
536
 
13 scott 537
            // LAYOUT
538
            TextReader rLayout = new StringReader(layout);
539
            DockManager.RestoreLayout(rLayout);
540
 
541
            // now restore the pads and documents that we already wanted to show
542
            foreach (IPad p in oldPads)
8 scott 543
            {
13 scott 544
                ShowPad(p);
8 scott 545
            }
13 scott 546
            foreach (IDocument d in oldDocuments)
547
            {
548
                ShowDocument(d, d.Memento);
549
            }
8 scott 550
        }
18 scott 551
 
552
        /// <summary>
553
        /// Determine whether the specified IDocument is active.
554
        /// </summary>
555
        /// <param name="document"></param>
556
        /// <returns></returns>
557
        public bool IsActive(IDocument doc)
558
        {
559
            return (DockManager.ActiveDocument == m_documentLookup[doc]);
560
        }
8 scott 561
        #endregion
562
    }
563
}