-
-
Notifications
You must be signed in to change notification settings - Fork 193
Expand file tree
/
Copy pathPane.js
More file actions
1603 lines (1412 loc) · 59.1 KB
/
Pane.js
File metadata and controls
1603 lines (1412 loc) · 59.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
* Original work Copyright (c) 2014 - 2021 Adobe Systems Incorporated. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/
// @INCLUDE_IN_API_DOCS
/**
* Pane objects host views of files, editors, etc... Clients cannot access
* Pane objects directly. Instead the implementation is protected by the
* MainViewManager -- however View Factories are given a Pane object which
* they can use to add views. References to Pane objects should not be kept
* as they may be destroyed and removed from the DOM.
*
* To get a custom view, there are two components:
*
* 1) A View Factory
* 2) A View Object
*
* View objects are anonymous object that have a particular interface.
*
* Views can be added to a pane but do not have to exist in the Pane object's view list.
* Such views are "temporary views". Temporary views are not serialized with the Pane state
* or reconstituted when the pane is serialized from disk. They are destroyed at the earliest
* opportunity.
*
* Temporary views are added by calling `Pane.showView()` and passing it the view object. The view
* will be destroyed when the next view is shown, the pane is mereged with another pane or the "Close All"
* command is exectuted on the Pane. Temporary Editor Views do not contain any modifications and are
* added to the workingset (and are no longer tempoary views) once the document has been modified. They
* will remain in the working set until closed from that point on.
*
* Views that have a longer life span are added by calling addView to associate the view with a
* filename in the _views object. These views are not destroyed until they are removed from the pane
* by calling one of the following: removeView, removeViews, or _reset
*
* Pane Object Events:
*
* - viewListChange - Whenever there is a file change to a file in the working set. These 2 events: `DocumentManager.pathRemove`
* and `DocumentManager.fileNameChange` will cause a `viewListChange` event so the WorkingSetView can update.
*
* - currentViewChange - Whenever the current view changes.
* (e, newView:View, oldView:View)
*
* - viewDestroy - Whenever a view has been destroyed
* (e, view:View)
*
* View Interface:
*
* The view is an anonymous object which has the following method signatures. see ImageViewer for an example or the sample
* provided with Brackets `src/extensions/samples/BracketsConfigCentral`
*
* ```js
* {
* $el:jQuery
* getFile: function ():!File
* updateLayout: function(forceRefresh:boolean)
* destroy: function()
* getScrollPos: function():*=
* adjustScrollPos: function(state:Object=, heightDelta:number)=
* notifyContainerChange: function()=
* notifyVisibilityChange: function(boolean)=
* focus:function()=
* }
*```
* When views are created they can be added to the pane by calling `pane.addView()`.
* Views can be created and parented by attaching directly to `pane.$el`
*
* this._codeMirror = new CodeMirror(pane.$el, ...)
*
* Factories can create a view that's initially hidden by calling `pane.addView(view)` and passing `false` for the show parameter.
* Hidden views can be later shown by calling `pane.showView(view)`
*
* `$el:jQuery!`
*
* property that stores the jQuery wrapped DOM element of the view. All views must have one so pane objects can manipulate the DOM
* element when necessary (e.g. `showView`, `_reparent`, etc...)
*
* `getFile():File!`
*
* Called throughout the life of a View when the current file is queried by the system.
*
* `updateLayout(forceRefresh:boolean)`
*
* Called to notify the view that it should be resized to fit its parent container. This may be called several times
* or only once. Views can ignore the `forceRefresh` flag. It is used for editor views to force a relayout of the editor
* which probably isn't necessary for most views. Views should implement their html to be dynamic and not rely on this
* function to be called whenever possible.
*
* `destroy()`
*
* Views must implement a destroy method to remove their DOM element at the very least. There is no default
* implementation and views are hidden before this method is called. The Pane object doesn't make assumptions
* about when it is safe to remove a node. In some instances other cleanup must take place before a the DOM
* node is destroyed so the implementation details are left to the view.
*
* Views can implement a simple destroy by calling
*
* this.$el.remove()
*
* These members are optional and need not be implemented by Views
*
* getScrollPos()
* adjustScrollPos()
*
* The system at various times will want to save and restore a view's scroll position. The data returned by `getScrollPos()`
* is specific to the view and will be passed back to `adjustScrollPos()` when the scroll position needs to be restored.
*
* When Modal Bars are invoked, the system calls `getScrollPos()` so that the current scroll psotion of all visible Views can be cached.
* That cached scroll position is later passed to `adjustScrollPos()` along with a height delta. The height delta is used to
* scroll the view so that it doesn't appear to have "jumped" when invoking the Modal Bar.
*
* Height delta will be a positive when the Modal Bar is being shown and negative number when the Modal Bar is being hidden.
*
* `getViewState()` is another optional member that is used to cache a view's state when hiding or destroying a view or closing the project.
* The data returned by this member is stored in `ViewStateManager` and is saved with the project.
*
* Views or View Factories are responsible for restoring the view state when the view of that file is created by recalling the cached state
*
* var view = createIconView(file, pane);
* view.restoreViewState(ViewStateManager.getViewState(file.fullPath));
*
* Notifications
* The following optional methods receive notifications from the Pane object when certain events take place which affect the view:
*
* `notifyContainerChange()`
*
* Optional Notification callback called when the container changes. The view can perform any synchronization or state update
* it needs to do when its parent container changes.
*
* `notifyVisiblityChange()`
*
* Optional Notification callback called when the view's vsibility changes. The view can perform any synchronization or
* state update it needs to do when its visiblity state changes.
*/
define(function (require, exports, module) {
var _ = require("thirdparty/lodash"),
Mustache = require("thirdparty/mustache/mustache"),
EventDispatcher = require("utils/EventDispatcher"),
FileSystem = require("filesystem/FileSystem"),
InMemoryFile = require("document/InMemoryFile"),
Editor = require("editor/Editor").Editor,
ViewStateManager = require("view/ViewStateManager"),
MainViewManager = require("view/MainViewManager"),
PreferencesManager = require("preferences/PreferencesManager"),
DocumentManager = require("document/DocumentManager"),
CommandManager = require("command/CommandManager"),
Commands = require("command/Commands"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
ViewUtils = require("utils/ViewUtils"),
ProjectManager = require("project/ProjectManager"),
paneTemplate = require("text!htmlContent/pane.html");
/**
* Internal pane id
* @const
* @private
*/
var FIRST_PANE = "first-pane";
/**
* Internal pane id
* @const
* @private
*/
var SECOND_PANE = "second-pane";
// Define showPaneHeaderButtons, which controls when to show close and flip-view buttons
// on the header.
PreferencesManager.definePreference("pane.showPaneHeaderButtons", "string", "hover", {
description: Strings.DESCRIPTION_SHOW_PANE_HEADER_BUTTONS,
values: ["hover", "always", "never"]
});
// Define mergePanesWhenLastFileClosed, which controls if a split view pane should be
// closed when the last file is closed, skipping the "Open a file while this pane has focus"
// step completely.
PreferencesManager.definePreference("pane.mergePanesWhenLastFileClosed", "boolean", false, {
description: Strings.DESCRIPTION_MERGE_PANES_WHEN_LAST_FILE_CLOSED
});
/**
* Make an index request object
* @private
* @param {boolean} requestIndex - true to request an index, false if not
* @param {number} index - the index to request
* @return {{indexRequested: boolean, index: number}} An object that can be passed to
* {@link Pane#addToViewList} to insert the item at a specific index
* @see Pane#addToViewList
*/
function _makeIndexRequestObject(requestIndex, index) {
return { indexRequested: requestIndex, index: index };
}
/**
* Ensures that the given pane is focused after other focus related events occur
* @params {string} paneId - paneId of the pane to focus
* @private
*/
function _ensurePaneIsFocused(paneId) {
var pane = MainViewManager._getPane(paneId);
// Defer the focusing until other focus events have occurred.
setTimeout(function () {
// Focus has most likely changed: give it back to the given pane.
pane.focus();
this._lastFocusedElement = pane.$el[0];
MainViewManager.setActivePaneId(paneId);
}, 1);
}
/**
* Pane Objects are constructed by the MainViewManager object when a Pane view is needed.
* @see {@link MainViewManager} for more information
*
* @constructor
* @param {!string} id - The id to use to identify this pane.
* @param {!jQuery} $container - The parent jQuery container to place the pane view.
*/
function Pane(id, $container) {
this._initialize();
// Setup the container and the element we're inserting
var self = this,
showPaneHeaderButtonsPref = PreferencesManager.get("pane.showPaneHeaderButtons"),
$el = $container.append(Mustache.render(paneTemplate, { id: id })).find("#" + id),
$header = $el.find(".pane-header"),
$headerText = $header.find(".pane-header-text"),
$headerFlipViewBtn = $header.find(".pane-header-flipview-btn"),
$headerCloseBtn = $header.find(".pane-header-close-btn"),
$content = $el.find(".pane-content");
$el.on("focusin.pane", function (e) {
self._lastFocusedElement = e.target;
});
// Flips the current file to the other pane when clicked
$headerFlipViewBtn.on("click.pane", function (e) {
var currentFile = self.getCurrentlyViewedFile();
var otherPaneId = self.id === FIRST_PANE ? SECOND_PANE : FIRST_PANE;
var otherPane = MainViewManager._getPane(otherPaneId);
var sameDocInOtherView = otherPane.getViewForPath(currentFile.fullPath);
// If the same doc view is present in the destination, show the file instead of flipping it
if (sameDocInOtherView) {
CommandManager.execute(Commands.FILE_OPEN, {
fullPath: currentFile.fullPath,
paneId: otherPaneId
}).always(function () {
_ensurePaneIsFocused(otherPaneId);
});
return;
}
// Currently active pane is not necessarily self.id as just clicking the button does not
// give focus to the pane. This way it is possible to flip multiple panes to the active one
// without losing focus.
var activePaneIdBeforeFlip = MainViewManager.getActivePaneId();
MainViewManager._moveView(self.id, otherPaneId, currentFile).always(function () {
CommandManager.execute(Commands.FILE_OPEN, {
fullPath: currentFile.fullPath,
paneId: otherPaneId
}).always(function () {
// Trigger view list changes for both panes
self.trigger("viewListChange");
otherPane.trigger("viewListChange");
_ensurePaneIsFocused(activePaneIdBeforeFlip);
});
});
});
// Closes the current view on the pane when clicked. If pane has no files, merge
// panes.
$headerCloseBtn.on("click.pane", function () {
//set clicked pane as active to ensure that this._currentView is updated before closing
MainViewManager.setActivePaneId(self.id);
var file = self.getCurrentlyViewedFile();
if (file) {
CommandManager.execute(Commands.FILE_CLOSE, { File: file, paneId: self.id });
if (!self.getCurrentlyViewedFile() && PreferencesManager.get("pane.mergePanesWhenLastFileClosed")) {
MainViewManager.setLayoutScheme(1, 1);
}
} else {
MainViewManager.setLayoutScheme(1, 1);
}
});
this._lastFocusedElement = $el[0];
// Make these properties read only
Object.defineProperty(this, "id", {
get: function () {
return id;
},
set: function () {
console.error("cannot change the id of a working pane");
}
});
Object.defineProperty(this, "$el", {
get: function () {
return $el;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$header", {
get: function () {
return $header;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$headerText", {
get: function () {
return $headerText;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$headerFlipViewBtn", {
get: function () {
return $headerFlipViewBtn;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$headerCloseBtn", {
get: function () {
return $headerCloseBtn;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$content", {
get: function () {
return $content;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
Object.defineProperty(this, "$container", {
get: function () {
return $container;
},
set: function () {
console.error("cannot change the DOM node of a working pane");
}
});
this.updateHeaderText();
switch (showPaneHeaderButtonsPref) {
case "always":
this.$header.addClass("always-show-header-buttons");
break;
case "never":
this.$headerFlipViewBtn.css("display", "none");
this.$headerCloseBtn.css("display", "none");
break;
}
// Listen to document events so we can update ourself
DocumentManager.on(this._makeEventName("fileNameChange"), _.bind(this._handleFileNameChange, this));
DocumentManager.on(this._makeEventName("pathDeleted"), _.bind(this._handleFileDeleted, this));
MainViewManager.on(this._makeEventName("activePaneChange"), _.bind(this._handleActivePaneChange, this));
MainViewManager.on(this._makeEventName("workingSetAdd"), _.bind(this.updateHeaderText, this));
MainViewManager.on(this._makeEventName("workingSetRemove"), _.bind(this.updateHeaderText, this));
MainViewManager.on(this._makeEventName("workingSetAddList"), _.bind(this.updateHeaderText, this));
MainViewManager.on(this._makeEventName("workingSetRemoveList"), _.bind(this.updateHeaderText, this));
MainViewManager.on(this._makeEventName("paneLayoutChange"), _.bind(this.updateFlipViewIcon, this));
}
EventDispatcher.makeEventDispatcher(Pane.prototype);
/**
* id of the pane
* @readonly
* @type {!string}
*/
Pane.prototype.id = null;
/**
* container where the pane lives
* @readonly
* @type {JQuery}
*/
Pane.prototype.$container = null;
/**
* the wrapped DOM node of this pane
* @readonly
* @type {JQuery}
*/
Pane.prototype.$el = null;
/**
* the wrapped DOM node container that contains name of current view and the switch view button, or informational string if there is no view
* @readonly
* @type {JQuery}
*/
Pane.prototype.$header = null;
/**
* the wrapped DOM node that contains name of current view, or informational string if there is no view
* @readonly
* @type {JQuery}
*/
Pane.prototype.$headerText = null;
/**
* the wrapped DOM node that is used to flip the view to another pane
* @readonly
* @type {JQuery}
*/
Pane.prototype.$headerFlipViewBtn = null;
/**
* close button of the pane
* @readonly
* @type {JQuery}
*/
Pane.prototype.$headerCloseBtn = null;
/**
* the wrapped DOM node that contains views
* @readonly
* @type {JQuery}
*/
Pane.prototype.$content = null;
/**
* The list of files views
* @private
* @type {Array.<File>}
*/
Pane.prototype._viewList = [];
/**
* The list of files views in MRU order
* @private
* @type {Array.<File>}
*/
Pane.prototype._viewListMRUOrder = [];
/**
* The list of files views in Added order
* @private
* @type {Array.<File>}
*/
Pane.prototype._viewListAddedOrder = [];
/**
* Dictionary mapping fullpath to view
* @type {Object.<!string, !View>}
* @private
*/
Pane.prototype._views = {};
/**
* The current view
* @type {?View}
* @private
*/
Pane.prototype._currentView = null;
/**
* The last thing that received a focus event
* @type {?DomElement}
* @private
*/
Pane.prototype._lastFocusedElement = null;
/**
* Initializes the Pane to its default state
* @private
*/
Pane.prototype._initialize = function () {
this._viewList = [];
this._viewListMRUOrder = [];
this._viewListAddedOrder = [];
this._views = {};
this._currentView = null;
this.showInterstitial(true);
};
/**
* Creates a pane event namespaced to this pane
* (pass an empty string to generate just the namespace key to pass to jQuery to turn off all events handled by this pane)
* @private
* @param {!string} name - the name of the event to namespace
* @return {string} an event namespaced to this pane
*/
Pane.prototype._makeEventName = function (name) {
return name + ".pane-" + this.id;
};
/**
* Reparents a view to this pane
* @private
* @param {!View} view - the view to reparent
*/
Pane.prototype._reparent = function (view) {
view.$el.appendTo(this.$content);
this._views[view.getFile().fullPath] = view;
if (view.notifyContainerChange) {
view.notifyContainerChange();
}
};
/**
* Hides the current view if there is one, shows the
* interstitial screen and notifies that the view changed
* @private
*/
Pane.prototype._hideCurrentView = function () {
if (this._currentView) {
var currentView = this._currentView;
this._setViewVisibility(this._currentView, false);
this.showInterstitial(true);
this._currentView = null;
this._notifyCurrentViewChange(null, currentView);
}
};
/**
* moves a view from one pane to another
* @param {!File} file - the File to move
* @param {Pane} destinationPane - the destination pane
* @param {Number} destinationIndex - the working set index of the file in the destination pane
* @return {jQuery.Promise} a promise object which resolves after the view has been moved and its
* replacement document has been opened
* @private
*/
Pane.prototype.moveView = function (file, destinationPane, destinationIndex) {
var self = this,
openNextPromise = new $.Deferred(),
result = new $.Deferred();
// if we're moving the currently viewed file we
// need to open another file so wait for that operation
// to finish before we move the view
if ((this.getCurrentlyViewedPath() === file.fullPath)) {
var nextFile = this.traverseViewListByMRU(1, file.fullPath);
if (nextFile) {
this._execOpenFile(nextFile.fullPath)
.fail(function () {
// the FILE_OPEN failed
self._hideCurrentView();
})
.always(function () {
openNextPromise.resolve();
});
} else {
this._hideCurrentView();
openNextPromise.resolve();
}
} else {
openNextPromise.resolve();
}
// Once the next file has opened, we can
// move the item in the working set and
// open it in the destination pane
openNextPromise.done(function () {
var viewListIndex = self.findInViewList(file.fullPath);
var shouldAddView = viewListIndex !== -1;
var view = self._views[file.fullPath];
// If the file isn't in working set, destroy the view and delete it from
// source pane's view map and return as solved
if (!shouldAddView) {
if (view) {
self._doDestroyView(view);
}
return result.resolve();
}
// Remove file from all 3 view lists
self._viewList.splice(viewListIndex, 1);
self._viewListMRUOrder.splice(self.findInViewListMRUOrder(file.fullPath), 1);
self._viewListAddedOrder.splice(self.findInViewListAddedOrder(file.fullPath), 1);
// insert the view into the working set
destinationPane._addToViewList(file, _makeIndexRequestObject(true, destinationIndex));
// if we had a view, it had previously been opened
// otherwise, the file was in the working set unopened
if (view) {
// delete it from the source pane's view map and add it to the destination pane's view map
delete self._views[file.fullPath];
destinationPane.addView(view, !destinationPane.getCurrentlyViewedFile());
// we're done
result.resolve();
} else if (!destinationPane.getCurrentlyViewedFile()) {
// The view has not have been created and the pane was
// not showing anything so open the file moved in to the pane
destinationPane._execOpenFile(file.fullPath).always(function () {
// wait until the file has been opened before
// we resolve the promise so the working set
// view can sync appropriately
result.resolve();
});
} else {
// nothing to do, we're done
result.resolve();
}
});
return result.promise();
};
/**
* Merges the another Pane object's contents into this Pane
* @param {!Pane} other - Pane from which to copy
*/
Pane.prototype.mergeFrom = function (other) {
// save this because we're setting it to null and we
// may need to destroy it if it's a temporary view
var otherCurrentView = other._currentView;
// Hide the current view while we
// merge the 2 panes together
other._hideCurrentView();
// Copy the File lists
this._viewList = _.union(this._viewList, other._viewList);
this._viewListMRUOrder = _.union(this._viewListMRUOrder, other._viewListMRUOrder);
this._viewListAddedOrder = _.union(this._viewListAddedOrder, other._viewListAddedOrder);
var self = this,
viewsToDestroy = [];
// Copy the views
_.forEach(other._views, function (view) {
var file = view.getFile(),
fullPath = file && file.fullPath;
if (fullPath && other.findInViewList(fullPath) !== -1) {
// switch the container to this Pane
self._reparent(view);
} else {
// We don't copy temporary views so destroy them
viewsToDestroy.push(view);
}
});
// 1-off views
if (otherCurrentView && !other._isViewNeeded(otherCurrentView) && viewsToDestroy.indexOf(otherCurrentView) === -1) {
viewsToDestroy.push(otherCurrentView);
}
// Destroy temporary views
_.forEach(viewsToDestroy, function (view) {
self.trigger("viewDestroy", view);
view.destroy();
});
// this _reset all internal data structures
// and will set the current view to null
other._initialize();
};
/**
* Removes the DOM node for the Pane, removes all
* event handlers and _resets all internal data structures
*/
Pane.prototype.destroy = function () {
if (this._currentView ||
Object.keys(this._views).length > 0 ||
this._viewList.length > 0) {
console.warn("destroying a pane that isn't empty");
}
this._reset();
DocumentManager.off(this._makeEventName(""));
MainViewManager.off(this._makeEventName(""));
this.$el.off(".pane");
this.$el.remove();
};
/**
* Returns a copy of the view file list
* @return {!Array.<File>}
*/
Pane.prototype.getViewList = function () {
return _.clone(this._viewList);
};
/**
* Returns the number of entries in the view file list
* @return {number}
*/
Pane.prototype.getViewListSize = function () {
return this._viewList.length;
};
/**
* Returns the index of the item in the view file list
* @param {!string} fullPath the full path of the item to look for
* @return {number} index of the item or -1 if not found
*/
Pane.prototype.findInViewList = function (fullPath) {
return _.findIndex(this._viewList, function (file) {
return file.fullPath === fullPath;
});
};
/**
* Returns the order in which the item was added
* @param {!string} fullPath the full path of the item to look for
* @return {number} order of the item or -1 if not found
*/
Pane.prototype.findInViewListAddedOrder = function (fullPath) {
return _.findIndex(this._viewListAddedOrder, function (file) {
return file.fullPath === fullPath;
});
};
/**
* Returns the order in which the item was last used
* @param {!string} fullPath the full path of the item to look for
* @return {number} order of the item or -1 if not found.
* 0 indicates most recently used, followed by 1 and so on...
*/
Pane.prototype.findInViewListMRUOrder = function (fullPath) {
return _.findIndex(this._viewListMRUOrder, function (file) {
return file.fullPath === fullPath;
});
};
/**
* Return value from reorderItem when the Item was not found
* @see {@link Pane#reorderItem}
* @const
*/
Pane.prototype.ITEM_NOT_FOUND = -1;
/**
* Return value from reorderItem when the Item was found at its natural index
* and the workingset does not need to be resorted
* @see {@link Pane#reorderItem}
* @const
*/
Pane.prototype.ITEM_FOUND_NO_SORT = 0;
/**
* Return value from reorderItem when the Item was found and reindexed
* and the workingset needs to be resorted
* @see {@link Pane#reorderItem}
* @const
*/
Pane.prototype.ITEM_FOUND_NEEDS_SORT = 1;
/**
* reorders the specified file in the view list to the desired position
*
* @param {File} file - the file object of the item to reorder
* @param {number=} index - the new position of the item
* @param {boolean=} force - true to force the item into that position, false otherwise. (Requires an index be requested)
* @return {number} this function returns one of the following manifest constants:
* ITEM_NOT_FOUND : The request file object was not found
* ITEM_FOUND_NO_SORT : The request file object was found but it was already at the requested index
* ITEM_FOUND_NEEDS_SORT : The request file object was found and moved to a new index and the list should be resorted
*/
Pane.prototype.reorderItem = function (file, index, force) {
var indexRequested = (index !== undefined && index !== null && index >= 0),
curIndex = this.findInViewList(file.fullPath);
if (curIndex !== -1) {
// File is in view list, but not at the specifically requested index - only need to reorder
if (force || (indexRequested && curIndex !== index)) {
var entry = this._viewList.splice(curIndex, 1)[0];
this._viewList.splice(index, 0, entry);
return this.ITEM_FOUND_NEEDS_SORT;
}
return this.ITEM_FOUND_NO_SORT;
}
return this.ITEM_NOT_FOUND;
};
/**
* Determines if a file can be added to our file list
* @private
* @param {!File} file - file object to test
* @return {boolean} true if it can be added, false if not
*/
Pane.prototype._canAddFile = function (file) {
return ((this._views.hasOwnProperty(file.fullPath) && this.findInViewList(file.fullPath) === -1) ||
(MainViewManager._getPaneIdForPath(file.fullPath) !== this.id));
};
/**
* Adds the given file to the end of the workingset, if it is not already in the list
* @private
* @param {!File} file
* @param {Object=} inPlace record with inPlace add data (index, indexRequested). Used internally
*/
Pane.prototype._addToViewList = function (file, inPlace) {
if (inPlace && inPlace.indexRequested) {
// If specified, insert into the workingset at this 0-based index
this._viewList.splice(inPlace.index, 0, file);
} else {
// If no index is specified, just add the file to the end of the workingset.
this._viewList.push(file);
}
// Add to MRU order: either first or last, depending on whether it's already the current doc or not
var currentPath = this.getCurrentlyViewedPath();
if (currentPath && currentPath === file.fullPath) {
this._viewListMRUOrder.unshift(file);
} else {
this._viewListMRUOrder.push(file);
}
// Add first to Added order
this._viewListAddedOrder.unshift(file);
};
/**
* Adds the given file to the end of the workingset, if it is not already in the list
* Does not change which document is currently open in the editor. Completes synchronously.
* @param {!File} file - file to add
* @param {number=} index - position where to add the item
* @return {number} index of where the item was added
*/
Pane.prototype.addToViewList = function (file, index) {
var indexRequested = (index !== undefined && index !== null && index >= 0 && index < this._viewList.length);
this._addToViewList(file, _makeIndexRequestObject(indexRequested, index));
if (!indexRequested) {
index = this._viewList.length - 1;
}
return index;
};
/**
* Adds the given file list to the end of the workingset.
* @param {!Array.<File>} fileList
* @return {!Array.<File>} list of files added to the list
*/
Pane.prototype.addListToViewList = function (fileList) {
var self = this,
uniqueFileList = [];
// Process only files not already in view list
fileList.forEach(function (file) {
if (self._canAddFile(file)) {
self._addToViewList(file);
uniqueFileList.push(file);
}
});
return uniqueFileList;
};
/**
* Dispatches a currentViewChange event
* @private
* @param {?View} newView - the view become the current view
* @param {?View} oldView - the view being replaced
*/
Pane.prototype._notifyCurrentViewChange = function (newView, oldView) {
this.updateHeaderText();
this.trigger("currentViewChange", newView, oldView);
};
/**
* Destroys a view and removes it from the view map. If it is the current view then the view
* is first hidden and the interstitial page is displayed
* @private
* @param {!View} view - view to destroy
*/
Pane.prototype._doDestroyView = function (view) {
if (this._currentView === view) {
// if we're removing the current
// view then we need to hide the view
this._hideCurrentView();
}
delete this._views[view.getFile().fullPath];
this.trigger("viewDestroy", view);
view.destroy();
};
/**
* Removes the specifed file from all internal lists, destroys the view of the file (if there is one)
* and shows the interstitial page if the current view is destroyed
* @private
* @param {!File} file - file to remove
* @param {boolean} preventViewChange - false to hide the current view if removing the current view, true
* to prevent the current view from changing.
*
* When passing true for preventViewChange, it is assumed that the caller will perform an OPEN_FILE op
* to show the next file in line to view. Since the file was removed from the workingset in _doRemove
* its view is now considered to be a temporary view and the call to showView for the OPEN_FILE op
* will destroy the view. the caller needs to handle the reject case in the event of failure
*
* @return {boolean} true if removed, false if the file was not found either in a list or view
*/
Pane.prototype._doRemove = function (file, preventViewChange) {
// If it's in the view list then we need to remove it
var index = this.findInViewList(file.fullPath);
if (index > -1) {
// Remove it from all 3 view lists
this._viewList.splice(index, 1);
this._viewListMRUOrder.splice(this.findInViewListMRUOrder(file.fullPath), 1);
this._viewListAddedOrder.splice(this.findInViewListAddedOrder(file.fullPath), 1);
}
// Destroy the view
var view = this._views[file.fullPath];
if (view) {
if (!preventViewChange) {
this._doDestroyView(view);
}
}
return ((index > -1) || Boolean(view));
};
/**
* Moves the specified file to the front of the MRU (Most Recently Used) list.
* @param {!File} file - The file to move to the front of the MRU list.
*/
Pane.prototype.makeViewMostRecent = function (file) {
var index = this.findInViewListMRUOrder(file.fullPath);
if (index !== -1) {
this._viewListMRUOrder.splice(index, 1);
this._viewListMRUOrder.unshift(file);
}
};
/**
* Sorts items in the pane's view list.
* @param {function(string, string, string): number} compareFn - The function used to compare items in the view list.
*/
Pane.prototype.sortViewList = function (compareFn) {
this._viewList.sort(_.partial(compareFn, this.id));
};
/**
* Moves a working set item from one index to another, shifting the items after
* it in the working set up and reinserting it at the desired location.
* @param {!number} fromIndex - The index of the item to move.
* @param {!number} toIndex - The index to move the item to.
* @private
*/
Pane.prototype.moveWorkingSetItem = function (fromIndex, toIndex) {
this._viewList.splice(toIndex, 0, this._viewList.splice(fromIndex, 1)[0]);
};
/**
* Swaps two items in the file view list (used while dragging items in the working set view)
* @param {number} index1 - the index of the first item to swap
* @param {number} index2 - the index of the second item to swap
* @return {boolean}} true