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/. */
4
5this.EXPORTED_SYMBOLS = ["CommonDialog"];
6
7const Ci = Components.interfaces;
8const Cr = Components.results;
9const Cc = Components.classes;
10const Cu = Components.utils;
11
12Cu.import("resource://gre/modules/Services.jsm");
13Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
15                                  "resource://gre/modules/SharedPromptUtils.jsm");
16
17
18this.CommonDialog = function CommonDialog(args, ui) {
19    this.args = args;
20    this.ui   = ui;
21}
22
23CommonDialog.prototype = {
24    args : null,
25    ui   : null,
26
27    hasInputField : true,
28    numButtons    : undefined,
29    iconClass     : undefined,
30    soundID       : undefined,
31    focusTimer    : null,
32
33    onLoad : function(xulDialog) {
34        switch (this.args.promptType) {
35          case "alert":
36          case "alertCheck":
37            this.hasInputField = false;
38            this.numButtons    = 1;
39            this.iconClass     = ["alert-icon"];
40            this.soundID       = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
41            break;
42          case "confirmCheck":
43          case "confirm":
44            this.hasInputField = false;
45            this.numButtons    = 2;
46            this.iconClass     = ["question-icon"];
47            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
48            break;
49          case "confirmEx":
50            var numButtons = 0;
51            if (this.args.button0Label)
52                numButtons++;
53            if (this.args.button1Label)
54                numButtons++;
55            if (this.args.button2Label)
56                numButtons++;
57            if (this.args.button3Label)
58                numButtons++;
59            if (numButtons == 0)
60                throw "A dialog with no buttons? Can not haz.";
61            this.numButtons    = numButtons;
62            this.hasInputField = false;
63            this.iconClass     = ["question-icon"];
64            this.soundID       = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
65            break;
66          case "prompt":
67            this.numButtons = 2;
68            this.iconClass  = ["question-icon"];
69            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
70            this.initTextbox("login", this.args.value);
71            // Clear the label, since this isn't really a username prompt.
72            this.ui.loginLabel.setAttribute("value", "");
73            break;
74          case "promptUserAndPass":
75            this.numButtons = 2;
76            this.iconClass  = ["authentication-icon", "question-icon"];
77            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
78            this.initTextbox("login",     this.args.user);
79            this.initTextbox("password1", this.args.pass);
80            break;
81          case "promptPassword":
82            this.numButtons = 2;
83            this.iconClass  = ["authentication-icon", "question-icon"];
84            this.soundID    = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
85            this.initTextbox("password1", this.args.pass);
86            // Clear the label, since the message presumably indicates its purpose.
87            this.ui.password1Label.setAttribute("value", "");
88            break;
89          default:
90            Cu.reportError("commonDialog opened for unknown type: " + this.args.promptType);
91            throw "unknown dialog type";
92        }
93
94        // set the document title
95        let title = this.args.title;
96        // OS X doesn't have a title on modal dialogs, this is hidden on other platforms.
97        let infoTitle = this.ui.infoTitle;
98        infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));
99        if (xulDialog)
100            xulDialog.ownerDocument.title = title;
101
102        // Set button labels and visibility
103        //
104        // This assumes that button0 defaults to a visible "ok" button, and
105        // button1 defaults to a visible "cancel" button. The other 2 buttons
106        // have no default labels (and are hidden).
107        switch (this.numButtons) {
108          case 4:
109            this.setLabelForNode(this.ui.button3, this.args.button3Label);
110            this.ui.button3.hidden = false;
111            // fall through
112          case 3:
113            this.setLabelForNode(this.ui.button2, this.args.button2Label);
114            this.ui.button2.hidden = false;
115            // fall through
116          case 2:
117            // Defaults to a visible "cancel" button
118            if (this.args.button1Label)
119                this.setLabelForNode(this.ui.button1, this.args.button1Label);
120            break;
121
122          case 1:
123            this.ui.button1.hidden = true;
124            break;
125        }
126        // Defaults to a visible "ok" button
127        if (this.args.button0Label)
128            this.setLabelForNode(this.ui.button0, this.args.button0Label);
129
130        // display the main text
131        let croppedMessage = "";
132        if (this.args.text) {
133            // Bug 317334 - crop string length as a workaround.
134            croppedMessage = this.args.text.substr(0, 10000);
135        }
136        let infoBody = this.ui.infoBody;
137        infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));
138
139        let label = this.args.checkLabel;
140        if (label) {
141            // Only show the checkbox if label has a value.
142            this.ui.checkboxContainer.hidden = false;
143            this.setLabelForNode(this.ui.checkbox, label);
144            this.ui.checkbox.checked = this.args.checked;
145        }
146
147        // set the icon
148        let icon = this.ui.infoIcon;
149        if (icon)
150            this.iconClass.forEach((el, idx, arr) => icon.classList.add(el));
151
152        // set default result to cancelled
153        this.args.ok = false;
154        this.args.buttonNumClicked = 1;
155
156
157        // Set the default button
158        let b = (this.args.defaultButtonNum || 0);
159        let button = this.ui["button" + b];
160
161        if (xulDialog)
162            xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
163        else
164            button.setAttribute("default", "true");
165
166        // Set default focus / selection.
167        this.setDefaultFocus(true);
168
169        if (this.args.enableDelay) {
170            this.delayHelper = new EnableDelayHelper({
171                disableDialog: () => this.setButtonsEnabledState(false),
172                enableDialog: () => this.setButtonsEnabledState(true),
173                focusTarget: this.ui.focusTarget
174            });
175        }
176
177        // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
178        try {
179            if (xulDialog && this.soundID) {
180                Cc["@mozilla.org/sound;1"].
181                createInstance(Ci.nsISound).
182                playEventSound(this.soundID);
183            }
184        } catch (e) {
185            Cu.reportError("Couldn't play common dialog event sound: " + e);
186        }
187
188        let topic = "common-dialog-loaded";
189        if (!xulDialog)
190            topic = "tabmodal-dialog-loaded";
191        Services.obs.notifyObservers(this.ui.prompt, topic, null);
192    },
193
194    setLabelForNode: function(aNode, aLabel) {
195        // This is for labels which may contain embedded access keys.
196        // If we end in (&X) where X represents the access key, optionally preceded
197        // by spaces and/or followed by the ':' character, store the access key and
198        // remove the access key placeholder + leading spaces from the label.
199        // Otherwise a character preceded by one but not two &s is the access key.
200        // Store it and remove the &.
201
202        // Note that if you change the following code, see the comment of
203        // nsTextBoxFrame::UpdateAccessTitle.
204        var accessKey = null;
205        if (/ *\(\&([^&])\)(:?)$/.test(aLabel)) {
206            aLabel = RegExp.leftContext + RegExp.$2;
207            accessKey = RegExp.$1;
208        } else if (/^([^&]*)\&(([^&]).*$)/.test(aLabel)) {
209            aLabel = RegExp.$1 + RegExp.$2;
210            accessKey = RegExp.$3;
211        }
212
213        // && is the magic sequence to embed an & in your label.
214        aLabel = aLabel.replace(/\&\&/g, "&");
215        aNode.label = aLabel;
216
217        // XXXjag bug 325251
218        // Need to set this after aNode.setAttribute("value", aLabel);
219        if (accessKey)
220            aNode.accessKey = accessKey;
221    },
222
223
224    initTextbox : function (aName, aValue) {
225        this.ui[aName + "Container"].hidden = false;
226        this.ui[aName + "Textbox"].setAttribute("value",
227                                                aValue !== null ? aValue : "");
228    },
229
230    setButtonsEnabledState : function(enabled) {
231        this.ui.button0.disabled = !enabled;
232        // button1 (cancel) remains enabled.
233        this.ui.button2.disabled = !enabled;
234        this.ui.button3.disabled = !enabled;
235    },
236
237    setDefaultFocus : function(isInitialLoad) {
238        let b = (this.args.defaultButtonNum || 0);
239        let button = this.ui["button" + b];
240
241        if (!this.hasInputField) {
242            let isOSX = ("nsILocalFileMac" in Components.interfaces);
243            if (isOSX)
244                this.ui.infoBody.focus();
245            else
246                button.focus();
247        } else if (this.args.promptType == "promptPassword") {
248            // When the prompt is initialized, focus and select the textbox
249            // contents. Afterwards, only focus the textbox.
250            if (isInitialLoad)
251                this.ui.password1Textbox.select();
252            else
253                this.ui.password1Textbox.focus();
254        } else if (isInitialLoad) {
255                this.ui.loginTextbox.select();
256        } else {
257                this.ui.loginTextbox.focus();
258        }
259    },
260
261    onCheckbox : function() {
262        this.args.checked = this.ui.checkbox.checked;
263    },
264
265    onButton0 : function() {
266        this.args.promptActive = false;
267        this.args.ok = true;
268        this.args.buttonNumClicked = 0;
269
270        let username = this.ui.loginTextbox.value;
271        let password = this.ui.password1Textbox.value;
272
273        // Return textfield values
274        switch (this.args.promptType) {
275          case "prompt":
276            this.args.value = username;
277            break;
278          case "promptUserAndPass":
279            this.args.user = username;
280            this.args.pass = password;
281            break;
282          case "promptPassword":
283            this.args.pass = password;
284            break;
285        }
286    },
287
288    onButton1 : function() {
289        this.args.promptActive = false;
290        this.args.buttonNumClicked = 1;
291    },
292
293    onButton2 : function() {
294        this.args.promptActive = false;
295        this.args.buttonNumClicked = 2;
296    },
297
298    onButton3 : function() {
299        this.args.promptActive = false;
300        this.args.buttonNumClicked = 3;
301    },
302
303    abortPrompt : function() {
304        this.args.promptActive = false;
305        this.args.promptAborted = true;
306    },
307
308};
309