Vegastrike 0.5.1 rc1  1.0
Original sources for Vegastrike Evolved
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
picker.cpp
Go to the documentation of this file.
1 /*
2  * Vega Strike
3  * Copyright (C) 2003 Mike Byron
4  *
5  * http://vegastrike.sourceforge.net/
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21 
22 #include "vegastrike.h"
23 
24 #include "picker.h"
25 
26 #include "eventmanager.h"
27 #include "scroller.h"
28 #include "painttext.h"
29 
30 #include "vs_globals.h"
31 #include "config_xml.h"
32 #include "xml_support.h"
33 
34 #include <list>
35 
36 //Calculation for indenting children. Use a factor times total cell height.
37 static const float CHILD_INDENT_FACTOR = 0.6;
38 
39 //Make sure we don't get too many re-alloc's in the display vector for cells.
40 static const int DISPLAY_VECTOR_RESERVE = 30;
41 
42 //When scrolling a cell into view, how many lines between the cell and the edge.
43 static const int SCROLL_EDGE_EXTRA = 1;
44 
45 //Find a cell by id. Returns NULL if not found.
46 PickerCell* PickerCells::cellWithId( const std::string &id )
47 {
48  for (int i = 0; i < count(); i++) {
49  PickerCell *cell = cellAt( i );
50  if (cell->id() == id)
51  //Found it.
52  return cell;
53  }
54  //Didn't find a cell with the specified id.
55  return NULL;
56 }
57 
58 bool PickerCells::saveOpenCategories( std::list< std::list< std::string > > &masterList,
59  const std::list< std::string > &parentHier,
60  PickerCell *selectedCell ) const
61 {
62  bool hasSelectedCell = false;
63  for (int i = 0; i < count(); i++) {
64  const PickerCell *cell = cellAt( i );
65  if (cell == selectedCell)
66  hasSelectedCell = true;
67  if ( !cell->hideChildren() ) {
68  masterList.push_back( parentHier );
69  std::list< std::string > *newItem = &masterList.back();
70  (*newItem).push_back( cell->id() );
71  PickerCells *newCells = cell->children();
72  bool savedCell = false;
73  if (newCells)
74  savedCell = newCells->saveOpenCategories( masterList, (*newItem), selectedCell );
75  if (savedCell)
76  (*newItem).push_back( selectedCell->id() );
77  }
78  }
79  return hasSelectedCell;
80 }
81 
82 void Picker::saveOpenCategories( std::list< std::list< std::string > > &idList ) const
83 {
84  std::list< std::string >base;
85  cells()->saveOpenCategories( idList, base, m_selectedCell );
86 }
87 
88 int Picker::restoreOpenCategories( const std::list< std::list< std::string > > &idList )
89 {
90  int numRestored = 0;
91  PickerCell *selectedCell = NULL;
92  for (std::list< std::list< std::string > >::const_iterator catIt = idList.begin();
93  catIt != idList.end();
94  ++catIt) {
95  PickerCells *celllist = cells();
96  for (std::list< std::string >::const_iterator travIt = (*catIt).begin();
97  travIt != (*catIt).end();
98  ++travIt) {
99  PickerCell *cell = celllist->cellWithId( (*travIt) );
100  if (!cell)
101  break;
102  celllist = cell->children();
103  if (!celllist || celllist->count() == 0) {
104  selectedCell = cell;
105  break;
106  }
107  cell->setHideChildren( false );
108  }
109  }
110  setMustRecalc();
111  if (selectedCell)
112  selectCell( selectedCell, true );
113  return numRestored;
114 }
115 
116 //Draw the picker
117 void Picker::draw( void )
118 {
119  //If we need to change the displayed cells, do that first.
121  recalcDisplay();
122  //Draw the background.
123  drawBackground();
124 
125  const float cellHeight = totalCellHeight();
126 
127  //This is the current cell rect. Start with control rect and adjust y.
128  Rect rect( m_rect );
129  rect.origin.y += m_rect.size.height-cellHeight;
130  rect.size.height = cellHeight;
131  for (vector< DisplayCell >::size_type i = m_scrollPosition; i< m_displayCells.size() && rect.origin.y >m_rect.origin.y;
132  i++) {
133  DisplayCell &display = m_displayCells[i];
134  const PickerCell *cell = display.cell; //Get the next cell.
135 
136  //Figure background and text color.
137  GFXColor backgroundColor = GUI_CLEAR;
138  GFXColor textColor = cell->textColor();
139  if (cell == m_selectedCell) {
140  //Selected state more important than highlighted state.
141  backgroundColor = m_selectionColor;
142  if ( isClear( textColor ) ) textColor = m_selectionTextColor;
143  }
144  //Selection color might be clear, or might be highlighted cell.
145  if (isClear( backgroundColor ) && cell == m_highlightedCell) {
146  //Highlighted cell.
147  backgroundColor = m_highlightColor;
148  if ( isClear( textColor ) ) textColor = m_highlightTextColor;
149  }
150  if ( !isClear( backgroundColor ) )
151  drawRect( rect, backgroundColor );
152  //If we haven't got a text color yet, use the control's color.
153  if ( isClear( textColor ) ) textColor = m_textColor;
154  //Include indent in drawing rect.
155  //Indent is based on cell height.
156  const float indentPerLevel = m_displayCells[i].level*cellHeight*CHILD_INDENT_FACTOR;
157  Rect drawRect = rect;
158  drawRect.inset( m_textMargins );
159  drawRect.origin.x += indentPerLevel;
160  drawRect.size.width -= indentPerLevel;
161 
162  //Paint the text.
163  //There is a PaintText object in each DisplayCell so that we don't have to re-layout the text
164  //all the time. This code should be smarter about only setting the attributes of the text
165  //object when things change, but that means cell changes need to be communicated back to
166  //this object, and they aren't now.
167  display.paintText.setRect( drawRect );
168  display.paintText.setText( cell->text() );
169  display.paintText.setFont( m_font );
170  display.paintText.setColor( textColor );
171  display.paintText.draw();
172 
173  rect.origin.y -= cellHeight;
174  }
175 }
176 
177 //Return the index of the current selected cell in the list of cells.
178 //This can only be used if the list simple, not a tree.
179 //Returns -1 if no selection, or if the selection is a child.
181 {
182  if (m_cells != NULL && m_selectedCell != NULL) {
183  //If we have a selection, find it in the list. Won't find it if it's a child.
184  for (int i = 0; i < m_cells->count(); i++) {
185  PickerCell *cell = m_cells->cellAt( i );
186  if (cell == m_selectedCell)
187  //Found it.
188  return i;
189  }
190  }
191  //Didn't find it.
192  return -1;
193 }
194 
195 //Find the cell for a mouse point.
197 {
198  if ( m_rect.inside( point ) ) {
199  const vector< DisplayCell >::size_type index = float_to_int(
201  if ( index < m_displayCells.size() )
202  //It's within the cells we are displaying.
203  return m_displayCells[index].cell;
204  }
205  //Didn't find anything.
206  return NULL;
207 }
208 
209 //Actually cause a cell to be selected.
210 void Picker::selectCell( PickerCell *cell, bool scroll )
211 {
212  PickerCell *oldCell = m_selectedCell;
213  m_selectedCell = cell;
214  //If the cell has children, flip whether the children are displayed.
215  if (cell != NULL) {
216  PickerCells *list = cell->children();
217  if (list != NULL && list->count() > 0) {
218  const bool hideChildren = !cell->hideChildren();
219  cell->setHideChildren( hideChildren );
220  if (!hideChildren) {
221  recalcDisplay();
222  //Make sure the children are visible.
223  PickerCells *loopList = list;
224  PickerCell *lastChild = NULL;
225  while (true) {
226  lastChild = loopList->cellAt( loopList->count()-1 );
227  if ( lastChild->hideChildren() )
228  //Can't see children below this. Done.
229  break;
230  loopList = lastChild->children();
231  if (loopList == NULL || loopList->count() == 0)
232  //lastChild has no children.
233  break;
234  }
235  scrollToCell( lastChild );
236  //Now make sure the original parent is still visible.
237  scrollToCell( cell );
238  } else {
239  setMustRecalc();
240  }
241  }
242  if (scroll)
243  //Make sure the cell is visible.
244  scrollToCell( cell );
245  }
246  if (oldCell != m_selectedCell)
247  sendCommand( "Picker::NewSelection", this );
248 }
249 
250 //Recursive routine that goes through a cell list and the children
251 //of the cells and puts them on the display list.
253 {
254  //Go through all the cells in this list.
255  for (int i = 0; i < list->count(); i++) {
256  PickerCell *cell = list->cellAt( i );
257  DisplayCell displayCell( cell, level );
258  m_displayCells.push_back( displayCell ); //Add this cell to the list.
259  PickerCells *children = cell->children();
260  if (!cell->hideChildren() && children != NULL)
261  //We have children to show, so add them, too.
262  addListToDisplay( children, level+1 );
263  }
264 }
265 
266 //Reload the list of cells that are being displayed.
267 //This should be called when a change is made in the lists of cells, or
268 //when we scroll, which again changes the cells we display.
269 //It does not need to be called for text or color changes, only when
270 //cells are added or removed, etc.
272 {
273  //Clear out the old display list.
274  m_displayCells.clear();
275 
276  //Recursively refill the display list.
278  if (m_scroller) {
279  //Update the scroller's view of the number of lines, and try to preserve the scroll position.
280  int oldScrollPosition = m_scrollPosition;
281  const int visibleCells = float_to_int( m_rect.size.height/totalCellHeight() );
282  m_scroller->setRangeValues( m_displayCells.size()-1, visibleCells );
283  m_scroller->setScrollPosition( oldScrollPosition );
284  }
285  //Mark that we don't need to recalc anymore.
286  m_needRecalcDisplay = false;
287 }
288 
289 //Make sure the cell is visible in the scroll area. If it is, nothing
290 //happens. If it's not, we move it into the visible section.
291 //If NULL, this routine does nothing.
292 void Picker::scrollToCell( const PickerCell *cell, bool middle )
293 {
294  if (!cell || !m_scroller) return;
295  //If we need to change the displayed cells, do that first.
297  recalcDisplay();
298  for (vector< DisplayCell >::size_type i = 0; i < m_displayCells.size(); i++)
299  if (cell == m_displayCells[i].cell) {
300  const int visibleCells = float_to_int( m_rect.size.height/totalCellHeight() );
301  if (middle) {
302  //Regardless of where cell is, try to put it in the middle of
303  //the visible area.
304  m_scroller->setScrollPosition( i-visibleCells/2 );
305  } else {
306  //Just make sure we can see it.
307  if (i < m_scrollPosition)
308  //Cell is too "high". Move it to the top line.
310  else if (i >= m_scrollPosition+visibleCells)
311  //Cell is too "low". Move it to the bottom line.
312  m_scroller->setScrollPosition( i-visibleCells+1+SCROLL_EDGE_EXTRA );
313  }
314  //Found the cell. Done with loop.
315  break;
316  }
317 }
318 
319 //Set the object that takes care of scrolling.
321 {
322  m_scroller = s;
323  s->setCommandTarget( this );
324 }
325 
326 //Process a command event.
327 bool Picker::processCommand( const EventCommandId &command, Control *control )
328 {
329  if (command == "Scroller::PositionChanged") {
330  assert( control == m_scroller );
332  return true;
333  }
334  return Control::processCommand( command, control );
335 }
336 
337 //Mouse clicked down.
339 {
340  static int zoominc = XMLSupport::parse_int( vs_config->getVariable( "general", "wheel_increment_lines", "3" ) );
341  if (event.code == LEFT_MOUSE_BUTTON) {
342  PickerCell *cell = cellForMouse( event.loc );
343  if (cell != NULL) {
344  //We found the cell that was clicked.
345  m_cellPressed = cell;
346  setModal( true ); //Make sure we don't miss anything.
347  //Make sure we see mouse events *first* until we get a mouse-up.
349  return true;
350  }
351  } else if (event.code == WHEELUP_MOUSE_BUTTON) {
352  if ( hitTest( event.loc ) )
354  } else if (event.code == WHEELDOWN_MOUSE_BUTTON) {
355  if ( hitTest( event.loc ) )
357  }
358  return Control::processMouseDown( event );
359 }
360 
361 //Mouse button up.
362 bool Picker::processMouseUp( const InputEvent &event )
363 {
364  if (m_cellPressed && event.code == LEFT_MOUSE_BUTTON) {
365  //Select if the mouse goes up inside the pressed cell.
366  //If not, consider the action cancelled.
367  const bool newSelection = (cellForMouse( event.loc ) == m_cellPressed);
368 
369  //Make sure we get off the event chain.
370  globalEventManager().removeResponder( this, true );
371  setModal( false );
372  //Select a new cell, after we've cleaned up the event handling.
373  if (newSelection)
375  m_cellPressed = NULL;
376 
377  return true;
378  }
379  return Control::processMouseUp( event );
380 }
381 
382 //Mouse moved over this control.
384 {
385  const PickerCell *cell = cellForMouse( event.loc );
386  if (cell != NULL)
387  //Change the highlighted cell.
388  m_highlightedCell = cell;
389  else
390  //Make sure it's clear.
391  m_highlightedCell = NULL;
392  return true;
393 }
394 
395 //CONSTRUCTION
396 Picker::Picker( void ) :
397  m_cells( NULL )
398  , m_selectionColor( GUI_CLEAR )
399  , m_selectionTextColor( GUI_CLEAR )
400  , m_highlightColor( GUI_CLEAR )
401  , m_highlightTextColor( GUI_CLEAR )
402  , m_extraCellHeight( 0.0 )
403  , m_textMargins( Size( 0.0, 0.0 ) )
404  , m_cellPressed( NULL )
405  , m_highlightedCell( NULL )
406  , m_selectedCell( NULL )
407  , m_scroller( NULL )
408  , m_scrollPosition( 0 )
409  , m_needRecalcDisplay( true )
410 {
412 }
413 
414 Picker::~Picker( void ) {}
415