r25048: From the archives (patch found in one of my old working trees):
[jelmer/samba4-debian.git] / webapps / qooxdoo-0.6.5-sdk / frontend / framework / source / class / qx / ui / basic / Label.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2004-2007 1&1 Internet AG, Germany, http://www.1and1.org
9
10    License:
11      LGPL: http://www.gnu.org/licenses/lgpl.html
12      EPL: http://www.eclipse.org/org/documents/epl-v10.php
13      See the LICENSE file in the project's top-level directory for details.
14
15    Authors:
16      * Sebastian Werner (wpbasti)
17      * Andreas Ecker (ecker)
18
19 ************************************************************************ */
20
21 /* ************************************************************************
22
23 #module(ui_basic)
24 #require(qx.renderer.font.FontCache)
25 #after(qx.renderer.font.FontObject)
26
27 ************************************************************************ */
28
29 qx.OO.defineClass("qx.ui.basic.Label", qx.ui.basic.Terminator,
30 function(vHtml, vMnemonic)
31 {
32   qx.ui.basic.Terminator.call(this);
33
34   // Apply constructor arguments
35   if (vHtml != null) {
36     this.setHtml(vHtml);
37   }
38
39   if (vMnemonic != null) {
40     this.setMnemonic(vMnemonic);
41   }
42
43   // Prohibit stretching through layout handler
44   this.setAllowStretchX(false);
45   this.setAllowStretchY(false);
46
47   // Auto Sized
48   this.auto();
49 });
50
51 qx.Class._measureNodes = {};
52
53
54
55
56
57 /*
58 ---------------------------------------------------------------------------
59   PROPERTIES
60 ---------------------------------------------------------------------------
61 */
62
63 qx.OO.changeProperty({ name : "appearance", type : "string", defaultValue : "label" });
64
65 /*!
66   Any text string which can contain HTML, too
67 */
68 qx.OO.addProperty({ name : "html" });
69
70 /*!
71   The alignment of the text.
72 */
73 qx.OO.addProperty({ name : "textAlign", type : "string", defaultValue : "left", possibleValues : [ "left", "center", "right", "justify" ] });
74
75 /*!
76   The styles which should be copied
77 */
78 qx.OO.addProperty({ name : "fontPropertiesProfile", type : "string", defaultValue : "default", possibleValues : [ "none", "default", "extended", "multiline", "extendedmultiline", "all" ] });
79
80 /*!
81   A single character which will be underlined inside the text.
82 */
83 qx.OO.addProperty({ name : "mnemonic", type : "string" });
84
85 /*!
86   The font property describes how to paint the font on the widget.
87 */
88 qx.OO.addProperty({ name : "font", type : "object", instance : "qx.renderer.font.Font", convert : qx.renderer.font.FontCache, allowMultipleArguments : true });
89
90 /*!
91   Wrap the text?
92 */
93 qx.OO.addProperty({ name : "wrap", type : "boolean", defaultValue : true });
94
95
96
97
98
99
100
101
102
103 /* ************************************************************************
104    Class data, properties and methods
105 ************************************************************************ */
106
107 /*
108 ---------------------------------------------------------------------------
109   DATA
110 ---------------------------------------------------------------------------
111 */
112
113 qx.ui.basic.Label.SYMBOL_ELLIPSIS = String.fromCharCode(8230);
114 qx.ui.basic.Label.SUPPORT_NATIVE_ELLIPSIS = qx.core.Client.getInstance().isMshtml();
115
116 // these are the properties what will be copied to the measuring frame.
117 qx.ui.basic.Label._fontProperties =
118 {
119   "none" : [],
120
121   "default" : ["fontFamily", "fontSize", "fontStyle", "fontWeight", "textDecoration"],
122   "extended" : ["fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "textDecoration", "textTransform", "whiteSpace", "wordSpacing"],
123
124   "multiline" : ["fontFamily", "fontSize", "fontStyle", "fontWeight", "textDecoration", "lineHeight", "wordWrap"],
125   "extendedmultiline" : ["fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "textDecoration", "textTransform", "whiteSpace", "wordSpacing", "lineHeight", "wordBreak", "wordWrap", "quotes"],
126
127   "all" : ["fontFamily", "fontSize", "fontStyle", "fontVariant", "fontWeight", "letterSpacing", "lineBreak", "lineHeight", "quotes", "textDecoration", "textIndent", "textShadow", "textTransform", "textUnderlinePosition", "whiteSpace", "wordBreak", "wordSpacing", "wordWrap"]
128 }
129
130
131 qx.ui.basic.Label.createMeasureNode = function(vId)
132 {
133   var vNode = qx.ui.basic.Label._measureNodes[vId];
134
135   if (!vNode)
136   {
137     vNode = document.createElement("div");
138     var vStyle = vNode.style;
139
140     vStyle.width = vStyle.height = "auto";
141     vStyle.visibility = "hidden";
142     vStyle.position = "absolute";
143     vStyle.zIndex = "-1";
144
145     document.body.appendChild(vNode);
146
147     qx.ui.basic.Label._measureNodes[vId] = vNode;
148   }
149
150   return vNode;
151 }
152
153
154
155
156
157
158
159
160 /* ************************************************************************
161    Instance data, properties and methods
162 ************************************************************************ */
163
164 /*
165 ---------------------------------------------------------------------------
166   MODIFIER
167 ---------------------------------------------------------------------------
168 */
169
170 qx.Proto._localized = false;
171 qx.Proto._htmlContent = "";
172 qx.Proto._htmlMode = false;
173 qx.Proto._hasMnemonic = false;
174 qx.Proto._mnemonicHtml = "";
175 qx.Proto._mnemonicTest = null;
176
177 qx.Proto._modifyHtml = function(propValue, propOldValue, propData)
178 {
179   this._localized = this.getHtml() instanceof qx.locale.LocalizedString;
180   this._updateHtml();
181   return true;
182 }
183
184 qx.Proto._updateHtml = function()
185 {
186   if (this._localized)
187   {
188     this._htmlContent = this.getHtml().toString();
189     qx.locale.Manager.getInstance().addEventListener("changeLocale", this._updateHtml, this);
190   }
191   else
192   {
193     this._htmlContent = this.getHtml() || "";
194     qx.locale.Manager.getInstance().removeEventListener("changeLocale", this._updateHtml, this);
195   }
196
197   this._htmlMode = qx.util.Validation.isValidString(this._htmlContent) && this._htmlContent.match(/<.*>/) ? true : false;
198
199   if (this._isCreated) {
200     this._applyContent();
201   }
202 };
203
204
205 qx.Proto._modifyTextAlign = function(propValue, propOldValue, propData)
206 {
207   this.setStyleProperty("textAlign", propValue);
208   return true;
209 }
210
211 qx.Proto._modifyMnemonic = function(propValue, propOldValue, propData)
212 {
213   this._hasMnemonic = qx.util.Validation.isValidString(propValue) && propValue.length == 1;
214
215   this._mnemonicHtml = this._hasMnemonic ? "(<span style=\"text-decoration:underline\">" + propValue + "</span>)" : "";
216   this._mnemonicTest = this._hasMnemonic ? new RegExp("^(((<([^>]|" + propValue + ")+>)|(&([^;]|" + propValue + ")+;)|[^&" + propValue + "])*)(" + propValue + ")", "i") : null;
217
218   return true;
219 }
220
221 qx.Proto._modifyFont = function(propValue, propOldValue, propData)
222 {
223   this._invalidatePreferredInnerDimensions();
224
225   if (propValue) {
226     propValue._applyWidget(this);
227   } else if (propOldValue) {
228     propOldValue._resetWidget(this);
229   }
230
231   return true;
232 }
233
234 qx.Proto._modifyWrap = function(propValue, propOldValue, propData)
235 {
236   this.setStyleProperty("whiteSpace", propValue ? "normal" : "nowrap");
237   return true;
238 }
239
240
241
242
243
244 /*
245 ---------------------------------------------------------------------------
246   HELPER FOR PREFERRED DIMENSION
247 ---------------------------------------------------------------------------
248 */
249
250 qx.Proto._computeObjectNeededDimensions = function()
251 {
252   // copy styles
253   var vNode = this._copyStyles();
254
255   // prepare html
256   var vHtml = this._htmlContent;
257
258   // test for mnemonic and fix content
259   if (this._hasMnemonic && !this._mnemonicTest.test(vHtml)) {
260     vHtml += this._mnemonicHtml;
261   }
262
263   // apply html
264   vNode.innerHTML = vHtml;
265
266   // store values
267   this._cachedPreferredInnerWidth = vNode.scrollWidth;
268   this._cachedPreferredInnerHeight = vNode.scrollHeight;
269 }
270
271 qx.Proto._copyStyles = function()
272 {
273   var vProps = this.getFontPropertiesProfile();
274   var vNode = qx.ui.basic.Label.createMeasureNode(vProps);
275   var vUseProperties=qx.ui.basic.Label._fontProperties[vProps];
276   var vUsePropertiesLength=vUseProperties.length-1;
277   var vProperty=vUseProperties[vUsePropertiesLength--];
278
279   var vStyle = vNode.style;
280   var vTemp;
281
282   if (!vProperty) {
283     return vNode;
284   }
285
286   do {
287     vStyle[vProperty] = qx.util.Validation.isValid(vTemp = this.getStyleProperty([vProperty])) ? vTemp : "";
288   } while(vProperty=vUseProperties[vUsePropertiesLength--]);
289
290   return vNode;
291 }
292
293
294
295
296
297
298 /*
299 ---------------------------------------------------------------------------
300   PREFERRED DIMENSIONS
301 ---------------------------------------------------------------------------
302 */
303
304 qx.Proto._computePreferredInnerWidth = function()
305 {
306   this._computeObjectNeededDimensions();
307   return this._cachedPreferredInnerWidth;
308 }
309
310 qx.Proto._computePreferredInnerHeight = function()
311 {
312   this._computeObjectNeededDimensions();
313   return this._cachedPreferredInnerHeight;
314 }
315
316
317
318
319
320
321 /*
322 ---------------------------------------------------------------------------
323   LAYOUT APPLY
324 ---------------------------------------------------------------------------
325 */
326
327 qx.Proto._postApply = function()
328 {
329   var vHtml = this._htmlContent;
330   var vElement = this._getTargetNode();
331   var vMnemonicMode = 0;
332
333   if (qx.util.Validation.isInvalidString(vHtml)) {
334     vElement.innerHTML = "";
335     return;
336   }
337
338   if (this._hasMnemonic) {
339     vMnemonicMode = this._mnemonicTest.test(vHtml) ? 1 : 2;
340   }
341
342   // works only with text, don't use when wrap is enabled
343   if (!this._htmlMode && !this.getWrap())
344   {
345     switch(this._computedWidthType)
346     {
347       case qx.ui.core.Widget.TYPE_PIXEL:
348       case qx.ui.core.Widget.TYPE_PERCENT:
349
350       //carstenl: enabled truncation code for flex sizing, too. Appears to work except for the
351       //          truncation code (gecko version), which I have disabled (see below).
352       case qx.ui.core.Widget.TYPE_FLEX:
353         var vNeeded = this.getPreferredInnerWidth();
354         var vInner = this.getInnerWidth();
355
356         if (vInner < vNeeded)
357         {
358           vElement.style.overflow = "hidden";
359
360           if (qx.ui.basic.Label.SUPPORT_NATIVE_ELLIPSIS)
361           {
362             vElement.style.textOverflow = "ellipsis";
363             vHtml += this._mnemonicHtml;
364           }
365           else
366           {
367             var vMeasureNode = this._copyStyles();
368
369             var vSplitString = vHtml.split(" ");
370             var vSplitLength = vSplitString.length;
371
372             var vWordIterator = 0;
373             var vCharaterIterator = 0;
374
375             var vPost = qx.ui.basic.Label.SYMBOL_ELLIPSIS;
376
377             var vUseInnerText = true;
378             if (vMnemonicMode == 2)
379             {
380               var vPost = this._mnemonicHtml + vPost;
381               vUseInnerText = false;
382             }
383
384             // Measure Words (if more than one)
385             if (vSplitLength > 1)
386             {
387               var vSplitTemp = [];
388
389               for (vWordIterator=0; vWordIterator<vSplitLength; vWordIterator++)
390               {
391                 vSplitTemp.push(vSplitString[vWordIterator]);
392
393                 var vLabelText = vSplitTemp.join(" ") + vPost;
394                 if (vUseInnerText) {
395                   qx.dom.Element.setTextContent(vMeasureNode, vLabelText);
396                 } else {
397                   vMeasureNode.innerHTML = vLabelText;
398                 }
399
400                 if ((vMeasureNode.scrollWidth > vInner)
401                   /* carstenl: The following code (truncate the text to fit in the available
402                    *           space, append ellipsis to indicate truncation) did not reliably
403                    *           work in my tests. Problem was that sometimes the measurer returned
404                    *           insanely high values for short texts, like "I..." requiring 738 px.
405                    *
406                    *           I don't have time to examine this code in detail. Since all of my
407                    *           tests used flex width and the truncation code never was intended
408                    *           for this, I am disabling truncation if flex is active.
409                    */
410                     && (this._computedWidthType != qx.ui.core.Widget.TYPE_FLEX)){
411                   break;
412                 }
413               }
414
415               // Remove last word which does not fit
416               vSplitTemp.pop();
417
418               // Building new temportary array
419               vSplitTemp = [ vSplitTemp.join(" ") ];
420
421               // Extracting remaining string
422               vCharaterString = vHtml.replace(vSplitTemp[0], "");
423             }
424             else
425             {
426               var vSplitTemp = [];
427               vCharaterString = vHtml;
428             }
429
430             var vCharaterLength = vCharaterString.length;
431
432             // Measure Chars
433             for (var vCharaterIterator=0; vCharaterIterator<vCharaterLength; vCharaterIterator++)
434             {
435               vSplitTemp.push(vCharaterString.charAt(vCharaterIterator));
436
437               var vLabelText = vSplitTemp.join("") + vPost;
438               if (vUseInnerText) {
439                 qx.dom.Element.setTextContent(vMeasureNode, vLabelText);
440               } else {
441                 vMeasureNode.innerHTML = vLabelText;
442               }
443
444               if (vMeasureNode.scrollWidth > vInner) {
445                 break;
446               }
447             }
448
449             // Remove last char which does not fit
450             vSplitTemp.pop();
451
452             // Add mnemonic and ellipsis symbol
453             vSplitTemp.push(vPost);
454
455             // Building Final HTML String
456             vHtml = vSplitTemp.join("");
457           }
458
459           break;
460         }
461         else
462         {
463           vHtml += this._mnemonicHtml;
464         }
465
466         // no break here
467
468       default:
469         vElement.style.overflow = "";
470
471         if (qx.ui.basic.Label.SUPPORT_NATIVE_ELLIPSIS) {
472           vElement.style.textOverflow = "";
473         }
474     }
475   }
476
477   if (vMnemonicMode == 1)
478   {
479     // re-test: needed to make ellipsis handling correct
480     this._mnemonicTest.test(vHtml);
481     vHtml = RegExp.$1 + "<span style=\"text-decoration:underline\">" + RegExp.$7 + "</span>" + RegExp.rightContext;
482   }
483
484   return this._postApplyHtml(vElement, vHtml, vMnemonicMode);
485 }
486
487
488 qx.Proto._postApplyHtml = function(vElement, vHtml, vMnemonicMode)
489 {
490   if (this._htmlMode || vMnemonicMode > 0)
491   {
492     vElement.innerHTML = vHtml;
493   }
494   else
495   {
496     try {
497       qx.dom.Element.setTextContent(vElement, vHtml);
498     } catch(ex) {
499       vElement.innerHTML = vHtml;
500     }
501   }
502 }