Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 // **********
6 // Title: trench.js
8 // ##########
9 // Class: Trench
10 //
11 // Class for drag-snapping regions; called "trenches" as they are long and narrow.
13 // Constructor: Trench
14 //
15 // Parameters:
16 // element - the DOM element for Item (GroupItem or TabItem) from which the trench is projected
17 // xory - either "x" or "y": whether the trench's <position> is along the x- or y-axis.
18 // In other words, if "x", the trench is vertical; if "y", the trench is horizontal.
19 // type - either "border" or "guide". Border trenches mark the border of an Item.
20 // Guide trenches extend out (unless they are intercepted) and act as "guides".
21 // edge - which edge of the Item that this trench corresponds to.
22 // Either "top", "left", "bottom", or "right".
23 function Trench(element, xory, type, edge) {
24 //----------
25 // Variable: id
26 // (integer) The id for the Trench. Set sequentially via <Trenches.nextId>
27 this.id = Trenches.nextId++;
29 // ---------
30 // Variables: Initial parameters
31 // element - (DOMElement)
32 // parentItem - <Item> which projects this trench; to be set with setParentItem
33 // xory - (string) "x" or "y"
34 // type - (string) "border" or "guide"
35 // edge - (string) "top", "left", "bottom", or "right"
36 this.el = element;
37 this.parentItem = null;
38 this.xory = xory; // either "x" or "y"
39 this.type = type; // "border" or "guide"
40 this.edge = edge; // "top", "left", "bottom", or "right"
42 this.$el = iQ(this.el);
44 //----------
45 // Variable: dom
46 // (array) DOM elements for visible reflexes of the Trench
47 this.dom = [];
49 //----------
50 // Variable: showGuide
51 // (boolean) Whether this trench will project a visible guide (dotted line) or not.
52 this.showGuide = false;
54 //----------
55 // Variable: active
56 // (boolean) Whether this trench is currently active or not.
57 // Basically every trench aside for those projected by the Item currently being dragged
58 // all become active.
59 this.active = false;
60 this.gutter = Items.defaultGutter;
62 //----------
63 // Variable: position
64 // (integer) position is the position that we should snap to.
65 this.position = 0;
67 //----------
68 // Variables: some Ranges
69 // range - (<Range>) explicit range; this is along the transverse axis
70 // minRange - (<Range>) the minimum active range
71 // activeRange - (<Range>) the currently active range
72 this.range = new Range(0,10000);
73 this.minRange = new Range(0,0);
74 this.activeRange = new Range(0,10000);
75 };
77 Trench.prototype = {
78 // ----------
79 // Function: toString
80 // Prints [Trench edge type (parentItem)] for debug use
81 toString: function Trench_toString() {
82 return "[Trench " + this.edge + " " + this.type +
83 (this.parentItem ? " (" + this.parentItem + ")" : "") +
84 "]";
85 },
87 //----------
88 // Variable: radius
89 // (integer) radius is how far away we should snap from
90 get radius() this.customRadius || Trenches.defaultRadius,
92 setParentItem: function Trench_setParentItem(item) {
93 if (!item.isAnItem) {
94 Utils.assert(false, "parentItem must be an Item");
95 return false;
96 }
97 this.parentItem = item;
98 return true;
99 },
101 //----------
102 // Function: setPosition
103 // set the trench's position.
104 //
105 // Parameters:
106 // position - (integer) px center position of the trench
107 // range - (<Range>) the explicit active range of the trench
108 // minRange - (<Range>) the minimum range of the trench
109 setPosition: function Trench_setPosition(position, range, minRange) {
110 this.position = position;
112 var page = Items.getPageBounds(true);
114 // optionally, set the range.
115 if (Utils.isRange(range)) {
116 this.range = range;
117 } else {
118 this.range = new Range(0, (this.xory == 'x' ? page.height : page.width));
119 }
121 // if there's a minRange, set that too.
122 if (Utils.isRange(minRange))
123 this.minRange = minRange;
125 // set the appropriate bounds as a rect.
126 if (this.xory == "x") // vertical
127 this.rect = new Rect(this.position - this.radius, this.range.min, 2 * this.radius, this.range.extent);
128 else // horizontal
129 this.rect = new Rect(this.range.min, this.position - this.radius, this.range.extent, 2 * this.radius);
131 this.show(); // DEBUG
132 },
134 //----------
135 // Function: setActiveRange
136 // set the trench's currently active range.
137 //
138 // Parameters:
139 // activeRange - (<Range>)
140 setActiveRange: function Trench_setActiveRange(activeRange) {
141 if (!Utils.isRange(activeRange))
142 return false;
143 this.activeRange = activeRange;
144 if (this.xory == "x") { // horizontal
145 this.activeRect = new Rect(this.position - this.radius, this.activeRange.min, 2 * this.radius, this.activeRange.extent);
146 this.guideRect = new Rect(this.position, this.activeRange.min, 0, this.activeRange.extent);
147 } else { // vertical
148 this.activeRect = new Rect(this.activeRange.min, this.position - this.radius, this.activeRange.extent, 2 * this.radius);
149 this.guideRect = new Rect(this.activeRange.min, this.position, this.activeRange.extent, 0);
150 }
151 return true;
152 },
154 //----------
155 // Function: setWithRect
156 // Set the trench's position using the given rect. We know which side of the rect we should match
157 // because we've already recorded this information in <edge>.
158 //
159 // Parameters:
160 // rect - (<Rect>)
161 setWithRect: function Trench_setWithRect(rect) {
163 if (!Utils.isRect(rect))
164 Utils.error('argument must be Rect');
166 // First, calculate the range for this trench.
167 // Border trenches are always only active for the length of this range.
168 // Guide trenches, however, still use this value as its minRange.
169 if (this.xory == "x")
170 var range = new Range(rect.top - this.gutter, rect.bottom + this.gutter);
171 else
172 var range = new Range(rect.left - this.gutter, rect.right + this.gutter);
174 if (this.type == "border") {
175 // border trenches have a range, so set that too.
176 if (this.edge == "left")
177 this.setPosition(rect.left - this.gutter, range);
178 else if (this.edge == "right")
179 this.setPosition(rect.right + this.gutter, range);
180 else if (this.edge == "top")
181 this.setPosition(rect.top - this.gutter, range);
182 else if (this.edge == "bottom")
183 this.setPosition(rect.bottom + this.gutter, range);
184 } else if (this.type == "guide") {
185 // guide trenches have no range, but do have a minRange.
186 if (this.edge == "left")
187 this.setPosition(rect.left, false, range);
188 else if (this.edge == "right")
189 this.setPosition(rect.right, false, range);
190 else if (this.edge == "top")
191 this.setPosition(rect.top, false, range);
192 else if (this.edge == "bottom")
193 this.setPosition(rect.bottom, false, range);
194 }
195 },
197 //----------
198 // Function: show
199 //
200 // Show guide (dotted line), if <showGuide> is true.
201 //
202 // If <Trenches.showDebug> is true, we will draw the trench. Active portions are drawn with 0.5
203 // opacity. If <active> is false, the entire trench will be
204 // very translucent.
205 show: function Trench_show() { // DEBUG
206 if (this.active && this.showGuide) {
207 if (!this.dom.guideTrench)
208 this.dom.guideTrench = iQ("<div/>").addClass('guideTrench').css({id: 'guideTrench'+this.id});
209 var guideTrench = this.dom.guideTrench;
210 guideTrench.css(this.guideRect);
211 iQ("body").append(guideTrench);
212 } else {
213 if (this.dom.guideTrench) {
214 this.dom.guideTrench.remove();
215 delete this.dom.guideTrench;
216 }
217 }
219 if (!Trenches.showDebug) {
220 this.hide(true); // true for dontHideGuides
221 return;
222 }
224 if (!this.dom.visibleTrench)
225 this.dom.visibleTrench = iQ("<div/>")
226 .addClass('visibleTrench')
227 .addClass(this.type) // border or guide
228 .css({id: 'visibleTrench'+this.id});
229 var visibleTrench = this.dom.visibleTrench;
231 if (!this.dom.activeVisibleTrench)
232 this.dom.activeVisibleTrench = iQ("<div/>")
233 .addClass('activeVisibleTrench')
234 .addClass(this.type) // border or guide
235 .css({id: 'activeVisibleTrench'+this.id});
236 var activeVisibleTrench = this.dom.activeVisibleTrench;
238 if (this.active)
239 activeVisibleTrench.addClass('activeTrench');
240 else
241 activeVisibleTrench.removeClass('activeTrench');
243 visibleTrench.css(this.rect);
244 activeVisibleTrench.css(this.activeRect || this.rect);
245 iQ("body").append(visibleTrench);
246 iQ("body").append(activeVisibleTrench);
247 },
249 //----------
250 // Function: hide
251 // Hide the trench.
252 hide: function Trench_hide(dontHideGuides) {
253 if (this.dom.visibleTrench)
254 this.dom.visibleTrench.remove();
255 if (this.dom.activeVisibleTrench)
256 this.dom.activeVisibleTrench.remove();
257 if (!dontHideGuides && this.dom.guideTrench)
258 this.dom.guideTrench.remove();
259 },
261 //----------
262 // Function: rectOverlaps
263 // Given a <Rect>, compute whether it overlaps with this trench. If it does, return an
264 // adjusted ("snapped") <Rect>; if it does not overlap, simply return false.
265 //
266 // Note that simply overlapping is not all that is required to be affected by this function.
267 // Trenches can only affect certain edges of rectangles... for example, a "left"-edge guide
268 // trench should only affect left edges of rectangles. We don't snap right edges to left-edged
269 // guide trenches. For border trenches, the logic is a bit different, so left snaps to right and
270 // top snaps to bottom.
271 //
272 // Parameters:
273 // rect - (<Rect>) the rectangle in question
274 // stationaryCorner - which corner is stationary? by default, the top left.
275 // "topleft", "bottomleft", "topright", "bottomright"
276 // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not
277 // keepProportional - (boolean) if we are allowed to change the rect's size, whether the
278 // dimensions should scaled proportionally or not.
279 //
280 // Returns:
281 // false - if rect does not overlap with this trench
282 // newRect - (<Rect>) an adjusted version of rect, if it is affected by this trench
283 rectOverlaps: function Trench_rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional) {
284 var edgeToCheck;
285 if (this.type == "border") {
286 if (this.edge == "left")
287 edgeToCheck = "right";
288 else if (this.edge == "right")
289 edgeToCheck = "left";
290 else if (this.edge == "top")
291 edgeToCheck = "bottom";
292 else if (this.edge == "bottom")
293 edgeToCheck = "top";
294 } else { // if trench type is guide or barrier...
295 edgeToCheck = this.edge;
296 }
298 rect.adjustedEdge = edgeToCheck;
300 switch (edgeToCheck) {
301 case "left":
302 if (this.ruleOverlaps(rect.left, rect.yRange)) {
303 if (stationaryCorner.indexOf('right') > -1)
304 rect.width = rect.right - this.position;
305 rect.left = this.position;
306 return rect;
307 }
308 break;
309 case "right":
310 if (this.ruleOverlaps(rect.right, rect.yRange)) {
311 if (assumeConstantSize) {
312 rect.left = this.position - rect.width;
313 } else {
314 var newWidth = this.position - rect.left;
315 if (keepProportional)
316 rect.height = rect.height * newWidth / rect.width;
317 rect.width = newWidth;
318 }
319 return rect;
320 }
321 break;
322 case "top":
323 if (this.ruleOverlaps(rect.top, rect.xRange)) {
324 if (stationaryCorner.indexOf('bottom') > -1)
325 rect.height = rect.bottom - this.position;
326 rect.top = this.position;
327 return rect;
328 }
329 break;
330 case "bottom":
331 if (this.ruleOverlaps(rect.bottom, rect.xRange)) {
332 if (assumeConstantSize) {
333 rect.top = this.position - rect.height;
334 } else {
335 var newHeight = this.position - rect.top;
336 if (keepProportional)
337 rect.width = rect.width * newHeight / rect.height;
338 rect.height = newHeight;
339 }
340 return rect;
341 }
342 }
344 return false;
345 },
347 //----------
348 // Function: ruleOverlaps
349 // Computes whether the given "rule" (a line segment, essentially), given by the position and
350 // range arguments, overlaps with the current trench. Note that this function assumes that
351 // the rule and the trench are in the same direction: both horizontal, or both vertical.
352 //
353 // Parameters:
354 // position - (integer) a position in px
355 // range - (<Range>) the rule's range
356 ruleOverlaps: function Trench_ruleOverlaps(position, range) {
357 return (this.position - this.radius < position &&
358 position < this.position + this.radius &&
359 this.activeRange.overlaps(range));
360 },
362 //----------
363 // Function: adjustRangeIfIntercept
364 // Computes whether the given boundary (given as a position and its active range), perpendicular
365 // to the trench, intercepts the trench or not. If it does, it returns an adjusted <Range> for
366 // the trench. If not, it returns false.
367 //
368 // Parameters:
369 // position - (integer) the position of the boundary
370 // range - (<Range>) the target's range, on the trench's transverse axis
371 adjustRangeIfIntercept: function Trench_adjustRangeIfIntercept(position, range) {
372 if (this.position - this.radius > range.min && this.position + this.radius < range.max) {
373 var activeRange = new Range(this.activeRange);
375 // there are three ways this can go:
376 // 1. position < minRange.min
377 // 2. position > minRange.max
378 // 3. position >= minRange.min && position <= minRange.max
380 if (position < this.minRange.min) {
381 activeRange.min = Math.min(this.minRange.min,position);
382 } else if (position > this.minRange.max) {
383 activeRange.max = Math.max(this.minRange.max,position);
384 } else {
385 // this should be impossible because items can't overlap and we've already checked
386 // that the range intercepts.
387 }
388 return activeRange;
389 }
390 return false;
391 },
393 //----------
394 // Function: calculateActiveRange
395 // Computes and sets the <activeRange> for the trench, based on the <GroupItems> around.
396 // This makes it so trenches' active ranges don't extend through other groupItems.
397 calculateActiveRange: function Trench_calculateActiveRange() {
399 // set it to the default: just the range itself.
400 this.setActiveRange(this.range);
402 // only guide-type trenches need to set a separate active range
403 if (this.type != 'guide')
404 return;
406 var groupItems = GroupItems.groupItems;
407 var trench = this;
408 groupItems.forEach(function(groupItem) {
409 if (groupItem.isDragging) // floating groupItems don't block trenches
410 return;
411 if (trench.el == groupItem.container) // groupItems don't block their own trenches
412 return;
413 var bounds = groupItem.getBounds();
414 var activeRange = new Range();
415 if (trench.xory == 'y') { // if this trench is horizontal...
416 activeRange = trench.adjustRangeIfIntercept(bounds.left, bounds.yRange);
417 if (activeRange)
418 trench.setActiveRange(activeRange);
419 activeRange = trench.adjustRangeIfIntercept(bounds.right, bounds.yRange);
420 if (activeRange)
421 trench.setActiveRange(activeRange);
422 } else { // if this trench is vertical...
423 activeRange = trench.adjustRangeIfIntercept(bounds.top, bounds.xRange);
424 if (activeRange)
425 trench.setActiveRange(activeRange);
426 activeRange = trench.adjustRangeIfIntercept(bounds.bottom, bounds.xRange);
427 if (activeRange)
428 trench.setActiveRange(activeRange);
429 }
430 });
431 }
432 };
434 // ##########
435 // Class: Trenches
436 // Singelton for managing all <Trench>es.
437 var Trenches = {
438 // ---------
439 // Variables:
440 // nextId - (integer) a counter for the next <Trench>'s <Trench.id> value.
441 // showDebug - (boolean) whether to draw the <Trench>es or not.
442 // defaultRadius - (integer) the default radius for new <Trench>es.
443 // disabled - (boolean) whether trench-snapping is disabled or not.
444 nextId: 0,
445 showDebug: false,
446 defaultRadius: 10,
447 disabled: false,
449 // ---------
450 // Variables: snapping preferences; used to break ties in snapping.
451 // preferTop - (boolean) prefer snapping to the top to the bottom
452 // preferLeft - (boolean) prefer snapping to the left to the right
453 preferTop: true,
454 get preferLeft() { return !UI.rtl; },
456 trenches: [],
458 // ----------
459 // Function: toString
460 // Prints [Trenches count=count] for debug use
461 toString: function Trenches_toString() {
462 return "[Trenches count=" + this.trenches.length + "]";
463 },
465 // ---------
466 // Function: getById
467 // Return the specified <Trench>.
468 //
469 // Parameters:
470 // id - (integer)
471 getById: function Trenches_getById(id) {
472 return this.trenches[id];
473 },
475 // ---------
476 // Function: register
477 // Register a new <Trench> and returns the resulting <Trench> ID.
478 //
479 // Parameters:
480 // See the constructor <Trench.Trench>'s parameters.
481 //
482 // Returns:
483 // id - (int) the new <Trench>'s ID.
484 register: function Trenches_register(element, xory, type, edge) {
485 var trench = new Trench(element, xory, type, edge);
486 this.trenches[trench.id] = trench;
487 return trench.id;
488 },
490 // ---------
491 // Function: registerWithItem
492 // Register a whole set of <Trench>es using an <Item> and returns the resulting <Trench> IDs.
493 //
494 // Parameters:
495 // item - the <Item> to project trenches
496 // type - either "border" or "guide"
497 //
498 // Returns:
499 // ids - array of the new <Trench>es' IDs.
500 registerWithItem: function Trenches_registerWithItem(item, type) {
501 var container = item.container;
502 var ids = {};
503 ids.left = Trenches.register(container,"x",type,"left");
504 ids.right = Trenches.register(container,"x",type,"right");
505 ids.top = Trenches.register(container,"y",type,"top");
506 ids.bottom = Trenches.register(container,"y",type,"bottom");
508 this.getById(ids.left).setParentItem(item);
509 this.getById(ids.right).setParentItem(item);
510 this.getById(ids.top).setParentItem(item);
511 this.getById(ids.bottom).setParentItem(item);
513 return ids;
514 },
516 // ---------
517 // Function: unregister
518 // Unregister one or more <Trench>es.
519 //
520 // Parameters:
521 // ids - (integer) a single <Trench> ID or (array) a list of <Trench> IDs.
522 unregister: function Trenches_unregister(ids) {
523 if (!Array.isArray(ids))
524 ids = [ids];
525 var self = this;
526 ids.forEach(function(id) {
527 self.trenches[id].hide();
528 delete self.trenches[id];
529 });
530 },
532 // ---------
533 // Function: activateOthersTrenches
534 // Activate all <Trench>es other than those projected by the current element.
535 //
536 // Parameters:
537 // element - (DOMElement) the DOM element of the Item being dragged or resized.
538 activateOthersTrenches: function Trenches_activateOthersTrenches(element) {
539 this.trenches.forEach(function(t) {
540 if (t.el === element)
541 return;
542 if (t.parentItem && (t.parentItem.isAFauxItem || t.parentItem.isDragging))
543 return;
544 t.active = true;
545 t.calculateActiveRange();
546 t.show(); // debug
547 });
548 },
550 // ---------
551 // Function: disactivate
552 // After <activateOthersTrenches>, disactivates all the <Trench>es again.
553 disactivate: function Trenches_disactivate() {
554 this.trenches.forEach(function(t) {
555 t.active = false;
556 t.showGuide = false;
557 t.show();
558 });
559 },
561 // ---------
562 // Function: hideGuides
563 // Hide all guides (dotted lines) en masse.
564 hideGuides: function Trenches_hideGuides() {
565 this.trenches.forEach(function(t) {
566 t.showGuide = false;
567 t.show();
568 });
569 },
571 // ---------
572 // Function: snap
573 // Used to "snap" an object's bounds to active trenches and to the edge of the window.
574 // If the meta key is down (<Key.meta>), it will not snap but will still enforce the rect
575 // not leaving the safe bounds of the window.
576 //
577 // Parameters:
578 // rect - (<Rect>) the object's current bounds
579 // stationaryCorner - which corner is stationary? by default, the top left.
580 // "topleft", "bottomleft", "topright", "bottomright"
581 // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not
582 // keepProportional - (boolean) if we are allowed to change the rect's size, whether the
583 // dimensions should scaled proportionally or not.
584 //
585 // Returns:
586 // (<Rect>) - the updated bounds, if they were updated
587 // false - if the bounds were not updated
588 snap: function Trenches_snap(rect,stationaryCorner,assumeConstantSize,keepProportional) {
589 // hide all the guide trenches, because the correct ones will be turned on later.
590 Trenches.hideGuides();
592 var updated = false;
593 var updatedX = false;
594 var updatedY = false;
596 var snappedTrenches = {};
598 for (var i in this.trenches) {
599 var t = this.trenches[i];
600 if (!t.active)
601 continue;
602 // newRect will be a new rect, or false
603 var newRect = t.rectOverlaps(rect,stationaryCorner,assumeConstantSize,keepProportional);
605 if (newRect) { // if rectOverlaps returned an updated rect...
607 if (assumeConstantSize && updatedX && updatedY)
608 break;
609 if (assumeConstantSize && updatedX && (newRect.adjustedEdge == "left"||newRect.adjustedEdge == "right"))
610 continue;
611 if (assumeConstantSize && updatedY && (newRect.adjustedEdge == "top"||newRect.adjustedEdge == "bottom"))
612 continue;
614 rect = newRect;
615 updated = true;
617 // register this trench as the "snapped trench" for the appropriate edge.
618 snappedTrenches[newRect.adjustedEdge] = t;
620 // if updatedX, we don't need to update x any more.
621 if (newRect.adjustedEdge == "left" && this.preferLeft)
622 updatedX = true;
623 if (newRect.adjustedEdge == "right" && !this.preferLeft)
624 updatedX = true;
626 // if updatedY, we don't need to update x any more.
627 if (newRect.adjustedEdge == "top" && this.preferTop)
628 updatedY = true;
629 if (newRect.adjustedEdge == "bottom" && !this.preferTop)
630 updatedY = true;
632 }
633 }
635 if (updated) {
636 rect.snappedTrenches = snappedTrenches;
637 return rect;
638 }
639 return false;
640 },
642 // ---------
643 // Function: show
644 // <Trench.show> all <Trench>es.
645 show: function Trenches_show() {
646 this.trenches.forEach(function(t) {
647 t.show();
648 });
649 },
651 // ---------
652 // Function: toggleShown
653 // Toggle <Trenches.showDebug> and trigger <Trenches.show>
654 toggleShown: function Trenches_toggleShown() {
655 this.showDebug = !this.showDebug;
656 this.show();
657 }
658 };