summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/bindings/prefwindow.xml
blob: 64173b6c7650f9b6cc6db4d91854ef5211e9d4b9 (plain)
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
<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!--
    SeaMonkey Extended Preferences Window Framework

    The binding implemented here mostly works like its toolkit ancestor, with
    one important difference: the <prefwindow> recognizes the first <tree> in
    its content and assumes that this is the navigation tree:

      <prefwindow>
        <tree>
          ...
          <treeitem id="prefTreeItemA" prefpane="prefPaneA">
          ...
        </tree>
        <prefpane id="prefPaneB">...</prefpane>
        <prefpane id="prefPaneA">
      </prefwindow>

    The <tree> structure defines the hierarchical layout of the preference
    window's navigation tree. A <treeitem>'s "prefpane" attribute references
    one of the <prefpane>s given on the <prefwindow>'s main level.
    All <prefpane>s not referenced by a <treeitem> will be appended to the
    navigation tree's top level. <treeitem>s can be nested as needed, but
    <treeitem>s without a related <prefpane> will be hidden.

    Furthermore, if the <prefwindow> has attribute "autopanes" set to "true",
    non-existing <prefpane>s will be generated automatically from certain
    attributes of the <treeitem>:
    - "url" must contain the <prefpane>'s url
    - "prefpane" should contain the <prefpane>'s desired id,
      otherwise its url will be used as id
    - "helpTopic" may contain an index into SeaMonkey's help

    Unlike in XPFE, where preferences panels were loaded into a separate
    iframe, <prefpane>s are an integral part of the <prefwindow> document,
    by virtue of loadOverlay. Hence <script>s will be loaded into the
    <prefwindow> scope and possibly clash. To avoid this, <prefpane>s should
    specify a "script" attribute with a whitespace delimited list of scripts
    to load into the <prefpane>'s context. The subscriptloader will take care
    of any internal scoping, so no this.* fest is necessary inside the script.

    <prefwindow> users who want to share the very same file between SeaMonkey
    and other toolkit apps should hide the <tree> (set <tree>.hidden=true);
    this binding will then unhide the <tree> if necessary, ie more than just
    one <prefpane> exists.
    Also, the <tree> will get the class "prefnavtree" added, so that it may be
    prestyled by the SeaMonkey themes.
    Setting <prefwindow xpfe="false"> will enforce the application of just the
    basic toolkit <prefwindow> even in SeaMonkey. The same "xpfe" attribute
    exists for <prefpane>, too.
-->

<!DOCTYPE bindings [
  <!ENTITY % dtdPrefs       SYSTEM "chrome://communicator/locale/pref/preferences.dtd"> %dtdPrefs;
  <!ENTITY % dtdGlobalPrefs SYSTEM "chrome://global/locale/preferences.dtd"> %dtdGlobalPrefs;
]>

<bindings id="prefwindowBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="prefwindow"
           extends="chrome://communicator/content/bindings/preferences.xml#prefwindow">
    <resources>
      <stylesheet src="chrome://communicator/skin/preferences.css"/>
    </resources>

    <!-- The only difference between the following <content> and its toolkit
         ancestor is the help button and the 'navTrees' <vbox> before the 'paneDeck'! -->
    <content dlgbuttons="accept,cancel" persist="lastSelected screenX screenY"
             closebuttonlabel="&preferencesCloseButton.label;"
             closebuttonaccesskey="&preferencesCloseButton.accesskey;"
             role="dialog">
      <xul:radiogroup anonid="selector" orient="horizontal" class="paneSelector chromeclass-toolbar"
                      role="listbox"/> <!-- Expose to accessibility APIs as a listbox -->
      <xul:hbox flex="1" class="paneDeckContainer">
        <xul:vbox anonid="navTrees">
          <children includes="tree"/>
        </xul:vbox>
        <xul:vbox flex="1">
          <xul:dialogheader anonid="paneHeader" hidden="true"/>
          <xul:deck anonid="paneDeck" flex="1">
            <children includes="prefpane"/>
          </xul:deck>
        </xul:vbox>
      </xul:hbox>
      <xul:hbox anonid="dlg-buttons" class="prefWindow-dlgbuttons" pack="end">
#ifdef XP_UNIX
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
        <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
        <xul:spacer anonid="spacer" flex="1"/>
        <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
        <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
#else
        <xul:button dlgtype="extra2" class="dialog-button" hidden="true"/>
        <xul:spacer anonid="spacer" flex="1"/>
        <xul:button dlgtype="accept" class="dialog-button" icon="accept"/>
        <xul:button dlgtype="extra1" class="dialog-button" hidden="true"/>
        <xul:button dlgtype="cancel" class="dialog-button" icon="cancel"/>
        <xul:button dlgtype="help" class="dialog-button" hidden="true" icon="help"/>
        <xul:button dlgtype="disclosure" class="dialog-button" hidden="true"/>
#endif
      </xul:hbox>
      <xul:hbox>
        <children/>
      </xul:hbox>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
          var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

          // grab the first child tree and try to tie it to the prefpanes
          var tree = this.getElementsByTagName('tree')[0];
          this.initNavigationTree(tree);
          // hide the toolkit pref strip if we have a tree
          if (this._navigationTree)
            this._selector.hidden = true;
        ]]>
      </constructor>

      <field name="_navigationTree">null</field>

      <!-- <prefwindow> users can call this method to exchange the <tree> -->
      <method name="initNavigationTree">
        <parameter name="aTreeElement"/>
        <body>
        <![CDATA[
          this._navigationTree = null;
          if (!aTreeElement)
            return;

          // don't grab trees in prefpanes etc.
          if (aTreeElement.parentNode != this)
            return;

          // autogenerate <prefpane>s from <treecell>.url if requested
          var autopanes = (this.getAttribute('autopanes') == 'true');
          if (!autopanes)
          {
            // without autopanes, we can return early: don't bother
            // with a navigation tree if we only have one prefpane
            aTreeElement.hidden = (this.preferencePanes.length < 2);
            if (aTreeElement.hidden)
              return;
          }

          // ensure that we have a tree body
          if (!aTreeElement.getElementsByTagName('treechildren').length)
            aTreeElement.appendChild(document.createElement('treechildren'));

          // ensure that we have a tree column
          if (!aTreeElement.getElementsByTagName('treecol').length)
          {
            var navcols = document.createElement('treecols');
            var navcol  = document.createElement('treecol');
            navcol.setAttribute('id', 'navtreecol');
            navcol.setAttribute('primary', true);
            navcol.setAttribute('flex', 1);
            navcol.setAttribute('hideheader', true);
            navcols.appendChild(navcol);
            aTreeElement.appendChild(navcols);
            aTreeElement.setAttribute('hidecolumnpicker', true);
          }

          // add the class "prefnavtree", so that themes can set defaults
          aTreeElement.className += ' prefnavtree';

          // Do some magic with the treeitem ingredient:
          // - if it has a label attribute but no treerow child,
          //   generate a treerow with a treecell child with that label
          // - if it has a prefpane attribute, tie it to that panel
          // - if still no panel found and a url attribute is present,
          //   autogenerate the prefpane and connect to it
          var treeitems = aTreeElement.getElementsByTagName('treeitem');
          for (var i = 0; i < treeitems.length; ++i)
          {
            var node  = treeitems[i];
            var label = node.getAttribute('label');
            if (label)
            {
              // autocreate the treecell?
              var row = node.firstChild;
              while (row && row.nodeName != 'treerow')
                row = row.nextSibling;
              if (!row)
              {
                var itemrow  = document.createElement('treerow');
                var itemcell = document.createElement('treecell');
                itemcell.setAttribute('label', label);
                itemrow.appendChild(itemcell);
                node.appendChild(itemrow);
              }
            }
            var paneID = node.getAttribute('prefpane');
            var pane = paneID && document.getElementById(paneID);
            if (!pane && autopanes)
            {
              // if we have a url, create a <prefpane> for it
              var paneURL = node.getAttribute('url');
              if (paneURL)
              {
                // reuse paneID if present, else use the url as id
                pane = document.createElement('prefpane');
                pane.setAttribute('id',    paneID || paneURL);
                pane.setAttribute('src',   paneURL);
                pane.setAttribute('label', label || paneID || paneURL);
                var helpTopic = node.getAttribute('helpTopic');
                if (helpTopic)
                {
                  pane.setAttribute('helpURI',   'chrome://communicator/locale/help/suitehelp.rdf');
                  pane.setAttribute('helpTopic', helpTopic);
                }
                // add pane to prefwindow
                this.appendChild(pane);
              }
            }
            node.prefpane = pane;
            if (pane)
              pane.preftreeitem = node;
            // hide unused treeitems
            node.hidden = !pane;
          }

          // now that the number of <prefpane>s is known, try to return early:
          // don't bother with a navigation tree if we only have one prefpane
          aTreeElement.hidden = (this.preferencePanes.length < 2);
          if (aTreeElement.hidden)
            return;
          this._navigationTree = aTreeElement;

          // append any still unreferenced <prefpane>s to the tree's top level
          for (var j = 0; j < this.preferencePanes.length; ++j)
          {
            // toolkit believes in fancy pane resizing - we don't
            var lostpane = this.preferencePanes[j];
            lostpane.setAttribute('flex', 1);

            if (!("preftreeitem" in lostpane))
            {
              var treebody = this._navigationTree
                                 .getElementsByTagName('treechildren')[0];
              var treeitem = document.createElement('treeitem');
              var treerow  = document.createElement('treerow');
              var treecell = document.createElement('treecell');
              var label = lostpane.getAttribute('label');
              if (!label)
                label = lostpane.getAttribute('id');
              treecell.setAttribute('label', label);
              treerow.appendChild(treecell);
              treeitem.appendChild(treerow);
              treebody.appendChild(treeitem);
              treeitem.prefpane     = lostpane;
              lostpane.preftreeitem = treeitem;
            }
          }

          // Some parts of the toolkit base binding's initialization code (like
          // panel select events) "fire" before we get here. Thus, we may need
          // to sync the tree manually now (again), if we added any panels or
          // if toolkit failed to select one.
          // (This is a loose copy from the toolkit ctor.)
          var lastPane = this.lastSelected &&
                         document.getElementById(this.lastSelected);
          if (!lastPane)
            this.lastSelected = "";
          if ("arguments" in window && window.arguments[0])
          {
            var initialPane = document.getElementById(window.arguments[0]);
            if (initialPane && initialPane.nodeName == "prefpane")
            {
              this.currentPane = initialPane;
              this.lastSelected = initialPane.id;
            }
          }
          else if (lastPane)
            this.currentPane = lastPane;
          try
          {
            this.showPane(this.currentPane); // may need to load it first
            this.syncTreeWithPane(this.currentPane, true);
          }
          catch (e)
          {
            dump('***** broken prefpane: ' + this.currentPane.id + '\n' + e + '\n');
          }
        ]]>
        </body>
      </method>

      <!-- don't do any fancy animations -->
      <property name="_shouldAnimate" onget="return false;"/>

      <method name="setPaneTitle">
        <parameter name="aPaneElement"/>
        <body>
#ifndef XP_MACOSX
        <![CDATA[
          // show pane title, if given
          var paneHeader = document.getAnonymousElementByAttribute(this, 'anonid', 'paneHeader');
          var paneHeaderLabel = '';
          if (aPaneElement)
            paneHeaderLabel = aPaneElement.getAttribute('label');
          paneHeader.hidden = !paneHeaderLabel;
          if (!paneHeader.hidden)
            paneHeader.setAttribute('title', paneHeaderLabel);
        ]]>
#endif
        </body>
      </method>

      <method name="syncPaneWithTree">
        <parameter name="aTreeIndex"/>
        <body>
        <![CDATA[
          var pane = null;
          if ((this._navigationTree) && (aTreeIndex >= 0))
          {
            // load the prefpane associated with this treeitem
            var treeitem = this._navigationTree.contentView
                               .getItemAtIndex(aTreeIndex);
            if ('prefpane' in treeitem)
            {
              pane = treeitem.prefpane;
              if (pane && (this.currentPane != pane))
              {
                try
                {
                  this.showPane(pane); // may need to load it first
                }
                catch (e)
                {
                  dump('***** broken prefpane: ' + pane.id + '\n' + e + '\n');
                  pane = null;
                }
              }
            }
          }
          // don't show broken panels
          this._paneDeck.hidden = (pane == null);
          this.setPaneTitle(pane);
        ]]>
        </body>
      </method>

      <method name="syncTreeWithPane">
        <parameter name="aPane"/>
        <parameter name="aExpand"/>
        <body>
        <![CDATA[
          if (this._navigationTree && aPane)
          {
            if ('preftreeitem' in aPane)
            {
              // make sure the treeitem is visible
              var container = aPane.preftreeitem;
              if (!aExpand)
                container = container.parentNode.parentNode;
              while (container != this._navigationTree)
              {
                container.setAttribute('open', true);
                container = container.parentNode.parentNode;
              }

              // mark selected pane in navigation tree
              var index = this._navigationTree.contentView
                              .getIndexOfItem(aPane.preftreeitem);
              this._navigationTree.view.selection.select(index);
            }
          }
          this.setPaneTitle(aPane);
          if (this.getAttribute("overflow") != "auto")
          {
            if (this.scrollHeight > window.innerHeight)
              window.innerHeight = this.scrollHeight;
            if (this.scrollWidth > window.innerWidth)
              window.innerWidth = this.scrollWidth;
          }
        ]]>
        </body>
      </method>

    <!-- copied from contextHelp.js
         Locate existing help window for this helpFileURI. -->
      <method name="locateHelpWindow">
        <parameter name="helpFileURI"/>
        <body>
        <![CDATA[
          const iterator = Services.wm.getEnumerator("suite:help");
          var topWindow = null;
          var aWindow;

          // Loop through help windows looking for one with selected helpFileURI
          while (iterator.hasMoreElements())
          {
            aWindow = iterator.getNext();
            if (aWindow.closed)
              continue;
            if (aWindow.getHelpFileURI() == helpFileURI)
              topWindow = aWindow;
          }
          return topWindow;
        ]]>
        </body>
      </method>

    <!-- copied from contextHelp.js
         Opens up the Help Viewer with the specified topic and helpFileURI. -->
      <method name="openHelp">
        <parameter name="topic"/>
        <parameter name="helpFileURI"/>
        <body>
        <![CDATA[
          // Empty help windows are not helpful...
          if (!helpFileURI)
            return;

          // Try to find previously opened help.
          var topWindow = this.locateHelpWindow(helpFileURI);
          if (topWindow)
          {
            // Open topic in existing window.
            topWindow.focus();
            topWindow.displayTopic(topic);
          }
          else
          {
            // Open topic in new window.
            const params = Cc["@mozilla.org/embedcomp/dialogparam;1"]
                             .createInstance(Ci.nsIDialogParamBlock);
            params.SetNumberStrings(2);
            params.SetString(0, helpFileURI);
            params.SetString(1, topic);
            Services.ww.openWindow(null,
                                   "chrome://help/content/help.xul",
                                   "_blank", 
                                   "chrome,all,alwaysRaised,dialog=no",
                                   params);
          }
        ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="dialoghelp">
      <![CDATA[
        this.openHelp(this.currentPane.helpTopic, this.currentPane.getAttribute("helpURI"));
      ]]>
      </handler>
      <handler event="select">
      <![CDATA[
        // navigation tree select or deck change?
        var target = event.originalTarget;
        if (target == this._navigationTree)
        {
          this.syncPaneWithTree(target.currentIndex);
        }
        else if (target == this._paneDeck)
        {
          // deck.selectedIndex is a string!
          var pane = this.preferencePanes[Number(target.selectedIndex)];
          this.syncTreeWithPane(pane, false);
        }
      ]]>
      </handler>

      <handler event="paneload">
      <![CDATA[
        // panes may load asynchronously,
        // so we have to "late-sync" those to our navigation tree
        this.syncTreeWithPane(event.originalTarget, false);
      ]]>
      </handler>

      <handler event="keypress" key="&focusSearch.key;" modifiers="accel">
      <![CDATA[
        var searchBox = this.currentPane.getElementsByAttribute("type", "search")[0];
        if (searchBox)
        {
          searchBox.focus();
          event.stopPropagation();
          event.preventDefault();
        }
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="prefpane"
           extends="chrome://communicator/content/bindings/preferences.xml#prefpane">
    <resources>
      <stylesheet src="chrome://communicator/skin/preferences.css"/>
    </resources>

    <handlers>
      <handler event="paneload">
      <![CDATA[
        // Since all <prefpane>s now share the same global document, their
        // <script>s might clash. Thus we expect the "script" attribute to
        // contain a whitespace delimited list of script files to be loaded
        // into the <prefpane>'s context.

        // list of scripts to load
        var scripts = this.getAttribute('script').match(/\S+/g);
        if (!scripts)
          return;
        var count = scripts.length;
        for (var i = 0; i < count; ++i)
        {
          var script = scripts[i];
          if (script)
          {
            try
            {
              Services.scriptloader.loadSubScript(script, this);
            }
            catch (e)
            {
              let errorStr =
                "prefpane.paneload: loadSubScript(" + script + ") failed:\n" +
                (e.fileName ? "at " + e.fileName + " : " + e.lineNumber + "\n"
                            : "") +
                e + " - " + e.stack + "\n";
              dump(errorStr);
              Cu.reportError(errorStr);
            }
          }
        }

        // if we have a Startup method, call it
        if ('Startup' in this)
          this.Startup();
      ]]>
      </handler>
    </handlers>
  </binding>

</bindings>