Leaked source code of windows server 2003
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.

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