gcsx_treeview.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. /* GCSx
  2. ** TREEVIEW.CPP
  3. **
  4. ** Treeview window, for browsing worlds, libraries, etc.
  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. int TreeView::iconX[2] = { 0, 0 };
  25. int TreeView::iconY[2] = { 0, 0 };
  26. int TreeView::iconWidth[2] = { 0, 0 };
  27. int TreeView::iconHeight[2] = { 0, 0 };
  28. int TreeView::iconIndent[2] = { 0, 0 };
  29. int TreeView::itemHeight = 0;
  30. int TreeView::itemYOffset = 0;
  31. const string TreeView::wtButtonDown("n");
  32. const string TreeView::wtButtonRight("k");
  33. SDL_Surface* TreeView::iconSurface = NULL;
  34. long int TreeView::iconsLoaded = 0;
  35. const string TreeView::resourceError("Error loading resource/treeicon.bmp");
  36. TreeView::TreeView(const string& name, void* tPtr, int tCode, int (*tAction)(void* ptr, int code, int command, int check), int hideTop, int caseSensitive) : item(name), sort(name), subtree() { start_func
  37. isCaseSensitive = caseSensitive;
  38. if (!isCaseSensitive) toLower(sort);
  39. if (!iconsLoaded) {
  40. iconWidth[0] = fontHeight(FONT_WIDGET);
  41. iconHeight[0] = iconWidth[0];
  42. iconIndent[0] = iconWidth[0] + GUI_TREEVIEW_ICON_PADDING * 2;
  43. iconWidth[1] = GUI_TREEVIEW_ICON_WIDTH;
  44. iconHeight[1] = GUI_TREEVIEW_ICON_HEIGHT;
  45. iconIndent[1] = iconWidth[1] + GUI_TREEVIEW_ICON_PADDING * 2;
  46. itemHeight = fontHeight();
  47. if (iconHeight[0] > itemHeight) itemHeight = iconHeight[0];
  48. if (iconHeight[1] > itemHeight) itemHeight = iconHeight[1];
  49. iconY[0] = (itemHeight - iconHeight[0]) / 2;
  50. iconX[0] = (iconIndent[0] - iconWidth[0]) / 2;
  51. iconY[1] = (itemHeight - iconHeight[1]) / 2;
  52. iconX[1] = iconIndent[0] + (iconIndent[1] - iconWidth[1]) / 2;
  53. iconIndent[1] += iconIndent[0];
  54. itemYOffset = (itemHeight - fontHeight()) / 2;
  55. string iconFile;
  56. createFilename(resourceDir->c_str(), "treeicon.bmp", iconFile);
  57. iconSurface = SDL_LoadBMP(iconFile.c_str());
  58. if (iconSurface == NULL) {
  59. guiErrorBox(resourceError, errorTitleResourceLoad);
  60. }
  61. else {
  62. SDL_SetColorKey(iconSurface, SDL_SRCCOLORKEY, SDL_MapRGB(iconSurface->format, 255, 0, 0));
  63. }
  64. }
  65. ptr = tPtr;
  66. code = tCode;
  67. action = tAction;
  68. hideItem = hideTop;
  69. myFrame = NULL;
  70. myScroll = NULL;
  71. parentIsTree = 0;
  72. // (limit length of display but don't affect actual data)
  73. itemWidth = hideItem ? 0 : fontWidth(item.substr(0, MAX_LINELENGTH));
  74. subitems = 0;
  75. expanded = hideItem; // If item is hidden, we're always expanded
  76. currentSelection = -1;
  77. iconOpen = -1;
  78. iconClosed = -1;
  79. open = 0;
  80. setDirty();
  81. width = (hideItem ? 0 : iconIndent[1]) + itemWidth;
  82. height = (hideItem ? 0 : itemHeight);
  83. ++iconsLoaded;
  84. }
  85. TreeView::~TreeView() { start_func
  86. // We assume we've been removed from any parent listview or from the desktop
  87. vector<TreeView*>::iterator end = subtree.end();
  88. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  89. delete *pos;
  90. }
  91. if (--iconsLoaded == 0) {
  92. if (iconSurface) SDL_FreeSurface(iconSurface);
  93. iconSurface = NULL;
  94. }
  95. }
  96. int TreeView::wantsToBeDeleted() const { start_func
  97. return 0;
  98. }
  99. Window::CommandSupport TreeView::supportsCommand(int cmdCode) const { start_func
  100. // If we're not selected, propogate command downward to selected first
  101. if (currentSelection > 0) {
  102. CommandSupport result = subtree[currentSelection - 1]->supportsCommand(cmdCode);
  103. if (result != COMMAND_HIDE) return result;
  104. }
  105. // Either we're selected, or our child refused it, so we'll try it now
  106. // Note that SDL_COMMAND is the only action that propogates down the tree
  107. if (action) {
  108. return (Window::CommandSupport)action(ptr, code, cmdCode, 1);
  109. }
  110. return COMMAND_HIDE;
  111. }
  112. int TreeView::event(int hasFocus, const SDL_Event* event) { start_func
  113. assert(event);
  114. int handleKey = 0;
  115. int repeat = 1;
  116. SDL_Event eventCopy;
  117. switch (event->type) {
  118. case SDL_CLOSE:
  119. open = 0;
  120. myFrame = NULL;
  121. myScroll = NULL;
  122. return 1;
  123. case SDL_COMMAND:
  124. // If we're not selected, propogate command downward to selected first
  125. if (currentSelection > 0) {
  126. if (subtree[currentSelection - 1]->event(hasFocus, event)) return 1;
  127. }
  128. // Either we're selected, or our child refused it, so we'll try it now
  129. // Note that SDL_COMMAND is the only action that propogates down the tree
  130. if (action) {
  131. return action(ptr, code, event->user.code, 0);
  132. }
  133. return 0;
  134. case SDL_KEYDOWN:
  135. // Handle top-level events here (we assume we're top level, as we
  136. // don't allow these keys to propogate further)
  137. // Change PGDN / PGUP into repeated keypresses sent to ourselves
  138. // Handle END / HOME
  139. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  140. case SDLK_HOME:
  141. if ((subitems) && (hideItem) && (expanded)) {
  142. subtree[0]->selectMe(); // (start with top item selected)
  143. }
  144. else selectMe();
  145. return 1;
  146. case SDLK_END:
  147. selectLast();
  148. return 1;
  149. case SDLK_PAGEDOWN:
  150. eventCopy = *event;
  151. eventCopy.key.keysym.sym = SDLK_DOWN;
  152. repeat = viewHeight / fontHeight() - 1;
  153. if (repeat < 1) repeat = 1;
  154. for (; repeat > 0; --repeat) {
  155. TreeView::event(hasFocus, &eventCopy);
  156. }
  157. return 1;
  158. case SDLK_PAGEUP:
  159. eventCopy = *event;
  160. eventCopy.key.keysym.sym = SDLK_UP;
  161. repeat = viewHeight / fontHeight() - 1;
  162. if (repeat < 1) repeat = 1;
  163. for (; repeat > 0; --repeat) {
  164. TreeView::event(hasFocus, &eventCopy);
  165. }
  166. return 1;
  167. }
  168. // If we're not selected, propogate key downward to selected
  169. if (currentSelection > 0) {
  170. if (subtree[currentSelection - 1]->event(hasFocus, event)) return 1;
  171. handleKey = 1;
  172. }
  173. // If we're selected, handle key
  174. // If the item actually selected didn't use the key, we'll try to
  175. if ((handleKey) || (currentSelection == 0)) {
  176. switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
  177. case SDLK_KP_ENTER:
  178. case SDLK_RETURN:
  179. case SDLK_SPACE:
  180. if (action) {
  181. action(ptr, code, LV_SELECT, 0);
  182. }
  183. // We always return 1, we don't want lower level to intercept
  184. return 1;
  185. case SDLK_BACKSPACE:
  186. // Go up a level
  187. if (currentSelection > 0) {
  188. if (!hideItem) selectMe();
  189. return 1;
  190. }
  191. break;
  192. case SDLK_DELETE:
  193. if (action) {
  194. action(ptr, code, LV_DELETE, 0);
  195. }
  196. // We always return 1, we don't want lower level to intercept
  197. return 1;
  198. case SDLK_RIGHT:
  199. // Expand or go inward a level
  200. // We use function (not variable) to support directory view
  201. // where items aren't loaded until expand() is called
  202. if (hasSubItems()) {
  203. if ((currentSelection == 0) && (expanded) && (subitems)) subtree[0]->selectMe();
  204. else expand(1);
  205. }
  206. return 1;
  207. case SDLK_LEFT:
  208. // Collapse or go up a level
  209. if (currentSelection > 0) {
  210. if (!hideItem) selectMe();
  211. return 1;
  212. }
  213. else if ((subitems) && (expanded)) {
  214. expand(0);
  215. return 1;
  216. }
  217. break;
  218. case SDLK_DOWN:
  219. // Next line
  220. if ((currentSelection > 0) && (currentSelection < subitems)) {
  221. // currentSelection - 1, plus one for next item
  222. subtree[currentSelection]->selectMe();
  223. return 1;
  224. }
  225. // First item of us if expanded
  226. else if ((currentSelection == 0) && (expanded) && (subitems)) {
  227. subtree[0]->selectMe();
  228. return 1;
  229. }
  230. break;
  231. case SDLK_UP:
  232. // Previous line
  233. if (currentSelection > 1) {
  234. // currentSelection - 1, minus one for previous item
  235. subtree[currentSelection - 2]->selectLast();
  236. return 1;
  237. }
  238. // Select self
  239. else if ((currentSelection == 1) && (!hideItem)) {
  240. selectMe();
  241. return 1;
  242. }
  243. break;
  244. default: {
  245. // Jump to first/next matching item based on letter
  246. if (selectFind(tolower(event->key.keysym.sym))) return 1;
  247. break;
  248. }
  249. }
  250. }
  251. break;
  252. case SDL_MOUSEBUTTONDBL:
  253. case SDL_MOUSEBUTTONDOWN:
  254. // (either button OK)
  255. if ((event->button.button == SDL_BUTTON_LEFT) || (event->button.button == SDL_BUTTON_RIGHT)) {
  256. if (event->button.y < height) {
  257. // Ourselves?
  258. if ((event->button.y < itemHeight) && (!hideItem)) {
  259. // Button?
  260. if (event->button.x < iconIndent[0]) {
  261. expand(!expanded);
  262. }
  263. // Item?
  264. else if (event->button.x < itemWidth + iconIndent[1]) {
  265. selectMe();
  266. // Double-left-click?
  267. if ((event->button.button == SDL_BUTTON_LEFT) && (event->type == SDL_MOUSEBUTTONDBL)) {
  268. if (expanded) expand(0);
  269. else if (hasSubItems()) expand(1);
  270. else if (action) return action(ptr, code, LV_SELECT, 0);
  271. }
  272. else if ((action) && (event->type == SDL_MOUSEBUTTONDOWN)) {
  273. if (event->button.button == SDL_BUTTON_LEFT) return action(ptr, code, LV_LCLICK, 0);
  274. else if (event->button.button == SDL_BUTTON_RIGHT) return action(ptr, code, LV_RCLICK, 0);
  275. }
  276. }
  277. else {
  278. return 0;
  279. }
  280. return 1;
  281. }
  282. // A subitem
  283. else if (expanded) {
  284. // New event, move towards next item
  285. eventCopy = *event;
  286. if (!hideItem) {
  287. eventCopy.button.x -= iconIndent[0];
  288. eventCopy.button.y -= itemHeight;
  289. }
  290. vector<TreeView*>::iterator end = subtree.end();
  291. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  292. // This item?
  293. if (eventCopy.button.y < (*pos)->height) {
  294. // Recurse
  295. return (*pos)->event(hasFocus, &eventCopy);
  296. }
  297. // Move towards next item
  298. eventCopy.button.y -= (*pos)->height;
  299. }
  300. return 0;
  301. }
  302. }
  303. }
  304. break;
  305. }
  306. return 0;
  307. }
  308. void TreeView::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
  309. Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
  310. vector<TreeView*>::iterator end = subtree.end();
  311. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  312. (*pos)->resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
  313. }
  314. }
  315. void TreeView::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
  316. assert(destSurface);
  317. if (visible) {
  318. // This will be the final displayed amount, we update it as we go but still work
  319. // off of the original dirtied area for efficiency
  320. Rect updatedArea = toDisplay;
  321. xOffset += x;
  322. yOffset += y;
  323. // This window attempts to only redraw lines that have changed (or dirty)
  324. // Our top member-
  325. if (!hideItem) {
  326. // Determine clip area for our item
  327. Rect itemClip = { xOffset, yOffset, width, itemHeight };
  328. intersectRects(itemClip, clipArea);
  329. // If dirty and visible, or intersects with dirty area
  330. if (((dirty) && (itemClip.w)) || (intersectRects(itemClip, toDisplay))) {
  331. // Add rect into updated area
  332. boundRects(updatedArea, itemClip);
  333. SDL_SetClipRect(destSurface, &itemClip);
  334. // Our background- just our item
  335. SDL_FillRect(destSurface, &itemClip, guiPacked[COLOR_TEXTBOX]);
  336. // Icon +/-
  337. if (hasSubItems()) {
  338. drawGuiBox(xOffset + iconX[0], yOffset + iconY[0], iconWidth[0], iconHeight[0], 1, destSurface);
  339. drawText(expanded ? wtButtonDown : wtButtonRight, guiRGB[COLOR_TEXT], xOffset + iconX[0], yOffset + iconY[0], destSurface, FONT_WIDGET);
  340. }
  341. // Icon symbol
  342. int iconSymbol = 0;
  343. if ((iconSurface) && (iconOpen >= 0)) {
  344. iconSymbol = 1;
  345. blit((expanded ? iconOpen : iconClosed) * GUI_TREEVIEW_ICON_WIDTH, 0, iconSurface, xOffset + iconX[1], yOffset + iconY[1], destSurface, 16, 16);
  346. }
  347. // Our text- selected?
  348. if (currentSelection == 0) {
  349. drawGradient(xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, itemWidth, itemHeight, guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
  350. drawText(item, guiRGB[COLOR_TEXTBOX], xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, destSurface);
  351. drawFocusBox(xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, itemWidth, itemHeight, destSurface);
  352. }
  353. else {
  354. drawText(item, guiRGB[COLOR_TEXT], xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, destSurface);
  355. }
  356. }
  357. }
  358. // Subtree?
  359. if ((expanded) && (subitems) && ((childDirty) || (totalDirty) || (toDisplay.w))) {
  360. int atY = yOffset + (hideItem ? 0 : itemHeight);
  361. vector<TreeView*>::iterator end = subtree.end();
  362. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  363. // Determine clip area for this item
  364. Rect itemClip = { xOffset, atY, width, (*pos)->height };
  365. intersectRects(itemClip, clipArea);
  366. // Dirty area
  367. Rect itemDirty = itemClip;
  368. // If dirty and visible, or intersects with dirty area
  369. // Order of "if" evaluation matters here-
  370. // If total dirty, entire area will be redrawn; if just dirty,
  371. // only area clipped to "toDisplay" will be enforced as redrawn
  372. if ((itemDirty.w) && ((totalDirty) || (intersectRects(itemDirty, toDisplay)) || ((*pos)->isDirty()))) {
  373. // Give it a full background too- it will draw over it anyways
  374. // We have to do this because if item may not cover it's full width
  375. SDL_SetClipRect(destSurface, &itemClip);
  376. SDL_FillRect(destSurface, &itemDirty, guiPacked[COLOR_TEXTBOX]);
  377. // (don't use move, to prevent unneeded setdirties)
  378. (*pos)->x = hideItem ? 0 : iconIndent[0];
  379. (*pos)->y = atY - yOffset;
  380. (*pos)->display(destSurface, itemDirty, itemClip, xOffset, yOffset);
  381. // Add into redrawn area
  382. boundRects(updatedArea, itemDirty);
  383. }
  384. atY += (*pos)->height;
  385. }
  386. }
  387. toDisplay = updatedArea;
  388. dirty = totalDirty = childDirty = 0;
  389. }
  390. }
  391. Window::WindowType TreeView::windowType() const { start_func
  392. return WINDOW_CLIENT;
  393. }
  394. void TreeView::setIcon(int tIconClosed, int tIconOpen) { start_func
  395. iconClosed = tIconClosed;
  396. iconOpen = tIconOpen;
  397. if (iconOpen == -1) iconOpen = iconClosed;
  398. setDirty();
  399. }
  400. void TreeView::changeName(const string& name) { start_func
  401. item = name;
  402. sort = name;
  403. if (!isCaseSensitive) toLower(sort);
  404. setDirty();
  405. if ((parent) && (parentIsTree)) {
  406. dynamic_cast<TreeView*>(parent)->childRenamed(this);
  407. }
  408. if (myFrame) {
  409. myFrame->setTitle(item);
  410. }
  411. if (!hideItem) {
  412. // (limit length of display but don't affect actual data)
  413. itemWidth = fontWidth(item.substr(0, MAX_LINELENGTH));
  414. // Scan all items for widest (add in iconIndent when done)
  415. int widest = itemWidth;
  416. vector<TreeView*>::iterator end = subtree.end();
  417. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  418. if ((*pos)->width > widest) widest = (*pos)->width;
  419. }
  420. resize(widest + iconIndent[1], height);
  421. }
  422. }
  423. void TreeView::childRenamed(TreeView* child) { start_func
  424. assert(child);
  425. // Selected?
  426. int wasSelected = 0;
  427. if ((currentSelection > 0) && (subtree[currentSelection - 1] == child)) {
  428. wasSelected = 1;
  429. currentSelection = 0;
  430. }
  431. // Find existing and remove
  432. vector<TreeView*>::iterator pos = subtree.begin();
  433. vector<TreeView*>::iterator end = subtree.end();
  434. int count = 0;
  435. for (; pos != end; ++count, ++pos) {
  436. if (*pos == child) break;
  437. }
  438. assert(pos != end);
  439. if ((currentSelection > 0) && (count < currentSelection)) --currentSelection;
  440. subtree.erase(pos);
  441. // Insert
  442. pos = subtree.begin();
  443. end = subtree.end();
  444. count = 0;
  445. for (; pos != end; ++count, ++pos) {
  446. if ((*pos)->sort > child->sort) break;
  447. }
  448. if ((currentSelection > 0) && (count < currentSelection)) ++currentSelection;
  449. subtree.insert(pos, child);
  450. // Select?
  451. if (wasSelected) currentSelection = count + 1;
  452. // We're 100% dirty
  453. setDirty(1);
  454. }
  455. void TreeView::insert(TreeView* toInsert, int makeCurrent) { start_func
  456. assert(toInsert);
  457. // Insert
  458. vector<TreeView*>::iterator pos = subtree.begin();
  459. vector<TreeView*>::iterator end = subtree.end();
  460. int count = 0;
  461. int atY = hideItem ? 0 : itemHeight;
  462. for (; pos != end; ++count, ++pos) {
  463. if ((*pos)->sort > toInsert->sort) break;
  464. atY += (*pos)->height;
  465. }
  466. if ((currentSelection > 0) && (count < currentSelection)) ++currentSelection;
  467. subtree.insert(pos, toInsert);
  468. toInsert->setParent(this);
  469. toInsert->parentIsTree = 1;
  470. ++subitems;
  471. // We may resize if needed
  472. int newWidth = width;
  473. int newHeight = height;
  474. // Ensure our width is large enough to include this item
  475. if (toInsert->width + (hideItem ? 0 : iconIndent[1]) > width) {
  476. newWidth = toInsert->width + (hideItem ? 0 : iconIndent[1]);
  477. }
  478. // If we're expanded, add subitem's height to ours
  479. if (expanded) {
  480. newHeight += toInsert->height;
  481. }
  482. // Modify size
  483. if ((newWidth != width) || (newHeight != height)) {
  484. // (sets dirty)
  485. resize(newWidth, newHeight);
  486. }
  487. toInsert->move(hideItem ? 0 : iconIndent[0], atY);
  488. // Set this item as selected?
  489. if (makeCurrent) toInsert->selectMe();
  490. // Otherwise, rescroll to view current selection
  491. else positionChildren(1);
  492. }
  493. void TreeView::childResized(int oldW, int oldH, int newW, int newH, Window* child) { start_func
  494. // We may resize if needed
  495. int newWidth = width;
  496. int newHeight = height;
  497. // Need to be wider?
  498. if ((newW > oldW) && (newW + (hideItem ? 0 : iconIndent[1]) > width)) {
  499. newWidth = newW + (hideItem ? 0 : iconIndent[1]);
  500. }
  501. // Possibly need to be thinner?
  502. if ((newW < oldW) && ((oldW + (hideItem ? 0 : iconIndent[1])) == width)) {
  503. // Scan all items for widest (add in iconIndent when done)
  504. int widest = itemWidth;
  505. vector<TreeView*>::iterator end = subtree.end();
  506. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  507. if ((*pos)->width > widest) widest = (*pos)->width;
  508. }
  509. newWidth = widest + (hideItem ? 0 : iconIndent[1]);
  510. }
  511. // If we're expanded, then height changes also affect us, and all
  512. // other children after the child that resized
  513. if (expanded) {
  514. int heightDiff = newH - oldH;
  515. if (heightDiff) {
  516. newHeight += heightDiff;
  517. int after = 0;
  518. vector<TreeView*>::iterator end = subtree.end();
  519. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  520. if (after) (*pos)->move((*pos)->x, (*pos)->y + heightDiff);
  521. if (*pos == child) after = 1;
  522. }
  523. }
  524. }
  525. // Resize?
  526. if ((newWidth != width) || (newHeight != height)) {
  527. // (sets dirty)
  528. resize(newWidth, newHeight);
  529. }
  530. }
  531. TreeView* TreeView::findRecursive(void* fPtr) { start_func
  532. vector<TreeView*>::iterator end = subtree.end();
  533. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  534. TreeView* result = (*pos)->findRecursive(fPtr);
  535. if (result) return result;
  536. if ((*pos)->ptr == fPtr) return *pos;
  537. }
  538. return NULL;
  539. }
  540. TreeView* TreeView::find(void* fPtr) { start_func
  541. vector<TreeView*>::iterator end = subtree.end();
  542. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  543. if ((*pos)->ptr == fPtr) return *pos;
  544. }
  545. return NULL;
  546. }
  547. TreeView* TreeView::findRecursive(int fCode, int (*fAction)(void* ptr, int code, int command, int check)) { start_func
  548. vector<TreeView*>::iterator end = subtree.end();
  549. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  550. TreeView* result = (*pos)->findRecursive(fCode, fAction);
  551. if (result) return result;
  552. if (((*pos)->code == fCode) &&
  553. (((*pos)->action == fAction) || (fAction == NULL))) return *pos;
  554. }
  555. return NULL;
  556. }
  557. TreeView* TreeView::find(const string& fItem) { start_func
  558. vector<TreeView*>::iterator end = subtree.end();
  559. if (!isCaseSensitive) {
  560. string lowercase = fItem;
  561. for (int pos = lowercase.size() - 1; pos >= 0; --pos) {
  562. lowercase[pos] = tolower(lowercase[pos]);
  563. }
  564. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  565. if ((*pos)->sort == lowercase) return *pos;
  566. }
  567. }
  568. else {
  569. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  570. if ((*pos)->item == fItem) return *pos;
  571. }
  572. }
  573. return NULL;
  574. }
  575. TreeView* TreeView::findParent() { start_func
  576. if ((parent) && (parentIsTree)) {
  577. return dynamic_cast<TreeView*>(parent);
  578. }
  579. return NULL;
  580. }
  581. TreeView* TreeView::findSelected() { start_func
  582. if (currentSelection > 0) return subtree[currentSelection - 1]->findSelected();
  583. if (currentSelection == 0) return this;
  584. return NULL;
  585. }
  586. void TreeView::removeAll() { start_func
  587. expand(0);
  588. // Select self instead of any child
  589. if (currentSelection > 0) {
  590. selectMe();
  591. }
  592. vector<TreeView*>::iterator end = subtree.end();
  593. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  594. (*pos)->parentIsTree = 0;
  595. (*pos)->setParent(NULL);
  596. delete (*pos);
  597. }
  598. subtree.clear();
  599. subitems = 0;
  600. // We may resize if needed
  601. int newWidth = itemWidth + (hideItem ? 0 : iconIndent[1]);
  602. int newHeight = (hideItem ? 0 : itemHeight);
  603. if ((newWidth != width) || (newHeight != height)) {
  604. // (sets dirty)
  605. resize(newWidth, newHeight);
  606. }
  607. }
  608. void TreeView::remove(TreeView* toRemove, int deleteIt) { start_func
  609. // Track if we passed selected item
  610. int sawSelected = 0;
  611. vector<TreeView*>::iterator end = subtree.end();
  612. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  613. if (*pos == toRemove) {
  614. // If was selected, select self instead
  615. if ((currentSelection > 0) && (subtree[currentSelection - 1] == toRemove)) {
  616. sawSelected = 1;
  617. selectMe();
  618. }
  619. subtree.erase(pos);
  620. // Must use toRemove from here onward
  621. toRemove->parentIsTree = 0;
  622. toRemove->setParent(NULL);
  623. --subitems;
  624. childResized(toRemove->width, toRemove->height, 0, 0, toRemove);
  625. if (deleteIt) delete toRemove;
  626. // Decrease selected pointer because we removed above it?
  627. if ((currentSelection > 0) && (!sawSelected)) --currentSelection;
  628. break;
  629. }
  630. else if ((currentSelection > 0) && (subtree[currentSelection - 1] == *pos)) {
  631. sawSelected = 1;
  632. }
  633. }
  634. }
  635. void TreeView::expand(int state) { start_func
  636. int newHeight = height;
  637. // Can't change if top item hidden- always expanded
  638. if (hideItem) return;
  639. // Expanding?
  640. if ((state) && (!expanded)) {
  641. expanded = 1;
  642. // Add height of all items
  643. vector<TreeView*>::iterator end = subtree.end();
  644. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  645. newHeight += (*pos)->height;
  646. }
  647. }
  648. // Contracting?
  649. else if ((!state) && (expanded)) {
  650. expanded = 0;
  651. newHeight = itemHeight;
  652. }
  653. // Size change?
  654. if (newHeight != height) {
  655. // (sets dirty)
  656. resize(width, newHeight);
  657. }
  658. // Select ourselves if a subitem of us is selected and we just contracted
  659. if ((!expanded) && (currentSelection > 0)) selectMe();
  660. }
  661. void TreeView::addTo(Dialog* dialog, int showWidth, int showLines, int cId) { start_func
  662. assert(!myFrame);
  663. assert(dialog);
  664. // Set to standard size
  665. if (showWidth <= 0) {
  666. string standard(GUI_TREEVIEW_DEFAULTWIDTH, 'X');
  667. showWidth = fontWidth(standard);
  668. }
  669. if (showLines <= 0) {
  670. showLines = GUI_TREEVIEW_DEFAULTLINES;
  671. }
  672. expand(1); // (start with top level expanded)
  673. if (hasSubItems()) {
  674. subtree[0]->selectMe(); // (start with top item selected)
  675. }
  676. else {
  677. selectMe(); // (start with self selected)
  678. }
  679. myScroll = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL, this, showWidth, showLines * itemHeight, cId);
  680. dialog->addWidget(myScroll);
  681. }
  682. void TreeView::runWindowed() { start_func
  683. assert(!myScroll);
  684. // Prevent duplication
  685. if (myFrame) {
  686. desktop->bringToTop(myFrame);
  687. return;
  688. }
  689. expand(1); // (start with top level expanded)
  690. if (hasSubItems()) {
  691. subtree[0]->selectMe(); // (start with top item selected)
  692. }
  693. else {
  694. selectMe(); // (start with self selected)
  695. }
  696. // We remember the frame pointer even though it'll delete itself
  697. myFrame = new FrameWindow(item, FrameWindow::RESIZING_NORMAL, FrameWindow::FRAMETYPE_BEVEL_TEXT, this);
  698. open = 1;
  699. // Cascade, use cascade height, but use OUR width
  700. myFrame->show(FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CURRMIN, FrameWindow::SHOW_CASCADE);
  701. }
  702. void TreeView::scrollToView(int sX, int sY, int sWidth, int sHeight) { start_func
  703. if (parent) {
  704. if (parentIsTree) {
  705. dynamic_cast<TreeView*>(parent)->positionChildren(0);
  706. dynamic_cast<TreeView*>(parent)->scrollToView(sX + x, sY + y, sWidth, sHeight);
  707. }
  708. else if (parent == myFrame) {
  709. myFrame->scrollToView(sX, sY, sWidth, sHeight);
  710. }
  711. else if (parent == myScroll) {
  712. myScroll->scrollToView(sX, sY, sWidth, sHeight);
  713. }
  714. }
  715. }
  716. int TreeView::selectFind(int letter) { start_func
  717. if (expanded) {
  718. int pos = currentSelection - 1;
  719. if (pos < 0) pos = 0;
  720. for (; pos < subitems; ++pos) {
  721. // (doesn't match root of currently selected item
  722. if ((tolower(subtree[pos]->item[0]) == letter) && (pos >= currentSelection)) {
  723. subtree[pos]->selectMe();
  724. return 1;
  725. }
  726. // (search subtree)
  727. if (subtree[pos]->selectFind(letter)) return 1;
  728. }
  729. }
  730. return 0;
  731. }
  732. void TreeView::selectLast() { start_func
  733. if ((expanded) && (subitems)) subtree[subitems - 1]->selectLast();
  734. else selectMe();
  735. }
  736. void TreeView::selectFirst() { start_func
  737. if ((expanded) && (subitems)) subtree[0]->selectMe();
  738. else selectMe();
  739. }
  740. void TreeView::selectMe() { start_func
  741. // Already selected?
  742. if (currentSelection == 0) return;
  743. // Subitem currently selected?
  744. if (currentSelection > 0) {
  745. subtree[currentSelection - 1]->unselectMe();
  746. }
  747. // Scroll to view it
  748. currentSelection = 0;
  749. scrollToView(0, 0, iconIndent[1] + itemWidth, itemHeight);
  750. // Tell parent, if any, that we're selected
  751. if ((parent) && (parentIsTree)) {
  752. dynamic_cast<TreeView*>(parent)->selectChild(this);
  753. }
  754. // Event
  755. if (action) action(ptr, code, LV_MOVE, 0);
  756. setDirty();
  757. }
  758. void TreeView::positionChildren(int rescroll) { start_func
  759. if (subitems) {
  760. int atY = hideItem ? 0 : itemHeight;
  761. vector<TreeView*>::iterator end = subtree.end();
  762. for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
  763. // (don't use move, to prevent unneeded setdirties)
  764. (*pos)->x = hideItem ? 0 : iconIndent[0];
  765. (*pos)->y = atY;
  766. if (expanded) atY += (*pos)->height;
  767. }
  768. }
  769. if (rescroll) {
  770. if (currentSelection == 0) scrollToView(0, 0, iconIndent[1] + itemWidth, itemHeight);
  771. else if (currentSelection > 0) scrollToView(subtree[currentSelection - 1]->x, subtree[currentSelection - 1]->y,
  772. subtree[currentSelection - 1]->itemWidth,
  773. subtree[currentSelection - 1]->itemHeight);
  774. }
  775. }
  776. void TreeView::selectChild(TreeView* child) { start_func
  777. // Find child
  778. int cPos = findChild(child);
  779. // Found?
  780. if (cPos >= 0) {
  781. // Already selected?
  782. if (currentSelection == cPos + 1) return;
  783. // Subitem currently selected?
  784. if (currentSelection > 0) {
  785. subtree[currentSelection - 1]->unselectMe();
  786. }
  787. else if (currentSelection == 0) {
  788. // We're dirty if we WERE selected
  789. setDirty();
  790. }
  791. currentSelection = cPos + 1;
  792. // Tell parent, if any, that we're selected
  793. if ((parent) && (parentIsTree)) {
  794. dynamic_cast<TreeView*>(parent)->selectChild(this);
  795. }
  796. }
  797. }
  798. void TreeView::unselectMe() { start_func
  799. // Already unselected?
  800. if (currentSelection == -1) return;
  801. // Subitem currently selected?
  802. if (currentSelection > 0) {
  803. subtree[currentSelection - 1]->unselectMe();
  804. }
  805. else {
  806. // We were selected, we're now dirty
  807. setDirty();
  808. }
  809. currentSelection = -1;
  810. }
  811. int TreeView::findChild(const TreeView* child) const { start_func
  812. for (int pos = 0; pos < subitems; ++pos) {
  813. if (subtree[pos] == child) return pos;
  814. }
  815. return -1;
  816. }
  817. int TreeView::hasSubItems() const { start_func
  818. return subitems > 0;
  819. }