001: /*
002: * CONFIDENTIAL AND PROPRIETARY SOURCE CODE OF
003: * NETSCAPE COMMUNICATIONS CORPORATION
004: *
005: * Copyright (c) 1996 Netscape Communications Corporation.
006: * All Rights Reserved.
007: * Use of this Source Code is subject to the terms of the applicable
008: * license agreement from Netscape Communications Corporation.
009: */
010:
011: package graphical;
012:
013: import netscape.application.Application;
014: import netscape.application.Bitmap;
015: import netscape.application.BezelBorder;
016: import netscape.application.Border;
017: import netscape.application.Button;
018: import netscape.application.Color;
019: import netscape.application.Graphics;
020: import netscape.application.InternalWindow;
021: import netscape.application.KeyEvent;
022: import netscape.application.ListItem;
023: import netscape.application.ListView;
024: import netscape.application.MouseEvent;
025: import netscape.application.Point;
026: import netscape.application.RootView;
027: import netscape.application.ScrollGroup;
028: import netscape.application.Size;
029: import netscape.application.Target;
030: import netscape.application.TextField;
031: import netscape.application.TextFieldOwner;
032: import netscape.application.TextFilter;
033: import netscape.application.View;
034: import netscape.application.Window;
035: import netscape.util.ClassInfo;
036: import netscape.util.Codable;
037: import netscape.util.CodingException;
038: import netscape.util.Decoder;
039: import netscape.util.Encoder;
040: import netscape.util.InconsistencyException;
041: import netscape.util.Vector;
042:
043: /**
044: * Based on IFC 1.0 example class. Added target/command handling stuff, mostly
045: * by stealing from Popup but did a "parallel" command instead, since
046: * ComboBox seems less ecumenical w/ it's commands - so it's not
047: * a kosher implementation, it does the command only at the widget
048: * level, not the ListItem level.
049: * Also added enable/disable, fixed for correct display in different
050: * types of windows, fixed closing on loss of focus (by adding
051: * a new subclass off InternalWindow, which shouldn't be right),
052: * and a host of other stuff.
053: * <p>
054: * Not tidy, but I'm in a hurry.
055: */
056: public class ComboBox extends View implements Target, TextFilter,
057: TextFieldOwner, Codable {
058: TransInternalWindow dropWindow;
059: // InternalWindow dropWindow;
060: ScrollGroup scrollGroup;
061: ListView listView;
062: TextField textField;
063: Button dropButton;
064: boolean listIsVisible;
065: Border border;
066: int maxShowCount;
067: TextFilter textFilter;
068: TextFieldOwner textFieldOwner;
069: boolean isDownInListView;
070:
071: // SGP
072: Target target;
073: String otherCommand;
074: View this ViewDammit; // SGP: used?
075: boolean isEnabled;
076: boolean typeAhead;
077: InternalWindow iw_hack;
078:
079: private static final String CHOOSE_ITEM = "chooseItem";
080: private static final String DROP_LIST = "dropList";
081:
082: final static String DROPWINDOW_KEY = "dropWindow",
083: SCROLLGROUP_KEY = "scrollGroup", LISTVIEW_KEY = "listView",
084: TEXTFIELD_KEY = "textField", DROPBUTTON_KEY = "dropButton",
085: BORDER_KEY = "border", MAXSHOWCOUNT_KEY = "maxShowCount",
086: TEXTFILTER_KEY = "textFilter",
087: TEXTFIELDOWNER_KEY = "textFieldOwner";
088:
089: /// creation methods
090: public ComboBox() {
091: this (0, 0, 0, 0);
092: }
093:
094: Bitmap buttonImage;
095:
096: public ComboBox(int x, int y, int width, int height) {
097: super (x, y, width, height);
098:
099: // Bitmap buttonImage;
100: buttonImage = Bitmap
101: .bitmapNamed("netscape/application/ScrollDownArrow.gif");
102:
103: // Set up default values
104: listIsVisible = false;
105: maxShowCount = 6;
106: border = BezelBorder.loweredBezel();
107: textFilter = null;
108: textFieldOwner = null;
109: isDownInListView = false;
110:
111: // create components
112: // dropWindow = new InternalWindow(Window.BLANK_TYPE,0,
113: dropWindow = new TransInternalWindow(Window.BLANK_TYPE, 0,
114: height, width, height);
115: scrollGroup = new ScrollGroup(0, 0, width, height);
116: dropButton = new Button(width - buttonImage.width()
117: - border.rightMargin(), border.topMargin(), buttonImage
118: .width(), height - border.heightMargin());
119:
120: // sgp: to support tab selection
121: dropButton.setTarget(this );
122: dropButton.setCommand(DROP_LIST);
123:
124: // There is some space between dropButton and textField
125: textField = new TextField(border.leftMargin() + 1, border
126: .topMargin(), width - dropButton.width() - 6 - 1,
127: height - border.heightMargin());
128: listView = new ListView(0, 0, textField.width(), height);
129:
130: // prepare components
131: listView.setAllowsEmptySelection(true);
132: listView.setTracksMouseOutsideBounds(false);
133: listView.setTarget(this );
134: listView.setCommand(CHOOSE_ITEM);
135:
136: scrollGroup.setContentView(listView);
137: // scrollGroup.setHasVertScrollBar(true);
138: scrollGroup
139: .setVertScrollBarDisplay(ScrollGroup.AS_NEEDED_DISPLAY);
140: scrollGroup.setHorizResizeInstruction(WIDTH_CAN_CHANGE);
141: scrollGroup.setVertResizeInstruction(HEIGHT_CAN_CHANGE);
142:
143: dropWindow.addSubview(scrollGroup);
144:
145: dropButton.setBordered(true);
146: dropButton.setImage(buttonImage);
147:
148: textField.setBorder(null);
149: textField.setFilter(this );
150: textField.setOwner(this );
151:
152: this .addSubview(textField);
153: this .addSubview(dropButton);
154:
155: // SGP
156: target = null;
157: otherCommand = null;
158: this ViewDammit = null;
159: iw_hack = null;
160: isEnabled = true;
161: typeAhead = false;
162: }
163:
164: /// view handling methods
165: public void drawView(Graphics g) {
166: border.drawInRect(g, 0, 0, width(), height());
167: }
168:
169: public void performCommand(String command, Object object) {
170: if (DROP_LIST.equals(command)) {
171: setListIsVisible(!listIsVisible);
172: } else if (CHOOSE_ITEM.equals(command)) {
173: updateTextField();
174: setListIsVisible(false);
175: } else {
176: throw new NoSuchMethodError("unknown command: " + command);
177: }
178: sendCommand(); // target/command support addition
179: }
180:
181: /// mouse handling method
182: public View viewForMouse(int x, int y) {
183: View realView = super .viewForMouse(x, y);
184: if (realView == dropButton)
185: return this ;
186: else
187: return realView;
188: }
189:
190: public boolean mouseDown(MouseEvent event) {
191: isDownInListView = false;
192: if (super .viewForMouse(event.x, event.y) == dropButton) {
193: setListIsVisible(!listIsVisible);
194: this .setFocusedView();
195: return true;
196: }
197:
198: return false;
199: }
200:
201: public void mouseDragged(MouseEvent event) {
202: MouseEvent convEvent;
203:
204: if (!listIsVisible) {
205: return;
206: }
207:
208: convEvent = convertEventToView(dropWindow, event);
209: if (!isDownInListView && convEvent.x > 0
210: && convEvent.x < dropWindow.bounds.width
211: && convEvent.y > 0
212: && convEvent.y < dropWindow.bounds.height) {
213: listView.mouseDown(convertEventToView(listView, event));
214: isDownInListView = true;
215: } else if (isDownInListView) {
216: listView.mouseDragged(convertEventToView(listView, event));
217: }
218: }
219:
220: public void mouseUp(MouseEvent event) {
221: if (super .viewForMouse(event.x, event.y) == dropButton) {
222: return;
223: }
224: if (listIsVisible) {
225: listView.mouseUp(convertEventToView(listView, event));
226: }
227: }
228:
229: public void stopFocus() {
230: updateListViewSelection();
231: if (listIsVisible)
232: setListIsVisible(false);
233: }
234:
235: /// TextFilter method
236: public boolean acceptsEvent(Object anObject, KeyEvent event,
237: Vector eventVector) {
238: if (textFilter != null
239: && !textFilter.acceptsEvent(anObject, event,
240: eventVector)) {
241: return false;
242: }
243:
244: if (event.isUpArrowKey()) {
245: selectPrevItem();
246: if (selectedIndex() > -1)
247: listView.scrollItemAtToVisible(selectedIndex());
248: textField.setFocusedView();
249: setListIsVisible(false);
250: return false;
251: } else if (event.isDownArrowKey()) {
252: selectNextItem();
253: if (selectedIndex() > -1)
254: listView.scrollItemAtToVisible(selectedIndex());
255: textField.setFocusedView();
256: setListIsVisible(false);
257: return false;
258: }
259: return true;
260:
261: }
262:
263: /// List Item handling methods
264: public void setListIsVisible(boolean value) {
265: int rowHeight, itemCount, showCount;
266:
267: // sgp enable/disable fix
268: if (!isEnabled) {
269: return;
270: }
271:
272: itemCount = listView.count();
273:
274: if (value && itemCount > 0) {
275: dropWindow.setRootView(rootView());
276: dropButton.setState(true);
277: listView.sizeToMinSize();
278: rowHeight = listView.rowHeight(); // sgp fix, was item minHeight()
279: if (itemCount >= maxShowCount)
280: showCount = maxShowCount + 1;
281: else
282: showCount = itemCount;
283:
284: // sgp correction to dropWindow moveTo
285: Point p = new Point(bounds.x, bounds.maxY());
286: convertPointToView(null, p, p);
287: p.x -= bounds.x;
288: p.y -= bounds.y;
289: dropWindow.moveTo(p.x, p.y);
290:
291: dropWindow.sizeTo(bounds.width, rowHeight * showCount);
292: dropWindow.show();
293: listIsVisible = true;
294: // sgp fix for certain bad window behaviour
295: dropWindow.showModally();
296: dropButton.setState(false);
297: listIsVisible = false;
298: } else {
299: dropButton.setState(false);
300: dropWindow.hide();
301: listIsVisible = false;
302: }
303:
304: }
305:
306: private void updateTextField() {
307: if (listView.selectedItem() != null)
308: textField.setStringValue(listView.selectedItem().title());
309: }
310:
311: private void updateListViewSelection() {
312: int selectedRow = rowWithTitle(stringValue());
313: if (selectedRow > -1) {
314: listView.selectItemAt(selectedRow);
315: listView.scrollItemAtToVisible(selectedRow);
316: }
317: }
318:
319: /// Cover Methods
320: public void addItem(String value) {
321: listView.addItem().setTitle(value);
322: }
323:
324: public void insertItemAt(String value, int index) {
325: listView.insertItemAt(index).setTitle(value);
326: }
327:
328: public void addItems(Vector values) {
329: int i, max;
330: max = values.count();
331: for (i = 0; i < max; i++) {
332: listView.addItem().setTitle((String) values.elementAt(i));
333: }
334: }
335:
336: public void removeItem(String value) {
337: int targetRow = rowWithTitle(value);
338: if (targetRow > -1)
339: listView.removeItemAt(targetRow);
340: }
341:
342: public void removeItemAt(int index) {
343: listView.removeItemAt(index);
344: }
345:
346: public void removeAllItems() {
347: listView.removeAllItems();
348: }
349:
350: public int selectedIndex() {
351: return listView.selectedIndex();
352: }
353:
354: public int rowWithTitle(String title) {
355: int i, max;
356:
357: max = listView.count();
358: for (i = 0; i < max; i++) {
359: if (listView.itemAt(i).title() != null) {
360: if (listView.itemAt(i).title().equals(title))
361: return i;
362: }
363: }
364: return -1;
365: }
366:
367: // SGP
368: public int rowWithTitlePrefix(String title) {
369: int max = listView.count();
370:
371: for (int i = 0; i < max; i++) {
372: String s = listView.itemAt(i).title();
373: if (s != null) {
374: if (s.startsWith(title)) {
375: return i;
376: }
377: }
378: }
379: return -1;
380: }
381:
382: public String rowTitleWithTitlePrefix(String title) {
383: int max = listView.count();
384:
385: for (int i = 0; i < max; i++) {
386: String s = listView.itemAt(i).title();
387: if (s != null) {
388: if (s.startsWith(title)) {
389: return s;
390: }
391: }
392: }
393: return null;
394: }
395:
396: public int count() {
397: return listView.count();
398: }
399:
400: public String stringValue() {
401: return textField.stringValue();
402: }
403:
404: public void setStringValue(String value) {
405: textField.setStringValue(value);
406: }
407:
408: /**
409: * Modified to return the indice.
410: * The indice is < 0 if row is not found.
411: * Also sets the string value to blank if nothing found.
412: */
413: public int selectRowWithTitle(String title) {
414: int selectedRow = rowWithTitle(title);
415: if (selectedRow > -1) {
416: listView.selectItemAt(selectedRow);
417: updateTextField();
418: } else {
419: textField.setStringValue("");
420: }
421: return selectedRow;
422: }
423:
424: public void selectRow(int index) {
425: if (index > -1 && index < (listView.count() - 1)) {
426: listView.selectItemAt(index);
427: updateTextField();
428: }
429: }
430:
431: public void selectNextItem() {
432: if (listView.selectedIndex() < listView.count() - 1) {
433: listView.selectItemAt(listView.selectedIndex() + 1);
434: }
435: updateTextField();
436: }
437:
438: public void selectPrevItem() {
439: if (listView.selectedIndex() > 0 && listView.count() > 1) {
440: listView.selectItemAt(listView.selectedIndex() - 1);
441: }
442: updateTextField();
443: }
444:
445: /// Ivar accessors
446: public int maxShowCount() {
447: return maxShowCount;
448: }
449:
450: public void setMaxShowCount(int value) {
451: if (value >= 0)
452: maxShowCount = value;
453: else
454: throw new InconsistencyException(this
455: + " setMaxShowCount must be greater than zero.");
456: }
457:
458: public TextFilter filter() {
459: return textFilter;
460: }
461:
462: public void setFilter(TextFilter object) {
463: textFilter = object;
464: }
465:
466: public TextFieldOwner owner() {
467: return textFieldOwner;
468: }
469:
470: public void setOwner(TextFieldOwner object) {
471: textFieldOwner = object;
472: }
473:
474: /// TextFieldOwner messages
475: public void textEditingDidBegin(TextField textfield) {
476: if (textFieldOwner != null) {
477: textFieldOwner.textEditingDidBegin(textfield);
478: }
479: }
480:
481: public void textEditingDidEnd(TextField textfield,
482: int endCondition, boolean contentsChanged) {
483: updateListViewSelection();
484: if (textFieldOwner != null) {
485: textFieldOwner.textEditingDidEnd(textfield, endCondition,
486: contentsChanged);
487: }
488:
489: // SGP
490: if (typeAhead) {
491: String s1 = textField.stringValue();
492:
493: String s2 = rowTitleWithTitlePrefix(textField.stringValue());
494:
495: if (s2 != null) {
496: textField.setStringValue(s2);
497: lastLength = s2.length();
498: } else {
499: s2 = listView.itemAt(0).title();
500: if (s2 != null) {
501: textField.setStringValue(s2);
502: lastLength = s2.length();
503: } else {
504: textField.setStringValue("");
505: lastLength = 0;
506: }
507: }
508:
509: sendCommand();
510: }
511:
512: }
513:
514: public boolean textEditingWillEnd(TextField textfield,
515: int endCondition, boolean contentsChanged) {
516: if (textFieldOwner != null) {
517: return textFieldOwner.textEditingWillEnd(textfield,
518: endCondition, contentsChanged);
519: }
520: return true;
521: }
522:
523: public void textWasModified(TextField textfield) {
524: if (textFieldOwner != null) {
525: textFieldOwner.textWasModified(textfield);
526: }
527:
528: /// SGP
529: if (typeAhead) {
530: String s1 = textField.stringValue();
531:
532: if (s1.length() != 0) {
533: String s2 = rowTitleWithTitlePrefix(textField
534: .stringValue());
535:
536: if ((s2 != null) && (s1.length() != (lastLength - 1))) {
537: textField.setStringValue(s2);
538: textField.setInsertionPoint(s2.length());
539: lastLength = s2.length();
540: } else {
541: lastLength = s1.length();
542: }
543: }
544:
545: sendCommand();
546: }
547: }
548:
549: // SGP
550: int lastLength = -1;
551:
552: /// Codable Interface
553: public void describeClassInfo(ClassInfo info) {
554: super .describeClassInfo(info);
555:
556: info.addClass("ComboBox", 1);
557: info.addField(DROPWINDOW_KEY, OBJECT_TYPE);
558: info.addField(SCROLLGROUP_KEY, OBJECT_TYPE);
559: info.addField(LISTVIEW_KEY, OBJECT_TYPE);
560: info.addField(TEXTFIELD_KEY, OBJECT_TYPE);
561: info.addField(DROPBUTTON_KEY, OBJECT_TYPE);
562: info.addField(BORDER_KEY, OBJECT_TYPE);
563: info.addField(MAXSHOWCOUNT_KEY, INT_TYPE);
564: info.addField(TEXTFILTER_KEY, OBJECT_TYPE);
565: info.addField(TEXTFIELDOWNER_KEY, OBJECT_TYPE);
566: }
567:
568: public void decode(Decoder decoder) throws CodingException {
569: super .decode(decoder);
570:
571: // dropWindow = (InternalWindow)decoder.decodeObject(DROPWINDOW_KEY);
572: dropWindow = (TransInternalWindow) decoder
573: .decodeObject(DROPWINDOW_KEY);
574: scrollGroup = (ScrollGroup) decoder
575: .decodeObject(SCROLLGROUP_KEY);
576: listView = (ListView) decoder.decodeObject(LISTVIEW_KEY);
577: textField = (TextField) decoder.decodeObject(TEXTFIELD_KEY);
578: dropButton = (Button) decoder.decodeObject(DROPBUTTON_KEY);
579: border = (Border) decoder.decodeObject(BORDER_KEY);
580: maxShowCount = decoder.decodeInt(MAXSHOWCOUNT_KEY);
581: textFilter = (TextFilter) decoder.decodeObject(TEXTFILTER_KEY);
582: textFieldOwner = (TextFieldOwner) decoder
583: .decodeObject(TEXTFIELDOWNER_KEY);
584:
585: }
586:
587: public void encode(Encoder encoder) throws CodingException {
588: super .encode(encoder);
589:
590: encoder.encodeObject(DROPWINDOW_KEY, dropWindow);
591: encoder.encodeObject(SCROLLGROUP_KEY, scrollGroup);
592: encoder.encodeObject(LISTVIEW_KEY, listView);
593: encoder.encodeObject(TEXTFIELD_KEY, textField);
594: encoder.encodeObject(DROPBUTTON_KEY, dropButton);
595: encoder.encodeObject(BORDER_KEY, border);
596: encoder.encodeInt(MAXSHOWCOUNT_KEY, maxShowCount);
597: encoder.encodeObject(TEXTFILTER_KEY, textFilter);
598: encoder.encodeObject(TEXTFIELDOWNER_KEY, textFieldOwner);
599: }
600:
601: public void finishDecoding() throws CodingException {
602: super .finishDecoding();
603: }
604:
605: /* ---< additional methods for target/command support >------*/
606: public void setTarget(Target newTarget) {
607: target = newTarget;
608: }
609:
610: /** Returns the Popup's Target.
611: * @see #setTarget
612: */
613: public Target target() {
614: return target;
615: }
616:
617: /** Sets the Popup's command. The Popup sends this command to its Target
618: * if the selected ListItem does not have a command.
619: */
620: public void setCommand(String newCommand) {
621: // popupList.setCommand(newCommand);
622: otherCommand = newCommand;
623: }
624:
625: /** Returns the Popup's command.
626: * @see #setCommand
627: */
628: public String command() {
629: // return popupList.command();
630: return otherCommand;
631: }
632:
633: /** Sends a command to the Popup's Target. This command is either the
634: * selected ListItem's command, or the Popup's command (if the ListItem
635: * has no command).
636: */
637: public void sendCommand() {
638: if (target != null) {
639: /*
640: String realCommand = null;
641:
642: if (selectedItem != null)
643: realCommand = selectedItem.command();
644:
645: if (realCommand == null)
646: realCommand = command();
647:
648: target.performCommand(realCommand, this);
649: */
650: target.performCommand(otherCommand, this );
651: }
652: }
653:
654: public ListView comboboxList() {
655: return listView;
656: }
657:
658: public void sizeTo(int width, int height) {
659: Size sz;
660: super .sizeTo(width, height);
661: dropButton.moveTo(width - buttonImage.width()
662: - border.rightMargin(), dropButton.y());
663: // There is some space between dropButton and textField
664: textField.sizeTo(width - dropButton.width() - 6 - 1, height
665: - border.heightMargin());
666: sz = listView.minSize();
667: listView.sizeTo(textField.width(), Math.max(sz.height, height));
668: sz = scrollGroup.minSize();
669: }
670:
671: public int fudgeWidth() {
672: // return dropButton.minSize().width;
673: return scrollGroup.vertScrollBar().minSize().width
674: + border.rightMargin() + border.leftMargin();
675: }
676:
677: public boolean isEnabled() {
678: return isEnabled;
679: }
680:
681: public void setEnabled(boolean b) {
682: isEnabled = b;
683:
684: dropButton.setEnabled(isEnabled);
685: textField.setEditable(isEnabled);
686: setListIsVisible(false);
687:
688: if (isEnabled) {
689: textField.setBackgroundColor(Color.white);
690: } else {
691: textField.setBackgroundColor(Color.lightGray);
692: }
693: }
694:
695: public boolean typeAhead() {
696: return typeAhead;
697: }
698:
699: /**
700: * Forces "type ahead" and validation - exiting also
701: * forces a selection.
702: */
703: public void setTypeAhead(boolean b) {
704: typeAhead = b;
705: }
706:
707: /**
708: * Disables typing in the box. Pulldown still active.
709: */
710: public void setEditable(boolean b) {
711: textField.setEditable(b);
712: if (b) {
713: textField.setBackgroundColor(Header.ACTIVECOLOR);
714: } else {
715: textField.setBackgroundColor(Header.INACTIVECOLOR);
716: }
717: }
718:
719: public void willBecomeSelected() {
720: System.out.println("willBecomeSelected()");
721: }
722: }
|