1 /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Mozilla Communicator client code, released
16 * March 31, 1998.
17 *
18 * The Initial Developer of the Original Code is
19 * David Hyatt.
20 * Portions created by the Initial Developer are Copyright (C) 2002
21 * the Initial Developer. All Rights Reserved.
22 *
23 * Contributor(s):
24 * David Hyatt (hyatt@apple.com)
25 * Blake Ross (blaker@netscape.com)
26 * Joe Hewitt (hewitt@netscape.com)
27 * Michael Buettner <michael.buettner@sun.com>
28 *
29 * Alternatively, the contents of this file may be used under the terms of
30 * either the GNU General Public License Version 2 or later (the "GPL"), or
31 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
40 *
41 * ***** END LICENSE BLOCK ***** */
42
43 /**
44 * Global variables in addition to those specified in
45 * mail/base/content/customizeToolbar.js
46 */
47 var gPreviousLocation = null;
48 var gRepositionOnce = false;
49 var gIsMainApplicationContext = (window.arguments[3] != null);
50
51 /**
52 * Since we want to 'inherit' several global functions
53 * we retrieve the current values in order to call them
54 * from their overriden version.
55 */
56 var gOnLoad = onLoad;
57 var gInitDialog = initDialog;
58 var gOnCancel = onCancel;
59
60 /**
61 * initDialog() gets called from the load handler and
62 * is responsible for initializing global variables, etc.
63 */
64 initDialog = function() {
65
66 // Don't do any extra processing in case we're not
67 // customizing one of the main application toolbars.
68 // The customize toolbar dialog contains special features
69 // that don't apply in that case (location drop down, etc.).
70 if (gIsMainApplicationContext) {
71
72 // remember initial toolbar location and set the
73 // menulist accordingly. this applies to the mode toolbar only.
74 gPreviousLocation = gToolbox.getAttribute("location");
75 document.getElementById("location-list").value = gPreviousLocation;
76
77 // set the toolbar selection according to the current mode.
78 // this list allows to hop from one toolbar to another without
79 // leaving the customize dialog. if this feature is to be used outside
80 // of mail/news main application window, we detect this case and disable
81 // all relevant controls and stuff.
82 var selectorList = document.getElementById("selector-list");
83 selectorList.value = window.arguments[3];
84 if (selectorList.selectedItem.value != window.arguments[3]) {
85 document.getElementById("selector-container").collapsed = true;
86 }
87 }
88
89 // now call the original initDialog() function
90 gInitDialog();
91 }
92
93 /**
94 * onCancel() is called if the customize toolbar dialog has been canceled.
95 * we're returning to the previous state and discarding any changes made
96 * to the current toolbar. please note that this applies to the current toolbar
97 * only. in case we've been switched from one to another, only the changes
98 * made to the most recent toolbar will be discarded.
99 */
100 onCancel = function() {
101
102 // Don't do any extra processing in case we're not
103 // customizing one of the main application toolbars.
104 // The customize toolbar dialog contains special features
105 // that don't apply in that case (location drop down, etc.).
106 if (gIsMainApplicationContext) {
107 updateToolbarLocation(gPreviousLocation);
108 }
109
110 gOnCancel();
111 }
112
113 /**
114 * repositionDialog() to find a good position for the customize dialog.
115 * it is called during initialization and subsequently if any option has been
116 * altered that causes the toolbar to change size or location. we need to
117 * override this function to add the ability to set the position *above*
118 * the toolbar in question, since this is necessary for the mode toolbar
119 * which is set at the lower left of the application window by default.
120 */
121 _repositionDialog = function() {
122
123 // Christian said it's better to not make the dialog jump...
124 if (gRepositionOnce)
125 return;
126 gRepositionOnce = true;
127
128 // Position the dialog touching the bottom of the toolbox and centered with
129 // it. We must resize the window smaller first so that it is positioned properly.
130 var screenX = gToolbox.boxObject.screenX + ((gToolbox.boxObject.width - kWindowWidth) / 2);
131 var screenY = gToolbox.boxObject.screenY + gToolbox.boxObject.height;
132
133 var newHeight = kWindowHeight;
134 if (newHeight >= screen.availHeight - screenY - kVSizeSlop) {
135 // the customize window doesn't fit below the toolbar. first try if there's
136 // enough space at the top of the toolbar. if neither works, shrink the height.
137 if(gToolbox.boxObject.screenY - newHeight - kVSizeSlop < 0) {
138 newHeight = screen.availHeight - screenY - kVSizeSlop;
139 } else {
140 screenY = gToolbox.boxObject.screenY - newHeight;
141 }
142 }
143
144 if(screenX < 0) {
145 screenX = 0;
146 }
147
148 window.resizeTo(kWindowWidth, newHeight);
149 window.moveTo(screenX, screenY);
150 }
151
152 /**
153 * onLoad() is called by the load event handler. we need to override
154 * this function to initialize the newly introduced controls.
155 */
156 onLoad = function() {
157
158 // remove the box containing all the relevant controls from
159 // the dom tree, we bring a new one to the table. this is necessary
160 // since those controls don't specify id's, so we need to override
161 // all of them.
162 var mainbox = document.getElementById("main-box");
163 var controlbox = document.getElementById("control-box");
164 mainbox.removeChild(controlbox.nextSibling);
165
166 // Don't do any extra processing in case we're not
167 // customizing one of the main application toolbars.
168 // The customize toolbar dialog contains special features
169 // that don't apply in that case (location drop down, etc.).
170 if (gIsMainApplicationContext) {
171
172 document.getElementById("selector-container")
173 .removeAttribute("collapsed");
174
175 // show the location option (place toolbar at top or bottom)
176 // if this feature has been requested.
177 updateLocationVisibility(window.arguments[3] == 'mode');
178 }
179
180 // now call the default implementation of the load handler, since
181 // this retrieves the toolbox element from the arguments.
182 gOnLoad();
183 }
184
185 /**
186 * updateToolbarLocation() is called to handle a new location for
187 * the toolbar (top or bottom). basically, we just set the appropriate
188 * attribute on all customizable toolbars and rely on them intercepting
189 * this modification and act accordingly.
190 */
191 _updateToolbarLocation = function(aLocation) {
192
193 // since the current toolbar will change its location in
194 // the dom tree (most probably), we need to unwrap the toolbar items
195 // and reset anything that could get in the way.
196 removeToolboxListeners();
197 unwrapToolbarItems(false);
198
199 var toolboxId = gToolbox.id;
200
201 // set the new location on the toolbox...
202 setAttribute(gToolbox, "location", aLocation);
203 gToolboxDocument.persist(gToolbox.id, "location");
204
205 // ...and each customizable toolbar
206 for (var i = 0; i < gToolbox.childNodes.length; ++i) {
207 var toolbar = getToolbarAt(i);
208 if (isCustomizableToolbar(toolbar)) {
209 setAttribute(toolbar, "location", aLocation);
210 gToolboxDocument.persist(toolbar.id, "location");
211 }
212 }
213
214 gToolbox = gToolboxDocument.getElementById(toolboxId);
215 gToolboxDocument = gToolbox.ownerDocument;
216
217 gToolbox.addEventListener("draggesture", onToolbarDragGesture, false);
218 gToolbox.addEventListener("dragover", onToolbarDragOver, false);
219 gToolbox.addEventListener("dragexit", onToolbarDragExit, false);
220 gToolbox.addEventListener("dragdrop", onToolbarDragDrop, false);
221
222 // Now re-wrap the items on the toolbar, but don't clobber previousset.
223 wrapToolbarItems(false);
224 }
225
226 /**
227 * Handler that takes care of a new toolbar being selected for customization.
228 */
229 _updateToolbarSelection = function(aSelection) {
230
231 var callback = window.arguments[2];
232 if(callback) {
233
234 // first of all, we need to remove our listeners and unwrap
235 // the toolbar items. this is important to do first, before calling
236 // the outside world, since they possibly want to change those items...
237 removeToolboxListeners();
238 unwrapToolbarItems(true);
239
240 // persist the current set of buttons in all
241 // customizable toolbars to localstore.
242 persistCurrentSets();
243
244 // execute the supplied callback function. we expect to receive
245 // the toolbox we're supposed to customize as a result.
246 var toolbox = callback(aSelection);
247
248 // store some internal states in the window arguments
249 // since we're going to call initDialog() again...
250 window.arguments[0] = toolbox;
251 window.arguments[3] = aSelection;
252
253 // show or hide the location menu dependend
254 // on the toolbar we're switching to.
255 updateLocationVisibility(aSelection == 'mode');
256
257 // nothing has changed so far...
258 gToolboxChanged = false;
259
260 // now just call the default load handler in order to
261 // initialize the dialog for a fresh start.
262 gOnLoad();
263 }
264 }
265
266 /**
267 * Show or hide the location selection, this option shouldn't be always visible
268 */
269 _updateLocationVisibility = function(aShow) {
270
271 var controls = document.getElementsByAttribute("location-option", "true");
272 for (var i=0;i<controls.length;i++) {
273 if (aShow) {
274 controls[i].removeAttribute("collapsed");
275 } else {
276 controls[i].setAttribute("collapsed","true");
277 }
278 }
279 }
280
281 /**
282 * Builds the palette of draggable items that are not yet in a toolbar.
283 */
284 _buildPalette = function() {
285
286 // Empty the palette first.
287 var paletteBox = document.getElementById("palette-box");
288 while (paletteBox.lastChild)
289 paletteBox.removeChild(paletteBox.lastChild);
290
291 var currentRow = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
292 "hbox");
293 currentRow.setAttribute("class", "paletteRow");
294
295 // Add the toolbar separator item.
296 var templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
297 "toolbarseparator");
298 templateNode.id = "separator";
299 wrapPaletteItem(templateNode, currentRow, null);
300
301 // Add the toolbar spring item.
302 templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
303 "toolbarspring");
304 templateNode.id = "spring";
305 templateNode.flex = 1;
306 wrapPaletteItem(templateNode, currentRow, null);
307
308 // Add the toolbar spacer item.
309 templateNode = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
310 "toolbarspacer");
311 templateNode.id = "spacer";
312 templateNode.flex = 1;
313 wrapPaletteItem(templateNode, currentRow, null);
314
315 var rowSlot = 3;
316
317 var currentItems = getCurrentItemIds();
318 templateNode = gToolbox.palette.firstChild;
319 while (templateNode) {
320 // Check if the item is already in a toolbar before adding it to the palette.
321 if (!(templateNode.id in currentItems)) {
322
323 var nodeMode = templateNode.getAttribute('mode');
324 if (!nodeMode)
325 nodeMode = 'mail';
326
327 if (nodeMode == window.arguments[3]) {
328
329 var paletteItem = templateNode.cloneNode(true);
330
331 if (rowSlot == kRowMax) {
332 // Append the old row.
333 paletteBox.appendChild(currentRow);
334
335 // Make a new row.
336 currentRow = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
337 "hbox");
338 currentRow.setAttribute("class", "paletteRow");
339 rowSlot = 0;
340 }
341
342 ++rowSlot;
343 wrapPaletteItem(paletteItem, currentRow, null);
344 }
345 }
346
347 templateNode = templateNode.nextSibling;
348 }
349
350 if (currentRow) {
351 fillRowWithFlex(currentRow);
352 paletteBox.appendChild(currentRow);
353 }
354 }
355
356 if (gIsMainApplicationContext) {
357 repositionDialog = _repositionDialog;
358 updateToolbarLocation = _updateToolbarLocation;
359 updateToolbarSelection = _updateToolbarSelection;
360 updateLocationVisibility = _updateLocationVisibility;
361 buildPalette = _buildPalette;
362 }