1 /*
2  * Modification History
3  *
4  * 2001-September-16		Jason Rohrer
5  * Created.
6  *
7  * 2001-November-3		Jason Rohrer
8  * Fixed a gutter size bug that occurred when there was
9  * only one row or column.
10  * Changed so that rows are filled before adding new columns.
11  * Fixed a bug in setSelectedIndex.
12  */
13 
14 
15 #ifndef MULTI_BUTTON_GL_INCLUDED
16 #define MULTI_BUTTON_GL_INCLUDED
17 
18 #include "GUIContainerGL.h"
19 #include "StickyButtonGL.h"
20 
21 #include "minorGems/graphics/Image.h"
22 
23 #include "minorGems/ui/event/ActionListenerList.h"
24 #include "minorGems/ui/event/ActionListener.h"
25 #include "minorGems/ui/GUIComponent.h"
26 
27 #include <math.h>
28 #include <stdio.h>
29 
30 /**
31  * A set of buttons that allows only one to be depressed
32  * at a time.
33  *
34  * @author Jason Rohrer
35  */
36 class MultiButtonGL : public GUIContainerGL,
37 					public ActionListener,
38 					public ActionListenerList {
39 
40 
41 	public:
42 
43 
44 
45 		/**
46 		 * Constructs a set of buttons.
47 		 *
48 		 * @param inAnchorX the x position of the upper left corner
49 		 *   of this component.
50 		 * @param inAnchorY the y position of the upper left corner
51 		 *   of this component.
52 		 * @param inWidth the width of this component.
53 		 * @param inHeight the height of this component.
54 		 * @param inNumButtons the number of buttons in the set.
55 		 * @param inUnpressedImages the images to display
56 		 *   when each button is unpressed.  Images must have dimensions
57 		 *   that are powers of 2.
58 		 *   Will be destroyed when this class is destroyed.
59 		 * @param inPressedImages the images to display
60 		 *   when each button is pressed.  Images must have dimensions
61 		 *   that are powers of 2.
62 		 *   Will be destroyed when this class is destroyed.
63 		 * @param inGutterFraction the fraction of each row
64 		 *   and column that should be taken up by the gutters
65 		 *   (the spaces between buttons).  In [0, 1.0].
66 		 */
67 		MultiButtonGL(
68 			double inAnchorX, double inAnchorY, double inWidth,
69 			double inHeight, int inNumButtons,
70 			Image **inUnpressedImages,
71 			Image **inPressedImages,
72 			double inGutterFraction );
73 
74 
75 
76 		~MultiButtonGL();
77 
78 
79 
80 		/**
81 		 * Sets the currently depressed button.
82 		 *
83 		 * Note that if this function causes a change
84 		 * in the state of the set, an action will
85 		 * be fired to all registered listeners.
86 		 *
87 		 * @param inButtonIndex the index of the button to depress.
88 		 */
89 		void setSelectedButton( int inButtonIndex );
90 
91 
92 
93 		/**
94 		 * Gets the index of the currently depressed button.
95 		 *
96 		 * Note that if this function causes a change
97 		 * in the state of the set, an action will
98 		 * be fired to all registered listeners.
99 		 *
100 		 * @return the index of the button that is depressed.
101 		 */
102 		int getSelectedButton();
103 
104 
105 
106 		// we don't need to override any mouse
107 		// or redraw functions, since the container
108 		// and the buttons should handle these correctly
109 
110 
111 		// implements the ActionListener interface
112 		virtual void actionPerformed( GUIComponent *inTarget );
113 
114 
115 	protected:
116 		int mNumButtons;
117 
118 		StickyButtonGL **mButtons;
119 
120 
121 		int mSelectedIndex;
122 
123 
124 		char mIgnoreEvents;
125 
126 		/**
127 		 * Maps a button in mButtons to its index in the
128 		 * mButtons array.
129 		 *
130 		 * @param inButton the button to get an index for.
131 		 *
132 		 * @return the index of inButton in the mButtons array.
133 		 */
134 		int buttonToIndex( StickyButtonGL *inButton );
135 
136 
137 
138 	};
139 
140 
141 
MultiButtonGL(double inAnchorX,double inAnchorY,double inWidth,double inHeight,int inNumButtons,Image ** inUnpressedImages,Image ** inPressedImages,double inGutterFraction)142 inline MultiButtonGL::MultiButtonGL(
143 	double inAnchorX, double inAnchorY, double inWidth,
144 	double inHeight, int inNumButtons,
145 	Image **inUnpressedImages,
146 	Image **inPressedImages,
147 	double inGutterFraction )
148 	: GUIContainerGL( inAnchorX, inAnchorY, inWidth, inHeight ),
149 	  mNumButtons( inNumButtons ),
150 	  mButtons( new StickyButtonGL*[inNumButtons] ),
151 	  mSelectedIndex( 0 ),
152 	  mIgnoreEvents( false ) {
153 
154 	int numColumns = (int)( sqrt( mNumButtons ) );
155 	int numRows = mNumButtons / numColumns;
156 
157 
158 	while( numRows * numColumns < mNumButtons ) {
159 		numRows++;
160 		}
161 
162 	// determine gutter and button sizes
163 
164 	double gutterSizeX;
165 	double gutterSizeY;
166 
167 	if( numRows == 1 ) {
168 		gutterSizeY = 0;
169 		}
170 	else {
171 		gutterSizeY =
172 			( inHeight * inGutterFraction ) / ( numRows - 1 );
173 		}
174 	if( numColumns == 1 ) {
175 		gutterSizeX = 0;
176 		}
177 	else {
178 		gutterSizeX =
179 			( inWidth * inGutterFraction ) / ( numColumns - 1 );
180 		}
181 
182 
183 	double buttonSizeX =
184 		( inWidth * ( 1 - inGutterFraction ) ) / ( numColumns );
185 	double buttonSizeY =
186 		( inHeight * ( 1 - inGutterFraction ) ) / ( numRows );
187 
188 
189 	// setup each button
190 
191 	int buttonIndex = 0;
192 
193 	for( int r=0; r<numRows; r++ ) {
194 		for( int c=0; c<numColumns; c++ ) {
195 
196 			double anchorX = inAnchorX +
197 				c * ( buttonSizeX + gutterSizeX );
198 			double anchorY = inAnchorY +
199 				r * ( buttonSizeY + gutterSizeY );
200 
201 			mButtons[ buttonIndex ] =
202 				new StickyButtonGL( anchorX, anchorY,
203 									buttonSizeX, buttonSizeY,
204 									inUnpressedImages[ buttonIndex ],
205 									inPressedImages[ buttonIndex ] );
206 
207 			GUIContainerGL::add( mButtons[ buttonIndex ] );
208 
209 			buttonIndex++;
210 
211 			if( buttonIndex >= mNumButtons ) {
212 				// jump out of our loop if we run out of buttons
213 				c = numColumns;
214 				r = numRows;
215 				}
216 			}
217 		}
218 
219 	// now we've added all of our buttons
220 
221 	// press one of them before we add any listeners
222 	mButtons[ mSelectedIndex ]->setPressed( true );
223 
224 
225 	// now add ourselves as an action listener to each button
226 	for( int i=0; i<mNumButtons; i++ ) {
227 		mButtons[ i ]->addActionListener( this );
228 		}
229 
230 	// delete the arrays pointing to the images, since
231 	// they are no longer needed
232 	delete [] inUnpressedImages;
233 	delete [] inPressedImages;
234 
235 	}
236 
237 
238 
~MultiButtonGL()239 inline MultiButtonGL::~MultiButtonGL() {
240 	// note that we don't need to delete the buttons
241 	// themselves, since they will be deleted by GUIContainer
242 	// destructor
243 
244 	delete [] mButtons;
245 	}
246 
247 
248 
setSelectedButton(int inButtonIndex)249 inline void MultiButtonGL::setSelectedButton( int inButtonIndex ) {
250 	// simply press the appropriate button, and let the
251 	// action handlers do the rest
252 
253 	mButtons[ inButtonIndex ]->setPressed( true );
254 	}
255 
256 
257 
getSelectedButton()258 inline int MultiButtonGL::getSelectedButton() {
259 	return mSelectedIndex;
260 	}
261 
262 
263 
actionPerformed(GUIComponent * inTarget)264 inline void MultiButtonGL::actionPerformed( GUIComponent *inTarget ) {
265 	if( !mIgnoreEvents ) {
266 		int buttonIndex = buttonToIndex( (StickyButtonGL*)inTarget );
267 
268 		// if another button is being pressed
269 		if( buttonIndex != mSelectedIndex
270 			&& mButtons[ buttonIndex ]->isPressed() ) {
271 
272 
273 			// unpress the old button, ignoring the resulting event
274 			mIgnoreEvents = true;
275 			mButtons[ mSelectedIndex ]->setPressed( false );
276 			mIgnoreEvents = false;
277 
278 			mSelectedIndex = buttonIndex;
279 
280 			fireActionPerformed( this );
281 			}
282 		// else if our selected button is being unpressed
283 		else if( buttonIndex == mSelectedIndex
284 				 && !( mButtons[ buttonIndex ]->isPressed() ) ) {
285 			// don't allow it to become unpressed
286 
287 			// re-press it, ignoring the resulting event
288 			mIgnoreEvents = true;
289 			mButtons[ mSelectedIndex ]->setPressed( true );
290 			mIgnoreEvents = false;
291 
292 			// don't fire an action, since our state
293 			// has not changed
294 			}
295 		}
296 	}
297 
298 
299 
buttonToIndex(StickyButtonGL * inButton)300 inline int MultiButtonGL::buttonToIndex( StickyButtonGL *inButton ) {
301 	for( int i=0; i<mNumButtons; i++ ) {
302 		if( mButtons[i] == inButton ) {
303 			return i;
304 			}
305 		}
306 
307 	// return the first button index by default
308 	printf( "MultiButtonGL:  no matching button found.\n" );
309 	return 0;
310 	}
311 
312 
313 
314 #endif
315