1/*
2 * tkTreeMarquee.c --
3 *
4 *	This module implements the selection rectangle for treectrl widgets.
5 *
6 * Copyright (c) 2002-2009 Tim Baker
7 *
8 * RCS: @(#) $Id: tkTreeMarquee.c,v 1.20 2010/02/27 21:11:04 treectrl Exp $
9 */
10
11#include "tkTreeCtrl.h"
12
13typedef struct TreeMarquee_ TreeMarquee_;
14
15/*
16 * The following structure holds info about the selection rectangle.
17 * There is one of these per TreeCtrl.
18 */
19struct TreeMarquee_
20{
21    TreeCtrl *tree;
22    Tk_OptionTable optionTable;
23    int visible;			/* -visible option. */
24    int x1, y1, x2, y2;			/* Opposing corners. */
25    int onScreen;			/* TRUE if it was drawn. */
26    int sx, sy;				/* Offset of canvas from top-left
27					 * corner of the window when we
28					 * were drawn. */
29    int sw, sh;				/* Width & height when drawn. */
30};
31
32#define MARQ_CONF_VISIBLE		0x0001
33
34static Tk_OptionSpec optionSpecs[] = {
35    {TK_OPTION_BOOLEAN, "-visible", (char *) NULL, (char *) NULL,
36	"0", -1, Tk_Offset(TreeMarquee_, visible),
37	0, (ClientData) NULL, MARQ_CONF_VISIBLE},
38    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
39	(char *) NULL, 0, -1, 0, 0, 0}
40};
41
42/*
43 *----------------------------------------------------------------------
44 *
45 * TreeMarquee_Init --
46 *
47 *	Perform marquee-related initialization when a new TreeCtrl is
48 *	created.
49 *
50 * Results:
51 *	A standard Tcl result.
52 *
53 * Side effects:
54 *	Memory is allocated.
55 *
56 *----------------------------------------------------------------------
57 */
58
59int
60TreeMarquee_Init(
61    TreeCtrl *tree		/* Widget info. */
62    )
63{
64    TreeMarquee marquee;
65
66    marquee = (TreeMarquee) ckalloc(sizeof(TreeMarquee_));
67    memset(marquee, '\0', sizeof(TreeMarquee_));
68    marquee->tree = tree;
69    marquee->optionTable = Tk_CreateOptionTable(tree->interp, optionSpecs);
70    if (Tk_InitOptions(tree->interp, (char *) marquee, marquee->optionTable,
71	tree->tkwin) != TCL_OK) {
72	WFREE(marquee, TreeMarquee_);
73	return TCL_ERROR;
74    }
75    tree->marquee = marquee;
76    return TCL_OK;
77}
78
79/*
80 *----------------------------------------------------------------------
81 *
82 * TreeMarquee_Free --
83 *
84 *	Free marquee-related resources when a TreeCtrl is deleted.
85 *
86 * Results:
87 *	None.
88 *
89 * Side effects:
90 *	Memory is deallocated.
91 *
92 *----------------------------------------------------------------------
93 */
94
95void
96TreeMarquee_Free(
97    TreeMarquee marquee	/* Marquee token. */
98    )
99{
100    Tk_FreeConfigOptions((char *) marquee, marquee->optionTable,
101	marquee->tree->tkwin);
102    WFREE(marquee, TreeMarquee_);
103}
104
105/*
106 *----------------------------------------------------------------------
107 *
108 * TreeMarquee_IsXOR --
109 *
110 *	Return true if the marquee is being drawn with XOR.
111 *
112 * Results:
113 *	None.
114 *
115 * Side effects:
116 *	None.
117 *
118 *----------------------------------------------------------------------
119 */
120
121int TreeMarquee_IsXOR(TreeMarquee marquee)
122{
123#if defined(WIN32)
124    return FALSE; /* TRUE on XP, FALSE on Win7 (lots of flickering) */
125#elif defined(MAC_TK_CARBON)
126    return TRUE;
127#elif defined(MAC_TK_COCOA)
128    return FALSE;
129#else
130    return TRUE; /* X11 */
131#endif
132}
133
134/*
135 *----------------------------------------------------------------------
136 *
137 * TreeMarquee_IsVisible --
138 *
139 *	Return true if the marquee is being drawn.
140 *
141 * Results:
142 *	None.
143 *
144 * Side effects:
145 *	None.
146 *
147 *----------------------------------------------------------------------
148 */
149
150int TreeMarquee_IsVisible(TreeMarquee marquee)
151{
152    return marquee->visible;
153}
154
155/*
156 *----------------------------------------------------------------------
157 *
158 * TreeMarquee_Display --
159 *
160 *	Draw the selection rectangle if it is not already displayed and if
161 *	it's -visible option is TRUE.
162 *
163 * Results:
164 *	None.
165 *
166 * Side effects:
167 *	Stuff is drawn.
168 *
169 *----------------------------------------------------------------------
170 */
171
172void
173TreeMarquee_Display(
174    TreeMarquee marquee		/* Marquee token. */
175    )
176{
177    TreeCtrl *tree = marquee->tree;
178
179    if (!marquee->onScreen && marquee->visible) {
180	if (TreeMarquee_IsXOR(marquee)) {
181	    marquee->sx = 0 - tree->xOrigin;
182	    marquee->sy = 0 - tree->yOrigin;
183	    TreeMarquee_DrawXOR(marquee, Tk_WindowId(tree->tkwin),
184		marquee->sx, marquee->sy);
185	} else {
186	    marquee->sx = MIN(marquee->x1, marquee->x2) - tree->xOrigin;
187	    marquee->sy = MIN(marquee->y1, marquee->y2) - tree->yOrigin;
188	    marquee->sw = abs(marquee->x2 - marquee->x1) + 1;
189	    marquee->sh = abs(marquee->y2 - marquee->y1) + 1;
190/*	    Tree_InvalidateItemArea(tree, marquee->sx, marquee->sy,
191		marquee->sx + marquee->sw, marquee->sy + marquee->sh);*/
192	    Tree_EventuallyRedraw(tree);
193	}
194	marquee->onScreen = TRUE;
195    }
196}
197
198/*
199 *----------------------------------------------------------------------
200 *
201 * TreeMarquee_Undisplay --
202 *
203 *	Erase the selection rectangle if it is displayed.
204 *
205 * Results:
206 *	None.
207 *
208 * Side effects:
209 *	Stuff is drawn.
210 *
211 *----------------------------------------------------------------------
212 */
213
214void
215TreeMarquee_Undisplay(
216    TreeMarquee marquee		/* Marquee token. */
217    )
218{
219    TreeCtrl *tree = marquee->tree;
220
221    if (marquee->onScreen) {
222	if (TreeMarquee_IsXOR(marquee)) {
223	    TreeMarquee_DrawXOR(marquee, Tk_WindowId(tree->tkwin), marquee->sx, marquee->sy);
224	} else {
225/*	    Tree_InvalidateItemArea(tree, marquee->sx, marquee->sy,
226		marquee->sx + marquee->sw, marquee->sy + marquee->sh);*/
227	    Tree_EventuallyRedraw(tree);
228	}
229	marquee->onScreen = FALSE;
230    }
231}
232
233/*
234 *----------------------------------------------------------------------
235 *
236 * TreeMarquee_DrawXOR --
237 *
238 *	Draw (or erase) the selection rectangle.
239 *
240 * Results:
241 *	None.
242 *
243 * Side effects:
244 *	Stuff is drawn (or erased, since this is XOR drawing).
245 *
246 *----------------------------------------------------------------------
247 */
248
249void
250TreeMarquee_DrawXOR(
251    TreeMarquee marquee,	/* Marquee token. */
252    Drawable drawable,		/* Where to draw. */
253    int x1, int y1		/* Offset of canvas from top-left corner
254				 * of the window. */
255    )
256{
257    TreeCtrl *tree = marquee->tree;
258    int x, y, w, h;
259    DotState dotState;
260
261    x = MIN(marquee->x1, marquee->x2);
262    w = abs(marquee->x1 - marquee->x2) + 1;
263    y = MIN(marquee->y1, marquee->y2);
264    h = abs(marquee->y1 - marquee->y2) + 1;
265
266    TreeDotRect_Setup(tree, drawable, &dotState);
267    TreeDotRect_Draw(&dotState, x1 + x, y1 + y, w, h);
268    TreeDotRect_Restore(&dotState);
269}
270
271/*
272 *----------------------------------------------------------------------
273 *
274 * TreeMarquee_Draw --
275 *
276 *	Draw the selection rectangle if it is visible.
277 *
278 * Results:
279 *	None.
280 *
281 * Side effects:
282 *	Stuff is drawn.
283 *
284 *----------------------------------------------------------------------
285 */
286
287void
288TreeMarquee_Draw(
289    TreeMarquee marquee,	/* Marquee token. */
290    TreeDrawable td)		/* Where to draw. */
291{
292#if 1 /* Use XOR dotted rectangles where possible. */
293    TreeCtrl *tree = marquee->tree;
294
295    if (!marquee->visible)
296	return;
297
298    /* Yes this is XOR drawing but we aren't erasing the previous
299     * marquee as when TreeMarquee_IsXOR() returns TRUE. */
300    TreeMarquee_DrawXOR(marquee, td.drawable,
301	0 - tree->xOrigin, 0 - tree->yOrigin);
302#else /* */
303    TreeCtrl *tree = marquee->tree;
304    int x, y, w, h;
305    GC gc;
306    XGCValues gcValues;
307    unsigned long mask;
308#ifdef WIN32
309    XPoint points[5];
310    XRectangle rect;
311#endif
312#if 0
313    XColor *colorPtr;
314#endif
315
316    if (!marquee->visible)
317	return;
318
319    x = MIN(marquee->x1, marquee->x2);
320    w = abs(marquee->x1 - marquee->x2) + 1;
321    y = MIN(marquee->y1, marquee->y2);
322    h = abs(marquee->y1 - marquee->y2) + 1;
323
324#if 0
325    colorPtr = Tk_GetColor(tree->interp, tree->tkwin, "gray50");
326    gc = Tk_GCForColor(colorPtr, Tk_WindowId(tree->tkwin));
327
328    XFillRectangle(tree->display, td.drawable, gc,
329	x - tree->drawableXOrigin, y - tree->drawableYOrigin,
330	w - 1, h - 1);
331#else /* Stippled rectangles: BUG not clipped to contentbox. */
332    gcValues.stipple = Tk_GetBitmap(tree->interp, tree->tkwin, "gray50");
333    gcValues.fill_style = FillStippled;
334    mask = GCStipple|GCFillStyle;
335    gc = Tk_GetGC(tree->tkwin, mask, &gcValues);
336
337#ifdef WIN32
338    /* XDrawRectangle ignores the stipple pattern. */
339    rect.x = x - tree->drawableXOrigin;
340    rect.y = y - tree->drawableYOrigin;
341    rect.width = w;
342    rect.height = h;
343    points[0].x = rect.x, points[0].y = rect.y;
344    points[1].x = rect.x + rect.width - 1, points[1].y = rect.y;
345    points[2].x = rect.x + rect.width - 1, points[2].y = rect.y + rect.height - 1;
346    points[3].x = rect.x, points[3].y = rect.y + rect.height - 1;
347    points[4] = points[0];
348    XDrawLines(tree->display, td.drawable, gc, points, 5, CoordModeOrigin);
349#else
350    XDrawRectangle(tree->display, td.drawable, gc,
351	x - tree->drawableXOrigin, y - tree->drawableYOrigin,
352	w - 1, h - 1);
353#endif
354    Tk_FreeGC(tree->display, gc);
355#endif
356#endif /* */
357}
358
359/*
360 *----------------------------------------------------------------------
361 *
362 * Marquee_Config --
363 *
364 *	This procedure is called to process an objc/objv list to set
365 *	configuration options for a Marquee.
366 *
367 * Results:
368 *	The return value is a standard Tcl result.  If TCL_ERROR is
369 *	returned, then an error message is left in interp's result.
370 *
371 * Side effects:
372 *	Configuration information, such as text string, colors, font,
373 *	etc. get set for marquee;  old resources get freed, if there
374 *	were any.  Display changes may occur.
375 *
376 *----------------------------------------------------------------------
377 */
378
379static int
380Marquee_Config(
381    TreeMarquee marquee,	/* Marquee record. */
382    int objc,			/* Number of arguments. */
383    Tcl_Obj *CONST objv[]	/* Argument values. */
384    )
385{
386    TreeCtrl *tree = marquee->tree;
387    Tk_SavedOptions savedOptions;
388    int error;
389    Tcl_Obj *errorResult = NULL;
390    int mask;
391
392    for (error = 0; error <= 1; error++) {
393	if (error == 0) {
394	    if (Tk_SetOptions(tree->interp, (char *) marquee, marquee->optionTable,
395		objc, objv, tree->tkwin, &savedOptions, &mask) != TCL_OK) {
396		mask = 0;
397		continue;
398	    }
399
400	    /* xxx */
401
402	    Tk_FreeSavedOptions(&savedOptions);
403	    break;
404	} else {
405	    errorResult = Tcl_GetObjResult(tree->interp);
406	    Tcl_IncrRefCount(errorResult);
407	    Tk_RestoreSavedOptions(&savedOptions);
408
409		/* xxx */
410
411	    Tcl_SetObjResult(tree->interp, errorResult);
412	    Tcl_DecrRefCount(errorResult);
413	    return TCL_ERROR;
414	}
415    }
416
417    if (mask & MARQ_CONF_VISIBLE) {
418	TreeMarquee_Undisplay(marquee);
419	TreeMarquee_Display(marquee);
420    }
421
422    return TCL_OK;
423}
424
425/*
426 *----------------------------------------------------------------------
427 *
428 * TreeMarqueeCmd --
429 *
430 *	This procedure is invoked to process the [marquee] widget
431 *	command.  See the user documentation for details on what it
432 *	does.
433 *
434 * Results:
435 *	A standard Tcl result.
436 *
437 * Side effects:
438 *	See the user documentation.
439 *
440 *----------------------------------------------------------------------
441 */
442
443int
444TreeMarqueeCmd(
445    ClientData clientData,	/* Widget info. */
446    Tcl_Interp *interp,		/* Current interpreter. */
447    int objc,			/* Number of arguments. */
448    Tcl_Obj *CONST objv[]	/* Argument values. */
449    )
450{
451    TreeCtrl *tree = clientData;
452    TreeMarquee marquee = tree->marquee;
453    static CONST char *commandNames[] = { "anchor", "cget", "configure",
454	"coords", "corner", "identify", (char *) NULL };
455    enum { COMMAND_ANCHOR, COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_COORDS,
456	COMMAND_CORNER, COMMAND_IDENTIFY };
457    int index;
458
459    if (objc < 3) {
460	Tcl_WrongNumArgs(interp, 2, objv, "command ?arg arg ...?");
461	return TCL_ERROR;
462    }
463
464    if (Tcl_GetIndexFromObj(interp, objv[2], commandNames, "command", 0,
465	&index) != TCL_OK) {
466	return TCL_ERROR;
467    }
468
469    switch (index) {
470	/* T marquee anchor ?x y?*/
471	case COMMAND_ANCHOR: {
472	    int x, y;
473
474	    if (objc != 3 && objc != 5) {
475		Tcl_WrongNumArgs(interp, 3, objv, "?x y?");
476		return TCL_ERROR;
477	    }
478	    if (objc == 3) {
479		FormatResult(interp, "%d %d", marquee->x1, marquee->y1);
480		break;
481	    }
482	    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK)
483		return TCL_ERROR;
484	    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)
485		return TCL_ERROR;
486	    if ((x == marquee->x1) && (y == marquee->y1))
487		break;
488	    TreeMarquee_Undisplay(tree->marquee);
489	    marquee->x1 = x;
490	    marquee->y1 = y;
491	    TreeMarquee_Display(tree->marquee);
492	    break;
493	}
494
495	/* T marquee cget option */
496	case COMMAND_CGET: {
497	    Tcl_Obj *resultObjPtr;
498
499	    if (objc != 4) {
500		Tcl_WrongNumArgs(interp, 3, objv, "option");
501		return TCL_ERROR;
502	    }
503	    resultObjPtr = Tk_GetOptionValue(interp, (char *) marquee,
504		marquee->optionTable, objv[3], tree->tkwin);
505	    if (resultObjPtr == NULL)
506		return TCL_ERROR;
507	    Tcl_SetObjResult(interp, resultObjPtr);
508	    break;
509	}
510
511	/* T marquee configure ?option? ?value? ?option value ...? */
512	case COMMAND_CONFIGURE: {
513	    Tcl_Obj *resultObjPtr;
514
515	    if (objc < 3) {
516		Tcl_WrongNumArgs(interp, 3, objv, "?option? ?value?");
517		return TCL_ERROR;
518	    }
519	    if (objc <= 4) {
520		resultObjPtr = Tk_GetOptionInfo(interp, (char *) marquee,
521		    marquee->optionTable,
522		    (objc == 3) ? (Tcl_Obj *) NULL : objv[3],
523		    tree->tkwin);
524		if (resultObjPtr == NULL)
525		    return TCL_ERROR;
526		Tcl_SetObjResult(interp, resultObjPtr);
527		break;
528	    }
529	    return Marquee_Config(marquee, objc - 3, objv + 3);
530	}
531
532	/* T marquee coords ?x y x y? */
533	case COMMAND_COORDS: {
534	    int x1, y1, x2, y2;
535
536	    if (objc != 3 && objc != 7) {
537		Tcl_WrongNumArgs(interp, 3, objv, "?x y x y?");
538		return TCL_ERROR;
539	    }
540	    if (objc == 3) {
541		FormatResult(interp, "%d %d %d %d", marquee->x1, marquee->y1,
542		    marquee->x2, marquee->y2);
543		break;
544	    }
545	    if (Tcl_GetIntFromObj(interp, objv[3], &x1) != TCL_OK)
546		return TCL_ERROR;
547	    if (Tcl_GetIntFromObj(interp, objv[4], &y1) != TCL_OK)
548		return TCL_ERROR;
549	    if (Tcl_GetIntFromObj(interp, objv[5], &x2) != TCL_OK)
550		return TCL_ERROR;
551	    if (Tcl_GetIntFromObj(interp, objv[6], &y2) != TCL_OK)
552		return TCL_ERROR;
553	    if (x1 == marquee->x1 && y1 == marquee->y1 &&
554		x2 == marquee->x2 && y2 == marquee->y2)
555		break;
556	    TreeMarquee_Undisplay(tree->marquee);
557	    marquee->x1 = x1;
558	    marquee->y1 = y1;
559	    marquee->x2 = x2;
560	    marquee->y2 = y2;
561	    TreeMarquee_Display(tree->marquee);
562	    break;
563	}
564
565	/* T marquee corner ?x y?*/
566	case COMMAND_CORNER: {
567	    int x, y;
568
569	    if (objc != 3 && objc != 5) {
570		Tcl_WrongNumArgs(interp, 3, objv, "?x y?");
571		return TCL_ERROR;
572	    }
573	    if (objc == 3) {
574		FormatResult(interp, "%d %d", marquee->x2, marquee->y2);
575		break;
576	    }
577	    if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK)
578		return TCL_ERROR;
579	    if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)
580		return TCL_ERROR;
581	    if (x == marquee->x2 && y == marquee->y2)
582		break;
583	    TreeMarquee_Undisplay(tree->marquee);
584	    marquee->x2 = x;
585	    marquee->y2 = y;
586	    TreeMarquee_Display(tree->marquee);
587	    break;
588	}
589
590	/* T marquee identify */
591	case COMMAND_IDENTIFY: {
592	    int x1, y1, x2, y2, n = 0;
593	    int totalWidth = Tree_TotalWidth(tree);
594	    int totalHeight = Tree_TotalHeight(tree);
595	    TreeItemList items;
596	    Tcl_Obj *listObj;
597
598	    if (objc != 3) {
599		Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL);
600		return TCL_ERROR;
601	    }
602
603	    x1 = MIN(marquee->x1, marquee->x2);
604	    x2 = MAX(marquee->x1, marquee->x2);
605	    y1 = MIN(marquee->y1, marquee->y2);
606	    y2 = MAX(marquee->y1, marquee->y2);
607
608	    if (x2 <= 0)
609		break;
610	    if (x1 >= totalWidth)
611		break;
612
613	    if (y2 <= 0)
614		break;
615	    if (y1 >= totalHeight)
616		break;
617
618	    if (x1 < 0)
619		x1 = 0;
620	    if (x2 > totalWidth)
621		x2 = totalWidth;
622
623	    if (y1 < 0)
624		y1 = 0;
625	    if (y2 > totalHeight)
626		y2 = totalHeight;
627
628	    Tree_ItemsInArea(tree, &items, x1, y1, x2, y2);
629	    if (TreeItemList_Count(&items) == 0) {
630		TreeItemList_Free(&items);
631		break;
632	    }
633
634	    listObj = Tcl_NewListObj(0, NULL);
635	    for (n = 0; n < TreeItemList_Count(&items); n++) {
636		Tcl_Obj *subListObj = Tcl_NewListObj(0, NULL);
637		TreeItem item = TreeItemList_Nth(&items, n);
638		Tcl_ListObjAppendElement(interp, subListObj,
639		    TreeItem_ToObj(tree, item));
640		TreeItem_Identify2(tree, item, x1, y1, x2, y2, subListObj);
641		Tcl_ListObjAppendElement(interp, listObj, subListObj);
642	    }
643	    TreeItemList_Free(&items);
644	    Tcl_SetObjResult(interp, listObj);
645	    break;
646	}
647    }
648
649    return TCL_OK;
650}
651