1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "tools/edit_gui_common.h"
30 
31 
32 #include "../../sys/win32/win_local.h"
33 #include "PropertyGrid.h"
34 
35 class rvPropertyGridItem
36 {
37 public:
38 
rvPropertyGridItem()39 	rvPropertyGridItem ( )
40 	{
41 	}
42 
43 	idStr						mName;
44 	idStr						mValue;
45 	rvPropertyGrid::EItemType	mType;
46 };
47 
48 /*
49 ================
50 rvPropertyGrid::rvPropertyGrid
51 
52 constructor
53 ================
54 */
rvPropertyGrid(void)55 rvPropertyGrid::rvPropertyGrid ( void )
56 {
57 	mWindow			= NULL;
58 	mEdit			= NULL;
59 	mListWndProc	= NULL;
60 	mSplitter		= 100;
61 	mSelectedItem	= -1;
62 	mEditItem		= -1;
63 	mState			= STATE_NORMAL;
64 }
65 
66 /*
67 ================
68 rvPropertyGrid::Create
69 
70 Create a new property grid control with the given id and parent
71 ================
72 */
Create(HWND parent,int id,int style)73 bool rvPropertyGrid::Create ( HWND parent, int id, int style )
74 {
75 	mStyle = style;
76 
77 	// Create the List view
78 	mWindow = CreateWindowEx ( 0, "LISTBOX", "", WS_VSCROLL|WS_CHILD|WS_VISIBLE|LBS_OWNERDRAWFIXED|LBS_NOINTEGRALHEIGHT|LBS_NOTIFY, 0, 0, 0, 0, parent, (HMENU)id, win32.hInstance, 0 );
79 	mListWndProc = (WNDPROC)GetWindowLong ( mWindow, GWL_WNDPROC );
80 	SetWindowLong ( mWindow, GWL_USERDATA, (LONG)this );
81 	SetWindowLong ( mWindow, GWL_WNDPROC, (LONG)WndProc );
82 
83 	LoadLibrary ( "Riched20.dll" );
84 	mEdit = CreateWindowEx ( 0, "RichEdit20A", "", WS_CHILD, 0, 0, 0, 0, mWindow, (HMENU) 999, win32.hInstance, NULL );
85 	SendMessage ( mEdit, EM_SETEVENTMASK, 0, ENM_KEYEVENTS );
86 
87 	// Set the font of the list box
88 	HDC			dc;
89 	LOGFONT		lf;
90 
91 	dc = GetDC ( mWindow );
92 	ZeroMemory ( &lf, sizeof(lf) );
93 	lf.lfHeight = -MulDiv(8, GetDeviceCaps(dc, LOGPIXELSY), 72);
94 	strcpy ( lf.lfFaceName, "MS Shell Dlg" );
95 	SendMessage ( mWindow, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
96 	SendMessage ( mEdit, WM_SETFONT, (WPARAM)CreateFontIndirect ( &lf ), 0 );
97 	ReleaseDC ( mWindow, dc );
98 
99 	RemoveAllItems ( );
100 
101 	return true;
102 }
103 
104 /*
105 ================
106 rvPropertyGrid::Move
107 
108 Move the window
109 ================
110 */
Move(int x,int y,int w,int h,BOOL redraw)111 void rvPropertyGrid::Move ( int x, int y, int w, int h, BOOL redraw )
112 {
113 	MoveWindow ( mWindow, x, y, w, h, redraw );
114 }
115 
116 /*
117 ================
118 rvPropertyGrid::StartEdit
119 
120 Start editing
121 ================
122 */
StartEdit(int item,bool label)123 void rvPropertyGrid::StartEdit ( int item, bool label )
124 {
125 	rvPropertyGridItem* gitem;
126 	RECT				rItem;
127 
128 	gitem = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, item, 0 );
129 	if ( NULL == gitem )
130 	{
131 		return;
132 	}
133 
134 	SendMessage ( mWindow, LB_GETITEMRECT, item, (LPARAM)&rItem );
135 	if ( label )
136 	{
137 		rItem.right = rItem.left + mSplitter - 1;
138 	}
139 	else
140 	{
141 		rItem.left = rItem.left + mSplitter + 1;
142 	}
143 
144 	mState = STATE_EDIT;
145 	mEditItem = item;
146 	mEditLabel = label;
147 
148 	SetWindowText ( mEdit, label?gitem->mName:gitem->mValue );
149 	MoveWindow ( mEdit, rItem.left, rItem.top + 2,
150 				rItem.right - rItem.left,
151 				rItem.bottom - rItem.top - 2, TRUE );
152 	ShowWindow ( mEdit, SW_SHOW );
153 
154 	SetFocus ( mEdit );
155 }
156 
157 /*
158 ================
159 rvPropertyGrid::FinishEdit
160 
161 Finish editing by copying the data in the edit control to the internal value
162 ================
163 */
FinishEdit(void)164 void rvPropertyGrid::FinishEdit ( void )
165 {
166 	char				value[1024];
167 	rvPropertyGridItem* item;
168 	bool				update;
169 
170 	if ( mState != STATE_EDIT )
171 	{
172 		return;
173 	}
174 
175 	assert ( mEditItem >= 0 );
176 
177 	mState = STATE_FINISHEDIT;
178 
179 	update = false;
180 	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, mEditItem, 0 );
181 	assert ( item );
182 
183 	GetWindowText ( mEdit, value, 1023 );
184 
185 	if ( !value[0] )
186 	{
187 		mState = STATE_EDIT;
188 		MessageBeep ( MB_ICONASTERISK );
189 		return;
190 	}
191 
192 	if ( !mEditLabel && item->mValue.Cmp ( value ) )
193 	{
194 		NMPROPGRID nmpg;
195 		nmpg.hdr.code = PGN_ITEMCHANGED;
196 		nmpg.hdr.hwndFrom = mWindow;
197 		nmpg.hdr.idFrom = GetWindowLong ( mWindow, GWL_ID );
198 		nmpg.mName  = item->mName;
199 		nmpg.mValue = value;
200 
201 		if ( !SendMessage ( GetParent ( mWindow ), WM_NOTIFY, 0, (LONG)&nmpg ) )
202 		{
203 			mState = STATE_EDIT;
204 			SetFocus ( mEdit );
205 			return;
206 		}
207 
208 		// The item may have been destroyed and recreated in the notify call so get it again
209 		item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, mEditItem, 0 );
210 		if ( item )
211 		{
212 			item->mValue = value;
213 			update = true;
214 		}
215 	}
216 	else if ( mEditLabel && item->mName.Cmp ( value ) )
217 	{
218 		int sel;
219 		sel = AddItem ( value, "", PGIT_STRING );
220 		SetCurSel ( sel );
221 		StartEdit ( sel, false );
222 		return;
223 	}
224 
225 	SetCurSel ( mEditItem );
226 
227 	mState = STATE_NORMAL;
228 	mEditItem = -1;
229 
230 	ShowWindow ( mEdit, SW_HIDE );
231 	SetFocus ( mWindow );
232 }
233 
234 /*
235 ================
236 rvPropertyGrid::CancelEdit
237 
238 Stop editing without saving the data
239 ================
240 */
CancelEdit(void)241 void rvPropertyGrid::CancelEdit ( void )
242 {
243 	if ( mState == STATE_EDIT && !mEditLabel )
244 	{
245 		if ( !*GetItemValue ( mEditItem ) )
246 		{
247 			RemoveItem ( mEditItem );
248 		}
249 	}
250 
251 	mSelectedItem = mEditItem;
252 	mEditItem = -1;
253 	mState = STATE_NORMAL;
254 	ShowWindow ( mEdit, SW_HIDE );
255 	SetFocus ( mWindow );
256 	SetCurSel ( mSelectedItem );
257 }
258 
259 /*
260 ================
261 rvPropertyGrid::AddItem
262 
263 Add a new item to the property grid
264 ================
265 */
AddItem(const char * name,const char * value,EItemType type)266 int rvPropertyGrid::AddItem ( const char* name, const char* value, EItemType type )
267 {
268 	rvPropertyGridItem* item;
269 	int					insert;
270 
271 	// Cant add headers if headers arent enabled
272 	if ( type == PGIT_HEADER && !(mStyle&PGS_HEADERS) )
273 	{
274 		return -1;
275 	}
276 
277 	item = new rvPropertyGridItem;
278 	item->mName = name;
279 	item->mValue = value;
280 	item->mType = type;
281 
282 	insert = SendMessage(mWindow,LB_GETCOUNT,0,0) - ((mStyle&PGS_ALLOWINSERT)?1:0);
283 
284 	return SendMessage ( mWindow, LB_INSERTSTRING, insert, (LONG)item );
285 }
286 
287 /*
288 ================
289 rvPropertyGrid::RemoveItem
290 
291 Remove the item at the given index
292 ================
293 */
RemoveItem(int index)294 void rvPropertyGrid::RemoveItem ( int index )
295 {
296 	if ( index < 0 || index >= SendMessage ( mWindow, LB_GETCOUNT, 0, 0 ) )
297 	{
298 		return;
299 	}
300 
301 	delete (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );
302 
303 	SendMessage ( mWindow, LB_DELETESTRING, index, 0 );
304 }
305 
306 /*
307 ================
308 rvPropertyGrid::RemoveAllItems
309 
310 Remove all items from the property grid
311 ================
312 */
RemoveAllItems(void)313 void rvPropertyGrid::RemoveAllItems ( void )
314 {
315 	int i;
316 
317 	// free the memory for all the items
318 	for ( i = SendMessage ( mWindow, LB_GETCOUNT, 0, 0 ); i > 0; i -- )
319 	{
320 		delete (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, i - 1, 0 );
321 	}
322 
323 	// remove all items from the listbox itself
324 	SendMessage ( mWindow, LB_RESETCONTENT, 0, 0 );
325 
326 	if ( mStyle & PGS_ALLOWINSERT )
327 	{
328 		// Add the item used to add items
329 		rvPropertyGridItem* item;
330 		item = new rvPropertyGridItem;
331 		item->mName = "";
332 		item->mValue = "";
333 		SendMessage ( mWindow, LB_ADDSTRING, 0, (LONG)item );
334 	}
335 }
336 
337 /*
338 ================
339 rvPropertyGrid::GetItemName
340 
341 Return name of item at given index
342 ================
343 */
GetItemName(int index)344 const char* rvPropertyGrid::GetItemName ( int index )
345 {
346 	rvPropertyGridItem* item;
347 
348 	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );
349 	if ( !item )
350 	{
351 		return "";
352 	}
353 
354 	return item->mName;
355 }
356 
357 /*
358 ================
359 rvPropertyGrid::GetItemValue
360 
361 Return value of item at given index
362 ================
363 */
GetItemValue(int index)364 const char* rvPropertyGrid::GetItemValue ( int index )
365 {
366 	rvPropertyGridItem* item;
367 
368 	item = (rvPropertyGridItem*)SendMessage ( mWindow, LB_GETITEMDATA, index, 0 );
369 	if ( !item )
370 	{
371 		return "";
372 	}
373 
374 	return item->mValue;
375 }
376 
377 /*
378 ================
379 rvPropertyGrid::WndProc
380 
381 Window procedure for property grid
382 ================
383 */
WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)384 LRESULT CALLBACK rvPropertyGrid::WndProc ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
385 {
386 	rvPropertyGrid* grid = (rvPropertyGrid*) GetWindowLong ( hWnd, GWL_USERDATA );
387 
388 	switch ( msg )
389 	{
390 		case WM_SETFOCUS:
391 //			grid->mEditItem = -1;
392 			break;
393 
394 		case WM_KEYDOWN:
395 		{
396 			NMKEY nmkey;
397 			nmkey.hdr.code = NM_KEYDOWN;
398 			nmkey.hdr.hwndFrom = grid->mWindow;
399 			nmkey.nVKey = wParam;
400 			nmkey.uFlags = HIWORD(lParam);
401 			nmkey.hdr.idFrom = GetWindowLong ( hWnd, GWL_ID );
402 			SendMessage ( GetParent ( hWnd ), WM_NOTIFY, nmkey.hdr.idFrom, (LPARAM)&nmkey );
403 			break;
404 		}
405 
406 		case WM_CHAR:
407 		{
408 			switch ( wParam )
409 			{
410 				case VK_RETURN:
411 					if ( grid->mSelectedItem >= 0 )
412 					{
413 						grid->StartEdit ( grid->mSelectedItem, (*grid->GetItemName ( grid->mSelectedItem ))?false:true);
414 					}
415 					break;
416 			}
417 			break;
418 		}
419 
420 		case WM_KILLFOCUS:
421 			grid->mSelectedItem = -1;
422 			break;
423 
424 		case WM_NOTIFY:
425 		{
426 			NMHDR* hdr;
427 			hdr = (NMHDR*)lParam;
428 			if ( hdr->idFrom == 999 )
429 			{
430 				if ( hdr->code == EN_MSGFILTER )
431 				{
432 					MSGFILTER* filter;
433 					filter = (MSGFILTER*)lParam;
434 					if ( filter->msg == WM_KEYDOWN )
435 					{
436 						switch ( filter->wParam )
437 						{
438 							case VK_RETURN:
439 							case VK_TAB:
440 								grid->FinishEdit ( );
441 								return 1;
442 
443 							case VK_ESCAPE:
444 								grid->CancelEdit ( );
445 								return 1;
446 						}
447 					}
448 
449 					if ( filter->msg == WM_CHAR || filter->msg == WM_KEYUP )
450 					{
451 						switch ( filter->wParam )
452 						{
453 							case VK_RETURN:
454 							case VK_TAB:
455 							case VK_ESCAPE:
456 								return 1;
457 						}
458 					}
459 				}
460 			}
461 			break;
462 		}
463 
464 		case WM_COMMAND:
465 			if ( lParam == (long)grid->mEdit )
466 			{
467 				if ( HIWORD(wParam) == EN_KILLFOCUS )
468 				{
469 					grid->FinishEdit ( );
470 					return true;
471 				}
472 			}
473 			break;
474 
475 		case WM_LBUTTONDBLCLK:
476 			grid->mSelectedItem = SendMessage ( hWnd, LB_ITEMFROMPOINT, 0, lParam );
477 
478 			// fall through
479 
480 		case WM_LBUTTONDOWN:
481 		{
482 			int					item;
483 			rvPropertyGridItem* gitem;
484 			RECT				rItem;
485 			POINT				pt;
486 
487 			if ( grid->mState == rvPropertyGrid::STATE_EDIT )
488 			{
489 				break;
490 			}
491 
492 			item  = (short)LOWORD(SendMessage ( hWnd, LB_ITEMFROMPOINT, 0, lParam ));
493 			if ( item == -1 )
494 			{
495 				break;
496 			}
497 
498 			gitem = (rvPropertyGridItem*)SendMessage ( hWnd, LB_GETITEMDATA, item, 0 );
499 			pt.x  = LOWORD(lParam);
500 			pt.y  = HIWORD(lParam);
501 
502 			SendMessage ( hWnd, LB_GETITEMRECT, item, (LPARAM)&rItem );
503 
504 			if ( !gitem->mName.Icmp ( "" ) )
505 			{
506 				rItem.right = rItem.left + grid->mSplitter - 1;
507 				if ( PtInRect ( &rItem, pt) )
508 				{
509 					grid->SetCurSel ( item );
510 					grid->StartEdit ( item, true );
511 				}
512 			}
513 			else if ( grid->mSelectedItem == item )
514 			{
515 				rItem.left = rItem.left + grid->mSplitter + 1;
516 				if ( PtInRect ( &rItem, pt) )
517 				{
518 					grid->StartEdit ( item, false );
519 				}
520 			}
521 
522 			if ( grid->mState == rvPropertyGrid::STATE_EDIT )
523 			{
524 				ClientToScreen ( hWnd, &pt );
525 				ScreenToClient ( grid->mEdit, &pt );
526 				SendMessage ( grid->mEdit, WM_LBUTTONDOWN, wParam, MAKELONG(pt.x,pt.y) );
527 				return 0;
528 			}
529 
530 			break;
531 		}
532 
533 		case WM_ERASEBKGND:
534 		{
535 			RECT rClient;
536 			GetClientRect ( hWnd, &rClient );
537 			FillRect ( (HDC)wParam, &rClient, GetSysColorBrush ( COLOR_3DFACE ) );
538 			return TRUE;
539 		}
540 
541 		case WM_SETCURSOR:
542 		{
543 			POINT point;
544 			GetCursorPos ( &point );
545 			ScreenToClient ( hWnd, &point );
546 			if ( point.x >= grid->mSplitter - 2 && point.x <= grid->mSplitter + 2 )
547 			{
548 				SetCursor ( LoadCursor ( NULL, MAKEINTRESOURCE(IDC_SIZEWE)));
549 				return TRUE;
550 			}
551 			break;
552 		}
553 	}
554 
555 	return CallWindowProc ( grid->mListWndProc, hWnd, msg, wParam, lParam );
556 }
557 
558 /*
559 ================
560 rvPropertyGrid::ReflectMessage
561 
562 Handle messages sent to the parent window
563 ================
564 */
ReflectMessage(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)565 bool rvPropertyGrid::ReflectMessage ( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
566 {
567 	switch ( msg )
568 	{
569 		case WM_COMMAND:
570 		{
571 			if ( (HWND)lParam == mWindow )
572 			{
573 				switch ( HIWORD(wParam) )
574 				{
575 					case LBN_SELCHANGE:
576 						mSelectedItem = SendMessage ( mWindow, LB_GETCURSEL, 0, 0 );
577 						break;
578 				}
579 			}
580 			break;
581 		}
582 
583 		case WM_DRAWITEM:
584 			HandleDrawItem ( wParam, lParam );
585 			return true;
586 
587 		case WM_MEASUREITEM:
588 		{
589 			MEASUREITEMSTRUCT* mis = (MEASUREITEMSTRUCT*) lParam;
590 			mis->itemHeight = 18;
591 			return true;
592 		}
593 	}
594 
595 	return false;
596 }
597 
598 /*
599 ================
600 rvPropertyGrid::HandleDrawItem
601 
602 Handle the draw item message
603 ================
604 */
HandleDrawItem(WPARAM wParam,LPARAM lParam)605 int rvPropertyGrid::HandleDrawItem ( WPARAM wParam, LPARAM lParam )
606 {
607 	DRAWITEMSTRUCT*		dis  = (DRAWITEMSTRUCT*) lParam;
608 	rvPropertyGridItem* item = (rvPropertyGridItem*) dis->itemData;
609 	RECT				rTemp;
610 	HBRUSH				brush;
611 
612 	if ( !item )
613 	{
614 		return 0;
615 	}
616 
617 	rTemp = dis->rcItem;
618 	if ( mStyle & PGS_HEADERS )
619 	{
620 		brush = GetSysColorBrush ( COLOR_SCROLLBAR );
621 		rTemp.right = rTemp.left + 10;
622 		FillRect ( dis->hDC, &rTemp, brush );
623 		rTemp.left = rTemp.right;
624 		rTemp.right = dis->rcItem.right;
625 	}
626 
627 	if ( item->mType == PGIT_HEADER )
628 	{
629 		brush = GetSysColorBrush ( COLOR_SCROLLBAR );
630 	}
631 	else if ( dis->itemState & ODS_SELECTED )
632 	{
633 		brush = GetSysColorBrush ( COLOR_HIGHLIGHT );
634 	}
635 	else
636 	{
637 		brush = GetSysColorBrush ( COLOR_WINDOW );
638 	}
639 
640 	FillRect ( dis->hDC, &rTemp, brush );
641 
642 	HPEN pen = CreatePen ( PS_SOLID, 1, GetSysColor ( COLOR_SCROLLBAR ) );
643 	HPEN oldpen = (HPEN)SelectObject ( dis->hDC, pen );
644 	MoveToEx ( dis->hDC, dis->rcItem.left, dis->rcItem.top, NULL );
645 	LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.top );
646 	MoveToEx ( dis->hDC, dis->rcItem.left, dis->rcItem.bottom, NULL );
647 	LineTo ( dis->hDC, dis->rcItem.right, dis->rcItem.bottom);
648 
649 	if ( item->mType != PGIT_HEADER )
650 	{
651 		MoveToEx ( dis->hDC, dis->rcItem.left + mSplitter, dis->rcItem.top, NULL );
652 		LineTo ( dis->hDC, dis->rcItem.left + mSplitter, dis->rcItem.bottom );
653 	}
654 	SelectObject ( dis->hDC, oldpen );
655 	DeleteObject ( pen );
656 
657 	int colorIndex = ( (dis->itemState & ODS_SELECTED ) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT );
658 	SetTextColor ( dis->hDC, GetSysColor ( colorIndex ) );
659 	SetBkMode ( dis->hDC, TRANSPARENT );
660 	SetBkColor ( dis->hDC, GetSysColor ( COLOR_3DFACE ) );
661 
662 	RECT rText;
663 	rText = rTemp;
664 	rText.right = rText.left + mSplitter;
665 	rText.left += 2;
666 
667 	DrawText ( dis->hDC, item->mName, item->mName.Length(), &rText, DT_LEFT|DT_VCENTER|DT_SINGLELINE );
668 
669 	rText.left = dis->rcItem.left + mSplitter + 2;
670 	rText.right = dis->rcItem.right;
671 	DrawText ( dis->hDC, item->mValue, item->mValue.Length(), &rText, DT_LEFT|DT_VCENTER|DT_SINGLELINE );
672 
673 	return 0;
674 }