Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1450 lines
37 KiB

  1. /*
  2. * tscroll.c
  3. *
  4. * standard table class.
  5. *
  6. * scrolling and selection routines
  7. *
  8. * see table.h for interface description
  9. *
  10. * This implementation currently only supports TM_SINGLE, not TM_MANY
  11. * modes of selection.
  12. */
  13. #include "windows.h"
  14. #include "commdlg.h"
  15. #include "gutils.h"
  16. #include "table.h"
  17. #include "tpriv.h"
  18. VOID
  19. gtab_extendsel(
  20. HWND hwnd,
  21. lpTable ptab,
  22. long startrow,
  23. long startcell,
  24. BOOL bNotify
  25. );
  26. /* handle a vscroll message */
  27. void
  28. gtab_msg_vscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
  29. {
  30. long change;
  31. switch(opcode) {
  32. case SB_THUMBPOSITION:
  33. case SB_THUMBTRACK:
  34. change = (pos * ptab->scrollscale) - ptab->toprow;
  35. break;
  36. case SB_LINEUP:
  37. change = -1;
  38. break;
  39. case SB_LINEDOWN:
  40. change = 1;
  41. break;
  42. case SB_PAGEUP:
  43. change = - (ptab->nlines - 3);
  44. if (change>=0)
  45. change = -1; // consider nlines <=3!
  46. break;
  47. case SB_PAGEDOWN:
  48. change = (ptab->nlines - 3);
  49. if (change<=0)
  50. change = 1; // consider nlines <=3!
  51. break;
  52. default:
  53. return;
  54. }
  55. gtab_dovscroll(hwnd, ptab, change);
  56. }
  57. /* handle a hscroll message */
  58. void
  59. gtab_msg_hscroll(HWND hwnd, lpTable ptab, int opcode, int pos)
  60. {
  61. int change;
  62. switch(opcode) {
  63. case SB_THUMBPOSITION:
  64. case SB_THUMBTRACK:
  65. change = pos - ptab->scroll_dx;
  66. break;
  67. case SB_LINEUP:
  68. change = -(ptab->avewidth);
  69. break;
  70. case SB_LINEDOWN:
  71. change = ptab->avewidth;
  72. break;
  73. case SB_PAGEUP:
  74. change = - (ptab->winwidth * 2 / 3);
  75. break;
  76. case SB_PAGEDOWN:
  77. change = (ptab->winwidth * 2 / 3);
  78. break;
  79. default:
  80. return;
  81. }
  82. gtab_dohscroll(hwnd, ptab, change);
  83. }
  84. /*
  85. * set new vertical scroll pos,
  86. * adjust linedata array
  87. * set line win-relative start posns & clip top/bottom posns
  88. * revise display.
  89. */
  90. void
  91. gtab_dovscroll(HWND hwnd, lpTable ptab, long change)
  92. {
  93. int cury, i;
  94. long ncopy;
  95. lpCellPos cp;
  96. LineData ldtemp;
  97. RECT rc, rcpaint;
  98. long range;
  99. long newtop;
  100. int newpos;
  101. BOOL fWasVisible = FALSE;
  102. if (ptab->selvisible)
  103. {
  104. fWasVisible = TRUE;
  105. ptab->selvisible = FALSE;
  106. gtab_invertsel(hwnd, ptab, NULL);
  107. }
  108. range = ptab->hdr.nrows - (ptab->nlines - 1);
  109. newtop = ptab->toprow + change;
  110. if (range < 0) {
  111. range = 0;
  112. }
  113. if (newtop > range) {
  114. change = range - ptab->toprow;
  115. } else if (newtop < 0) {
  116. change = -(ptab->toprow);
  117. }
  118. ptab->toprow += change;
  119. newpos = (int) (newtop / ptab->scrollscale);
  120. SetScrollPos(hwnd, SB_VERT, newpos, TRUE);
  121. if (ptab->hdr.sendscroll) {
  122. gtab_sendtq(hwnd, TQ_SCROLL, ptab->toprow);
  123. }
  124. /* adjust data ptrs rather than invalidate, to retain the
  125. * data we know is still valid
  126. */
  127. if (abs(change) >= ptab->nlines) {
  128. gtab_invallines(hwnd, ptab, ptab->hdr.fixedrows,
  129. ptab->nlines - ptab->hdr.fixedrows);
  130. InvalidateRect(hwnd, NULL, FALSE);
  131. change = 0;
  132. } else if (change < 0) {
  133. /* copy data down */
  134. ncopy = (ptab->nlines - ptab->hdr.fixedrows) - abs(change);
  135. for (i = ptab->nlines - 1;
  136. i >= (ptab->hdr.fixedrows + abs(change)); i--) {
  137. ldtemp = ptab->pdata[i - abs(change)];
  138. ptab->pdata[i - abs(change)] = ptab->pdata[i];
  139. ptab->pdata[i] = ldtemp;
  140. }
  141. gtab_invallines(hwnd, ptab,
  142. ptab->hdr.fixedrows, (int) abs(change));
  143. } else if (change > 0) {
  144. ncopy = (ptab->nlines - ptab->hdr.fixedrows) - change;
  145. for (i = ptab->hdr.fixedrows;
  146. i < (ncopy + ptab->hdr.fixedrows); i++) {
  147. ldtemp = ptab->pdata[i + change];
  148. ptab->pdata[i + change] = ptab->pdata[i];
  149. ptab->pdata[i] = ldtemp;
  150. }
  151. gtab_invallines(hwnd, ptab,
  152. (int) ncopy + ptab->hdr.fixedrows, (int) change);
  153. }
  154. /* scroll window */
  155. GetClientRect(hwnd, &rc);
  156. rcpaint = rc;
  157. if (change > 0) {
  158. rc.top += (int) (change + ptab->hdr.fixedrows) * ptab->rowheight;
  159. rcpaint.top = (ptab->hdr.fixedrows * ptab->rowheight);
  160. rcpaint.top += rc.bottom - rc.top;
  161. } else if (change < 0) {
  162. rc.top += (ptab->hdr.fixedrows * ptab->rowheight);
  163. rc.bottom += (int) (change * ptab->rowheight);
  164. rcpaint.bottom -= rc.bottom - rc.top;
  165. }
  166. /* loop through each line setting relative posn and clipping */
  167. /* set up all rows - the fixed/moveable difference for
  168. * rows is made at fetch-time during painting, when we remember
  169. * which absolute row nr to ask for, for a given screen line
  170. */
  171. cury = 0;
  172. for (i = 0; i < ptab->nlines; i++) {
  173. cp = &ptab->pdata[i].linepos;
  174. cp->start = cury;
  175. cp->clipstart = cury;
  176. cp->clipend = cury + cp->size;
  177. cury += cp->size;
  178. }
  179. /* now move and repaint the window */
  180. if (change != 0) {
  181. if (rc.top < rc.bottom) {
  182. ScrollWindow(hwnd, 0, (int) -(change * ptab->rowheight),
  183. &rc, NULL);
  184. }
  185. // don't repaint the fixed rows
  186. rc.top = 0;
  187. rc.bottom = ptab->hdr.fixedrows * ptab->rowheight;
  188. ValidateRect(hwnd, &rc);
  189. /* force repaint now, not just post message for later,
  190. * since we want to repaint that line before the next
  191. * scroll down occurs
  192. */
  193. ValidateRect(hwnd, &rcpaint);
  194. RedrawWindow(hwnd, &rcpaint, NULL,
  195. RDW_NOERASE | RDW_INVALIDATE | RDW_INTERNALPAINT);
  196. }
  197. if (fWasVisible)
  198. {
  199. gtab_invertsel(hwnd, ptab, NULL);
  200. ptab->selvisible = TRUE;
  201. }
  202. }
  203. /*
  204. * set new horizontal scroll pos,
  205. * set col win-relative start posns & clip left/right posns
  206. * revise display.
  207. */
  208. void
  209. gtab_dohscroll(HWND hwnd, lpTable ptab, long change)
  210. {
  211. int curx, i;
  212. int moveable;
  213. lpCellPos cp;
  214. int newdx, range;
  215. /* check that the new scroll pos is still within the valid range */
  216. range = ptab->rowwidth - ptab->winwidth;
  217. newdx = ptab->scroll_dx + (int) change;
  218. if (range < 0) {
  219. range = 0;
  220. }
  221. if (newdx > range) {
  222. change = range - ptab->scroll_dx;
  223. } else if (newdx < 0) {
  224. change = -(ptab->scroll_dx);
  225. }
  226. ptab->scroll_dx += (int) change;
  227. SetScrollPos(hwnd, SB_HORZ, ptab->scroll_dx, TRUE);
  228. if (ptab->hdr.fixedcols > 0) {
  229. RECT rc;
  230. GetClientRect(hwnd, &rc);
  231. rc.left = ptab->pcellpos[ptab->hdr.fixedcols - 1].clipend;
  232. InvalidateRect(hwnd, &rc, FALSE);
  233. } else {
  234. InvalidateRect(hwnd, NULL, FALSE);
  235. }
  236. /* loop through each col setting relative posn and clipping */
  237. /* clip off 1 pixel left and right (we added 2 on to size for this) */
  238. /* first set up fixed columns */
  239. curx = 0;
  240. for (i = 0; i < ptab->hdr.fixedcols; i++) {
  241. cp = &ptab->pcellpos[i];
  242. cp->start = curx + 1;
  243. cp->clipstart = cp->start;
  244. cp->clipend = cp->start + cp->size - 2;
  245. curx += cp->size;
  246. }
  247. /* now moveable columns. remember start of moveable cols */
  248. moveable = curx;
  249. curx = - ptab->scroll_dx; /* rel. pos of col */
  250. for (i = ptab->hdr.fixedcols; i < ptab->hdr.ncols; i++) {
  251. cp = &ptab->pcellpos[i];
  252. cp->start = curx + moveable + 1;
  253. cp->clipstart = max(moveable+1, cp->start);
  254. cp->clipend = cp->start + cp->size - 2;
  255. curx += cp->size;
  256. }
  257. }
  258. /*
  259. * convert screen line nr to table row nr
  260. */
  261. long
  262. gtab_linetorow(HWND hwnd, lpTable ptab, int line)
  263. {
  264. if (line < ptab->hdr.fixedrows) {
  265. return(line);
  266. }
  267. return (line + ptab->toprow);
  268. }
  269. /*
  270. * convert table row nr to screen line nr or -1 if not on screen
  271. */
  272. int
  273. gtab_rowtoline(HWND hwnd, lpTable ptab, long row)
  274. {
  275. if (row < ptab->hdr.fixedrows) {
  276. return( (int) row);
  277. }
  278. row -= ptab->toprow;
  279. if ((row >= ptab->hdr.fixedrows) && (row < ptab->nlines)) {
  280. return ( (int) row);
  281. }
  282. return(-1);
  283. }
  284. /*
  285. * check if a given location is within the current selection.
  286. * Returns true if it is inside the current selection, or false if
  287. * either there is no selection, or the row, cell passed is outside it.
  288. */
  289. BOOL
  290. gtab_insideselection(
  291. lpTable ptab,
  292. long row,
  293. long cell)
  294. {
  295. long startrow, endrow;
  296. long startcell, endcell;
  297. if (0 == ptab->select.nrows) {
  298. // no selection
  299. return FALSE;
  300. }
  301. // selection maintains anchor point as startrow,
  302. // so the selection can extend forwards or backwards from there.
  303. // need to convert to forward only for comparison
  304. startrow = ptab->select.startrow;
  305. if (ptab->select.nrows < 0) {
  306. endrow = startrow;
  307. startrow += ptab->select.nrows + 1;
  308. } else {
  309. endrow = startrow + ptab->select.nrows - 1;
  310. }
  311. if ((row < startrow) || (row > endrow)) {
  312. return FALSE;
  313. }
  314. // if we are in row-select mode, then that's it - its inside
  315. if (ptab->hdr.selectmode & TM_ROW) {
  316. return TRUE;
  317. }
  318. // same calculation for cells
  319. startcell = ptab->select.startcell;
  320. if (ptab->select.ncells < 0) {
  321. endcell = startcell;
  322. startcell += ptab->select.ncells + 1;
  323. } else {
  324. endcell = startcell + ptab->select.ncells - 1;
  325. }
  326. if ((cell < startcell) || (cell > endcell)) {
  327. return FALSE;
  328. }
  329. return TRUE;
  330. }
  331. /*
  332. * replace old selection with new. Notify owner if bNotify. Change
  333. * display to reflect new display.
  334. */
  335. void
  336. gtab_select(
  337. HWND hwnd,
  338. lpTable ptab,
  339. long row,
  340. long col,
  341. long nrows,
  342. long ncells,
  343. BOOL bNotify)
  344. {
  345. /* if in ROW mode, force col and ncells to reflect the entire row. */
  346. if (ptab->hdr.selectmode & TM_ROW) {
  347. col = 0;
  348. ncells = ptab->hdr.ncols;
  349. }
  350. /* clear existing sel if valid and visible */
  351. if ((ptab->select.nrows != 0) && (ptab->selvisible == TRUE)) {
  352. /* only clear sel if it is different from the new one */
  353. if ((ptab->select.startrow != row) ||
  354. (ptab->select.startcell != col) ||
  355. (ptab->select.nrows != nrows) ||
  356. (ptab->select.ncells != ncells)) {
  357. gtab_invertsel(hwnd, ptab, NULL);
  358. ptab->selvisible = FALSE;
  359. }
  360. }
  361. /* set select fields and send TQ_SELECT */
  362. if (row < ptab->hdr.nrows) {
  363. ptab->select.startrow = row;
  364. ptab->select.startcell = col;
  365. ptab->select.nrows = nrows;
  366. ptab->select.ncells = ncells;
  367. } else {
  368. ptab->select.nrows = 0;
  369. ptab->select.startrow = 0;
  370. ptab->select.startcell = 0;
  371. ptab->select.ncells = 0;
  372. }
  373. if (bNotify) {
  374. gtab_sendtq(hwnd, TQ_SELECT, (LPARAM) &ptab->select);
  375. }
  376. /* paint in selection */
  377. if (nrows != 0) {
  378. if (!ptab->selvisible) {
  379. gtab_invertsel(hwnd, ptab, NULL);
  380. ptab->selvisible = TRUE;
  381. }
  382. } else {
  383. if (ptab->selvisible) {
  384. gtab_invertsel(hwnd, ptab, NULL);
  385. ptab->selvisible = FALSE;
  386. }
  387. ptab->selvisible = FALSE;
  388. }
  389. }
  390. /*
  391. * convert window y co-ord to a line nr
  392. */
  393. int
  394. gtab_ytoline(HWND hwnd, lpTable ptab, int y)
  395. {
  396. return(y / ptab->rowheight);
  397. }
  398. /*
  399. * convert window x co-ord to a cell nr
  400. */
  401. int
  402. gtab_xtocol(HWND hwnd, lpTable ptab, int x)
  403. {
  404. int i;
  405. lpCellPos ppos;
  406. for (i = 0; i < ptab->hdr.ncols; i++) {
  407. ppos = &ptab->pcellpos[i];
  408. if (ppos->clipstart < ppos->clipend) {
  409. if ( (x >= ppos->clipstart) && (x < ppos->clipend)) {
  410. return(i);
  411. }
  412. }
  413. }
  414. return(-1);
  415. }
  416. /*
  417. * check if x co-ord is 'near' (+- 2 pixels) the right border of given cell
  418. */
  419. BOOL
  420. gtab_isborder(HWND hwnd, lpTable ptab, long x, long col)
  421. {
  422. if (abs(ptab->pcellpos[col].clipend - x) < 2) {
  423. return(TRUE);
  424. } else {
  425. return(FALSE);
  426. }
  427. }
  428. /*
  429. * set selection and send 'TQ_ENTER' event to owner
  430. */
  431. void
  432. gtab_enter(HWND hwnd, lpTable ptab, long row, long col, long nrows,
  433. long ncells)
  434. {
  435. /* clear existing sel if valid and visible */
  436. if ((ptab->select.nrows != 0) && (ptab->selvisible == TRUE)) {
  437. /* only clear sel if it is different from the new one */
  438. if ((ptab->select.startrow != row) ||
  439. (ptab->select.startcell != col) ||
  440. (ptab->select.nrows != nrows) ||
  441. (ptab->select.ncells != ncells)) {
  442. gtab_invertsel(hwnd, ptab, NULL);
  443. ptab->selvisible = FALSE;
  444. }
  445. }
  446. /* set select fields and send TQ_ENTER */
  447. if (row < ptab->hdr.nrows) {
  448. ptab->select.startrow = row;
  449. ptab->select.startcell = col;
  450. ptab->select.nrows = nrows;
  451. ptab->select.ncells = ncells;
  452. } else {
  453. ptab->select.nrows = 0;
  454. ptab->select.startrow = 0;
  455. ptab->select.startcell = 0;
  456. ptab->select.ncells = 0;
  457. }
  458. /* paint in selection */
  459. if (nrows != 0) {
  460. if (!ptab->selvisible) {
  461. gtab_invertsel(hwnd, ptab, NULL);
  462. ptab->selvisible = TRUE;
  463. }
  464. /* do this at end because it could cause a layout-change */
  465. gtab_sendtq(hwnd, TQ_ENTER, (LPARAM) &ptab->select);
  466. } else {
  467. if (ptab->selvisible) {
  468. gtab_invertsel(hwnd, ptab, NULL);
  469. }
  470. ptab->selvisible = FALSE;
  471. }
  472. }
  473. /*
  474. * start re-sizing a column
  475. */
  476. void
  477. gtab_trackcol(HWND hwnd, lpTable ptab, long col, long x)
  478. {
  479. /* ensure we see the mouse-up */
  480. SetCapture(hwnd);
  481. ptab->trackmode = TRACK_COLUMN;
  482. #ifdef WIN32
  483. ptab->tracknr = col;
  484. ptab->trackline1 = x;
  485. #else
  486. // maximum 32767 columns is a reasonable limit!
  487. ptab->tracknr = (int) (col & 0x7fff);
  488. ptab->trackline1 = (int) (x & 0x7fff);
  489. #endif
  490. /* if line at other side of cell is visible, draw that too */
  491. if (ptab->pcellpos[col].start >= ptab->pcellpos[col].clipstart) {
  492. ptab->trackline2 = ptab->pcellpos[col].start;
  493. } else {
  494. ptab->trackline2 = -1;
  495. }
  496. gtab_drawvertline(hwnd, ptab);
  497. }
  498. /*
  499. * called on right-click events. Select the cell clicked on, and if
  500. * valid, send on to owner for any context-menu type operation
  501. */
  502. void
  503. gtab_rightclick(HWND hwnd, lpTable ptab, int x, int y)
  504. {
  505. long cell, ncells;
  506. long row;
  507. HWND hOwner;
  508. /* find which col, row he selected */
  509. cell = gtab_xtocol(hwnd, ptab, x);
  510. if (cell == -1) {
  511. return;
  512. }
  513. row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));
  514. /* is he selecting a disabled fixed area ? */
  515. if ( (row < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols)) {
  516. if (ptab->hdr.fixedselectable == FALSE) {
  517. return;
  518. }
  519. }
  520. // ignore if beyond data
  521. if ((row >= ptab->hdr.nrows) ||
  522. (cell >= ptab->hdr.ncols)) {
  523. return;
  524. }
  525. /* is this within the already-selected area? */
  526. if (!gtab_insideselection(ptab, row, cell)) {
  527. // no selection, or clicked outside the selection - make new selection
  528. // before sending the right-click
  529. // if shift is down, extend selection
  530. if (GetKeyState(VK_SHIFT) & 0x8000) {
  531. gtab_extendsel(hwnd, ptab, row, cell, TRUE);
  532. } else {
  533. /* record and paint new selection */
  534. if (ptab->hdr.selectmode & TM_ROW) {
  535. cell = 0;
  536. ncells = ptab->hdr.ncols;
  537. } else {
  538. ncells = 1;
  539. }
  540. gtab_select(hwnd, ptab, row, cell, 1, ncells, TRUE);
  541. }
  542. }
  543. // now we have sent the selection, pass the message onto him
  544. hOwner = (HANDLE) GetWindowLongPtr(hwnd, WW_OWNER);
  545. SendMessage(hOwner, WM_RBUTTONDOWN, 0, MAKELONG( (short)x, (short)y));
  546. }
  547. /*
  548. * called on mouse-down events. decide what to start tracking.
  549. */
  550. void
  551. gtab_press(HWND hwnd, lpTable ptab, int x, int y)
  552. {
  553. long cell, ncells;
  554. long row;
  555. if (ptab->trackmode != TRACK_NONE) {
  556. return;
  557. }
  558. /* has he grabbed a cell-edge to resize ? */
  559. cell = gtab_xtocol(hwnd, ptab, x);
  560. if (cell == -1) {
  561. return;
  562. }
  563. if (gtab_isborder(hwnd, ptab, x, cell)) {
  564. gtab_trackcol(hwnd, ptab, cell, x);
  565. return;
  566. }
  567. if ( (cell > 0) && gtab_isborder(hwnd, ptab, x, cell-1)) {
  568. gtab_trackcol(hwnd, ptab, cell, x);
  569. return;
  570. }
  571. /* find which line he selected */
  572. row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));
  573. /* is he selecting a disabled fixed area ? */
  574. if ( (row < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols)) {
  575. if (ptab->hdr.fixedselectable == FALSE) {
  576. return;
  577. }
  578. }
  579. // ignore if beyond data
  580. if ((row >= ptab->hdr.nrows) ||
  581. (cell >= ptab->hdr.ncols)) {
  582. return;
  583. }
  584. /* ok, start cell selection */
  585. ptab->trackmode = TRACK_CELL;
  586. SetCapture(hwnd);
  587. /* record and paint new selection */
  588. if (ptab->hdr.selectmode & TM_ROW) {
  589. cell = 0;
  590. ncells = ptab->hdr.ncols;
  591. } else {
  592. ncells = 1;
  593. }
  594. /*
  595. * if the shift key is down, then extend the selection to this
  596. * new anchor point, rather than create a new selection
  597. */
  598. if (GetKeyState(VK_SHIFT) & 0x8000) {
  599. gtab_extendsel(hwnd, ptab, row, cell, FALSE);
  600. } else {
  601. gtab_select(hwnd, ptab, row, cell, 1, ncells, FALSE);
  602. }
  603. return;
  604. }
  605. /*
  606. * called on mouse-up. complete any tracking that was happening
  607. */
  608. void
  609. gtab_release(HWND hwnd, lpTable ptab, int x, int y)
  610. {
  611. lpCellPos ppos;
  612. lpProps pprop;
  613. long row, cell;
  614. int cx;
  615. switch(ptab->trackmode) {
  616. case TRACK_NONE:
  617. return;
  618. case TRACK_COLUMN:
  619. /* erase marker lines */
  620. gtab_drawvertline(hwnd, ptab);
  621. ReleaseCapture();
  622. ptab->trackmode = TRACK_NONE;
  623. /* adjust cell width */
  624. ppos = &ptab->pcellpos[ptab->tracknr];
  625. cx = ptab->trackline1 - ppos->start;
  626. pprop = &ptab->pcolhdr[ptab->tracknr].props;
  627. pprop->valid |= P_WIDTH;
  628. pprop->width = cx;
  629. gtab_calcwidths(hwnd, ptab);
  630. gtab_setsize(hwnd, ptab);
  631. InvalidateRect(hwnd, NULL, FALSE);
  632. return;
  633. case TRACK_CELL:
  634. row = gtab_linetorow(hwnd, ptab, gtab_ytoline(hwnd, ptab, y));
  635. cell = gtab_xtocol(hwnd, ptab, x);
  636. ReleaseCapture();
  637. ptab->trackmode = TRACK_NONE;
  638. // ignore if before or beyond data
  639. if ( (row < ptab->hdr.fixedrows) ||
  640. (cell < ptab->hdr.fixedcols)) {
  641. if (ptab->hdr.fixedselectable == FALSE) {
  642. gtab_select(
  643. hwnd,
  644. ptab,
  645. ptab->select.startrow,
  646. ptab->select.startcell,
  647. ptab->select.nrows,
  648. ptab->select.ncells,
  649. TRUE);
  650. return;
  651. }
  652. }
  653. if ((row >= ptab->hdr.nrows) ||
  654. (cell >= ptab->hdr.ncols)) {
  655. gtab_select(
  656. hwnd,
  657. ptab,
  658. ptab->select.startrow,
  659. ptab->select.startcell,
  660. ptab->select.nrows,
  661. ptab->select.ncells,
  662. TRUE);
  663. return;
  664. }
  665. /*
  666. * Extend to this new selection end point
  667. * we used to only do this if shift key pressed, but that
  668. * is not a good UI.
  669. */
  670. gtab_extendsel(hwnd, ptab, row, cell, TRUE);
  671. return;
  672. }
  673. }
  674. /* called on mouse-move. if tracking - adjust position, if not,
  675. * set correct cursor
  676. */
  677. void
  678. gtab_move(HWND hwnd, lpTable ptab, int x, int y)
  679. {
  680. BOOL fOK;
  681. int line;
  682. long row;
  683. int col;
  684. lpCellPos ppos;
  685. switch(ptab->trackmode) {
  686. case TRACK_NONE:
  687. col = gtab_xtocol(hwnd, ptab, x);
  688. if (col == -1) {
  689. SetCursor(hNormCurs);
  690. return;
  691. }
  692. if (gtab_isborder(hwnd, ptab, x, col)) {
  693. SetCursor(hVertCurs);
  694. return;
  695. }
  696. if ( (col > 0) && gtab_isborder(hwnd, ptab, x, col-1)) {
  697. SetCursor(hVertCurs);
  698. return;
  699. }
  700. SetCursor(hNormCurs);
  701. return;
  702. case TRACK_CELL:
  703. line = gtab_ytoline(hwnd, ptab, y);
  704. // we used to only allow drag to extend
  705. // the selection if the shift key was down.
  706. // this doesn't seem to work as a UI - you expect
  707. // to drag and extend.
  708. /* if extending selection then
  709. * allow scrolling by dragging off window
  710. */
  711. if (line < 0) {
  712. gtab_dovscroll(hwnd, ptab, -1);
  713. line = gtab_ytoline(hwnd, ptab, y);
  714. } else if (line >= ptab->nlines) {
  715. gtab_dovscroll(hwnd, ptab, 1);
  716. line = gtab_ytoline(hwnd, ptab, y);
  717. }
  718. row = gtab_linetorow(hwnd, ptab, line);
  719. col = gtab_xtocol(hwnd, ptab, x);
  720. // ignore if before or beyond data
  721. if ( (row < ptab->hdr.fixedrows) || (col < ptab->hdr.fixedcols)) {
  722. if (ptab->hdr.fixedselectable == FALSE) {
  723. return;
  724. }
  725. }
  726. if ((row >= ptab->hdr.nrows) ||
  727. (col >= ptab->hdr.ncols)) {
  728. return;
  729. }
  730. /*
  731. * extend to this new selection end point
  732. */
  733. gtab_extendsel(hwnd, ptab, row, col, FALSE);
  734. return;
  735. case TRACK_COLUMN:
  736. /* check that new x is still visible/valid */
  737. ppos = &ptab->pcellpos[ptab->tracknr];
  738. fOK = FALSE;
  739. if (ptab->tracknr < ptab->hdr.fixedcols) {
  740. if ((x > ppos->start) && (x < ptab->winwidth)) {
  741. fOK = TRUE;
  742. }
  743. } else {
  744. if ((x > ppos->clipstart) && (x < ptab->winwidth)) {
  745. fOK = TRUE;
  746. }
  747. }
  748. if (fOK == TRUE) {
  749. gtab_drawvertline(hwnd, ptab);
  750. ptab->trackline1 = x;
  751. gtab_drawvertline(hwnd, ptab);
  752. }
  753. return;
  754. }
  755. }
  756. /* dbl-click - send an TQ_ENTER event to the owner (if valid) */
  757. void
  758. gtab_dblclick(HWND hwnd, lpTable ptab, int x, int y)
  759. {
  760. int cell, line;
  761. long row;
  762. line = gtab_ytoline(hwnd, ptab, y);
  763. cell = gtab_xtocol(hwnd, ptab, x);
  764. if ( (line < ptab->hdr.fixedrows) || (cell < ptab->hdr.fixedcols) ) {
  765. if (!ptab->hdr.fixedselectable) {
  766. return;
  767. }
  768. }
  769. row = gtab_linetorow(hwnd, ptab, line);
  770. if (ptab->hdr.selectmode & TM_ROW) {
  771. gtab_enter(hwnd, ptab, row, 0, 1, ptab->hdr.ncols);
  772. } else {
  773. gtab_enter(hwnd, ptab, row, cell, 1, 1);
  774. }
  775. }
  776. /*
  777. * move selection area to visible part of window. argument bToBottom
  778. * indicates whether to move the line onto the bottom or the top of the
  779. * window if not visible - this affects the smoothness of scrolling
  780. * line-by-line.
  781. */
  782. void
  783. gtab_showsel(HWND hwnd, lpTable ptab, BOOL bToBottom)
  784. {
  785. int line;
  786. long change;
  787. line = gtab_rowtoline(hwnd, ptab, ptab->select.startrow);
  788. /* move up if last line or not at all visible */
  789. if ( (line < 0) || line == (ptab->nlines - 1)) {
  790. change = ptab->select.startrow - ptab->toprow;
  791. if (bToBottom) {
  792. /* change to bottom of window. subtract 2 not 1
  793. * since nlines includes one line that is only
  794. * partly visible
  795. */
  796. change -= (ptab->nlines - 2);
  797. }
  798. change -= ptab->hdr.fixedrows;
  799. gtab_dovscroll(hwnd, ptab, change);
  800. }
  801. /* add support for TM_CELL here! */
  802. }
  803. /*
  804. * scroll the window so that if possible, the selected row is in the
  805. * middle 60% of the screen so that context around it is visible.
  806. */
  807. void
  808. gtab_showsel_middle(HWND hwnd, lpTable ptab, long dyRowsFromTop)
  809. {
  810. int line = ptab->select.startrow;
  811. long change = 0;
  812. int mid_top, mid_end;
  813. BOOL fScroll = FALSE;
  814. if (dyRowsFromTop >= 0)
  815. {
  816. fScroll = TRUE;
  817. change = (ptab->select.startrow - dyRowsFromTop) - ptab->toprow;
  818. change -= ptab->hdr.fixedrows;
  819. }
  820. /* is this within the middle 60 % ? */
  821. mid_top = ptab->toprow + (ptab->nlines * 20 / 100);
  822. mid_end = ptab->toprow + (ptab->nlines * 80 / 100);
  823. if ((line < mid_top + change) || (line > mid_end + change))
  824. {
  825. /* no - scroll so that selected line is at
  826. * the 20% mark
  827. */
  828. fScroll = TRUE;
  829. change = (ptab->select.startrow - mid_top);
  830. change -= ptab->hdr.fixedrows;
  831. }
  832. if (fScroll)
  833. {
  834. gtab_dovscroll(hwnd, ptab, change);
  835. }
  836. /* again - need code here for TM_CELL mode to ensure that
  837. * active cell is horizontally scrolled correctly
  838. */
  839. }
  840. /*
  841. * extend the selection to set the new anchor point as startrow, startcell.
  842. *
  843. * nrows and ncells will then be set to include the end row of the previous
  844. * selection. nrows, ncells < 0 indicate left and up. -1 and +1 both indicate
  845. * just one cell or row selected.
  846. */
  847. VOID
  848. gtab_extendsel(
  849. HWND hwnd,
  850. lpTable ptab,
  851. long startrow,
  852. long startcell,
  853. BOOL bNotify
  854. )
  855. {
  856. long endrow, endcell, nrows, ncells;
  857. /*
  858. * if no current selection, then just select the new anchor point
  859. */
  860. if (ptab->select.nrows == 0) {
  861. gtab_select(hwnd, ptab, startrow, startcell, 1,
  862. (ptab->hdr.selectmode & TM_ROW) ? ptab->hdr.ncols:1,
  863. bNotify);
  864. return;
  865. }
  866. if (startrow >= ptab->hdr.nrows) {
  867. startrow = ptab->hdr.nrows -1;
  868. } else if (startrow < 0) {
  869. startrow = 0;
  870. }
  871. if (startcell >= ptab->hdr.ncols) {
  872. startcell = ptab->hdr.ncols-1;
  873. } else if (startcell < 0) {
  874. startcell = 0;
  875. }
  876. /* calculate the row just beyond the selection
  877. * this is one above for upwards sels, and one below for
  878. * downard-extending sels. Then adjust down or up one
  879. * to be the actual (inclusive) last row.
  880. */
  881. endrow = ptab->select.startrow + ptab->select.nrows;
  882. if (ptab->select.nrows < 0) {
  883. endrow++;
  884. } else {
  885. endrow--;
  886. }
  887. if (endrow >= ptab->hdr.nrows) {
  888. endrow = ptab->hdr.nrows-1;
  889. }
  890. nrows = endrow - startrow;
  891. if (nrows >= 0) {
  892. // convert from exclusive to inclusive
  893. nrows++;
  894. } else {
  895. // convert from exclusive to inclusive
  896. nrows--;
  897. }
  898. /* same calculation for cells */
  899. endcell = ptab->select.startcell + ptab->select.ncells;
  900. if (ptab->select.ncells < 0) {
  901. endcell++;
  902. } else {
  903. endcell--;
  904. }
  905. ncells = endcell - startcell;
  906. if (ncells >= 0) {
  907. ncells++;
  908. } else {
  909. ncells--;
  910. }
  911. gtab_select(hwnd, ptab, startrow, startcell, nrows, ncells, bNotify);
  912. }
  913. /* move the selection a specified nr of rows or cells
  914. * if no selection, select first visible unit
  915. *
  916. * if bExtend is true and there is a current selection, then extend it rather than
  917. * replace it. Note that (startrow, startcell) will always be set to the newly
  918. * selected position - this is the anchor point. nrows or ncells may be negative
  919. * if the selection extends upwards above the anchor. nrows == -1 is the same
  920. * as nrows == 1, meaning only the current row is visible. Similarly
  921. * (in TM_CELL mode), ncells may be negative.
  922. *
  923. * Move the selection (ie anchor point) to make it visible. bToBottom
  924. * indicates whether it should be moved to the bottom or the top
  925. * of the window.
  926. */
  927. VOID
  928. gtab_changesel(
  929. HWND hwnd,
  930. lpTable ptab,
  931. long rowincr,
  932. int cellincr,
  933. BOOL bToBottom,
  934. BOOL bExtend
  935. )
  936. {
  937. long row, col, ncols;
  938. /* is there a selection ? */
  939. if (ptab->select.nrows == 0) {
  940. /* no selection - force a selection
  941. * at the first visible unit
  942. */
  943. if (ptab->hdr.fixedselectable) {
  944. row = 0;
  945. col = 0;
  946. } else {
  947. row = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
  948. /* should really check for first visible cell */
  949. col = ptab->hdr.fixedcols;
  950. }
  951. ncols = 1;
  952. if (ptab->hdr.selectmode & TM_ROW) {
  953. col = 0;
  954. ncols = ptab->hdr.ncols;
  955. }
  956. gtab_select(hwnd, ptab, row, col, 1, ncols, TRUE);
  957. } else {
  958. /* move the anchor point by rowincr, cellincr */
  959. row = ptab->select.startrow + rowincr;
  960. col = ptab->select.startcell + cellincr;
  961. /*
  962. * ensure that new anchor point is in a valid position
  963. */
  964. while (col >= ptab->hdr.ncols) {
  965. col -= ptab->hdr.ncols;
  966. row++;
  967. }
  968. while (col < 0) {
  969. col += ptab->hdr.ncols;
  970. row--;
  971. }
  972. if (row < 0) {
  973. row = 0;
  974. }
  975. if (row >= ptab->hdr.nrows) {
  976. row = ptab->hdr.nrows-1;
  977. }
  978. /* check we haven't moved into non-selectable region */
  979. if ((row < ptab->hdr.fixedrows) &&
  980. (!ptab->hdr.fixedselectable)) {
  981. row = ptab->hdr.fixedrows;
  982. }
  983. if (bExtend) {
  984. gtab_extendsel(hwnd, ptab, row, col, TRUE);
  985. } else {
  986. gtab_select(
  987. hwnd,
  988. ptab,
  989. row,
  990. col,
  991. 1,
  992. (ptab->hdr.selectmode & TM_ROW) ? ptab->hdr.ncols : 1,
  993. TRUE);
  994. }
  995. }
  996. /* ensure selection visible */
  997. gtab_showsel(hwnd, ptab, bToBottom);
  998. }
  999. /*
  1000. * set the topmost selectable unit in window as the selection
  1001. *
  1002. * if bExtend is TRUE, then extend the selection to include this, rather
  1003. * than replacing the existing selection. Note that (startrow, startcell)
  1004. * is always the anchor point - ie most recently selected end, and the
  1005. * (nrows, ncells) can be + or - to extend the selection downwards or upwards.
  1006. */
  1007. void
  1008. gtab_selhome(HWND hwnd, lpTable ptab, BOOL bExtend)
  1009. {
  1010. long startrow, startcell, ncells;
  1011. if (ptab->hdr.selectmode & TM_ROW) {
  1012. ncells = ptab->hdr.ncols;
  1013. } else {
  1014. ncells = 1;
  1015. }
  1016. startcell = 0;
  1017. if (ptab->hdr.fixedselectable) {
  1018. startrow = gtab_linetorow(hwnd, ptab, 0);
  1019. } else {
  1020. startrow = gtab_linetorow(hwnd, ptab, ptab->hdr.fixedrows);
  1021. if (!(ptab->hdr.selectmode & TM_ROW)) {
  1022. startcell = ptab->hdr.fixedcols;
  1023. }
  1024. }
  1025. if (bExtend) {
  1026. gtab_extendsel(hwnd, ptab, startrow, startcell, TRUE);
  1027. } else {
  1028. gtab_select(hwnd, ptab, startrow, startcell, 1, ncells, TRUE);
  1029. }
  1030. }
  1031. /* handle key-down events - scroll windows and/or move selection */
  1032. int
  1033. gtab_key(HWND hwnd, lpTable ptab, int vkey)
  1034. {
  1035. long startrow, ncells, startcell;
  1036. BOOL bControl = FALSE;
  1037. BOOL bShift = FALSE;
  1038. if (GetKeyState(VK_CONTROL) & 0x8000) {
  1039. bControl = TRUE;
  1040. }
  1041. if (GetKeyState(VK_SHIFT) & 0x8000) {
  1042. /* ignore shift key here if TM_MANY -multiple selection flag- is
  1043. * not selected
  1044. */
  1045. if (ptab->hdr.selectmode & TM_MANY) {
  1046. bShift = TRUE;
  1047. }
  1048. }
  1049. switch(vkey) {
  1050. case VK_UP:
  1051. if (bControl) {
  1052. /* control-uparrow scrolls window without selection.
  1053. * the selection is de-selected (to avoid surprises
  1054. * moving back to it).
  1055. */
  1056. gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
  1057. gtab_dovscroll(hwnd, ptab, -1);
  1058. } else {
  1059. /* uparrow moves selection up one line */
  1060. gtab_changesel(hwnd, ptab, -1, 0, FALSE, bShift);
  1061. }
  1062. return(0);
  1063. case VK_DOWN:
  1064. if (bControl) {
  1065. /* control downarrow scrolls window without
  1066. * a selection.
  1067. */
  1068. gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
  1069. gtab_dovscroll(hwnd, ptab, 1);
  1070. } else {
  1071. /* the normal gtab_changesel behaviour is
  1072. * that if the selected line is not visible now,
  1073. * we scroll it to the top of the window. This is fine
  1074. * in most cases but causes unacceptable jumps when
  1075. * repeatedly scrolling down with the down key.
  1076. *
  1077. * Thus we now have an argument to changesel to say
  1078. * that in this case, if you need to move the line onto
  1079. * the window, move it to the bottom and not the top
  1080. */
  1081. gtab_changesel(hwnd, ptab, 1, 0, TRUE, bShift);
  1082. }
  1083. return(0);
  1084. case VK_LEFT:
  1085. /* if cell-selection mode, move left one cell.
  1086. * otherwise the whole row is selected - scroll
  1087. * the line left a little
  1088. */
  1089. if (ptab->hdr.selectmode & TM_ROW) {
  1090. if (bControl) {
  1091. /* ctrl-left moves to start of line */
  1092. gtab_dohscroll(hwnd, ptab, -(ptab->scroll_dx));
  1093. } else {
  1094. gtab_dohscroll(hwnd, ptab, -(ptab->avewidth));
  1095. }
  1096. } else {
  1097. gtab_changesel(hwnd, ptab, 0, -1, FALSE, bShift);
  1098. }
  1099. return(0);
  1100. case VK_RIGHT:
  1101. /* if cell-selection mode, move right one cell.
  1102. * otherwise the whole row is selected - scroll
  1103. * the line right a little
  1104. */
  1105. if (ptab->hdr.selectmode & TM_ROW) {
  1106. if (bControl) {
  1107. /* control-right moves to right end of line */
  1108. gtab_dohscroll(hwnd, ptab, ptab->rowwidth -
  1109. ptab->winwidth);
  1110. } else {
  1111. gtab_dohscroll(hwnd, ptab, ptab->avewidth);
  1112. }
  1113. } else {
  1114. gtab_changesel(hwnd, ptab, 0, 1, TRUE, bShift);
  1115. }
  1116. return(0);
  1117. case VK_HOME:
  1118. if (bControl) {
  1119. /* control-home == top of file */
  1120. gtab_dovscroll(hwnd, ptab, -(ptab->toprow));
  1121. }
  1122. /* top of window */
  1123. gtab_selhome(hwnd, ptab, bShift);
  1124. gtab_showsel(hwnd, ptab, FALSE);
  1125. return(0);
  1126. case VK_END:
  1127. if (bControl) {
  1128. /* control-end -> end of file */
  1129. startrow = ptab->hdr.nrows-1;
  1130. } else {
  1131. startrow = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
  1132. if (startrow >= ptab->hdr.nrows) {
  1133. startrow = ptab->hdr.nrows-1;
  1134. }
  1135. }
  1136. startcell = 0;
  1137. ncells = ptab->hdr.ncols;
  1138. if (!(ptab->hdr.selectmode & TM_ROW)) {
  1139. startcell = ptab->hdr.ncols-1;
  1140. ncells = 1;
  1141. }
  1142. if (bShift) {
  1143. gtab_extendsel(hwnd, ptab, startrow, startcell, TRUE);
  1144. } else {
  1145. gtab_select(hwnd, ptab, startrow, startcell, 1, ncells, TRUE);
  1146. }
  1147. /* we have selected the bottom line. We don't want to
  1148. * move it up into the window, since the intended
  1149. * effect is to select the lowest line. This doesn't
  1150. * apply to the ctrl-end behaviour (move to bottom of
  1151. * buffer.
  1152. */
  1153. if (bControl) {
  1154. /* move the selection to make it visible - but move it
  1155. * to the bottom and not to the top of the window
  1156. */
  1157. gtab_showsel(hwnd, ptab, TRUE);
  1158. }
  1159. return(0);
  1160. case VK_RETURN:
  1161. if (ptab->select.nrows != 0) {
  1162. gtab_showsel(hwnd, ptab, FALSE);
  1163. gtab_enter(hwnd, ptab, ptab->select.startrow,
  1164. ptab->select.startcell,
  1165. ptab->select.nrows, ptab->select.ncells);
  1166. }
  1167. return(0);
  1168. case VK_SPACE:
  1169. /* toggle the selection */
  1170. if (ptab->select.nrows == 0) {
  1171. /* no selection - make one */
  1172. gtab_changesel(hwnd, ptab, 0, 0, TRUE, FALSE);
  1173. } else {
  1174. /* there is a selection - deselect it */
  1175. gtab_select(hwnd, ptab, 0, 0, 0, 0, TRUE);
  1176. }
  1177. return(0);
  1178. case VK_PRIOR: /* page up */
  1179. if (ptab->nlines > 3) {
  1180. gtab_dovscroll(hwnd, ptab, -(ptab->nlines - 3));
  1181. }
  1182. gtab_selhome(hwnd, ptab, bShift);
  1183. return(0);
  1184. case VK_NEXT: /* page down */
  1185. /* scroll down one page */
  1186. if (ptab->nlines > 3) {
  1187. gtab_dovscroll(hwnd, ptab, (ptab->nlines - 3));
  1188. }
  1189. /* select new bottom line */
  1190. startrow = gtab_linetorow(hwnd, ptab, ptab->nlines - 1);
  1191. if (startrow >= ptab->hdr.nrows) {
  1192. startrow = ptab->hdr.nrows-1;
  1193. }
  1194. startcell = 0;
  1195. ncells = ptab->hdr.ncols;
  1196. if (!(ptab->hdr.selectmode & TM_ROW)) {
  1197. startcell = ptab->hdr.ncols-1;
  1198. ncells = 1;
  1199. }
  1200. /* select bottom line, but don't call showsel
  1201. * since we don't want to adjust it's position - we
  1202. * want it to remain at the bottom of the window
  1203. */
  1204. if (bShift) {
  1205. gtab_extendsel(hwnd, ptab, startrow, startcell, TRUE);
  1206. } else {
  1207. gtab_select(hwnd, ptab, startrow, startcell, 1, ncells, TRUE);
  1208. }
  1209. return(0);
  1210. default:
  1211. return(1);
  1212. }
  1213. }
  1214. int gtab_mousewheel(HWND hwnd, lpTable ptab, DWORD fwKeys, int zDelta)
  1215. {
  1216. static ULONG uScrollLines = 0;
  1217. if (fwKeys & MK_MBUTTON) {
  1218. return 1;
  1219. }
  1220. if (uScrollLines == 0) {
  1221. SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uScrollLines, FALSE);
  1222. if (uScrollLines == 0) {
  1223. uScrollLines = 3;
  1224. }
  1225. }
  1226. zDelta /= -WHEEL_DELTA;
  1227. if (fwKeys & MK_CONTROL) {
  1228. //
  1229. // Left-Right scroll
  1230. //
  1231. if (ptab->hdr.selectmode & TM_ROW) {
  1232. if (fwKeys & MK_SHIFT) {
  1233. zDelta = (zDelta > 0) ? ptab->rowwidth : -ptab->rowwidth;
  1234. }
  1235. gtab_dohscroll(hwnd, ptab, ptab->avewidth * zDelta);
  1236. return 0;
  1237. }
  1238. return 1;
  1239. }
  1240. if (fwKeys & MK_SHIFT) {
  1241. //
  1242. // Page scroll
  1243. //
  1244. if (ptab->nlines > 3) {
  1245. zDelta *= ptab->nlines - 3;
  1246. }
  1247. }
  1248. else {
  1249. if (uScrollLines) {
  1250. zDelta *= uScrollLines;
  1251. zDelta = min(zDelta, ptab->nlines - 3);
  1252. }
  1253. }
  1254. gtab_dovscroll(hwnd, ptab, zDelta);
  1255. return 0;
  1256. }