1/*
2 * shellicon.c --
3 *
4 *	This is a Tk extension that adds a new element type to TkTreeCtrl.
5 *	The element type's name is "shellicon". A shellicon element can
6 *	display the icon for a file or folder using Win32 Shell API calls.
7 *
8 * Copyright (c) 2005 Tim Baker
9 *
10 * RCS: @(#) $Id: shellicon.c,v 1.4 2007/01/31 23:20:15 treectrl Exp $
11 */
12
13#include "tkTreeCtrl.h"
14#include "tkTreeElem.h"
15#include "tkWinInt.h"
16
17#ifndef _WIN32_IE
18#define _WIN32_IE 0x0501
19#endif
20
21#include <basetyps.h>
22#include <commctrl.h>
23#include <objbase.h>
24#include <shellapi.h>
25
26#ifndef SHGFI_ADDOVERLAYS
27#if (_WIN32_IE >= 0x0500)
28#define SHGFI_ADDOVERLAYS 0x000000020
29#define SHGFI_OVERLAYINDEX 0x000000040
30#endif
31#endif
32
33#ifndef SHIL_LARGE
34#define SHIL_LARGE          0
35#define SHIL_SMALL          1
36#define SHIL_EXTRALARGE     2
37#if 0
38const GUID IID_IImageList = {0x2C247F21, 0x8591, 0x11D1,{ 0xB1, 0x6A, 0x00,0xC0, 0xF0, 0x28,0x36, 0x28} };
39HRESULT (*SHGetImageListProc)(int iImageList, REFIID riid, void **ppv);
40#endif
41#endif
42
43HIMAGELIST gImgListSmall = NULL;
44HIMAGELIST gImgListLarge = NULL;
45
46TreeCtrlStubs *stubs;
47#define TreeCtrl_RegisterElementType(i,t) \
48	stubs->TreeCtrl_RegisterElementType(i,t)
49#define Tree_RedrawElement(t,i,e) \
50	stubs->Tree_RedrawElement(t,i,e)
51#define Tree_ElementIterateBegin(t,et) \
52	stubs->Tree_ElementIterateBegin(t,et)
53#define Tree_ElementIterateNext(i) \
54	stubs->Tree_ElementIterateNext(i)
55#define Tree_ElementIterateGet(i) \
56	stubs->Tree_ElementIterateGet(i)
57#define Tree_ElementIterateChanged(i,m) \
58	stubs->Tree_ElementIterateChanged(i,m)
59#define PerStateInfo_Free(t,ty,in) \
60	stubs->PerStateInfo_Free(t,ty,in)
61#define PerStateInfo_FromObj(t,pr,ty,in) \
62	stubs->PerStateInfo_FromObj(t,pr,ty,in)
63#define PerStateInfo_ForState(t,ty,in,st,ma) \
64	stubs->PerStateInfo_ForState(t,ty,in,st,ma)
65#define PerStateInfo_ObjForState(t,ty,in,st,ma) \
66	stubs->PerStateInfo_ObjForState(t,ty,in,st,ma)
67#define PerStateInfo_Undefine(t,ty,in,st) \
68	stubs->PerStateInfo_Undefine(t,ty,in,st)
69#undef pstBoolean
70#define pstBoolean \
71	(*stubs->TreeCtrl_pstBoolean)
72#define PerStateBoolean_ForState(t,in,st,ma) \
73	stubs->PerStateBoolean_ForState(t,in,st,ma)
74#define PSTSave(in,sa) \
75	stubs->PSTSave(in,sa)
76#define PSTRestore(t,ty,in,sa) \
77	stubs->PSTRestore(t,ty,in,sa)
78#define TreeStateFromObj \
79	stubs->TreeStateFromObj
80#define BooleanCO_Init(ot,on) \
81	stubs->BooleanCO_Init(ot,on)
82#define StringTableCO_Init(ot,on,ta) \
83	stubs->StringTableCO_Init(ot,on,ta)
84#define PerStateCO_Init(a,b,c,d) \
85	stubs->PerStateCO_Init(a,b,c,d)
86
87static void AdjustForSticky(int sticky, int cavityWidth, int cavityHeight,
88    int expandX, int expandY,
89    int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)
90{
91    int dx = 0;
92    int dy = 0;
93
94    if (cavityWidth > *widthPtr) {
95	dx = cavityWidth - *widthPtr;
96    }
97
98    if (cavityHeight > *heightPtr) {
99	dy = cavityHeight - *heightPtr;
100    }
101
102    if ((sticky & STICKY_W) && (sticky & STICKY_E)) {
103	if (expandX)
104	    *widthPtr += dx;
105	else
106	    sticky &= ~(STICKY_W | STICKY_E);
107    }
108    if ((sticky & STICKY_N) && (sticky & STICKY_S)) {
109	if (expandY)
110	    *heightPtr += dy;
111	else
112	    sticky &= ~(STICKY_N | STICKY_S);
113    }
114    if (!(sticky & STICKY_W)) {
115	*xPtr += (sticky & STICKY_E) ? dx : dx / 2;
116    }
117    if (!(sticky & STICKY_N)) {
118	*yPtr += (sticky & STICKY_S) ? dy : dy / 2;
119    }
120}
121
122/* This macro gets the value of a per-state option for an element, then
123 * looks for a better match from the master element if it exists */
124#define OPTION_FOR_STATE(xFUNC,xTYPE,xVAR,xFIELD,xSTATE) \
125    xVAR = xFUNC(tree, &elemX->xFIELD, xSTATE, &match); \
126    if ((match != MATCH_EXACT) && (masterX != NULL)) { \
127	xTYPE varM = xFUNC(tree, &masterX->xFIELD, xSTATE, &match2); \
128	if (match2 > match) \
129	    xVAR = varM; \
130    }
131#define BOOLEAN_FOR_STATE(xVAR,xFIELD,xSTATE) \
132    OPTION_FOR_STATE(PerStateBoolean_ForState,int,xVAR,xFIELD,xSTATE)
133
134/* This macro gets the object for a per-state option for an element, then
135 * looks for a better match from the master element if it exists */
136#define OBJECT_FOR_STATE(xVAR,xTYPE,xFIELD,xSTATE) \
137    xVAR = PerStateInfo_ObjForState(tree, &xTYPE, &elemX->xFIELD, xSTATE, &match); \
138    if ((match != MATCH_EXACT) && (masterX != NULL)) { \
139	Tcl_Obj *objM = PerStateInfo_ObjForState(tree, &xTYPE, &masterX->xFIELD, xSTATE, &matchM); \
140	if (matchM > match) \
141	    xVAR = objM; \
142    }
143
144typedef struct ElementShellIcon ElementShellIcon;
145
146struct ElementShellIcon
147{
148    TreeElement_ header;
149    PerStateInfo draw;
150    Tcl_Obj *pathObj;		/* path of file or directory */
151    char *path;
152    Tcl_Obj *widthObj;
153    int width;
154    Tcl_Obj *heightObj;
155    int height;
156#define TYPE_DIRECTORY 0
157#define TYPE_FILE 1
158    int type;			/* If specified, 'path' is assumed to exist */
159#define SIZE_LARGE 0
160#define SIZE_SMALL 1
161    int size;			/* SIZE_LARGE if unspecified */
162    HIMAGELIST hImgList;	/* the system image list */
163    int iIcon;			/* index into hImgList */
164    int addOverlays;		/* only when useImgList is FALSE */
165    int useImgList;		/* if false, create icons */
166    HICON hIcon;		/* icon */
167    HICON hIconSel;		/* selected icon */
168};
169
170#define SHELLICON_CONF_ICON 0x0001
171#define SHELLICON_CONF_SIZE 0x0002
172#define SHELLICON_CONF_DRAW 0x0004
173
174static CONST char *sizeST[] = {
175    "large", "small", (char *) NULL
176};
177static CONST char *typeST[] = {
178    "directory", "file", (char *) NULL
179};
180
181static Tk_OptionSpec shellIconOptionSpecs[] = {
182    {TK_OPTION_CUSTOM, "-addoverlays", (char *) NULL, (char *) NULL,
183     (char *) NULL, -1, Tk_Offset(ElementShellIcon, addOverlays),
184     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON},
185    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
186     (char *) NULL, Tk_Offset(ElementShellIcon, draw.obj),
187     Tk_Offset(ElementShellIcon, draw),
188     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_DRAW},
189    {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL,
190     (char *) NULL, Tk_Offset(ElementShellIcon, heightObj),
191     Tk_Offset(ElementShellIcon, height),
192     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_SIZE},
193    {TK_OPTION_STRING, "-path", (char *) NULL, (char *) NULL,
194     (char *) NULL, Tk_Offset(ElementShellIcon, pathObj),
195     Tk_Offset(ElementShellIcon, path),
196     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON},
197    {TK_OPTION_CUSTOM, "-size",(char *) NULL, (char *) NULL,
198     (char *) NULL, -1, Tk_Offset(ElementShellIcon, size),
199     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON},
200    {TK_OPTION_CUSTOM, "-type", (char *) NULL, (char *) NULL,
201     (char *) NULL, -1, Tk_Offset(ElementShellIcon, type),
202     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON},
203    {TK_OPTION_CUSTOM, "-useimagelist", (char *) NULL, (char *) NULL,
204     (char *) NULL, -1, Tk_Offset(ElementShellIcon, useImgList),
205     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_ICON},
206    {TK_OPTION_PIXELS, "-width", (char *) NULL, (char *) NULL,
207     (char *) NULL, Tk_Offset(ElementShellIcon, widthObj),
208     Tk_Offset(ElementShellIcon, width),
209     TK_OPTION_NULL_OK, (ClientData) NULL, SHELLICON_CONF_SIZE},
210    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
211     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
212};
213
214static void LoadIconIfNeeded(TreeElementArgs *args)
215{
216    TreeCtrl *tree = args->tree;
217    TreeElement elem = args->elem;
218    ElementShellIcon *elemX = (ElementShellIcon *) elem;
219    ElementShellIcon *masterX = (ElementShellIcon *) elem->master;
220    SHFILEINFO sfi;
221    Tcl_DString dString1, dString2;
222    UINT uFlags = SHGFI_SYSICONINDEX;
223    DWORD dwFileAttributes = 0;
224    char *nativePath;
225    int size = SIZE_LARGE;
226    int overlays = 1;
227    int type = -1;
228    int useImgList = TRUE;
229    HRESULT result;
230
231    /* -useimagelist boolean */
232    if (elemX->useImgList != -1)
233	useImgList = elemX->useImgList;
234    else if (masterX != NULL && masterX->useImgList != -1)
235	useImgList = masterX->useImgList;
236
237    /* Not using the system image list. */
238    if (!useImgList) {
239
240	/* Already have an icon, or no path is given so no icon used */
241	if ((elemX->hIcon != NULL) || (elemX->path == NULL))
242	    return;
243
244	/* Equivalent to "file nativename $path" */
245	nativePath = Tcl_TranslateFileName(tree->interp, elemX->path, &dString1);
246	if (nativePath == NULL)
247	    return;
248
249	/* This will be passed to system calls, so convert from UTF8 */
250	nativePath = Tcl_UtfToExternalDString(NULL, nativePath, -1, &dString2);
251
252	uFlags = SHGFI_ICON;
253
254	/* -addoverlays boolean */
255	if (elemX->addOverlays != -1)
256	    overlays = elemX->addOverlays;
257	else if (masterX != NULL && masterX->addOverlays != -1)
258	    overlays = masterX->addOverlays;
259	if (overlays)
260	    uFlags |= SHGFI_ADDOVERLAYS;
261
262	/* -size small or large */
263	if (elemX->size != -1)
264	    size = elemX->size;
265	else if (masterX != NULL && masterX->size != -1)
266	    size = masterX->size;
267	switch (size) {
268	    case SIZE_LARGE: uFlags |= SHGFI_LARGEICON | SHGFI_SHELLICONSIZE; break;
269	    case SIZE_SMALL: uFlags |= SHGFI_SMALLICON | SHGFI_SHELLICONSIZE; break;
270	}
271
272	/* -type file or -type directory */
273	if (elemX->type != -1)
274	    type = elemX->type;
275	else if (masterX != NULL && masterX->type != -1)
276	    type = masterX->type;
277	/* If SHGFI_USEFILEATTRIBUTES is set, SHGetFileInfo is supposed to
278	 * assume that the file is real but not look for it on disk. This
279	 * can be used to get the icon for a certain type of file, ex *.exe.
280	 * In practice, lots of files get a non-generic icon when a
281	 * valid file path is given. */
282	if (type != -1) {
283	    dwFileAttributes = (type == TYPE_FILE) ?
284		FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY;
285	    uFlags |= SHGFI_USEFILEATTRIBUTES;
286	}
287
288	/* MSDN says SHGFI_OPENICON returns the image list containing the
289	 * small open icon. In practice the large open icon gets returned
290	 * for SHGFI_LARGEICON, so we support it. */
291	if (/*(size == SIZE_SMALL) && */(args->state & STATE_OPEN))
292	    uFlags |= SHGFI_OPENICON;
293
294	CoInitialize(NULL);
295
296	result = SHGetFileInfo(
297	    nativePath,
298	    dwFileAttributes,
299	    &sfi,
300	    sizeof(sfi),
301	    uFlags);
302	if (result) {
303	    elemX->hIcon = sfi.hIcon;
304
305	    /* Remember the image list so we can get the icon size */
306	    elemX->hImgList = (size == SIZE_LARGE) ? gImgListLarge : gImgListSmall;
307	}
308
309	result = SHGetFileInfo(
310	    nativePath,
311	    dwFileAttributes,
312	    &sfi,
313	    sizeof(sfi),
314	    uFlags | SHGFI_SELECTED);
315	if (result)
316	    elemX->hIconSel = sfi.hIcon;
317
318
319	CoUninitialize();
320
321	Tcl_DStringFree(&dString1);
322	Tcl_DStringFree(&dString2);
323
324	return;
325    }
326
327    /* Using the system image list */
328    if ((elemX->hImgList == NULL) && (elemX->path != NULL)) {
329
330	/* Equivalent to "file nativename $path" */
331	nativePath = Tcl_TranslateFileName(tree->interp, elemX->path, &dString1);
332	if (nativePath == NULL)
333	    return;
334
335	/* This will be passed to system calls, so convert from UTF8 */
336	nativePath = Tcl_UtfToExternalDString(NULL, nativePath, -1, &dString2);
337
338	/* -size small or large */
339	if (elemX->size != -1)
340	    size = elemX->size;
341	else if (masterX != NULL && masterX->size != -1)
342	    size = masterX->size;
343
344	switch (size) {
345	    case SIZE_SMALL: uFlags |= SHGFI_SMALLICON | SHGFI_SHELLICONSIZE; break;
346	    case SIZE_LARGE: uFlags |= SHGFI_LARGEICON | SHGFI_SHELLICONSIZE; break;
347	}
348
349	/* -type file or -type directory */
350	if (elemX->type != -1)
351	    type = elemX->type;
352	else if (masterX != NULL && masterX->type != -1)
353	    type = masterX->type;
354	/* If SHGFI_USEFILEATTRIBUTES is set, SHGetFileInfo is supposed to
355	 * assume that the file is real but not look for it on disk. This
356	 * can be used to get the icon for a certain type of file, ex *.exe.
357	 * In practice, lots of files get a non-generic icon when a
358	 * valid file path is given. */
359	if (type != -1) {
360	    dwFileAttributes = (type == TYPE_FILE) ?
361		FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY;
362	    uFlags |= SHGFI_USEFILEATTRIBUTES;
363	}
364
365	/* MSDN says SHGFI_OPENICON returns the image list containing the
366	 * small open icon. In practice the large open icon gets returned
367	 * for SHGFI_LARGEICON. */
368	if (/*(size == SIZE_SMALL) && */(args->state & STATE_OPEN))
369	    uFlags |= SHGFI_OPENICON;
370
371	CoInitialize(NULL);
372
373	elemX->hImgList = (HIMAGELIST) SHGetFileInfo(
374	    nativePath,
375	    dwFileAttributes,
376	    &sfi,
377	    sizeof(sfi),
378	    uFlags);
379	if (elemX->hImgList != NULL) {
380	    elemX->iIcon = sfi.iIcon;
381	}
382
383	CoUninitialize();
384
385	Tcl_DStringFree(&dString1);
386	Tcl_DStringFree(&dString2);
387    }
388}
389
390static void ForgetIcon(ElementShellIcon *elemX)
391{
392    if (elemX->hIcon != NULL)
393	DestroyIcon(elemX->hIcon);
394    if (elemX->hIconSel != NULL)
395	DestroyIcon(elemX->hIconSel);
396    elemX->hImgList = NULL;
397    elemX->hIcon = elemX->hIconSel = NULL;
398}
399
400static void DeleteProcShellIcon(TreeElementArgs *args)
401{
402    TreeElement elem = args->elem;
403    ElementShellIcon *elemX = (ElementShellIcon *) elem;
404
405    ForgetIcon(elemX);
406}
407
408static int WorldChangedProcShellIcon(TreeElementArgs *args)
409{
410    ElementShellIcon *elemX = (ElementShellIcon *) args->elem;
411    int flagM = args->change.flagMaster;
412    int flagS = args->change.flagSelf;
413    int mask = 0;
414
415    if ((flagS | flagM) & (SHELLICON_CONF_ICON | SHELLICON_CONF_SIZE))
416	mask |= CS_DISPLAY | CS_LAYOUT;
417    if ((flagS | flagM) & (SHELLICON_CONF_DRAW))
418	mask |= CS_DISPLAY;
419
420    if ((flagS | flagM) & SHELLICON_CONF_ICON) {
421	ForgetIcon(elemX);
422    }
423
424    return mask;
425}
426
427static int ConfigProcShellIcon(TreeElementArgs *args)
428{
429    TreeCtrl *tree = args->tree;
430    TreeElement elem = args->elem;
431    ElementShellIcon *elemX = (ElementShellIcon *) elem;
432    Tk_SavedOptions savedOptions;
433    int error;
434    Tcl_Obj *errorResult = NULL;
435
436    for (error = 0; error <= 1; error++) {
437	if (error == 0) {
438	    if (Tk_SetOptions(tree->interp, (char *) elemX,
439			elem->typePtr->optionTable,
440			args->config.objc, args->config.objv, tree->tkwin,
441			&savedOptions, &args->config.flagSelf) != TCL_OK) {
442		args->config.flagSelf = 0;
443		continue;
444	    }
445
446	    /* xxx */
447
448	    Tk_FreeSavedOptions(&savedOptions);
449	    break;
450	} else {
451	    errorResult = Tcl_GetObjResult(tree->interp);
452	    Tcl_IncrRefCount(errorResult);
453	    Tk_RestoreSavedOptions(&savedOptions);
454
455	    /* xxx */
456
457	    Tcl_SetObjResult(tree->interp, errorResult);
458	    Tcl_DecrRefCount(errorResult);
459	    return TCL_ERROR;
460	}
461    }
462
463    return TCL_OK;
464}
465
466static int CreateProcShellIcon(TreeElementArgs *args)
467{
468    ElementShellIcon *elemX = (ElementShellIcon *) args->elem;
469
470    elemX->type = -1;
471    elemX->size = -1;
472    elemX->addOverlays = -1;
473    elemX->useImgList = -1;
474
475    return TCL_OK;
476}
477
478static void DisplayProcShellIcon(TreeElementArgs *args)
479{
480    TreeCtrl *tree = args->tree;
481    TreeElement elem = args->elem;
482    ElementShellIcon *elemX = (ElementShellIcon *) elem;
483    ElementShellIcon *masterX = (ElementShellIcon *) elem->master;
484    int state = args->state;
485    int x = args->display.x, y = args->display.y;
486    int width, height;
487    int match, match2;
488    int draw;
489    HDC hDC;
490    TkWinDCState dcState;
491    UINT fStyle = ILD_TRANSPARENT;
492    HICON hIcon;
493
494    BOOLEAN_FOR_STATE(draw, draw, state)
495    if (!draw)
496	return;
497
498    LoadIconIfNeeded(args);
499
500    if (elemX->hImgList == NULL)
501	return;
502
503    (void) ImageList_GetIconSize(elemX->hImgList, &width, &height);
504    AdjustForSticky(args->display.sticky,
505	args->display.width, args->display.height,
506	FALSE, FALSE,
507	&x, &y, &width, &height);
508
509    hDC = TkWinGetDrawableDC(tree->display, args->display.drawable, &dcState);
510    hIcon = (state & STATE_SELECTED) ? elemX->hIconSel : elemX->hIcon;
511    if (hIcon != NULL) {
512#if 1
513	/* If DI_DEFAULTSIZE is used, small icons get stretched */
514	DrawIconEx(hDC, x, y, hIcon, 0, 0, FALSE, NULL, DI_IMAGE | DI_MASK /*| DI_DEFAULTSIZE*/);
515#else
516	/* Works fine for large, but not for small (they get stretched) */
517	DrawIcon(hDC, x, y, hIcon);
518#endif
519    } else {
520	if (state & STATE_SELECTED)
521	    fStyle |= ILD_SELECTED;
522	ImageList_Draw(elemX->hImgList, elemX->iIcon, hDC, x, y, fStyle);
523    }
524    TkWinReleaseDrawableDC(args->display.drawable, hDC, &dcState);
525
526    /* If this is a master element, forget the icon being used because the
527     * appearance may change between items based on the open state */
528    if (masterX == NULL) {
529	ForgetIcon(elemX);
530    }
531}
532
533static void NeededProcShellIcon(TreeElementArgs *args)
534{
535    TreeElement elem = args->elem;
536    ElementShellIcon *elemX = (ElementShellIcon *) elem;
537    ElementShellIcon *masterX = (ElementShellIcon *) elem->master;
538    int width = 0, height = 0;
539    int size = SIZE_LARGE;
540    HIMAGELIST hImgList = NULL;
541
542    /* The icon is not loaded until it is actually going to be displayed.
543     * This is a good thing since loading the icons can take a while */
544/*    LoadIconIfNeeded(args);*/
545
546    if (elemX->hImgList != NULL) {
547	hImgList = elemX->hImgList;
548
549    } else if (elemX->path != NULL) {
550	if (elemX->size != -1)
551	    size = elemX->size;
552	else if (masterX != NULL && masterX->size != -1)
553	    size = masterX->size;
554	switch (size) {
555	    case SIZE_SMALL: hImgList = gImgListSmall; break;
556	    case SIZE_LARGE: hImgList = gImgListLarge; break;
557	}
558    }
559
560    if (hImgList != NULL) {
561	(void) ImageList_GetIconSize(hImgList, &width, &height);
562    }
563
564    if (elemX->widthObj != NULL)
565	width = elemX->width;
566    else if ((masterX != NULL) && (masterX->widthObj != NULL))
567	width = masterX->width;
568
569    if (elemX->heightObj != NULL)
570	height = elemX->height;
571    else if ((masterX != NULL) && (masterX->heightObj != NULL))
572	height = masterX->height;
573
574    args->needed.width = width;
575    args->needed.height = height;
576}
577
578static int StateProcShellIcon(TreeElementArgs *args)
579{
580    TreeCtrl *tree = args->tree;
581    TreeElement elem = args->elem;
582    ElementShellIcon *elemX = (ElementShellIcon *) elem;
583    ElementShellIcon *masterX = (ElementShellIcon *) elem->master;
584    int match, match2;
585    int draw1, draw2;
586    int sel1, sel2;
587    int open1, open2;
588    int mask = 0;
589
590    BOOLEAN_FOR_STATE(draw1, draw, args->states.state1)
591    if (draw1 == -1)
592	draw1 = 1;
593    open1 = (args->states.state1 & STATE_OPEN) != 0;
594    sel1 = (args->states.state1 & STATE_SELECTED) != 0;
595
596    BOOLEAN_FOR_STATE(draw2, draw, args->states.state2)
597    if (draw2 == -1)
598	draw2 = 1;
599    open2 = (args->states.state2 & STATE_OPEN) != 0;
600    sel2 = (args->states.state2 & STATE_SELECTED) != 0;
601
602    if (elemX->path == NULL)
603	open1 = open2 = sel1 = sel2 = 0;
604
605    if ((draw1 != draw2) || (sel1 != sel2) || (open1 != open2))
606	mask |= CS_DISPLAY;
607
608    /* Directories may have an open and closed icon. */
609    if (open1 != open2) {
610	ForgetIcon(elemX);
611    }
612
613    return mask;
614}
615
616static int UndefProcShellIcon(TreeElementArgs *args)
617{
618    TreeCtrl *tree = args->tree;
619    ElementShellIcon *elemX = (ElementShellIcon *) args->elem;
620    int modified = 0;
621
622    modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, args->state);
623    return modified;
624}
625
626static int ActualProcShellIcon(TreeElementArgs *args)
627{
628    TreeCtrl *tree = args->tree;
629    ElementShellIcon *elemX = (ElementShellIcon *) args->elem;
630    ElementShellIcon *masterX = (ElementShellIcon *) args->elem->master;
631    static CONST char *optionName[] = {
632	"-draw",
633	(char *) NULL };
634    int index, match, matchM;
635    Tcl_Obj *obj = NULL;
636
637    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
638		"option", 0, &index) != TCL_OK)
639	return TCL_ERROR;
640
641    switch (index) {
642	case 0:
643	{
644	    OBJECT_FOR_STATE(obj, pstBoolean, draw, args->state)
645	    break;
646	}
647    }
648    if (obj != NULL)
649	Tcl_SetObjResult(tree->interp, obj);
650    return TCL_OK;
651}
652
653TreeElementType elemTypeShellIcon = {
654    "shellicon",
655    sizeof(ElementShellIcon),
656    shellIconOptionSpecs,
657    NULL,
658    CreateProcShellIcon,
659    DeleteProcShellIcon,
660    ConfigProcShellIcon,
661    DisplayProcShellIcon,
662    NeededProcShellIcon,
663    NULL, /* heightProc */
664    WorldChangedProcShellIcon,
665    StateProcShellIcon,
666    UndefProcShellIcon,
667    ActualProcShellIcon,
668    NULL /* onScreenProc */
669};
670
671DLLEXPORT int Shellicon_Init(Tcl_Interp *interp)
672{
673#if 1
674    SHFILEINFO sfi;
675#else
676    HMODULE hLib;
677#endif
678
679#ifdef USE_TCL_STUBS
680    if (Tcl_InitStubs(interp, "8.4", 0) == NULL) {
681	return TCL_ERROR;
682    }
683#endif
684#ifdef USE_TK_STUBS
685    if (Tk_InitStubs(interp, "8.4", 0) == NULL) {
686	return TCL_ERROR;
687    }
688#endif
689
690    /* InitCommonControlsEx must be called to use the ImageList functions */
691    /* This is already done by Tk on NT */
692    if (TkWinGetPlatformId() != VER_PLATFORM_WIN32_NT) {
693	INITCOMMONCONTROLSEX comctl;
694	ZeroMemory(&comctl, sizeof(comctl));
695	(void) InitCommonControlsEx(&comctl);
696    }
697
698#if 1
699    /* Get the sytem image lists (small and large) */
700    CoInitialize(NULL);
701    gImgListSmall = (HIMAGELIST) SHGetFileInfo(".exe", FILE_ATTRIBUTE_NORMAL, &sfi,
702	sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
703    gImgListLarge = (HIMAGELIST) SHGetFileInfo(".exe", FILE_ATTRIBUTE_NORMAL, &sfi,
704	sizeof(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
705    CoUninitialize();
706#else
707    /* FIXME: WinXP only */
708    /* This is broken somewhere */
709    hLib = LoadLibraryA("shell32");
710    SHGetImageListProc = (FARPROC) GetProcAddress(hLib, (LPCSTR) 727);
711    SHGetImageListProc(SHIL_SMALL, &IID_IImageList, &gImgListSmall);
712    SHGetImageListProc(SHIL_LARGE, &IID_IImageList, &gImgListLarge);
713    SHGetImageListProc(SHIL_EXTRALARGE, &IID_IImageList, &gImgListXtraLarge);
714    dbwin("small %p large %p xtralarge %p\n", gImgListSmall, gImgListLarge, gImgListXtraLarge);
715    FreeLibrary(hLib);
716#endif
717
718    /* Load TkTreeCtrl */
719    if (Tcl_PkgRequire(interp, "treectrl", PACKAGE_PATCHLEVEL, TRUE) == NULL)
720	return TCL_ERROR;
721
722    /* Get the stubs table from TkTreeCtrl */
723    stubs = Tcl_GetAssocData(interp, "TreeCtrlStubs", NULL);
724    if (stubs == NULL)
725	return TCL_ERROR;
726
727    /* Initialize the options table */
728    BooleanCO_Init(shellIconOptionSpecs, "-addoverlays");
729    BooleanCO_Init(shellIconOptionSpecs, "-useimagelist");
730    PerStateCO_Init(shellIconOptionSpecs, "-draw", &pstBoolean,
731	    TreeStateFromObj);
732    StringTableCO_Init(shellIconOptionSpecs, "-size", sizeST);
733    StringTableCO_Init(shellIconOptionSpecs, "-type", typeST);
734
735    /* Add the "shellicon" element type */
736    if (TreeCtrl_RegisterElementType(interp, &elemTypeShellIcon) != TCL_OK)
737	return TCL_ERROR;
738
739    if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_PATCHLEVEL) != TCL_OK) {
740	return TCL_ERROR;
741    }
742
743    return TCL_OK;
744}
745
746DLLEXPORT int Shellicon_SafeInit(Tcl_Interp *interp)
747{
748    return Shellicon_Init(interp);
749}
750
751