gcsx_imgchoose.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /* GCSx
  2. ** IMGCHOOSE.CPP
  3. **
  4. ** Image "chooser" popup window or control
  5. */
  6. /*****************************************************************************
  7. ** Copyright (C) 2003-2006 Janson
  8. **
  9. ** This program is free software; you can redistribute it and/or modify
  10. ** it under the terms of the GNU General Public License as published by
  11. ** the Free Software Foundation; either version 2 of the License, or
  12. ** (at your option) any later version.
  13. **
  14. ** This program is distributed in the hope that it will be useful,
  15. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ** GNU General Public License for more details.
  18. **
  19. ** You should have received a copy of the GNU General Public License
  20. ** along with this program; if not, write to the Free Software
  21. ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
  22. *****************************************************************************/
  23. #include "all.h"
  24. ImageChooser::ImageChooser(TileSetEdit* myTileset) : Window() { start_func
  25. cursor = 0;
  26. dirtyRange.w = 0;
  27. haveFocus = 0;
  28. myFrame = NULL;
  29. myScroll = NULL;
  30. tileset = myTileset;
  31. if (tileset) tileset->markLock(); // @TODO: throw_File
  32. reloadTileStats();
  33. }
  34. ImageChooser::~ImageChooser() { start_func
  35. if (tileset) tileset->markUnlock();
  36. }
  37. void ImageChooser::addTo(Dialog* dialog, int showWidth, int showHeight, int cId) { start_func
  38. assert(!myFrame);
  39. assert(dialog);
  40. myScroll = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL_BK, this, showWidth, showHeight, cId);
  41. dialog->addWidget(myScroll);
  42. }
  43. int ImageChooser::runModal(int xPos, int yPos) { start_func
  44. assert(!myScroll);
  45. // Prevent duplication
  46. if (myFrame) return 0;
  47. // Keep a backup cause we clear myFrame on SDL_CLOSE
  48. FrameWindow* myFrameBackup;
  49. myFrameBackup = new FrameWindow(blankString, FrameWindow::RESIZING_SNAP, FrameWindow::FRAMETYPE_BEVEL_BK, this, FrameWindow::TITLEBAR_OFF);
  50. myFrame = myFrameBackup;
  51. // Frame window can NOT delete itself- we want to do that, to ensure it isn't
  52. // deleted twice on exception
  53. myFrameBackup->setWantsToBeDeleted(0);
  54. // (10 is an approximate offset to improve visuals)
  55. myFrameBackup->show(xPos, max(0, yPos - height - 10), FrameWindow::SHOW_CURRENT, FrameWindow::SHOW_CURRENT);
  56. // Recurse into desktop event loop
  57. int returnValue = desktop->eventLoop();
  58. // Delete frame
  59. delete myFrameBackup;
  60. myFrame = NULL;
  61. return returnValue;
  62. }
  63. void ImageChooser::setDirtyBox(int begin, int end) { start_func
  64. if (numTiles == 0) return;
  65. assert(begin >= 0);
  66. assert(begin < numTiles);
  67. assert(end >= 0);
  68. // ('end' can be greater than number of tiles to complete a box pattern)
  69. assert(begin <= end);
  70. assert((begin % tilesPerLine) <= (end % tilesPerLine));
  71. // Create a rectangle, include outermost gridlines
  72. Rect range;
  73. range.x = (begin % tilesPerLine) * tileWidth;
  74. range.y = (begin / tilesPerLine) * tileHeight;
  75. range.w = (end % tilesPerLine - begin % tilesPerLine + 1) * tileWidth;
  76. range.h = (end / tilesPerLine - begin / tilesPerLine + 1) * tileHeight;
  77. // Add rectangle into dirty range
  78. boundRects(dirtyRange, range);
  79. setDirty();
  80. }
  81. void ImageChooser::setDirtyRange(int first, int last) { start_func
  82. if (numTiles == 0) return;
  83. assert(first >= 0);
  84. assert(first < numTiles);
  85. assert(last >= 0);
  86. assert(last < numTiles);
  87. if (first > last) swap(first, last);
  88. // On the same line?
  89. if (first / tilesPerLine == last / tilesPerLine) {
  90. setDirtyBox(first, last);
  91. }
  92. else {
  93. // Have to cover entire lines
  94. setDirtyBox(first - first % tilesPerLine, last - last % tilesPerLine + tilesPerLine - 1);
  95. }
  96. }
  97. void ImageChooser::setTileset(TileSetEdit* newTileset) { start_func
  98. if (tileset) {
  99. tileset->markUnlock();
  100. tileset = NULL;
  101. }
  102. if (newTileset) {
  103. tileset = newTileset;
  104. tileset->markLock(); // @TODO: throw_File
  105. reloadTileStats();
  106. }
  107. else {
  108. setDirty(1);
  109. }
  110. }
  111. void ImageChooser::reloadTileStats() { start_func
  112. if (tileset) {
  113. numTiles = tileset->getCount();
  114. tileWidth = tileset->getWidth();
  115. tileHeight = tileset->getHeight();
  116. tilesPerLine = tileset->getTilesPerLine();
  117. fixedTilesPerLine = tilesPerLine ? 1 : 0;
  118. // (never put cursor below 0)
  119. if (numTiles) {
  120. if (cursor >= numTiles) cursor = numTiles - 1;
  121. }
  122. }
  123. else {
  124. cursor = tilesPerLine = fixedTilesPerLine = numTiles = 0;
  125. tileWidth = tileHeight = 1; // Prevents potential div/0
  126. }
  127. resize(width, height);
  128. setDirty(1);
  129. }
  130. void ImageChooser::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
  131. if (newViewWidth == -1) newViewWidth = viewWidth;
  132. // Calculate tiles per line, if not fixed
  133. if (!fixedTilesPerLine) {
  134. // For non fixed tiles, default width is 3/4ths desktop
  135. if (newViewWidth <= 0) newViewWidth = desktop->desktopWidth() * 3 / 4;
  136. tilesPerLine = newViewWidth / tileWidth;
  137. if (tilesPerLine < 1) tilesPerLine = 1;
  138. }
  139. // Size to match
  140. int myWidth = tilesPerLine * tileWidth;
  141. int myHeight = numTiles / tilesPerLine;
  142. if (numTiles % tilesPerLine) ++myHeight;
  143. myHeight = myHeight * tileHeight;
  144. // Recurse back to parent until we get a match
  145. if ((myWidth != width) || (myHeight != height)) fromParent = 0;
  146. Window::resize(myWidth, myHeight, newViewWidth, newViewHeight, fromParent);
  147. if (myFrame) myFrame->setScroll(tileWidth, tileHeight);
  148. if (myScroll) myScroll->setScroll(tileWidth, tileHeight);
  149. }
  150. int ImageChooser::event(int hasFocus, const SDL_Event* event) { start_func
  151. int key;
  152. int target;
  153. int changed = 0;
  154. ObjChange* obj;
  155. switch (event->type) {
  156. case SDL_CLOSE:
  157. myFrame = NULL;
  158. myScroll = NULL;
  159. return 1;
  160. case SDL_OBJECTCHANGE:
  161. obj = (ObjChange*)event->user.data1;
  162. if ((event->user.code & OBJ_TILESET) && (obj->obj == tileset)) {
  163. if (event->user.code & OBJMOD_DELETE) {
  164. tileset = NULL;
  165. closeWindow();
  166. }
  167. // @TODO: OBJMOD_COUNTCOLL- don't need to reload tile stats,
  168. // but other stuff will need reloading once we can edit collisions
  169. if (event->user.code & (OBJMOD_WIDTH | OBJMOD_HEIGHT | OBJMOD_COUNT | OBJMOD_PERLINE)) {
  170. reloadTileStats();
  171. }
  172. if (event->user.code & OBJMOD_TILE) {
  173. setDirtyRange(obj->info1 - 1, obj->info2 - 1);
  174. }
  175. }
  176. if ((event->user.code & OBJ_WORLD) && (tileset) && (obj->obj == tileset->getWorld())) {
  177. if (event->user.code & OBJMOD_DELETE) {
  178. tileset = NULL;
  179. closeWindow();
  180. }
  181. }
  182. return 1;
  183. case SDL_MOUSEBUTTONDOWN:
  184. case SDL_MOUSEBUTTONDBL:
  185. if (event->button.button == SDL_BUTTON_LEFT) {
  186. int mX = event->button.x / tileWidth;
  187. int mY = event->button.y / tileHeight;
  188. // (clip to edge, in cse off of it)
  189. if (mX >= tilesPerLine) mX = tilesPerLine - 1;
  190. if (mY >= (numTiles + tilesPerLine - 1) / tilesPerLine) mY = (numTiles + tilesPerLine - 1) / tilesPerLine - 1;
  191. cursorState(mX + mY * tilesPerLine);
  192. return 1;
  193. }
  194. break;
  195. case SDL_MOUSEBUTTONUP:
  196. if (event->button.button == SDL_BUTTON_LEFT) {
  197. if (myFrame) {
  198. desktop->setModalReturn(cursor + 1);
  199. closeWindow();
  200. }
  201. return 1;
  202. }
  203. break;
  204. case SDL_MOUSEMOTION:
  205. if (event->motion.state & SDL_BUTTON_LMASK) {
  206. int mX = ((Sint16)event->motion.x) / tileWidth;
  207. int mY = ((Sint16)event->motion.y) / tileHeight;
  208. // (clip to edge, in case off of it)
  209. if (mX < 0) mX = 0;
  210. if (mY < 0) mY = 0;
  211. if (mX >= tilesPerLine) mX = tilesPerLine - 1;
  212. if (mY >= (numTiles + tilesPerLine - 1) / tilesPerLine) mY = (numTiles + tilesPerLine - 1) / tilesPerLine - 1;
  213. cursorState(mX + mY * tilesPerLine);
  214. }
  215. return 1;
  216. case SDL_SPECIAL:
  217. // Refresh cursor color?
  218. if ((event->user.code == SDL_IDLECURSOR) && (haveFocus)) {
  219. setDirtyRange(cursor, cursor);
  220. }
  221. return 1;
  222. case SDL_INPUTFOCUS:
  223. if (event->user.code & 1) {
  224. if (!haveFocus) {
  225. haveFocus = 1;
  226. changed = 1;
  227. }
  228. }
  229. if (!(event->user.code & 1)) {
  230. if (haveFocus) {
  231. haveFocus = 0;
  232. changed = 1;
  233. }
  234. }
  235. if (changed) {
  236. setDirtyRange(cursor, cursor);
  237. }
  238. return 1;
  239. case SDL_KEYDOWN:
  240. key = combineKey(event->key.keysym.sym, event->key.keysym.mod & ~KMOD_SHIFT);
  241. switch (key) {
  242. case SDLK_ESCAPE:
  243. if (myFrame) {
  244. desktop->setModalReturn(0);
  245. closeWindow();
  246. }
  247. return 1;
  248. case SDLK_KP_ENTER:
  249. case SDLK_RETURN:
  250. case SDLK_SPACE:
  251. if (myFrame) {
  252. desktop->setModalReturn(cursor + 1);
  253. closeWindow();
  254. }
  255. return 1;
  256. case SDLK_RIGHT:
  257. cursorState(cursor + 1);
  258. return 1;
  259. case combineKey(SDLK_RIGHT, KMOD_CTRL):
  260. case SDLK_END:
  261. cursorState(cursor + tilesPerLine - (cursor % tilesPerLine) - 1);
  262. return 1;
  263. case SDLK_LEFT:
  264. if (cursor > 0) cursorState(cursor - 1);
  265. return 1;
  266. case combineKey(SDLK_LEFT, KMOD_CTRL):
  267. case SDLK_HOME:
  268. cursorState(cursor - (cursor % tilesPerLine));
  269. return 1;
  270. case SDLK_DOWN:
  271. cursorState(cursor + tilesPerLine);
  272. return 1;
  273. case SDLK_PAGEDOWN:
  274. cursorState(cursor + (viewHeight / tileHeight - 1) * tilesPerLine);
  275. return 1;
  276. case SDLK_PAGEUP:
  277. target = cursor - (viewHeight / tileHeight - 1) * tilesPerLine;
  278. if (target > 0) cursorState(target);
  279. else cursorState(0);
  280. return 1;
  281. case SDLK_UP:
  282. if (cursor - tilesPerLine > 0) cursorState(cursor - tilesPerLine);
  283. else cursorState(0);
  284. return 1;
  285. case combineKey(SDLK_HOME, KMOD_CTRL):
  286. cursorState(0);
  287. return 1;
  288. case combineKey(SDLK_END, KMOD_CTRL):
  289. cursorState(numTiles - 1);
  290. return 1;
  291. }
  292. break;
  293. }
  294. return 0;
  295. }
  296. int ImageChooser::cursorState(int newPos) { start_func
  297. if (newPos >= 0) {
  298. // Clip on right
  299. if ((newPos >= numTiles) && (numTiles)) newPos = numTiles - 1;
  300. // Dirty previous selection
  301. setDirtyRange(cursor,cursor);
  302. cursor = newPos;
  303. // Scroll?
  304. int newY = cursor / tilesPerLine * tileHeight;
  305. int newX = (cursor % tilesPerLine) * tileWidth;
  306. if (myFrame) myFrame->scrollToView(newX, newY, tileWidth, tileHeight);
  307. if (myScroll) myScroll->scrollToView(newX, newY, tileWidth, tileHeight);
  308. // Dirty
  309. setDirtyRange(cursor,cursor);
  310. }
  311. return cursor;
  312. }
  313. void ImageChooser::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  314. assert(destSurface);
  315. if (visible) {
  316. // If dirty, redraw range or all
  317. if (dirty) {
  318. if (totalDirty) {
  319. getRect(toDisplay);
  320. toDisplay.x += xOffset;
  321. toDisplay.y += yOffset;
  322. }
  323. else {
  324. dirtyRange.x += x + xOffset;
  325. dirtyRange.y += y + yOffset;
  326. // Range must include requested update area as well
  327. boundRects(toDisplay, dirtyRange);
  328. }
  329. dirty = totalDirty = 0;
  330. dirtyRange.w = 0;
  331. intersectRects(toDisplay, clipArea);
  332. }
  333. xOffset += x;
  334. yOffset += y;
  335. // Anything to draw?
  336. if (toDisplay.w) {
  337. SDL_SetClipRect(destSurface, &toDisplay);
  338. SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_BKFILL]);
  339. if (!tileset) return;
  340. int dX = xOffset;
  341. int lastX = xOffset + width;
  342. // Draw starting at the first line of the displayed area;
  343. int skip = (toDisplay.y - yOffset) / tileHeight;
  344. int dY = yOffset + skip * tileHeight;
  345. int lastY = toDisplay.y + toDisplay.h;
  346. int pos = tilesPerLine * skip;
  347. // Determine first/last tiles of each row we need to show
  348. // These numbers would be, for example, 0 for first tile, 1 for second,
  349. // up to tilesPerLine - 1
  350. int leftmost = (toDisplay.x - xOffset) / tileWidth;
  351. int rightmost = (toDisplay.x + toDisplay.w - xOffset - 1) / tileWidth;
  352. pos += leftmost;
  353. dX += tileWidth * leftmost;
  354. for (; (pos < numTiles) && (dY < lastY); ++pos) {
  355. int posMod = pos % tilesPerLine;
  356. // We use modulo to determine if this tile needs displaying
  357. if ((posMod >= leftmost) && (posMod <= rightmost)) {
  358. // Draw tile
  359. tileset->blitTile(pos + 1, destSurface, dX, dY);
  360. if (pos == cursor) {
  361. drawSelectRect(dX, dY, tileWidth, tileHeight,
  362. guiPacked[haveFocus ? COLOR_TILECURSOR : COLOR_TILESELECTION], destSurface,
  363. haveFocus ? desktop->currentCursorAlpha() : 128);
  364. drawBox(dX, dY, tileWidth, tileHeight, guiPacked[COLOR_TILECURSORBORDER1], destSurface);
  365. drawBox(dX + 1, dY + 1, tileWidth - 2, tileHeight - 2, guiPacked[COLOR_TILECURSORBORDER2], destSurface);
  366. }
  367. }
  368. dX += tileWidth;
  369. if (dX + tileWidth > lastX) {
  370. dX = xOffset;
  371. dY += tileHeight;
  372. }
  373. }
  374. }
  375. }
  376. }
  377. Window::WindowType ImageChooser::windowType() const { start_func
  378. return WINDOW_CLIENT;
  379. }
  380. Window::WindowSort ImageChooser::windowSort() const { start_func
  381. if (myScroll) return WINDOWSORT_NORMAL;
  382. return WINDOWSORT_MODAL;
  383. }
  384. int ImageChooser::wantsToBeDeleted() const { start_func
  385. if (myScroll) return 1;
  386. return 0;
  387. }