001: package com.xoetrope.swing.survey;
002:
003: import com.xoetrope.survey.Option;
004: import com.xoetrope.survey.Survey;
005: import com.xoetrope.survey.SurveyManager;
006: import java.awt.Color;
007: import java.awt.Component;
008: import java.awt.Dimension;
009: import java.awt.Font;
010: import java.awt.FontMetrics;
011: import java.awt.Graphics;
012: import java.awt.Image;
013: import java.awt.Point;
014: import java.awt.event.FocusEvent;
015: import java.awt.event.FocusListener;
016: import java.util.Hashtable;
017: import javax.swing.JComponent;
018:
019: import com.xoetrope.event.XClickListener;
020: import com.xoetrope.event.XStateListener;
021: import com.xoetrope.survey.Question;
022: import com.xoetrope.survey.QuestionManager;
023: import com.xoetrope.survey.XSurveyManager;
024: import net.xoetrope.swing.XEdit;
025: import net.xoetrope.xui.XAttributedComponent;
026: import net.xoetrope.xui.XTextHolder;
027: import net.xoetrope.xui.data.XDataBinding;
028: import net.xoetrope.xui.data.XModel;
029: import net.xoetrope.xui.style.XStyle;
030: import net.xoetrope.xui.style.XStyleFactory;
031: import net.xoetrope.xui.style.XStyleManager;
032: import net.xoetrope.xml.XmlElement;
033: import net.xoetrope.xui.XProject;
034: import net.xoetrope.xui.XProjectManager;
035:
036: /**
037: * <p>Title: XQuestion</p>
038: * <p>Description: A component for rendering a question and a set of mutually
039: * exclusive answers. The possible answers are rendered as a set of check marks</p>
040: *
041: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
042: * the GNU Public License (GPL), please see license.txt for more details. If
043: * you make commercial use of this software you must purchase a commercial
044: * license from Xoetrope.</p>
045: * <p> $Revision: 1.12 $</p>
046: */
047: public class XQuestion extends JComponent implements XStateListener,
048: XTextHolder, XAttributedComponent {
049: // gap between question text and the responses
050: protected int headerHeight = 10;
051:
052: // the stripe at the top of this componenet
053: protected int topBandHeight = 10;
054:
055: // gaps on the left and on the right side
056: protected int woffset = 40;
057:
058: // value of the arc used for rendering radiobuttons
059: protected int radioArc = 5;
060:
061: // radiobuttons and checkboxes size
062: protected int checkSize = 10;
063:
064: // width of "free text" component
065: protected int editWidth = 60;
066:
067: // height of "free text" component
068: protected int editHeight = 18;
069:
070: // radiobuttons and checkboxes height
071: protected int checkHeight = 10;
072:
073: // the gap between the responses comonent (like radiobutton)
074: // and the reponsse text
075: protected int responseTextGap = 9;
076:
077: // the gap between the question text and responses
078: protected int responseVGap = 13;
079:
080: // the gap between the left side of the main frame
081: // and the firs response
082: protected int responseHGap = 60;
083:
084: // size of the frame which is painted when the component is active
085: protected int frameSize = 2;
086:
087: // the minim scale defining gaps between responses
088: protected int minScale = 5;
089:
090: // question being rendered by this component
091: protected Question question;
092:
093: // how many responses will fit in this component
094: protected int scale;
095:
096: // the width of a single response (in pixels)
097: protected int xScale;
098:
099: // handles the mouse actions
100: protected XClickListener clickListener;
101:
102: // the name of the cue image file
103: private String cueImage;
104:
105: // stores the responses, according to this value
106: // the responses are rendered
107: protected int responseState = 0;
108:
109: /** Style settings */
110: protected static XStyleManager styleManager;
111: // top stripe
112: protected static Color cueColor;
113: // background of a "cue" image
114: protected static Color cueBkColor;
115: // responses (options) text
116: protected static Color responseTextColor;
117: // "shade" style of the responses checkboxes and radiobuttons
118: protected static Color responseTextBkColor;
119: // this component's background
120: protected static Color questionBkColor;
121: // question's text
122: protected static Color questionTextColor;
123: // filling of responses checkboxes and radiobuttons
124: protected static Color checkBkColor;
125: // frames of the checkboxes and radiobuttons
126: protected static Color checkColor;
127:
128: protected static Color shadowColor;
129:
130: protected static Color selectedColor;
131:
132: protected static Color shadeColor;
133:
134: // table containging edit edit field used to render responses
135: // in case the question type is "free text"
136: protected XEdit[] editTable;
137:
138: // the data to be rendered by this component are obtained from here
139: protected SurveyManager surveyManager;
140:
141: protected XProject currentProject;
142:
143: protected boolean printing;
144:
145: // if this question is the last one on this page,
146: // the last one is rendered with a stripe at the bottom
147: protected boolean isLastQuestion;
148:
149: // for use in the paint method
150: protected int hh;
151: protected FontMetrics fm;
152:
153: // indicates whether the scale has been specified explicitly
154: protected boolean scaleSpecified = false;
155:
156: // model node where the responses to this question will be stored
157: protected XModel responseModel;
158:
159: /**
160: * Create a new question component
161: */
162: public XQuestion() {
163: currentProject = XProjectManager.getCurrentProject();
164: surveyManager = (SurveyManager) currentProject
165: .getObject("SurveyManager");
166: setStyles();
167: }
168:
169: /**
170: * Sets the styles
171: */
172: private void setStyles() {
173: if (questionTextColor == null) {
174: styleManager = currentProject.getStyleManager();
175: XStyle questionStyle = styleManager
176: .getStyle("base/Question");
177: XStyle responseStyle = styleManager
178: .getStyle("base/Question/Response");
179: XStyle checkStyle = styleManager
180: .getStyle("base/Question/Check");
181: XStyle cueStyle = styleManager
182: .getStyle("base/Question/Cue");
183: XStyle shadeStyle = styleManager
184: .getStyle("base/Question/Shade");
185: XStyle threeDStyle = styleManager
186: .getStyle("base/Question/ThreeD");
187:
188: questionTextColor = questionStyle
189: .getStyleAsColor(XStyle.COLOR_FORE);
190: questionBkColor = questionStyle
191: .getStyleAsColor(XStyle.COLOR_BACK);
192: cueBkColor = cueStyle.getStyleAsColor(XStyle.COLOR_BACK);
193: cueColor = cueStyle.getStyleAsColor(XStyle.COLOR_FORE);
194: responseTextColor = responseStyle
195: .getStyleAsColor(XStyle.COLOR_FORE);
196: responseTextBkColor = responseStyle
197: .getStyleAsColor(XStyle.COLOR_BACK);
198: checkBkColor = checkStyle
199: .getStyleAsColor(XStyle.COLOR_BACK);
200: checkColor = checkStyle.getStyleAsColor(XStyle.COLOR_FORE);
201: shadowColor = threeDStyle
202: .getStyleAsColor(XStyle.COLOR_BACK);
203: selectedColor = threeDStyle
204: .getStyleAsColor(XStyle.COLOR_FORE);
205: shadeColor = shadeStyle.getStyleAsColor(XStyle.COLOR_BACK);
206: }
207: }
208:
209: /**
210: * Gets the preferred size of the component.
211: * @return the size as a Dimension
212: */
213: public Dimension getPreferredSize() {
214: return getSize();
215: }
216:
217: /**
218: * Sets the size and location of the component and rescales the content to match
219: * @param x
220: * @param y
221: * @param w
222: * @param h
223: */
224: public void setBounds(int x, int y, int w, int h) {
225: super .setBounds(x, y, w, h);
226: setScale(scale);
227: }
228:
229: /**
230: * Sets the scale. The scale controls the horizontal division of the question
231: * area. Normally this would be set to the maximum number of responses in a
232: * survey. For example if most questions have 5 possible answers then the scale
233: * would be set to 5
234: * @param newScale
235: */
236: public void setScale(int newScale) {
237: scale = (newScale >= minScale ? newScale : minScale);
238: xScale = (getSize().width - woffset * 2) / scale;
239: }
240:
241: /**
242: * Adds a new response option
243: * @param value the value of the response
244: * @param caption the text
245: */
246: public void addResponse(int value, String caption) {
247: question.addOption(value, caption);
248: }
249:
250: /**
251: * Refresh the display
252: * @param g the graphics context
253: */
254: public void update(Graphics g) {
255: paint(g);
256: }
257:
258: /**
259: * Print the page
260: * @param g the printer graphics context
261: */
262: public void print(Graphics g) {
263: printing = true;
264: paint(g);
265: printing = false;
266: }
267:
268: protected void paintTopBand(Graphics g) {
269: Dimension d = getSize();
270: g.setColor(cueColor);
271: g.fillRect(0, 0, d.width, topBandHeight);
272: g.setColor(questionBkColor);
273: g.fillRect(0, topBandHeight + 1, d.width, d.height);
274: }
275:
276: protected void paintQuestionText(Graphics g) {
277: int xOffset = woffset;
278: // XStyle questionStyle = styleManager.getStyle( "base/Question/Cue" );
279: XStyle questionStyle = styleManager.getStyle("base/Question");
280: Font f = styleManager.getFont(questionStyle);
281: if (f == null)
282: f = new Font("Dialog", 0, 10);
283: g.setFont(f);
284: fm = g.getFontMetrics(f);
285: hh = fm.getHeight() + headerHeight + topBandHeight;
286:
287: String qText = question.getText();
288: if (qText != null) {
289: g.setColor(questionTextColor);
290: g.drawString(question.getText(), xOffset, fm.getAscent()
291: + topBandHeight + 10);
292: }
293: }
294:
295: protected void paintCueImage(Graphics g) {
296: int xOffset = woffset;
297: if (cueImage != null) {
298: Image img = currentProject.getImage(cueImage);
299: if (img != null)
300: g.drawImage(img, xOffset, hh + 2, cueBkColor, this );
301: }
302: }
303:
304: protected void paintCheckBox(int x, int y, Graphics g) {
305: g.setColor(checkBkColor);
306: g.fillRect(x + 1, y, checkSize, checkSize - 2);
307: g.setColor(shadowColor);
308: g.drawRect(x + 1, y, checkSize, checkSize);
309: g.setColor(checkColor);
310: g.drawRect(x, y - 1, checkSize, checkSize);
311: }
312:
313: protected void paintRadioButton(int x, int y, Graphics g) {
314: g.setColor(checkBkColor);
315: g.fillRoundRect(x + 1, y, checkSize, checkSize - 2, radioArc,
316: radioArc);
317: g.setColor(shadowColor);
318: g.drawRoundRect(x + 1, y, checkSize, checkSize, radioArc,
319: radioArc);
320: g.setColor(checkColor);
321: g.drawRoundRect(x, y - 1, checkSize, checkSize, radioArc,
322: radioArc);
323: }
324:
325: protected void paintResponses(Graphics g) {
326: Dimension d = getSize();
327: XStyle questionStyle = styleManager
328: .getStyle("base/Question/Cue");
329: Font f = styleManager.getFont(questionStyle);
330: XStyle responseStyle = styleManager
331: .getStyle("base/Question/Response");
332: Font f2 = styleManager.getFont(responseStyle);
333: if (f2 == null)
334: f2 = f;
335: g.setFont(f2);
336: fm = g.getFontMetrics(f2);
337:
338: hh = Math.min(hh, d.height - fm.getAscent() - 11);
339:
340: int xOffset = responseHGap;
341: int numResponses = (question != null ? question.getNumOptions()
342: : 0);
343:
344: for (int i = 0; i < numResponses; i++) {
345: String responseText = question.getOptionText(i);
346: if (responseText != null) {
347: // multiple choice, rendered with checkboxes
348: if (question.getQuestionType() == Question.MULTIPLE_CHOICE) {
349: paintCheckBox(xOffset, hh + responseVGap, g);
350: g.setColor(responseTextColor);
351: g.drawString(responseText, xOffset + checkSize
352: + responseTextGap, hh + fm.getAscent()
353: + responseVGap - 3);
354: }
355: // mutually exclusive, rendered with radio buttons
356: else if (question.getQuestionType() == Question.MUTUALLY_EXCLUSIVE) {
357: g.setColor(checkBkColor);
358: paintRadioButton(xOffset, hh + responseVGap, g);
359: g.setColor(responseTextColor);
360: g.drawString(responseText, xOffset + checkSize
361: + responseTextGap, hh + fm.getAscent()
362: + responseVGap - 3);
363: }
364: // free text, rednered with edit fields
365: else if (question.getQuestionType() == Question.FREE_TEXT) {
366: g.setColor(responseTextColor);
367: g.drawString(responseText, xOffset + 2, hh
368: + fm.getAscent() + responseVGap - 3);
369: int w = (int) fm.getStringBounds(responseText, g)
370: .getWidth();
371: editTable[i].setLocation(xOffset + w + 6, hh
372: + responseVGap - 3);
373: }
374: }
375: xOffset += xScale;
376: }
377: }
378:
379: protected void paintHighlightFrame(Graphics g) {
380: if ((clickListener != null) && (clickListener.getIsEntered())) {
381: Dimension d = getSize();
382: g.setColor(checkColor);
383: int m = (isLastQuestion ? 2 : 1);
384: for (int i = 0; i < frameSize; i++)
385: g.drawRect(i, topBandHeight + i, d.width - 1 - 2 * i,
386: d.height - m * topBandHeight - 1 - 2 * i);
387: }
388: }
389:
390: protected void paintActiveCheckBox(int x, int y, Graphics g) {
391: g.setColor(shadowColor);
392: g.fillRect(x + 1, y + 1, checkSize - 1, checkSize - 1);
393: }
394:
395: protected void paintActiveRadioButton(int x, int y, Graphics g) {
396: g.setColor(shadowColor);
397: int w = checkSize;
398: int h = checkSize;
399: int arc = radioArc;
400: g.fillRoundRect(x, y, w, h, arc, arc);
401: g.setColor(checkColor);
402: g.drawRoundRect(x, y, w, h, arc, arc);
403: }
404:
405: protected void paintActiveResponses(Graphics g) {
406: // the filling of an active responses (in case of checkboxes and radiobuttons)
407: int questionType = question.getQuestionType();
408: if ((questionType != Question.FREE_TEXT)
409: && (clickListener != null)
410: && (clickListener.getIsEntered())) {
411: int activeResponse = clickListener.getActiveResponse();
412: if (activeResponse >= 0) {
413: int xOffset = responseHGap;
414:
415: if (questionType == Question.MULTIPLE_CHOICE) {
416: int x = xOffset + activeResponse * xScale;
417: int y = hh + responseVGap - 1;
418: paintActiveCheckBox(x, y, g);
419: } else if (questionType == Question.MUTUALLY_EXCLUSIVE) {
420: int x = xOffset + activeResponse * xScale;
421: int y = hh + responseVGap - 1;
422: paintActiveRadioButton(x, y, g);
423: }
424: }
425:
426: }
427: }
428:
429: protected void paintSelectedResponses(Graphics g) {
430: if (question.getQuestionType() == Question.FREE_TEXT)
431: return;
432: g.setColor(checkColor);
433: int xOffset = responseHGap;
434: int resState = responseState;
435: while (resState > 0) {
436: if ((resState & 0x1) == 1) {
437: int x = xOffset + 3;
438: int y = hh + responseVGap + 2;
439: int w = checkSize - 5;
440: int h = checkSize - 5;
441: g.fillRect(x, y, w, h);
442: }
443:
444: resState >>= 1;
445: xOffset += xScale;
446: }
447: }
448:
449: protected void paintBottomBand(Graphics g) {
450: Dimension d = getSize();
451: g.setColor(cueColor);
452: g.fillRect(0, d.height - topBandHeight, d.width, topBandHeight);
453: }
454:
455: /**
456: * Render the question. A header is drawn containing the question text, then
457: * the body of the question is filled. Within the body the cue image is drawn
458: * followed by each response spaced equally and left aligned
459: * @param sg the graphics context
460: */
461: public void paint(Graphics sg) {
462: // initialize
463: Image offscreen;
464: Graphics g;
465: if (!printing) {
466: offscreen = createImage(getSize().width, getSize().height);
467: g = offscreen.getGraphics();
468: } else {
469: offscreen = null;
470: g = sg;
471: }
472:
473: Dimension d = getSize();
474: g.setClip(0, 0, d.width, d.height);
475: //////////
476:
477: paintTopBand(g);
478: paintQuestionText(g);
479: paintCueImage(g);
480: paintResponses(g);
481:
482: // paint the edit fields that has been added
483: super .paint(g);
484: paintHighlightFrame(g);
485: paintActiveResponses(g);
486: paintSelectedResponses(g);
487:
488: if (isLastQuestion)
489: paintBottomBand(g);
490:
491: if (!printing) {
492: sg.drawImage(offscreen, 0, 0, getSize().width,
493: getSize().height, null);
494: g.dispose();
495: offscreen = null;
496: }
497:
498: }
499:
500: // to be invoked by sublclasses
501: protected void super Paint(Graphics g) {
502: super .paint(g);
503: }
504:
505: /**
506: * Gets the question's id
507: * @return The ID
508: */
509: public int getId() {
510: return question.getId();
511: }
512:
513: /**
514: * Gets the selected response
515: * @return the response value
516: */
517: public String getResponse() {
518: if (clickListener == null)
519: return null;
520:
521: if (clickListener.getSelectedResponse() < 0)
522: return "0";
523:
524: if (question.getQuestionType() == Question.MULTIPLE_CHOICE) {
525: int responses = question.getNumOptions();
526: String response = new String();
527: for (int i = 0; i < responses; i++) {
528: if (response.length() > 0)
529: response += QuestionManager.tokenSeparator;
530: if (isOptionSelected(i))
531: response += new Integer(question.getOptionId(i))
532: .toString();
533: else
534: response += "-";
535: }
536: return response;
537: }
538:
539: return new Integer(question.getOptionId(clickListener
540: .getSelectedResponse())).toString();
541: }
542:
543: /**
544: * Checks to see if a praticular option is selected
545: * @param idx the index of the option
546: * @return true if the option is selected
547: */
548: public boolean isOptionSelected(int idx) {
549: int response = 1;
550: response <<= idx;
551: return (responseState & response) > 0;
552: }
553:
554: /**
555: * Find a response state based on the coordinates of the mouse click
556: * @param x the x coordinate of the mouse click
557: * @param y the y coordinate of the mouse click
558: * @param defResponse the default response
559: * @return true if a response was found
560: */
561: public boolean setState(int x, int y, int defResponse) {
562: int activeResponse = findCurrentResponse(x, y);
563: if (activeResponse == Integer.MIN_VALUE)
564: activeResponse = defResponse;
565:
566: if (clickListener != null)
567: clickListener.setActiveResponse(activeResponse);
568: return (defResponse != activeResponse);
569: }
570:
571: /**
572: * Find a response state based on the coordinates of the mouse click
573: * @param x the x coordinate of the mouse click
574: * @param y the y coordinate of the mouse click
575: * @return the response id or Integer.MIN_VALUE if a response was not selected
576: */
577: public int findCurrentResponse(int x, int y) {
578: int numResponses = question.getNumOptions();
579: int activeResponse = x / xScale;
580: if ((activeResponse == 0) && (x < 0))
581: activeResponse = Integer.MIN_VALUE;
582: else if (activeResponse >= numResponses)
583: activeResponse = Integer.MIN_VALUE;
584:
585: return activeResponse;
586: }
587:
588: /**
589: * Called by XClickListener to check if a response event should be sent to the
590: * parent form. The control can also use this event to do post click processing
591: * @return true if the parent is to be notified
592: */
593: public boolean fireActionEvent() {
594: return true;
595: }
596:
597: /**
598: * Repaints the responses so that the current response is shown
599: */
600: public void paintStates() {
601: repaint();
602: }
603:
604: /**
605: * Updates the state of the options
606: */
607: public void updateSelectedState() {
608: int selectedResponse = -1;
609: if (clickListener != null)
610: selectedResponse = clickListener.getSelectedResponse();
611: if (selectedResponse >= 0) {
612: int response = 1;
613: response <<= selectedResponse;
614: if (question.getQuestionType() == question.MULTIPLE_CHOICE)
615: responseState ^= response; // toggle the response
616: else
617: responseState = response;
618: storeResponses();
619: }
620: }
621:
622: protected void retrieveResponses() {
623: responseState = 0;
624: int questionType = question.getQuestionType();
625: // multiple choice
626: if (questionType == Question.MULTIPLE_CHOICE) {
627: int numOptions = question.getNumOptions();
628: for (int i = 0; i < numOptions; i++) {
629: String optionId = String.valueOf(question.getOption(i)
630: .getId());
631: XModel optionModel = (XModel) responseModel
632: .get(optionId);
633:
634: Object o = optionModel.get();
635: if ((optionModel == null) || (o == null)
636: || (o.toString().equals("0")))
637: continue;
638: // mark the response
639: responseState |= (1 << i);
640: }
641: }
642: // mutually exclusive
643: else if (questionType == Question.MUTUALLY_EXCLUSIVE) {
644: String option = responseModel.getAttribValueAsString(0);
645: if ((option != null) && (!"".equals(option))) {
646: int optionId = question.getOptionIdByText(option);
647: int idx = question.getOptionIdx(optionId);
648: responseState |= (1 << idx);
649: }
650: }
651: // free text
652: else if (questionType == Question.FREE_TEXT) {
653: int numOptions = question.getNumOptions();
654: for (int i = 0; i < numOptions; i++) {
655: String optionId = String.valueOf(question.getOption(i)
656: .getId());
657: XModel optionModel = (XModel) responseModel
658: .get(optionId);
659: String text = optionModel.getAttribValueAsString(i);
660: if (text != null)
661: editTable[i].setText(text);
662: }
663: }
664: }
665:
666: protected void storeFreeTextResponses() {
667: int numOptions = question.getNumOptions();
668: for (int i = 0; i < numOptions; i++) {
669: String text = editTable[i].getText();
670: String optionId = String.valueOf(question.getOption(i)
671: .getId());
672: XModel optionModel = (XModel) responseModel.get(optionId);
673: optionModel.setAttribValue(0, text);
674: }
675: }
676:
677: protected void storeResponses() {
678: int state = responseState;
679: int questionType = question.getQuestionType();
680:
681: // mutliple choice
682: if (questionType == Question.MULTIPLE_CHOICE) {
683: int numOptions = question.getNumOptions();
684: for (int i = 0; i < numOptions; i++) {
685: String optionId = String.valueOf(question.getOption(i)
686: .getId());
687: XModel optionModel = (XModel) responseModel
688: .get(optionId);
689: optionModel.set(((state & 0x1) == 1) ? "1" : "0");
690: state >>= 1;
691: }
692:
693: }
694: // mutually exclusive
695: else if (questionType == Question.MUTUALLY_EXCLUSIVE) {
696: int selResponse = -1;
697: int numOptions = question.getNumOptions();
698: for (int i = 0; i < numOptions; i++) {
699: if ((state & 0x1) == 1) {
700: selResponse = i;
701: break;
702: }
703: state >>= 1;
704: }
705: if (selResponse >= 0) {
706: Option option = question.getOption(selResponse);
707: String optionText = option.getText();
708: responseModel.setAttribValue(0, optionText);
709: }
710:
711: }
712: // free text
713: else if (questionType == Question.FREE_TEXT) {
714: storeFreeTextResponses();
715: }
716: }
717:
718: /**
719: * Unchecks all responses
720: */
721: public void clear() {
722: if (clickListener != null)
723: clickListener.setSelectedResponse(-1);
724: repaint();
725: }
726:
727: /**
728: * Sets the control's text. Used to localize the control. In the case of this
729: * control the method does nothing since the questions are localized as part
730: * of an entire questionaire.
731: *
732: * @param s The new text to display.
733: */
734: public void setText(String s) {
735: if (question != null)
736: question.setText(s);
737: }
738:
739: /**
740: * Gets the questions text
741: * @return The question text
742: */
743: public String getText() {
744: return question.getText();
745: }
746:
747: /**
748: * Gets the question id.
749: * @return the Question ID
750: */
751: public int getQuestionId() {
752: return question.getId();
753: }
754:
755: /**
756: * Gets the question.
757: * @return the question or null if none isreferenced
758: */
759: public Question getQuestion() {
760: return question;
761: }
762:
763: /**
764: * Gets the question type.
765: * @return The question type
766: */
767: public int getQuestionType() {
768: return question.getQuestionType();
769: }
770:
771: /**
772: * Set one or more attributes of the component. Currently this handles the
773: * attributes
774: * <OL>
775: * <LI>cue, value=Cue image filename</LI>
776: * </OL>
777: * @return 0 for success, non zero otherwise
778: */
779: public int setAttribute(String attribName, Object attribValue) {
780: String attribNameLwr = attribName.toLowerCase();
781: String attribValueStr = (String) attribValue;
782: if (attribNameLwr.equals("cue"))
783: cueImage = attribValueStr;
784: else if (attribNameLwr.equals("scale")) {
785: scaleSpecified = true;
786: setScale(new Integer(attribValueStr).intValue());
787: } else if (attribNameLwr.equals("id"))
788: question.setId(new Integer(attribValueStr).intValue());
789: else if (attribNameLwr.equals("ref")) {
790: Survey survey = surveyManager.getCurrentSurvey();
791: question = survey.getNextQuestion();
792: responseModel = getResponsesModel(survey);
793: isLastQuestion = !survey.hasMoreQuestionsToShow();
794: if (!scaleSpecified) {
795: int numResponses = question.getNumOptions();
796: setScale(numResponses);
797: }
798: if (question.getQuestionType() != Question.FREE_TEXT) {
799: clickListener = new XClickListener(this , woffset);
800: addMouseListener(clickListener);
801: addMouseMotionListener(clickListener);
802: } else {
803: createEditFields();
804: clickListener = new XClickListener(this , woffset);
805: addMouseMotionListener(clickListener);
806: addMouseListener(clickListener);
807: }
808: retrieveResponses();
809: }
810:
811: if (isVisible())
812: repaint(100);
813:
814: return 0;
815: }
816:
817: /**
818: * Returns the XModel node that will store the user's given
819: * responsese to this question
820: * @param survey the currenty survey
821: * @return responses model node
822: */
823: protected XModel getResponsesModel(Survey survey) {
824: String path = "/xui_state/currentSurvey/";
825: path += survey.getCurrentPageNr() + "/";
826: path += survey.getCurrentGroup().getId() + "/";
827: path += survey.getCurrentQuestionId();
828: XModel model = (XModel) currentProject.getModel().get(path);
829: return model;
830: }
831:
832: /**
833: * In case the question type is "free text" creates
834: * and adds the XEdit components to render the question's
835: * answers.
836: */
837: private void createEditFields() {
838: Dimension dim = new Dimension(editWidth, editHeight);
839: XStyleFactory styleFactory = new XStyleFactory(currentProject,
840: "net.xoetrope.swing");
841: XStyle responseStyle = currentProject.getStyleManager()
842: .getStyle("base/Question/Response");
843:
844: int numResponses = question.getNumOptions();
845: editTable = new XEdit[numResponses];
846: for (int i = 0; i < numResponses; i++) {
847: XEdit edit = editTable[i] = new XEdit();
848:
849: edit.addFocusListener(new FocusListener() {
850: public void focusGained(FocusEvent e) {
851: storeFreeTextResponses();
852: }
853:
854: public void focusLost(FocusEvent e) {
855: storeFreeTextResponses();
856: }
857: });
858:
859: edit.setSize(dim);
860: edit.setPreferredSize(dim);
861: styleFactory.applyStyle(edit, responseStyle);
862: edit.setLocation(20, 20);
863: add(edit);
864: }
865: }
866:
867: /**
868: * Get the model path
869: */
870: public String getSourcePath() {
871: return "currentSurvey/question/"
872: + new Integer(question.getId()).toString();
873: }
874:
875: /**
876: * Get the output/save path
877: */
878: public String getOutputPath() {
879: return "currentSurvey/question/"
880: + new Integer(question.getId()).toString();
881: }
882:
883: /**
884: * Set the model path for the source data
885: */
886: public void setSourcePath(String newPath) {
887: }
888:
889: /**
890: * Set the model path for the output/state data
891: */
892: public void setOutputPath(String newPath) {
893: }
894:
895: /**
896: * Update the model node used in the binding. Note that this method does not
897: * modify the path values stored by this node.
898: * @param newNode the new model for the data source
899: */
900: public void setSource(XModel newNode) {
901: }
902:
903: /**
904: * Update the path values stored by this node. The output path is used to
905: * store selection data and state.
906: * @param newModel the new model for saving the output data
907: */
908: public void setOutput(XModel newModel, String outputPath) {
909: }
910:
911: /**
912: * Used for custom components with an argumentless constructor. Constructs the
913: * binding from the model path argument and the XML argument that defines
914: * the binding.
915: * This object does not implement this method and throws an UnsupportedOperationException.
916: * @param c the component to bind
917: * @param dataElement the path to the data source
918: * @param ele the XML element which contains the binding configuration
919: */
920: public void setup(Component c, String dataElement, XmlElement ele) {
921: throw new UnsupportedOperationException();
922: }
923:
924: /**
925: * Get the component to which this binding is attached
926: */
927: public Object getComponent() {
928: return this ;
929: }
930:
931: /**
932: * Get the file name of the cue image
933: * @return the file name
934: */
935: public String getCueFileName() {
936: return cueImage;
937: }
938:
939: /**
940: * Gets the scale being used by this question for its layout
941: * @return the scale
942: */
943: public int getScale() {
944: return scale;
945: }
946:
947: }
|