ListWindow.cpp (16477B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition 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 BFG Edition 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 #pragma hdrstop 30 #include "../idlib/precompiled.h" 31 32 #include "DeviceContext.h" 33 #include "Window.h" 34 #include "UserInterfaceLocal.h" 35 #include "SliderWindow.h" 36 #include "ListWindow.h" 37 38 // Number of pixels above the text that the rect starts 39 static const int pixelOffset = 3; 40 41 // number of pixels between columns 42 static const int tabBorder = 4; 43 44 // Time in milliseconds between clicks to register as a double-click 45 static const int doubleClickSpeed = 300; 46 47 void idListWindow::CommonInit() { 48 typed = ""; 49 typedTime = 0; 50 clickTime = 0; 51 currentSel.Clear(); 52 top = 0; 53 sizeBias = 0; 54 horizontal = false; 55 scroller = new (TAG_OLD_UI) idSliderWindow(gui); 56 multipleSel = false; 57 } 58 59 idListWindow::idListWindow(idUserInterfaceLocal *g) : idWindow(g) { 60 gui = g; 61 CommonInit(); 62 } 63 64 void idListWindow::SetCurrentSel( int sel ) { 65 currentSel.Clear(); 66 currentSel.Append( sel ); 67 } 68 69 void idListWindow::ClearSelection( int sel ) { 70 int cur = currentSel.FindIndex( sel ); 71 if ( cur >= 0 ) { 72 currentSel.RemoveIndex( cur ); 73 } 74 } 75 76 void idListWindow::AddCurrentSel( int sel ) { 77 currentSel.Append( sel ); 78 } 79 80 int idListWindow::GetCurrentSel() { 81 return ( currentSel.Num() ) ? currentSel[0] : 0; 82 } 83 84 bool idListWindow::IsSelected( int index ) { 85 return ( currentSel.FindIndex( index ) >= 0 ); 86 } 87 88 const char *idListWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) { 89 // need to call this to allow proper focus and capturing on embedded children 90 const char *ret = idWindow::HandleEvent(event, updateVisuals); 91 92 float vert = GetMaxCharHeight(); 93 int numVisibleLines = textRect.h / vert; 94 95 int key = event->evValue; 96 97 if ( event->evType == SE_KEY ) { 98 if ( !event->evValue2 ) { 99 // We only care about key down, not up 100 return ret; 101 } 102 103 if ( key == K_MOUSE1 || key == K_MOUSE2 ) { 104 // If the user clicked in the scroller, then ignore it 105 if ( scroller->Contains(gui->CursorX(), gui->CursorY()) ) { 106 return ret; 107 } 108 } 109 110 if ( ( key == K_ENTER || key == K_KP_ENTER ) ) { 111 RunScript( ON_ENTER ); 112 return cmd; 113 } 114 115 if ( key == K_MWHEELUP ) { 116 key = K_UPARROW; 117 } else if ( key == K_MWHEELDOWN ) { 118 key = K_DOWNARROW; 119 } 120 121 if ( key == K_MOUSE1) { 122 if (Contains(gui->CursorX(), gui->CursorY())) { 123 int cur = ( int )( ( gui->CursorY() - actualY - pixelOffset ) / vert ) + top; 124 if ( cur >= 0 && cur < listItems.Num() ) { 125 if ( multipleSel && ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) ) { 126 if ( IsSelected( cur ) ) { 127 ClearSelection( cur ); 128 } else { 129 AddCurrentSel( cur ); 130 } 131 } else { 132 if ( IsSelected( cur ) && ( gui->GetTime() < clickTime + doubleClickSpeed ) ) { 133 // Double-click causes ON_ENTER to get run 134 RunScript(ON_ENTER); 135 return cmd; 136 } 137 SetCurrentSel( cur ); 138 139 clickTime = gui->GetTime(); 140 } 141 } else { 142 SetCurrentSel( listItems.Num() - 1 ); 143 } 144 } 145 } else if ( key == K_UPARROW || key == K_PGUP || key == K_DOWNARROW || key == K_PGDN ) { 146 int numLines = 1; 147 148 if ( key == K_PGUP || key == K_PGDN ) { 149 numLines = numVisibleLines / 2; 150 } 151 152 if ( key == K_UPARROW || key == K_PGUP ) { 153 numLines = -numLines; 154 } 155 156 if ( idKeyInput::IsDown( K_LCTRL ) || idKeyInput::IsDown( K_RCTRL ) ) { 157 top += numLines; 158 } else { 159 SetCurrentSel( GetCurrentSel() + numLines ); 160 } 161 } else { 162 return ret; 163 } 164 } else if ( event->evType == SE_CHAR ) { 165 if ( !idStr::CharIsPrintable(key) ) { 166 return ret; 167 } 168 169 if ( gui->GetTime() > typedTime + 1000 ) { 170 typed = ""; 171 } 172 typedTime = gui->GetTime(); 173 typed.Append( key ); 174 175 for ( int i=0; i<listItems.Num(); i++ ) { 176 if ( idStr::Icmpn( typed, listItems[i], typed.Length() ) == 0 ) { 177 SetCurrentSel( i ); 178 break; 179 } 180 } 181 182 } else { 183 return ret; 184 } 185 186 if ( GetCurrentSel() < 0 ) { 187 SetCurrentSel( 0 ); 188 } 189 190 if ( GetCurrentSel() >= listItems.Num() ) { 191 SetCurrentSel( listItems.Num() - 1 ); 192 } 193 194 if ( scroller->GetHigh() > 0.0f ) { 195 if ( !idKeyInput::IsDown( K_LCTRL ) && !idKeyInput::IsDown( K_RCTRL ) ) { 196 if ( top > GetCurrentSel() - 1 ) { 197 top = GetCurrentSel() - 1; 198 } 199 if ( top < GetCurrentSel() - numVisibleLines + 2 ) { 200 top = GetCurrentSel() - numVisibleLines + 2; 201 } 202 } 203 204 if ( top > listItems.Num() - 2 ) { 205 top = listItems.Num() - 2; 206 } 207 if ( top < 0 ) { 208 top = 0; 209 } 210 scroller->SetValue(top); 211 } else { 212 top = 0; 213 scroller->SetValue(0.0f); 214 } 215 216 if ( key != K_MOUSE1 ) { 217 // Send a fake mouse click event so onAction gets run in our parents 218 const sysEvent_t ev = sys->GenerateMouseButtonEvent( 1, true ); 219 idWindow::HandleEvent(&ev, updateVisuals); 220 } 221 222 if ( currentSel.Num() > 0 ) { 223 for ( int i = 0; i < currentSel.Num(); i++ ) { 224 gui->SetStateInt( va( "%s_sel_%i", listName.c_str(), i ), currentSel[i] ); 225 } 226 } else { 227 gui->SetStateInt( va( "%s_sel_0", listName.c_str() ), 0 ); 228 } 229 gui->SetStateInt( va( "%s_numsel", listName.c_str() ), currentSel.Num() ); 230 231 return ret; 232 } 233 234 235 bool idListWindow::ParseInternalVar(const char *_name, idTokenParser *src) { 236 if (idStr::Icmp(_name, "horizontal") == 0) { 237 horizontal = src->ParseBool(); 238 return true; 239 } 240 if (idStr::Icmp(_name, "listname") == 0) { 241 ParseString(src, listName); 242 return true; 243 } 244 if (idStr::Icmp(_name, "tabstops") == 0) { 245 ParseString(src, tabStopStr); 246 return true; 247 } 248 if (idStr::Icmp(_name, "tabaligns") == 0) { 249 ParseString(src, tabAlignStr); 250 return true; 251 } 252 if (idStr::Icmp(_name, "multipleSel") == 0) { 253 multipleSel = src->ParseBool(); 254 return true; 255 } 256 if(idStr::Icmp(_name, "tabvaligns") == 0) { 257 ParseString(src, tabVAlignStr); 258 return true; 259 } 260 if(idStr::Icmp(_name, "tabTypes") == 0) { 261 ParseString(src, tabTypeStr); 262 return true; 263 } 264 if(idStr::Icmp(_name, "tabIconSizes") == 0) { 265 ParseString(src, tabIconSizeStr); 266 return true; 267 } 268 if(idStr::Icmp(_name, "tabIconVOffset") == 0) { 269 ParseString(src, tabIconVOffsetStr); 270 return true; 271 } 272 273 idStr strName = _name; 274 if(idStr::Icmp(strName.Left(4), "mtr_") == 0) { 275 idStr matName; 276 const idMaterial* mat; 277 278 ParseString(src, matName); 279 mat = declManager->FindMaterial(matName); 280 if ( mat != NULL && !mat->TestMaterialFlag( MF_DEFAULTED ) ) { 281 mat->SetSort(SS_GUI ); 282 } 283 iconMaterials.Set(_name, mat); 284 return true; 285 } 286 287 return idWindow::ParseInternalVar(_name, src); 288 } 289 290 idWinVar *idListWindow::GetWinVarByName(const char *_name, bool fixup, drawWin_t** owner) { 291 return idWindow::GetWinVarByName(_name, fixup, owner); 292 } 293 294 void idListWindow::PostParse() { 295 idWindow::PostParse(); 296 297 InitScroller(horizontal); 298 299 idList<int> tabStops; 300 idList<int> tabAligns; 301 if (tabStopStr.Length()) { 302 idParser src(tabStopStr, tabStopStr.Length(), "tabstops", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 303 idToken tok; 304 while (src.ReadToken(&tok)) { 305 if (tok == ",") { 306 continue; 307 } 308 tabStops.Append(atoi(tok)); 309 } 310 } 311 if (tabAlignStr.Length()) { 312 idParser src(tabAlignStr, tabAlignStr.Length(), "tabaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 313 idToken tok; 314 while (src.ReadToken(&tok)) { 315 if (tok == ",") { 316 continue; 317 } 318 tabAligns.Append(atoi(tok)); 319 } 320 } 321 idList<int> tabVAligns; 322 if (tabVAlignStr.Length()) { 323 idParser src(tabVAlignStr, tabVAlignStr.Length(), "tabvaligns", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 324 idToken tok; 325 while (src.ReadToken(&tok)) { 326 if (tok == ",") { 327 continue; 328 } 329 tabVAligns.Append(atoi(tok)); 330 } 331 } 332 333 idList<int> tabTypes; 334 if (tabTypeStr.Length()) { 335 idParser src(tabTypeStr, tabTypeStr.Length(), "tabtypes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 336 idToken tok; 337 while (src.ReadToken(&tok)) { 338 if (tok == ",") { 339 continue; 340 } 341 tabTypes.Append(atoi(tok)); 342 } 343 } 344 idList<idVec2> tabSizes; 345 if (tabIconSizeStr.Length()) { 346 idParser src(tabIconSizeStr, tabIconSizeStr.Length(), "tabiconsizes", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 347 idToken tok; 348 while (src.ReadToken(&tok)) { 349 if (tok == ",") { 350 continue; 351 } 352 idVec2 size; 353 size.x = atoi(tok); 354 355 src.ReadToken(&tok); //"," 356 src.ReadToken(&tok); 357 358 size.y = atoi(tok); 359 tabSizes.Append(size); 360 } 361 } 362 363 idList<float> tabIconVOffsets; 364 if (tabIconVOffsetStr.Length()) { 365 idParser src(tabIconVOffsetStr, tabIconVOffsetStr.Length(), "tabiconvoffsets", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS); 366 idToken tok; 367 while (src.ReadToken(&tok)) { 368 if (tok == ",") { 369 continue; 370 } 371 tabIconVOffsets.Append(atof(tok)); 372 } 373 } 374 375 int c = tabStops.Num(); 376 bool doAligns = (tabAligns.Num() == tabStops.Num()); 377 for (int i = 0; i < c; i++) { 378 idTabRect r; 379 r.x = tabStops[i]; 380 r.w = (i < c - 1) ? tabStops[i+1] - r.x - tabBorder : -1; 381 r.align = (doAligns) ? tabAligns[i] : 0; 382 if(tabVAligns.Num() > 0) { 383 r.valign = tabVAligns[i]; 384 } else { 385 r.valign = 0; 386 } 387 if(tabTypes.Num() > 0) { 388 r.type = tabTypes[i]; 389 } else { 390 r.type = TAB_TYPE_TEXT; 391 } 392 if(tabSizes.Num() > 0) { 393 r.iconSize = tabSizes[i]; 394 } else { 395 r.iconSize.Zero(); 396 } 397 if(tabIconVOffsets.Num() > 0 ) { 398 r.iconVOffset = tabIconVOffsets[i]; 399 } else { 400 r.iconVOffset = 0; 401 } 402 tabInfo.Append(r); 403 } 404 flags |= WIN_CANFOCUS; 405 } 406 407 /* 408 ================ 409 idListWindow::InitScroller 410 411 This is the same as in idEditWindow 412 ================ 413 */ 414 void idListWindow::InitScroller( bool horizontal ) 415 { 416 const char *thumbImage = "guis/assets/scrollbar_thumb.tga"; 417 const char *barImage = "guis/assets/scrollbarv.tga"; 418 const char *scrollerName = "_scrollerWinV"; 419 420 if (horizontal) { 421 barImage = "guis/assets/scrollbarh.tga"; 422 scrollerName = "_scrollerWinH"; 423 } 424 425 const idMaterial *mat = declManager->FindMaterial( barImage ); 426 mat->SetSort( SS_GUI ); 427 sizeBias = mat->GetImageWidth(); 428 429 idRectangle scrollRect; 430 if (horizontal) { 431 sizeBias = mat->GetImageHeight(); 432 scrollRect.x = 0; 433 scrollRect.y = (clientRect.h - sizeBias); 434 scrollRect.w = clientRect.w; 435 scrollRect.h = sizeBias; 436 } else { 437 scrollRect.x = (clientRect.w - sizeBias); 438 scrollRect.y = 0; 439 scrollRect.w = sizeBias; 440 scrollRect.h = clientRect.h; 441 } 442 443 scroller->InitWithDefaults(scrollerName, scrollRect, foreColor, matColor, mat->GetName(), thumbImage, !horizontal, true); 444 InsertChild(scroller, NULL); 445 scroller->SetBuddy(this); 446 } 447 448 void idListWindow::Draw(int time, float x, float y) { 449 idVec4 color; 450 idStr work; 451 int count = listItems.Num(); 452 idRectangle rect = textRect; 453 float scale = textScale; 454 float lineHeight = GetMaxCharHeight(); 455 456 float bottom = textRect.Bottom(); 457 float width = textRect.w; 458 459 if ( scroller->GetHigh() > 0.0f ) { 460 if ( horizontal ) { 461 bottom -= sizeBias; 462 } else { 463 width -= sizeBias; 464 rect.w = width; 465 } 466 } 467 468 if ( noEvents || !Contains(gui->CursorX(), gui->CursorY()) ) { 469 hover = false; 470 } 471 472 for (int i = top; i < count; i++) { 473 if ( IsSelected( i ) ) { 474 rect.h = lineHeight; 475 dc->DrawFilledRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, borderColor); 476 if ( flags & WIN_FOCUS ) { 477 idVec4 color = borderColor; 478 color.w = 1.0f; 479 dc->DrawRect(rect.x, rect.y + pixelOffset, rect.w, rect.h, 1.0f, color ); 480 } 481 } 482 rect.y ++; 483 rect.h = lineHeight - 1; 484 if ( hover && !noEvents && Contains(rect, gui->CursorX(), gui->CursorY()) ) { 485 color = hoverColor; 486 } else { 487 color = foreColor; 488 } 489 rect.h = lineHeight + pixelOffset; 490 rect.y --; 491 492 if ( tabInfo.Num() > 0 ) { 493 int start = 0; 494 int tab = 0; 495 int stop = listItems[i].Find('\t', 0); 496 while ( start < listItems[i].Length() ) { 497 if ( tab >= tabInfo.Num() ) { 498 common->Warning( "idListWindow::Draw: gui '%s' window '%s' tabInfo.Num() exceeded", gui->GetSourceFile(), name.c_str() ); 499 break; 500 } 501 listItems[i].Mid(start, stop - start, work); 502 503 rect.x = textRect.x + tabInfo[tab].x; 504 rect.w = (tabInfo[tab].w == -1) ? width - tabInfo[tab].x : tabInfo[tab].w; 505 dc->PushClipRect( rect ); 506 507 if ( tabInfo[tab].type == TAB_TYPE_TEXT ) { 508 dc->DrawText(work, scale, tabInfo[tab].align, color, rect, false, -1); 509 } else if (tabInfo[tab].type == TAB_TYPE_ICON) { 510 511 const idMaterial **hashMat; 512 const idMaterial *iconMat; 513 514 // leaving the icon name empty doesn't draw anything 515 if ( work[0] != '\0' ) { 516 517 if ( iconMaterials.Get(work, &hashMat) == false ) { 518 iconMat = declManager->FindMaterial("_default"); 519 } else { 520 iconMat = *hashMat; 521 } 522 523 idRectangle iconRect; 524 iconRect.w = tabInfo[tab].iconSize.x; 525 iconRect.h = tabInfo[tab].iconSize.y; 526 527 if(tabInfo[tab].align == idDeviceContext::ALIGN_LEFT) { 528 iconRect.x = rect.x; 529 } else if (tabInfo[tab].align == idDeviceContext::ALIGN_CENTER) { 530 iconRect.x = rect.x + rect.w/2.0f - iconRect.w/2.0f; 531 } else if (tabInfo[tab].align == idDeviceContext::ALIGN_RIGHT) { 532 iconRect.x = rect.x + rect.w - iconRect.w; 533 } 534 535 if(tabInfo[tab].valign == 0) { //Top 536 iconRect.y = rect.y + tabInfo[tab].iconVOffset; 537 } else if(tabInfo[tab].valign == 1) { //Center 538 iconRect.y = rect.y + rect.h/2.0f - iconRect.h/2.0f + tabInfo[tab].iconVOffset; 539 } else if(tabInfo[tab].valign == 2) { //Bottom 540 iconRect.y = rect.y + rect.h - iconRect.h + tabInfo[tab].iconVOffset; 541 } 542 543 dc->DrawMaterial(iconRect.x, iconRect.y, iconRect.w, iconRect.h, iconMat, idVec4(1.0f,1.0f,1.0f,1.0f), 1.0f, 1.0f); 544 545 } 546 } 547 548 dc->PopClipRect(); 549 550 start = stop + 1; 551 stop = listItems[i].Find('\t', start); 552 if ( stop < 0 ) { 553 stop = listItems[i].Length(); 554 } 555 tab++; 556 } 557 rect.x = textRect.x; 558 rect.w = width; 559 } else { 560 dc->DrawText(listItems[i], scale, 0, color, rect, false, -1); 561 } 562 rect.y += lineHeight; 563 if ( rect.y > bottom ) { 564 break; 565 } 566 } 567 } 568 569 void idListWindow::Activate(bool activate, idStr &act) { 570 idWindow::Activate(activate, act); 571 572 if ( activate ) { 573 UpdateList(); 574 } 575 } 576 577 void idListWindow::HandleBuddyUpdate(idWindow *buddy) { 578 top = scroller->GetValue(); 579 } 580 581 void idListWindow::UpdateList() { 582 idStr str, strName; 583 listItems.Clear(); 584 for (int i = 0; i < MAX_LIST_ITEMS; i++) { 585 if (gui->State().GetString( va("%s_item_%i", listName.c_str(), i), "", str) ) { 586 if ( str.Length() ) { 587 listItems.Append(str); 588 } 589 } else { 590 break; 591 } 592 } 593 float vert = GetMaxCharHeight(); 594 int fit = textRect.h / vert; 595 if ( listItems.Num() < fit ) { 596 scroller->SetRange(0.0f, 0.0f, 1.0f); 597 } else { 598 scroller->SetRange(0.0f, (listItems.Num() - fit) + 1.0f, 1.0f); 599 } 600 601 SetCurrentSel( gui->State().GetInt( va( "%s_sel_0", listName.c_str() ) ) ); 602 603 float value = scroller->GetValue(); 604 if ( value > listItems.Num() - 1 ) { 605 value = listItems.Num() - 1; 606 } 607 if ( value < 0.0f ) { 608 value = 0.0f; 609 } 610 scroller->SetValue(value); 611 top = value; 612 613 typedTime = 0; 614 clickTime = 0; 615 typed = ""; 616 } 617 618 void idListWindow::StateChanged( bool redraw ) { 619 UpdateList(); 620 } 621