Added reload of levels on F7 (Update levelpack) to ease the test of changes.
[enigmagame.git] / src / gui / LevelWidget.cc
blob4d352dfd3b030592feecef91d218045e827852e2
1 /*
2 * Copyright (C) 2002,2003,2004,2005,2006 Daniel Heck
3 * Copyright (C) 2006,2007 Ronald Lamprecht
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "gui/LevelWidget.hh"
22 #include "gui/LevelMenu.hh"
23 #include "gui/LevelInspector.hh"
25 #include "ecl.hh"
26 #include "main.hh"
27 #include "nls.hh"
28 #include "options.hh"
29 #include "SoundEffectManager.hh"
30 #include "StateManager.hh"
31 #include "video.hh"
32 #include "file.hh"
33 #include "lev/Proxy.hh"
34 #include <cassert>
36 using namespace ecl;
37 using namespace std;
40 namespace enigma { namespace gui {
41 /* -------------------- LevelWidget -------------------- */
43 LevelWidget::LevelWidget(bool withScoreIcons, bool withEditBorder) :
44 displayScoreIcons (withScoreIcons), displayEditBorder (withEditBorder),
45 listener(0), width (0), height (0), m_areas(),
46 isInvalidateUptodate (true), lastUpdate (0)
48 const video::VMInfo &vminfo = *video::GetInfo();
49 const int vshrink = vminfo.width < 640 ? 1 : 0;
51 buttonw = vminfo.thumbw + (vshrink?13:27); // min should be +30 for all modes but 640x480
52 buttonh = vminfo.thumbh + (vshrink?14:28);
53 curIndex = lev::Index::getCurrentIndex();
54 iselected = curIndex->getCurrentPosition();
55 ifirst = curIndex->getScreenFirstPosition();
56 preview_cache = LevelPreviewCache::instance();
57 scoreMgr = lev::ScoreManager::instance();
58 img_link = enigma::GetImage("ic-link");
59 img_copy = enigma::GetImage("ic-copy");
60 img_feather = enigma::GetImage("ic-feather");
61 img_easy = enigma::GetImage("completed-easy");
62 img_hard = enigma::GetImage("completed");
63 img_obsolete = enigma::GetImage(("ic-obsolete" + vminfo.thumbsext).c_str());
64 img_outdated = enigma::GetImage(("ic-outdated" + vminfo.thumbsext).c_str());
65 img_unavailable = enigma::GetImage("unavailable");
66 img_par = enigma::GetImage("par");
67 img_wrEasy = enigma::GetImage("ic-wr-easy");
68 img_wrDifficult = enigma::GetImage("ic-wr-difficult");
69 img_border = enigma::GetImage(("thumbborder" + vminfo.thumbsext).c_str());
70 img_editborder = enigma::GetImage(("editborder" + vminfo.thumbsext).c_str());
71 thumbmode = (vminfo.thumbw == 160) ? 2 : ((vminfo.thumbw == 120) ? 1 : 0);
74 void LevelWidget::syncFromIndexMgr() {
75 if (curIndex != lev::Index::getCurrentIndex()) {
76 curIndex = lev::Index::getCurrentIndex();
77 iselected = curIndex->getCurrentPosition();
78 ifirst = curIndex->getScreenFirstPosition();
79 invalidate();
80 sound::EmitSoundEvent ("menumove");
81 } else if (iselected != curIndex->getCurrentPosition()) {
82 sound::EmitSoundEvent ("menumove");
84 // repair ifirst on boot and screen resolution changes
85 set_selected(curIndex->getScreenFirstPosition(),
86 curIndex->getCurrentPosition());
89 void LevelWidget::syncToIndexMgr() {
90 curIndex->setCurrentPosition(iselected);
91 curIndex->setScreenFirstPosition(ifirst);
94 void LevelWidget::realize(const ecl::Rect &area_) {
95 Widget::realize(area_);
96 width = area_.w / buttonw;
97 height = area_.h / buttonh;
100 void LevelWidget::trigger_action() {
101 if (listener) {
102 listener->on_action(this);
106 void LevelWidget::scroll_up(int nlines)
108 int newFirst = ifirst;
109 int newSelected = iselected;
110 for (; nlines; --nlines) {
111 if (newFirst+width*height >= curIndex->size())
112 break;
113 newFirst += width;
114 if (newSelected < newFirst)
115 newSelected += width;
117 set_selected(newFirst, newSelected);
120 void LevelWidget::scroll_down(int nlines)
122 int newFirst = ifirst;
123 int newSelected = iselected;
124 for (; nlines; --nlines) {
125 if (newFirst == 0) {
126 break;
127 } else if (newFirst < width) {
128 newFirst = 0;
129 if (newSelected >= width*height)
130 newSelected = width*height - 1;
131 } else {
132 newFirst -= width;
133 if (newSelected >= newFirst+width*height)
134 newSelected -= width;
137 set_selected(newFirst, newSelected);
140 void LevelWidget::page_up() {
141 set_selected((ifirst >= width*height ? ifirst - width*height : 0),
142 (iselected >= width*height ? iselected - width*height : 0));
143 syncToIndexMgr();
146 void LevelWidget::page_down()
148 int s = lev::Index::getCurrentIndex()->size() - 1; // last position
149 int lastPageFirst = (s >= width*height ? (s / width - height + 1) * width : 0);
151 // make sure last page is shown as a whole
152 int first = std::min<int> (lastPageFirst, ifirst + width*height);
153 // set_selected (first, s-1);
154 set_selected(first, iselected + width*height);
155 syncToIndexMgr();
158 void LevelWidget::start() {
159 set_selected(0,0);
160 syncToIndexMgr();
163 void LevelWidget::end() {
164 int s = lev::Index::getCurrentIndex()->size() - 1; // last position
165 int lastPageFirst = (s >= width*height ? (s / width - height + 1) * width : 0);
166 set_selected(lastPageFirst, s);
167 syncToIndexMgr();
170 void LevelWidget::set_current(int newsel)
172 set_selected(ifirst, newsel);
175 void LevelWidget::set_selected(int newfirst, int newsel)
177 int numlevels = curIndex->size();
178 newsel = Clamp<int> (newsel, 0, numlevels-1);
179 if (newsel < 0) newsel = 0;
181 if (newsel < newfirst)
182 newfirst = (newsel/width)*width;
183 if (newsel >= newfirst+width*height)
184 newfirst = (newsel/width-height+1)*width;
186 newfirst = ecl::Clamp<int>(newfirst, 0, numlevels-1);
187 if (newfirst < 0) newfirst = 0;
189 size_t oldsel = iselected;
190 if (newfirst != ifirst) {
191 ifirst = newfirst;
192 iselected = newsel;
194 if (!m_areas.empty()) {
195 sound::EmitSoundEvent ("menumove");
196 if ((int)oldsel != newsel)
197 sound::EmitSoundEvent ("menuswitch");
198 invalidate();
201 else if (newsel != iselected) {
202 iselected = newsel;
204 if (!m_areas.empty()) {
205 sound::EmitSoundEvent ("menuswitch");
206 invalidate_area(m_areas[oldsel-ifirst]); // old selection
207 invalidate_area(m_areas[iselected-ifirst]); // new selection
212 int LevelWidget::thumb_off(int small, int medium, int large) {
213 return (thumbmode == 2) ? large : ((thumbmode == 1) ? medium : small);
216 bool LevelWidget::draw_level_preview(ecl::GC &gc, int x, int y, int borderWidth,
217 lev::Proxy *proxy, bool selected, bool isCross, bool locked,
218 bool allowGeneration, bool &didGenerate) {
219 // Draw button with level preview
221 Surface *img = preview_cache->getPreview(proxy, allowGeneration, didGenerate);
222 if (img == NULL)
223 return false;
225 if (selected) {
226 blit (gc, x - borderWidth, y - borderWidth, displayEditBorder ? img_editborder : img_border);
227 blit (gc, x, y, img);
228 } else {
229 img->set_alpha (127);
230 blit (gc, x, y, img);
231 img->set_alpha(255);
234 // Shade unavailable levels
235 if (locked)
236 blit (gc, x, y, img_unavailable);
238 if (displayScoreIcons) {
239 // Draw solved/changed icons on top of level preview
240 // Easy/difficult mode and solved status:
241 // None: Level not beaten - no easy mode available
242 // Feather: Level not beaten - easy mode available
243 // Feather + Gold: Level beaten in normal mode - easy available
244 // Silver: Level beaten in easy mode (normal mode available)
245 // Gold: Level beaten in normal mode - easy not availabe
246 // Silver + Gold: Level beaten in all modes - easy available
247 Surface *useAsEasy = NULL;
248 Surface *useAsDifficult = NULL;
249 if (proxy->hasEasyMode()) {
250 useAsEasy = img_feather;
251 if (scoreMgr->isSolved(proxy, DIFFICULTY_EASY))
252 useAsEasy = img_easy;
254 if (scoreMgr->isSolved(proxy, DIFFICULTY_HARD))
255 useAsDifficult = img_hard;
257 if (app.state->getInt("Difficulty") == DIFFICULTY_HARD) {
258 // draw golden medal over silber medal
259 if (useAsEasy != NULL)
260 blit (gc, x+thumb_off(3,3,24), y, useAsEasy);
261 if (useAsDifficult != NULL)
262 blit (gc, x+thumb_off(8,8,29), y, useAsDifficult);
264 else {
265 // draw silver medal over golden medal
266 if (useAsDifficult != NULL)
267 blit (gc, x+thumb_off(8,8,29), y, useAsDifficult);
268 if (useAsEasy != NULL)
269 blit (gc, x+thumb_off(3,3,24), y, useAsEasy);
272 // Add warning sign if level has been changed since player solved it
273 if (scoreMgr->isObsolete(proxy, app.state->getInt("Difficulty")))
274 blit(gc, x-2, y-2, img_obsolete);
275 else if (scoreMgr->isOutdated(proxy, app.state->getInt("Difficulty")))
276 blit(gc, x-2, y-2, img_outdated);
278 // Add icon if worldrecord or par
279 if (scoreMgr->bestScoreReached(proxy, app.state->getInt("Difficulty"))) {
280 blit(gc, x+thumb_off(5,35,59), y+thumb_off(2,5,20),
281 (app.state->getInt("Difficulty") != DIFFICULTY_HARD &&
282 proxy->hasEasyMode()) ? img_wrEasy : img_wrDifficult);
283 } else if (scoreMgr->parScoreReached(proxy, app.state->getInt("Difficulty"))){
284 blit(gc, x+thumb_off(33,33,55), y+thumb_off(12,12,12), img_par);
286 } else {
287 // Draw solved/changed icons on top of level preview
288 if (isCross)
289 blit (gc, x+4, y+4, img_link);
290 else
291 blit (gc, x+4, y+4, img_copy);
293 return true;
296 void LevelWidget::draw(ecl::GC &gc, const ecl::Rect &r) {
297 const video::VMInfo &vminfo = *video::GetInfo();
298 const int imgw = vminfo.thumbw; // Size of the preview images
299 const int imgh = vminfo.thumbh;
300 const int bwidth = vminfo.thumbborder_width;
302 const int hgap = Max(0, (get_w() - width*buttonw) / (width));
303 const int vgap = Max(0, (get_h() - height*buttonh)/ (height-1));
305 unsigned i=ifirst; // level index
306 bool allowGeneration = true;
308 for (int y=0; y<height; y++) {
309 for (int x=0; x<width; x++, i++) {
310 if ((int)i >= curIndex->size())
311 goto done_painting;
313 int xpos = get_x() + hgap/2 + x*(buttonw + hgap);
314 int ypos = get_y() + y*(buttonh + vgap);
316 Rect buttonarea(xpos, ypos, buttonw, buttonh);
317 if (!(r.overlaps(buttonarea) || r.w == 0))
318 continue; // r.w==0 if repainting whole screen
320 if( (i-ifirst) >= m_areas.size()) {
321 m_areas.push_back(buttonarea);
322 pending_redraws.push_back(false);
323 } else {
324 m_areas[(i-ifirst)] = buttonarea;
326 // Draw level preview
327 lev::Proxy *levelProxy = curIndex->getProxy(i);
328 int imgx = xpos + (buttonw-imgw)/2;
329 int imgy = ypos + bwidth;
330 if (levelProxy != NULL) {
331 bool didGenerate;
332 bool didDraw = draw_level_preview(gc, imgx, imgy, bwidth, levelProxy,
333 (int) i == iselected, !curIndex->isSource(levelProxy),
334 !curIndex->mayPlayLevel(i+1),
335 allowGeneration, didGenerate);
336 if (didGenerate) {
337 // do not generate more than 1 preview from level source
338 // per draw call
339 allowGeneration = false;
341 if (didDraw) {
342 pending_redraws[(i-ifirst)] = false;
343 } else {
344 // the button is not drawn - mark it to be drawn on
345 // a future tick
346 pending_redraws[(i-ifirst)] = true;
347 isInvalidateUptodate = false;
350 // Draw level name
351 Font *smallfnt = enigma::GetFont("levelmenu");
352 Font *altsmallfnt = enigma::GetFont("smallalternative");;
353 std::string caption = levelProxy->getTitle(); // TODO: may be null! Otherise line 330 redundant
354 smallfnt->render (gc,
355 xpos + buttonw/2 - ecl::Min(smallfnt->get_width(caption.c_str(), altsmallfnt)/2, (buttonw+hgap)/2),
356 imgy + imgh + 2,
357 caption, altsmallfnt, buttonw + hgap);
360 done_painting:
361 m_areas.resize(i-ifirst); // Remove unused areas (if any) from the list
362 return;
365 void LevelWidget::tick(double time) {
366 if (!isInvalidateUptodate) {
367 // invalidate just 1 button for redraw
368 bool isFirst = true;
369 for (unsigned i = 0; i < pending_redraws.size(); i++) {
370 if (pending_redraws[i] == true) {
371 if (isFirst) {
372 invalidate_area(m_areas[i]);
373 isInvalidateUptodate = true;
374 isFirst = false;
375 } else {
376 isInvalidateUptodate = false;
377 return;
384 bool LevelWidget::on_event(const SDL_Event &e) {
385 bool handled = Widget::on_event(e);
387 switch (e.type) {
388 case SDL_MOUSEMOTION:
389 if (get_area().contains(e.motion.x, e.motion.y)) {
390 int newsel=iselected;
391 for (unsigned i=0; i<m_areas.size(); ++i)
392 if (m_areas[i].contains(e.motion.x, e.motion.y))
394 newsel = ifirst+i;
395 break;
397 set_current(newsel);
398 handled = true;
400 break;
401 case SDL_MOUSEBUTTONDOWN:
402 if (get_area().contains(e.button.x, e.button.y))
403 handled = handle_mousedown (&e);
404 break;
405 case SDL_KEYDOWN:
406 handled = handle_keydown (&e);
407 break;
409 syncToIndexMgr();
410 return handled;
413 bool LevelWidget::handle_mousedown(const SDL_Event *e) {
414 switch (e->button.button) {
415 case SDL_BUTTON_LEFT:
416 for (unsigned i=0; i<m_areas.size(); ++i)
417 if (m_areas[i].contains(e->button.x, e->button.y))
419 sound::EmitSoundEvent ("menuok");
420 iselected = ifirst+i;
421 syncToIndexMgr();
422 if (SDL_GetModState() & KMOD_CTRL && !(SDL_GetModState() & KMOD_SHIFT)) {
423 // control key pressed - level inspector
424 LevelInspector m(curIndex->getProxy(iselected));
425 m.manage();
426 get_parent()->draw_all();
427 } else {
428 // no control key - start level
429 trigger_action();
431 return true;
433 break;
434 case SDL_BUTTON_RIGHT:
435 for (unsigned i=0; i<m_areas.size(); ++i)
436 if (m_areas[i].contains(e->button.x, e->button.y))
438 sound::EmitSoundEvent ("menuok");
439 iselected = ifirst+i;
440 syncToIndexMgr();
441 LevelInspector m(curIndex->getProxy(iselected));
442 m.manage();
443 get_parent()->draw_all();
444 return true;
446 break;
447 case 4: scroll_down(1); return true;
448 case 5: scroll_up(1); return true;
450 return false;
453 bool LevelWidget::handle_keydown(const SDL_Event *e) {
454 switch (e->key.keysym.sym) {
455 case SDLK_t:
456 // Generate new level preview for current level
457 preview_cache->updatePreview(curIndex->getProxy(iselected));
458 invalidate();
459 break;
461 case SDLK_LEFT:
462 if (!(SDL_GetModState() & KMOD_ALT)) {
463 set_current (iselected>1 ? iselected-1 : 0);
464 break;
465 } else
466 return false;
467 case SDLK_RIGHT:
468 if (!(SDL_GetModState() & KMOD_ALT)) {
469 set_current (iselected+1);
470 break;
471 } else
472 return false;
473 case SDLK_DOWN: set_current (iselected+width); break;
474 case SDLK_UP: set_current (iselected>width ? iselected-width : 0); break;
475 case SDLK_PAGEDOWN: page_down(); break;
476 case SDLK_PAGEUP: page_up(); break;
477 case SDLK_HOME: start(); break;
478 case SDLK_END: end(); break;
480 case SDLK_RETURN:
481 trigger_action();
482 break;
484 default:
485 return false; // key not handled
487 return true;
489 }} // namespace enigma::gui