2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file network_content_gui.cpp Implementation of the Network Content related GUIs. */
10 #include "../stdafx.h"
11 #include "../strings_func.h"
12 #include "../gfx_func.h"
13 #include "../window_func.h"
15 #include "../ai/ai.hpp"
16 #include "../game/game.hpp"
17 #include "../base_media_base.h"
18 #include "../openttd.h"
19 #include "../sortlist_type.h"
20 #include "../stringfilter_type.h"
21 #include "../querystring_gui.h"
22 #include "../core/geometry_func.hpp"
23 #include "../textfile_gui.h"
24 #include "network_content_gui.h"
27 #include "table/strings.h"
28 #include "../table/sprites.h"
32 #include "../safeguards.h"
35 /** Whether the user accepted to enter external websites during this session. */
36 static bool _accepted_external_search
= false;
39 /** Window for displaying the textfile of an item in the content list. */
40 struct ContentTextfileWindow
: public TextfileWindow
{
41 const ContentInfo
*ci
; ///< View the textfile of this ContentInfo.
43 ContentTextfileWindow(TextfileType file_type
, const ContentInfo
*ci
) : TextfileWindow(file_type
), ci(ci
)
45 const char *textfile
= this->ci
->GetTextfile(file_type
);
46 this->LoadTextfile(textfile
, GetContentInfoSubDir(this->ci
->type
));
49 StringID
GetTypeString() const
51 switch (this->ci
->type
) {
52 case CONTENT_TYPE_NEWGRF
: return STR_CONTENT_TYPE_NEWGRF
;
53 case CONTENT_TYPE_BASE_GRAPHICS
: return STR_CONTENT_TYPE_BASE_GRAPHICS
;
54 case CONTENT_TYPE_BASE_SOUNDS
: return STR_CONTENT_TYPE_BASE_SOUNDS
;
55 case CONTENT_TYPE_BASE_MUSIC
: return STR_CONTENT_TYPE_BASE_MUSIC
;
56 case CONTENT_TYPE_AI
: return STR_CONTENT_TYPE_AI
;
57 case CONTENT_TYPE_AI_LIBRARY
: return STR_CONTENT_TYPE_AI_LIBRARY
;
58 case CONTENT_TYPE_GAME
: return STR_CONTENT_TYPE_GAME_SCRIPT
;
59 case CONTENT_TYPE_GAME_LIBRARY
: return STR_CONTENT_TYPE_GS_LIBRARY
;
60 case CONTENT_TYPE_SCENARIO
: return STR_CONTENT_TYPE_SCENARIO
;
61 case CONTENT_TYPE_HEIGHTMAP
: return STR_CONTENT_TYPE_HEIGHTMAP
;
62 default: NOT_REACHED();
66 void SetStringParameters(int widget
) const override
68 if (widget
== WID_TF_CAPTION
) {
69 SetDParam(0, this->GetTypeString());
70 SetDParamStr(1, this->ci
->name
);
75 void ShowContentTextfileWindow(TextfileType file_type
, const ContentInfo
*ci
)
77 CloseWindowById(WC_TEXTFILE
, file_type
);
78 new ContentTextfileWindow(file_type
, ci
);
81 /** Nested widgets for the download window. */
82 static const NWidgetPart _nested_network_content_download_status_window_widgets
[] = {
83 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
84 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_NCDS_BACKGROUND
),
85 NWidget(NWID_SPACER
), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
+ 30),
86 NWidget(NWID_HORIZONTAL
),
87 NWidget(NWID_SPACER
), SetMinimalSize(125, 0),
88 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCDS_CANCELOK
), SetMinimalSize(101, 12), SetDataTip(STR_BUTTON_CANCEL
, STR_NULL
),
89 NWidget(NWID_SPACER
), SetFill(1, 0),
91 NWidget(NWID_SPACER
), SetMinimalSize(0, 4),
95 /** Window description for the download window */
96 static WindowDesc
_network_content_download_status_window_desc(
97 WDP_CENTER
, nullptr, 0, 0,
98 WC_NETWORK_STATUS_WINDOW
, WC_NONE
,
100 _nested_network_content_download_status_window_widgets
, lengthof(_nested_network_content_download_status_window_widgets
)
103 BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(WindowDesc
*desc
) :
104 Window(desc
), cur_id(UINT32_MAX
)
106 _network_content_client
.AddCallback(this);
107 _network_content_client
.DownloadSelectedContent(this->total_files
, this->total_bytes
);
109 this->InitNested(WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD
);
112 void BaseNetworkContentDownloadStatusWindow::Close()
114 _network_content_client
.RemoveCallback(this);
115 this->Window::Close();
118 void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect
&r
, int widget
) const
120 if (widget
!= WID_NCDS_BACKGROUND
) return;
122 /* Draw nice progress bar :) */
123 DrawFrameRect(r
.left
+ 20, r
.top
+ 4, r
.left
+ 20 + (int)((this->width
- 40LL) * this->downloaded_bytes
/ this->total_bytes
), r
.top
+ 14, COLOUR_MAUVE
, FR_NONE
);
126 SetDParam(0, this->downloaded_bytes
);
127 SetDParam(1, this->total_bytes
);
128 SetDParam(2, this->downloaded_bytes
* 100LL / this->total_bytes
);
129 DrawString(r
.left
+ 2, r
.right
- 2, y
, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE
, TC_FROMSTRING
, SA_HOR_CENTER
);
132 if (this->downloaded_bytes
== this->total_bytes
) {
133 str
= STR_CONTENT_DOWNLOAD_COMPLETE
;
134 } else if (!this->name
.empty()) {
135 SetDParamStr(0, this->name
);
136 SetDParam(1, this->downloaded_files
);
137 SetDParam(2, this->total_files
);
138 str
= STR_CONTENT_DOWNLOAD_FILE
;
140 str
= STR_CONTENT_DOWNLOAD_INITIALISE
;
143 y
+= FONT_HEIGHT_NORMAL
+ 5;
144 DrawStringMultiLine(r
.left
+ 2, r
.right
- 2, y
, y
+ FONT_HEIGHT_NORMAL
* 2, str
, TC_FROMSTRING
, SA_CENTER
);
147 void BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(const ContentInfo
*ci
, int bytes
)
149 if (ci
->id
!= this->cur_id
) {
150 this->name
= ci
->filename
;
151 this->cur_id
= ci
->id
;
152 this->downloaded_files
++;
155 this->downloaded_bytes
+= bytes
;
160 /** Window for showing the download status of content */
161 struct NetworkContentDownloadStatusWindow
: public BaseNetworkContentDownloadStatusWindow
{
163 std::vector
<ContentType
> receivedTypes
; ///< Types we received so we can update their cache
167 * Create a new download window based on a list of content information
168 * with flags whether to download them or not.
170 NetworkContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_network_content_download_status_window_desc
)
172 this->parent
= FindWindowById(WC_NETWORK_WINDOW
, WN_NETWORK_WINDOW_CONTENT_LIST
);
175 void Close() override
177 TarScanner::Mode mode
= TarScanner::NONE
;
178 for (auto ctype
: this->receivedTypes
) {
180 case CONTENT_TYPE_AI
:
181 case CONTENT_TYPE_AI_LIBRARY
:
182 /* AI::Rescan calls the scanner. */
184 case CONTENT_TYPE_GAME
:
185 case CONTENT_TYPE_GAME_LIBRARY
:
186 /* Game::Rescan calls the scanner. */
189 case CONTENT_TYPE_BASE_GRAPHICS
:
190 case CONTENT_TYPE_BASE_SOUNDS
:
191 case CONTENT_TYPE_BASE_MUSIC
:
192 mode
|= TarScanner::BASESET
;
195 case CONTENT_TYPE_NEWGRF
:
196 /* ScanNewGRFFiles calls the scanner. */
199 case CONTENT_TYPE_SCENARIO
:
200 case CONTENT_TYPE_HEIGHTMAP
:
201 mode
|= TarScanner::SCENARIO
;
209 TarScanner::DoScan(mode
);
211 /* Tell all the backends about what we've downloaded */
212 for (auto ctype
: this->receivedTypes
) {
214 case CONTENT_TYPE_AI
:
215 case CONTENT_TYPE_AI_LIBRARY
:
219 case CONTENT_TYPE_GAME
:
220 case CONTENT_TYPE_GAME_LIBRARY
:
224 case CONTENT_TYPE_BASE_GRAPHICS
:
225 BaseGraphics::FindSets();
226 SetWindowDirty(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
);
229 case CONTENT_TYPE_BASE_SOUNDS
:
230 BaseSounds::FindSets();
231 SetWindowDirty(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
);
234 case CONTENT_TYPE_BASE_MUSIC
:
235 BaseMusic::FindSets();
236 SetWindowDirty(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
);
239 case CONTENT_TYPE_NEWGRF
:
243 case CONTENT_TYPE_SCENARIO
:
244 case CONTENT_TYPE_HEIGHTMAP
:
245 extern void ScanScenarios();
247 InvalidateWindowData(WC_SAVELOAD
, 0, 0);
255 /* Always invalidate the download window; tell it we are going to be gone */
256 InvalidateWindowData(WC_NETWORK_WINDOW
, WN_NETWORK_WINDOW_CONTENT_LIST
, 2);
258 this->BaseNetworkContentDownloadStatusWindow::Close();
261 void OnClick(Point pt
, int widget
, int click_count
) override
263 if (widget
== WID_NCDS_CANCELOK
) {
264 if (this->downloaded_bytes
!= this->total_bytes
) {
265 _network_content_client
.CloseConnection();
268 /* If downloading succeeded, close the online content window. This will close
269 * the current window as well. */
270 CloseWindowById(WC_NETWORK_WINDOW
, WN_NETWORK_WINDOW_CONTENT_LIST
);
275 void OnDownloadProgress(const ContentInfo
*ci
, int bytes
) override
277 BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(ci
, bytes
);
278 include(this->receivedTypes
, ci
->type
);
280 /* When downloading is finished change cancel in ok */
281 if (this->downloaded_bytes
== this->total_bytes
) {
282 this->GetWidget
<NWidgetCore
>(WID_NCDS_CANCELOK
)->widget_data
= STR_BUTTON_OK
;
287 /** Filter data for NetworkContentListWindow. */
288 struct ContentListFilterData
{
289 StringFilter string_filter
; ///< Text filter of content list
290 std::bitset
<CONTENT_TYPE_END
> types
; ///< Content types displayed
293 /** Filter criteria for NetworkContentListWindow. */
294 enum ContentListFilterCriteria
{
295 CONTENT_FILTER_TEXT
= 0, ///< Filter by query sting
296 CONTENT_FILTER_TYPE_OR_SELECTED
,///< Filter by being of displayed type or selected for download
299 /** Window that lists the content that's at the content server */
300 class NetworkContentListWindow
: public Window
, ContentCallback
{
301 /** List with content infos. */
302 typedef GUIList
<const ContentInfo
*, ContentListFilterData
&> GUIContentList
;
304 static const uint EDITBOX_MAX_SIZE
= 50; ///< Maximum size of the editbox in characters.
306 static Listing last_sorting
; ///< The last sorting setting.
307 static Filtering last_filtering
; ///< The last filtering setting.
308 static GUIContentList::SortFunction
* const sorter_funcs
[]; ///< Sorter functions
309 static GUIContentList::FilterFunction
* const filter_funcs
[]; ///< Filter functions.
310 GUIContentList content
; ///< List with content
311 bool auto_select
; ///< Automatically select all content when the meta-data becomes available
312 ContentListFilterData filter_data
; ///< Filter for content list
313 QueryString filter_editbox
; ///< Filter editbox;
314 Dimension checkbox_size
; ///< Size of checkbox/"blot" sprite
316 const ContentInfo
*selected
; ///< The selected content info
317 int list_pos
; ///< Our position in the list
318 uint filesize_sum
; ///< The sum of all selected file sizes
319 Scrollbar
*vscroll
; ///< Cache of the vertical scrollbar
321 static char content_type_strs
[CONTENT_TYPE_END
][64]; ///< Cached strings for all content types.
323 /** Search external websites for content */
324 void OpenExternalSearch()
326 extern void OpenBrowser(const char *url
);
329 const char *last
= lastof(url
);
331 char *pos
= strecpy(url
, "https://grfsearch.openttd.org/?", last
);
333 if (this->auto_select
) {
334 pos
= strecpy(pos
, "do=searchgrfid&q=", last
);
337 for (const ContentInfo
*ci
: this->content
) {
338 if (ci
->state
!= ContentInfo::DOES_NOT_EXIST
) continue;
340 if (!first
) pos
= strecpy(pos
, ",", last
);
343 pos
+= seprintf(pos
, last
, "%08X", ci
->unique_id
);
344 pos
= strecpy(pos
, ":", last
);
345 pos
= md5sumToString(pos
, last
, ci
->md5sum
);
348 pos
= strecpy(pos
, "do=searchtext&q=", last
);
350 /* Escape search term */
351 for (const char *search
= this->filter_editbox
.text
.buf
; *search
!= '\0'; search
++) {
353 if (*search
== '\'' || *search
== '"') continue;
355 /* Escape special chars, such as &%,= */
356 if (*search
< 0x30) {
357 pos
+= seprintf(pos
, last
, "%%%02X", *search
);
358 } else if (pos
< last
) {
369 * Callback function for disclaimer about entering external websites.
371 static void ExternalSearchDisclaimerCallback(Window
*w
, bool accepted
)
374 _accepted_external_search
= true;
375 ((NetworkContentListWindow
*)w
)->OpenExternalSearch();
380 * (Re)build the network game list as its amount has changed because
381 * an item has been added or deleted for example
383 void BuildContentList()
385 if (!this->content
.NeedRebuild()) return;
387 /* Create temporary array of games to use for listing */
388 this->content
.clear();
390 bool all_available
= true;
392 for (ConstContentIterator iter
= _network_content_client
.Begin(); iter
!= _network_content_client
.End(); iter
++) {
393 if ((*iter
)->state
== ContentInfo::DOES_NOT_EXIST
) all_available
= false;
394 this->content
.push_back(*iter
);
397 this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL
, this->auto_select
&& all_available
);
399 this->FilterContentList();
400 this->content
.shrink_to_fit();
401 this->content
.RebuildDone();
402 this->SortContentList();
404 this->vscroll
->SetCount((int)this->content
.size()); // Update the scrollbar
405 this->ScrollToSelected();
408 /** Sort content by name. */
409 static bool NameSorter(const ContentInfo
* const &a
, const ContentInfo
* const &b
)
411 return strnatcmp(a
->name
.c_str(), b
->name
.c_str(), true) < 0; // Sort by name (natural sorting).
414 /** Sort content by type. */
415 static bool TypeSorter(const ContentInfo
* const &a
, const ContentInfo
* const &b
)
418 if (a
->type
!= b
->type
) {
419 r
= strnatcmp(content_type_strs
[a
->type
], content_type_strs
[b
->type
]);
421 if (r
== 0) return NameSorter(a
, b
);
425 /** Sort content by state. */
426 static bool StateSorter(const ContentInfo
* const &a
, const ContentInfo
* const &b
)
428 int r
= a
->state
- b
->state
;
429 if (r
== 0) return TypeSorter(a
, b
);
433 /** Sort the content list */
434 void SortContentList()
436 if (!this->content
.Sort()) return;
438 int idx
= find_index(this->content
, this->selected
);
439 if (idx
>= 0) this->list_pos
= idx
;
442 /** Filter content by tags/name */
443 static bool CDECL
TagNameFilter(const ContentInfo
* const *a
, ContentListFilterData
&filter
)
445 filter
.string_filter
.ResetState();
446 for (auto &tag
: (*a
)->tags
) filter
.string_filter
.AddLine(tag
.c_str());
448 filter
.string_filter
.AddLine((*a
)->name
.c_str());
449 return filter
.string_filter
.GetState();
452 /** Filter content by type, but still show content selected for download. */
453 static bool CDECL
TypeOrSelectedFilter(const ContentInfo
* const *a
, ContentListFilterData
&filter
)
455 if (filter
.types
.none()) return true;
456 if (filter
.types
[(*a
)->type
]) return true;
457 return ((*a
)->state
== ContentInfo::SELECTED
|| (*a
)->state
== ContentInfo::AUTOSELECTED
);
460 /** Filter the content list */
461 void FilterContentList()
464 bool changed
= false;
465 if (!this->filter_data
.string_filter
.IsEmpty()) {
466 this->content
.SetFilterType(CONTENT_FILTER_TEXT
);
467 changed
|= this->content
.Filter(this->filter_data
);
469 if (this->filter_data
.types
.any()) {
470 this->content
.SetFilterType(CONTENT_FILTER_TYPE_OR_SELECTED
);
471 changed
|= this->content
.Filter(this->filter_data
);
473 if (!changed
) return;
475 /* update list position */
476 int idx
= find_index(this->content
, this->selected
);
478 this->list_pos
= idx
;
482 /* previously selected item not in list anymore */
483 this->selected
= nullptr;
488 * Update filter state based on current window state.
489 * @return true if filter state was changed, otherwise false.
491 bool UpdateFilterState()
493 Filtering old_params
= this->content
.GetFiltering();
494 bool new_state
= !this->filter_data
.string_filter
.IsEmpty() || this->filter_data
.types
.any();
495 if (new_state
!= old_params
.state
) {
496 this->content
.SetFilterState(new_state
);
498 return new_state
!= old_params
.state
;
501 /** Make sure that the currently selected content info is within the visible part of the matrix */
502 void ScrollToSelected()
504 if (this->selected
== nullptr) return;
506 this->vscroll
->ScrollTowards(this->list_pos
);
509 friend void BuildContentTypeStringList();
512 * Create the content list window.
513 * @param desc the window description to pass to Window's constructor.
514 * @param select_all Whether the select all button is allowed or not.
515 * @param types the main type of content to display or #CONTENT_TYPE_END.
516 * When a type other than #CONTENT_TYPE_END is given, dependencies of
517 * other types are only shown when content that depend on them are
520 NetworkContentListWindow(WindowDesc
*desc
, bool select_all
, const std::bitset
<CONTENT_TYPE_END
> &types
) :
522 auto_select(select_all
),
523 filter_editbox(EDITBOX_MAX_SIZE
),
527 this->checkbox_size
= maxdim(maxdim(GetSpriteSize(SPR_BOX_EMPTY
), GetSpriteSize(SPR_BOX_CHECKED
)), GetSpriteSize(SPR_BLOT
));
529 this->CreateNestedTree();
530 this->vscroll
= this->GetScrollbar(WID_NCL_SCROLLBAR
);
531 this->FinishInitNested(WN_NETWORK_WINDOW_CONTENT_LIST
);
533 this->GetWidget
<NWidgetStacked
>(WID_NCL_SEL_ALL_UPDATE
)->SetDisplayedPlane(select_all
);
535 this->querystrings
[WID_NCL_FILTER
] = &this->filter_editbox
;
536 this->filter_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
537 this->SetFocusedWidget(WID_NCL_FILTER
);
538 this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL
, this->auto_select
);
539 this->filter_data
.types
= types
;
541 _network_content_client
.AddCallback(this);
542 this->content
.SetListing(this->last_sorting
);
543 this->content
.SetFiltering(this->last_filtering
);
544 this->content
.SetSortFuncs(this->sorter_funcs
);
545 this->content
.SetFilterFuncs(this->filter_funcs
);
546 this->UpdateFilterState();
547 this->content
.ForceRebuild();
548 this->FilterContentList();
549 this->SortContentList();
550 this->InvalidateData();
553 void Close() override
555 _network_content_client
.RemoveCallback(this);
556 this->Window::Close();
559 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
562 case WID_NCL_FILTER_CAPT
:
563 *size
= maxdim(*size
, GetStringBoundingBox(STR_CONTENT_FILTER_TITLE
));
566 case WID_NCL_CHECKBOX
:
567 size
->width
= this->checkbox_size
.width
+ WD_MATRIX_RIGHT
+ WD_MATRIX_LEFT
;
572 for (int i
= CONTENT_TYPE_BEGIN
; i
< CONTENT_TYPE_END
; i
++) {
573 d
= maxdim(d
, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS
+ i
- CONTENT_TYPE_BASE_GRAPHICS
));
575 size
->width
= d
.width
+ WD_MATRIX_RIGHT
+ WD_MATRIX_LEFT
;
580 resize
->height
= std::max(this->checkbox_size
.height
, (uint
)FONT_HEIGHT_NORMAL
) + WD_MATRIX_TOP
+ WD_MATRIX_BOTTOM
;
581 size
->height
= 10 * resize
->height
;
587 void DrawWidget(const Rect
&r
, int widget
) const override
590 case WID_NCL_FILTER_CAPT
:
591 DrawString(r
.left
, r
.right
, r
.top
, STR_CONTENT_FILTER_TITLE
, TC_FROMSTRING
, SA_RIGHT
);
594 case WID_NCL_DETAILS
:
595 this->DrawDetails(r
);
604 void OnPaint() override
606 const SortButtonState arrow
= this->content
.IsDescSortOrder() ? SBS_DOWN
: SBS_UP
;
608 if (this->content
.NeedRebuild()) {
609 this->BuildContentList();
614 switch (this->content
.SortType()) {
615 case WID_NCL_CHECKBOX
- WID_NCL_CHECKBOX
: this->DrawSortButtonState(WID_NCL_CHECKBOX
, arrow
); break;
616 case WID_NCL_TYPE
- WID_NCL_CHECKBOX
: this->DrawSortButtonState(WID_NCL_TYPE
, arrow
); break;
617 case WID_NCL_NAME
- WID_NCL_CHECKBOX
: this->DrawSortButtonState(WID_NCL_NAME
, arrow
); break;
622 * Draw/fill the matrix with the list of content to download.
623 * @param r The boundaries of the matrix.
625 void DrawMatrix(const Rect
&r
) const
627 const NWidgetBase
*nwi_checkbox
= this->GetWidget
<NWidgetBase
>(WID_NCL_CHECKBOX
);
628 const NWidgetBase
*nwi_name
= this->GetWidget
<NWidgetBase
>(WID_NCL_NAME
);
629 const NWidgetBase
*nwi_type
= this->GetWidget
<NWidgetBase
>(WID_NCL_TYPE
);
631 int line_height
= std::max(this->checkbox_size
.height
, (uint
)FONT_HEIGHT_NORMAL
);
633 /* Fill the matrix with the information */
634 int sprite_y_offset
= WD_MATRIX_TOP
+ (line_height
- this->checkbox_size
.height
) / 2 - 1;
635 int text_y_offset
= WD_MATRIX_TOP
+ (line_height
- FONT_HEIGHT_NORMAL
) / 2;
638 auto iter
= this->content
.begin() + this->vscroll
->GetPosition();
639 size_t last
= this->vscroll
->GetPosition() + this->vscroll
->GetCapacity();
640 auto end
= (last
< this->content
.size()) ? this->content
.begin() + last
: this->content
.end();
642 for (/**/; iter
!= end
; iter
++) {
643 const ContentInfo
*ci
= *iter
;
645 if (ci
== this->selected
) GfxFillRect(r
.left
+ 1, y
+ 1, r
.right
- 1, y
+ this->resize
.step_height
- 1, PC_GREY
);
648 SpriteID pal
= PAL_NONE
;
650 case ContentInfo::UNSELECTED
: sprite
= SPR_BOX_EMPTY
; break;
651 case ContentInfo::SELECTED
: sprite
= SPR_BOX_CHECKED
; break;
652 case ContentInfo::AUTOSELECTED
: sprite
= SPR_BOX_CHECKED
; break;
653 case ContentInfo::ALREADY_HERE
: sprite
= SPR_BLOT
; pal
= PALETTE_TO_GREEN
; break;
654 case ContentInfo::DOES_NOT_EXIST
: sprite
= SPR_BLOT
; pal
= PALETTE_TO_RED
; break;
655 default: NOT_REACHED();
657 DrawSprite(sprite
, pal
, nwi_checkbox
->pos_x
+ (pal
== PAL_NONE
? 2 : 3), y
+ sprite_y_offset
+ (pal
== PAL_NONE
? 1 : 0));
659 StringID str
= STR_CONTENT_TYPE_BASE_GRAPHICS
+ ci
->type
- CONTENT_TYPE_BASE_GRAPHICS
;
660 DrawString(nwi_type
->pos_x
, nwi_type
->pos_x
+ nwi_type
->current_x
- 1, y
+ text_y_offset
, str
, TC_BLACK
, SA_HOR_CENTER
);
662 DrawString(nwi_name
->pos_x
+ WD_FRAMERECT_LEFT
, nwi_name
->pos_x
+ nwi_name
->current_x
- WD_FRAMERECT_RIGHT
, y
+ text_y_offset
, ci
->name
, TC_BLACK
);
663 y
+= this->resize
.step_height
;
668 * Helper function to draw the details part of this window.
669 * @param r the rectangle to stay within while drawing
671 void DrawDetails(const Rect
&r
) const
673 static const int DETAIL_LEFT
= 5; ///< Number of pixels at the left
674 static const int DETAIL_RIGHT
= 5; ///< Number of pixels at the right
675 static const int DETAIL_TOP
= 5; ///< Number of pixels at the top
677 /* Height for the title banner */
678 int DETAIL_TITLE_HEIGHT
= 5 * FONT_HEIGHT_NORMAL
;
680 /* Create the nice grayish rectangle at the details top */
681 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ DETAIL_TITLE_HEIGHT
, PC_DARK_BLUE
);
682 DrawString(r
.left
+ WD_INSET_LEFT
, r
.right
- WD_INSET_RIGHT
, r
.top
+ FONT_HEIGHT_NORMAL
+ WD_INSET_TOP
, STR_CONTENT_DETAIL_TITLE
, TC_FROMSTRING
, SA_HOR_CENTER
);
684 /* Draw the total download size */
685 SetDParam(0, this->filesize_sum
);
686 DrawString(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, r
.bottom
- FONT_HEIGHT_NORMAL
- WD_PAR_VSEP_NORMAL
, STR_CONTENT_TOTAL_DOWNLOAD_SIZE
);
688 if (this->selected
== nullptr) return;
690 /* And fill the rest of the details when there's information to place there */
691 DrawStringMultiLine(r
.left
+ WD_INSET_LEFT
, r
.right
- WD_INSET_RIGHT
, r
.top
+ DETAIL_TITLE_HEIGHT
/ 2, r
.top
+ DETAIL_TITLE_HEIGHT
, STR_CONTENT_DETAIL_SUBTITLE_UNSELECTED
+ this->selected
->state
, TC_FROMSTRING
, SA_CENTER
);
693 /* Also show the total download size, so keep some space from the bottom */
694 const uint max_y
= r
.bottom
- FONT_HEIGHT_NORMAL
- WD_PAR_VSEP_WIDE
;
695 int y
= r
.top
+ DETAIL_TITLE_HEIGHT
+ DETAIL_TOP
;
697 if (this->selected
->upgrade
) {
698 SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS
+ this->selected
->type
- CONTENT_TYPE_BASE_GRAPHICS
);
699 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_UPDATE
);
700 y
+= WD_PAR_VSEP_WIDE
;
703 SetDParamStr(0, this->selected
->name
);
704 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_NAME
);
706 if (!this->selected
->version
.empty()) {
707 SetDParamStr(0, this->selected
->version
);
708 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_VERSION
);
711 if (!this->selected
->description
.empty()) {
712 SetDParamStr(0, this->selected
->description
);
713 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_DESCRIPTION
);
716 if (!this->selected
->url
.empty()) {
717 SetDParamStr(0, this->selected
->url
);
718 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_URL
);
721 SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS
+ this->selected
->type
- CONTENT_TYPE_BASE_GRAPHICS
);
722 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_TYPE
);
724 y
+= WD_PAR_VSEP_WIDE
;
725 SetDParam(0, this->selected
->filesize
);
726 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_FILESIZE
);
728 if (!this->selected
->dependencies
.empty()) {
729 /* List dependencies */
730 char buf
[DRAW_STRING_BUFFER
] = "";
732 for (auto &cid
: this->selected
->dependencies
) {
733 /* Try to find the dependency */
734 ConstContentIterator iter
= _network_content_client
.Begin();
735 for (; iter
!= _network_content_client
.End(); iter
++) {
736 const ContentInfo
*ci
= *iter
;
737 if (ci
->id
!= cid
) continue;
739 p
+= seprintf(p
, lastof(buf
), p
== buf
? "%s" : ", %s", (*iter
)->name
.c_str());
743 SetDParamStr(0, buf
);
744 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_DEPENDENCIES
);
747 if (!this->selected
->tags
.empty()) {
749 char buf
[DRAW_STRING_BUFFER
] = "";
751 for (auto &tag
: this->selected
->tags
) {
752 p
+= seprintf(p
, lastof(buf
), p
== buf
? "%s" : ", %s", tag
.c_str());
754 SetDParamStr(0, buf
);
755 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_TAGS
);
758 if (this->selected
->IsSelected()) {
759 /* When selected show all manually selected content that depends on this */
760 ConstContentVector tree
;
761 _network_content_client
.ReverseLookupTreeDependency(tree
, this->selected
);
763 char buf
[DRAW_STRING_BUFFER
] = "";
765 for (const ContentInfo
*ci
: tree
) {
766 if (ci
== this->selected
|| ci
->state
!= ContentInfo::SELECTED
) continue;
768 p
+= seprintf(p
, lastof(buf
), buf
== p
? "%s" : ", %s", ci
->name
.c_str());
771 SetDParamStr(0, buf
);
772 y
= DrawStringMultiLine(r
.left
+ DETAIL_LEFT
, r
.right
- DETAIL_RIGHT
, y
, max_y
, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF
);
777 void OnClick(Point pt
, int widget
, int click_count
) override
779 if (widget
>= WID_NCL_TEXTFILE
&& widget
< WID_NCL_TEXTFILE
+ TFT_END
) {
780 if (this->selected
== nullptr || this->selected
->state
!= ContentInfo::ALREADY_HERE
) return;
782 ShowContentTextfileWindow((TextfileType
)(widget
- WID_NCL_TEXTFILE
), this->selected
);
787 case WID_NCL_MATRIX
: {
788 uint id_v
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_NCL_MATRIX
);
789 if (id_v
>= this->content
.size()) return; // click out of bounds
791 this->selected
= this->content
[id_v
];
792 this->list_pos
= id_v
;
794 const NWidgetBase
*checkbox
= this->GetWidget
<NWidgetBase
>(WID_NCL_CHECKBOX
);
795 if (click_count
> 1 || IsInsideBS(pt
.x
, checkbox
->pos_x
, checkbox
->current_x
)) {
796 _network_content_client
.ToggleSelectedState(this->selected
);
797 this->content
.ForceResort();
800 if (this->filter_data
.types
.any()) {
801 this->content
.ForceRebuild();
804 this->InvalidateData();
808 case WID_NCL_CHECKBOX
:
811 if (this->content
.SortType() == widget
- WID_NCL_CHECKBOX
) {
812 this->content
.ToggleSortOrder();
813 if (this->content
.size() > 0) this->list_pos
= (int)this->content
.size() - this->list_pos
- 1;
815 this->content
.SetSortType(widget
- WID_NCL_CHECKBOX
);
816 this->content
.ForceResort();
817 this->SortContentList();
819 this->ScrollToSelected();
820 this->InvalidateData();
823 case WID_NCL_SELECT_ALL
:
824 _network_content_client
.SelectAll();
825 this->InvalidateData();
828 case WID_NCL_SELECT_UPDATE
:
829 _network_content_client
.SelectUpgrade();
830 this->InvalidateData();
833 case WID_NCL_UNSELECT
:
834 _network_content_client
.UnselectAll();
835 this->InvalidateData();
842 case WID_NCL_OPEN_URL
:
843 if (this->selected
!= nullptr) {
844 extern void OpenBrowser(const char *url
);
845 OpenBrowser(this->selected
->url
.c_str());
849 case WID_NCL_DOWNLOAD
:
850 if (BringWindowToFrontById(WC_NETWORK_STATUS_WINDOW
, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD
) == nullptr) new NetworkContentDownloadStatusWindow();
853 case WID_NCL_SEARCH_EXTERNAL
:
854 if (_accepted_external_search
) {
855 this->OpenExternalSearch();
857 ShowQuery(STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER_CAPTION
, STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER
, this, ExternalSearchDisclaimerCallback
);
863 EventState
OnKeyPress(WChar key
, uint16 keycode
) override
865 if (this->vscroll
->UpdateListPositionOnKeyPress(this->list_pos
, keycode
) == ES_NOT_HANDLED
) {
869 if (keycode
== WKC_RETURN
|| !IsWidgetFocused(WID_NCL_FILTER
)) {
870 if (this->selected
!= nullptr) {
871 _network_content_client
.ToggleSelectedState(this->selected
);
872 this->content
.ForceResort();
873 this->InvalidateData();
875 if (this->filter_data
.types
.any()) {
876 this->content
.ForceRebuild();
877 this->InvalidateData();
881 /* space is pressed and filter is focused. */
885 return ES_NOT_HANDLED
;
889 if (this->content
.size() == 0) {
890 if (this->UpdateFilterState()) {
891 this->content
.ForceRebuild();
892 this->InvalidateData();
897 this->selected
= this->content
[this->list_pos
];
899 if (this->UpdateFilterState()) {
900 this->content
.ForceRebuild();
902 /* Scroll to the new content if it is outside the current range. */
903 this->ScrollToSelected();
907 this->InvalidateData();
911 void OnEditboxChanged(int wid
) override
913 if (wid
== WID_NCL_FILTER
) {
914 this->filter_data
.string_filter
.SetFilterTerm(this->filter_editbox
.text
.buf
);
915 this->UpdateFilterState();
916 this->content
.ForceRebuild();
917 this->InvalidateData();
921 void OnResize() override
923 this->vscroll
->SetCapacityFromWidget(this, WID_NCL_MATRIX
);
926 void OnReceiveContentInfo(const ContentInfo
*rci
) override
928 if (this->auto_select
&& !rci
->IsSelected()) _network_content_client
.ToggleSelectedState(rci
);
929 this->content
.ForceRebuild();
930 this->InvalidateData(0, false);
933 void OnDownloadComplete(ContentID cid
) override
935 this->content
.ForceResort();
936 this->InvalidateData();
939 void OnConnect(bool success
) override
942 ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT
, INVALID_STRING_ID
, WL_ERROR
);
947 this->InvalidateData();
951 * Some data on this window has become invalid.
952 * @param data Information about the changed data.
953 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
955 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
957 if (!gui_scope
) return;
958 if (this->content
.NeedRebuild()) this->BuildContentList();
960 /* To sum all the bytes we intend to download */
961 this->filesize_sum
= 0;
962 bool show_select_all
= false;
963 bool show_select_upgrade
= false;
964 for (const ContentInfo
*ci
: this->content
) {
966 case ContentInfo::SELECTED
:
967 case ContentInfo::AUTOSELECTED
:
968 this->filesize_sum
+= ci
->filesize
;
971 case ContentInfo::UNSELECTED
:
972 show_select_all
= true;
973 show_select_upgrade
|= ci
->upgrade
;
981 /* If data == 2 then the status window caused this OnInvalidate */
982 this->SetWidgetDisabledState(WID_NCL_DOWNLOAD
, this->filesize_sum
== 0 || (FindWindowById(WC_NETWORK_STATUS_WINDOW
, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD
) != nullptr && data
!= 2));
983 this->SetWidgetDisabledState(WID_NCL_UNSELECT
, this->filesize_sum
== 0);
984 this->SetWidgetDisabledState(WID_NCL_SELECT_ALL
, !show_select_all
);
985 this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE
, !show_select_upgrade
);
986 this->SetWidgetDisabledState(WID_NCL_OPEN_URL
, this->selected
== nullptr || this->selected
->url
.empty());
987 for (TextfileType tft
= TFT_BEGIN
; tft
< TFT_END
; tft
++) {
988 this->SetWidgetDisabledState(WID_NCL_TEXTFILE
+ tft
, this->selected
== nullptr || this->selected
->state
!= ContentInfo::ALREADY_HERE
|| this->selected
->GetTextfile(tft
) == nullptr);
991 this->GetWidget
<NWidgetCore
>(WID_NCL_CANCEL
)->widget_data
= this->filesize_sum
== 0 ? STR_AI_SETTINGS_CLOSE
: STR_AI_LIST_CANCEL
;
995 Listing
NetworkContentListWindow::last_sorting
= {false, 1};
996 Filtering
NetworkContentListWindow::last_filtering
= {false, 0};
998 NetworkContentListWindow::GUIContentList::SortFunction
* const NetworkContentListWindow::sorter_funcs
[] = {
1004 NetworkContentListWindow::GUIContentList::FilterFunction
* const NetworkContentListWindow::filter_funcs
[] = {
1006 &TypeOrSelectedFilter
,
1009 char NetworkContentListWindow::content_type_strs
[CONTENT_TYPE_END
][64];
1012 * Build array of all strings corresponding to the content types.
1014 void BuildContentTypeStringList()
1016 for (int i
= CONTENT_TYPE_BEGIN
; i
< CONTENT_TYPE_END
; i
++) {
1017 GetString(NetworkContentListWindow::content_type_strs
[i
], STR_CONTENT_TYPE_BASE_GRAPHICS
+ i
- CONTENT_TYPE_BASE_GRAPHICS
, lastof(NetworkContentListWindow::content_type_strs
[i
]));
1021 /** The widgets for the content list. */
1022 static const NWidgetPart _nested_network_content_list_widgets
[] = {
1023 NWidget(NWID_HORIZONTAL
),
1024 NWidget(WWT_CLOSEBOX
, COLOUR_LIGHT_BLUE
),
1025 NWidget(WWT_CAPTION
, COLOUR_LIGHT_BLUE
), SetDataTip(STR_CONTENT_TITLE
, STR_NULL
),
1026 NWidget(WWT_DEFSIZEBOX
, COLOUR_LIGHT_BLUE
),
1028 NWidget(WWT_PANEL
, COLOUR_LIGHT_BLUE
, WID_NCL_BACKGROUND
),
1029 NWidget(NWID_SPACER
), SetMinimalSize(0, 7), SetResize(1, 0),
1030 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(8, 8, 8),
1032 NWidget(WWT_EMPTY
, COLOUR_LIGHT_BLUE
, WID_NCL_FILTER_CAPT
), SetFill(1, 0), SetResize(1, 0),
1033 NWidget(WWT_EDITBOX
, COLOUR_LIGHT_BLUE
, WID_NCL_FILTER
), SetFill(1, 0), SetResize(1, 0),
1034 SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
),
1036 NWidget(NWID_SPACER
), SetMinimalSize(0, 7), SetResize(1, 0),
1037 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(8, 8, 8),
1039 NWidget(NWID_VERTICAL
), SetPIP(0, 4, 0),
1040 NWidget(NWID_HORIZONTAL
),
1041 NWidget(NWID_VERTICAL
),
1042 NWidget(NWID_HORIZONTAL
),
1043 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_CHECKBOX
), SetMinimalSize(13, 1), SetDataTip(STR_EMPTY
, STR_NULL
),
1044 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_TYPE
),
1045 SetDataTip(STR_CONTENT_TYPE_CAPTION
, STR_CONTENT_TYPE_CAPTION_TOOLTIP
),
1046 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_NAME
), SetResize(1, 0), SetFill(1, 0),
1047 SetDataTip(STR_CONTENT_NAME_CAPTION
, STR_CONTENT_NAME_CAPTION_TOOLTIP
),
1049 NWidget(WWT_MATRIX
, COLOUR_LIGHT_BLUE
, WID_NCL_MATRIX
), SetResize(1, 14), SetFill(1, 1), SetScrollbar(WID_NCL_SCROLLBAR
), SetMatrixDataTip(1, 0, STR_CONTENT_MATRIX_TOOLTIP
),
1051 NWidget(NWID_VSCROLLBAR
, COLOUR_LIGHT_BLUE
, WID_NCL_SCROLLBAR
),
1053 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(0, 8, 0),
1054 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_NCL_SEL_ALL_UPDATE
), SetResize(1, 0), SetFill(1, 0),
1055 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_SELECT_UPDATE
), SetResize(1, 0), SetFill(1, 0),
1056 SetDataTip(STR_CONTENT_SELECT_UPDATES_CAPTION
, STR_CONTENT_SELECT_UPDATES_CAPTION_TOOLTIP
),
1057 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_SELECT_ALL
), SetResize(1, 0), SetFill(1, 0),
1058 SetDataTip(STR_CONTENT_SELECT_ALL_CAPTION
, STR_CONTENT_SELECT_ALL_CAPTION_TOOLTIP
),
1060 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_UNSELECT
), SetResize(1, 0), SetFill(1, 0),
1061 SetDataTip(STR_CONTENT_UNSELECT_ALL_CAPTION
, STR_CONTENT_UNSELECT_ALL_CAPTION_TOOLTIP
),
1065 NWidget(NWID_VERTICAL
), SetPIP(0, 4, 0),
1066 NWidget(WWT_PANEL
, COLOUR_LIGHT_BLUE
, WID_NCL_DETAILS
), SetResize(1, 1), SetFill(1, 1), EndContainer(),
1067 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(0, 8, 0),
1068 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_TEXTFILE
+ TFT_README
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README
, STR_NULL
),
1069 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_TEXTFILE
+ TFT_CHANGELOG
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG
, STR_NULL
),
1071 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(0, 8, 0),
1072 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_OPEN_URL
), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL
, STR_CONTENT_OPEN_URL_TOOLTIP
),
1073 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_TEXTFILE
+ TFT_LICENSE
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE
, STR_NULL
),
1077 NWidget(NWID_SPACER
), SetMinimalSize(0, 7), SetResize(1, 0),
1079 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(8, 8, 8),
1080 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_SEARCH_EXTERNAL
), SetResize(1, 0), SetFill(1, 0),
1081 SetDataTip(STR_CONTENT_SEARCH_EXTERNAL
, STR_CONTENT_SEARCH_EXTERNAL_TOOLTIP
),
1082 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPIP(0, 8, 0),
1083 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_CANCEL
), SetResize(1, 0), SetFill(1, 0),
1084 SetDataTip(STR_BUTTON_CANCEL
, STR_NULL
),
1085 NWidget(WWT_PUSHTXTBTN
, COLOUR_WHITE
, WID_NCL_DOWNLOAD
), SetResize(1, 0), SetFill(1, 0),
1086 SetDataTip(STR_CONTENT_DOWNLOAD_CAPTION
, STR_CONTENT_DOWNLOAD_CAPTION_TOOLTIP
),
1089 NWidget(NWID_SPACER
), SetMinimalSize(0, 2), SetResize(1, 0),
1090 /* Resize button. */
1091 NWidget(NWID_HORIZONTAL
),
1092 NWidget(NWID_SPACER
), SetFill(1, 0), SetResize(1, 0),
1093 NWidget(WWT_RESIZEBOX
, COLOUR_LIGHT_BLUE
),
1098 /** Window description of the content list */
1099 static WindowDesc
_network_content_list_desc(
1100 WDP_CENTER
, "list_content", 630, 460,
1101 WC_NETWORK_WINDOW
, WC_NONE
,
1103 _nested_network_content_list_widgets
, lengthof(_nested_network_content_list_widgets
)
1107 * Show the content list window with a given set of content
1108 * @param cv the content to show, or nullptr when it has to search for itself
1109 * @param type1 the first type to (only) show or #CONTENT_TYPE_END to show all.
1110 * @param type2 the second type to (only) show in addition to type1. If type2 is != #CONTENT_TYPE_END, then also type1 should be != #CONTENT_TYPE_END.
1111 * If type2 != #CONTENT_TYPE_END, then type1 != type2 must be true.
1113 void ShowNetworkContentListWindow(ContentVector
*cv
, ContentType type1
, ContentType type2
)
1115 #if defined(WITH_ZLIB)
1116 std::bitset
<CONTENT_TYPE_END
> types
;
1117 _network_content_client
.Clear();
1118 if (cv
== nullptr) {
1119 assert(type1
!= CONTENT_TYPE_END
|| type2
== CONTENT_TYPE_END
);
1120 assert(type1
== CONTENT_TYPE_END
|| type1
!= type2
);
1121 _network_content_client
.RequestContentList(type1
);
1122 if (type2
!= CONTENT_TYPE_END
) _network_content_client
.RequestContentList(type2
);
1124 if (type1
!= CONTENT_TYPE_END
) types
[type1
] = true;
1125 if (type2
!= CONTENT_TYPE_END
) types
[type2
] = true;
1127 _network_content_client
.RequestContentList(cv
, true);
1130 CloseWindowById(WC_NETWORK_WINDOW
, WN_NETWORK_WINDOW_CONTENT_LIST
);
1131 new NetworkContentListWindow(&_network_content_list_desc
, cv
!= nullptr, types
);
1133 ShowErrorMessage(STR_CONTENT_NO_ZLIB
, STR_CONTENT_NO_ZLIB_SUB
, WL_ERROR
);
1134 /* Connection failed... clean up the mess */
1135 if (cv
!= nullptr) {
1136 for (ContentInfo
*ci
: *cv
) delete ci
;
1138 #endif /* WITH_ZLIB */