# network_content_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: network_content_gui.cpp 25102 2013-03-17 20:58:40Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #if defined(ENABLE_NETWORK)
    00013 #include "../stdafx.h"
    00014 #include "../strings_func.h"
    00015 #include "../gfx_func.h"
    00016 #include "../window_func.h"
    00017 #include "../error.h"
    00018 #include "../ai/ai.hpp"
    00019 #include "../game/game.hpp"
    00020 #include "../base_media_base.h"
    00021 #include "../sortlist_type.h"
    00022 #include "../stringfilter_type.h"
    00023 #include "../querystring_gui.h"
    00024 #include "../core/geometry_func.hpp"
    00025 #include "../textfile_gui.h"
    00026 #include "network_content_gui.h"
    00027
    00028
    00029 #include "table/strings.h"
    00030 #include "../table/sprites.h"
    00031
    00032
    00034 static bool _accepted_external_search = false;
    00035
    00036
    00038 struct ContentTextfileWindow : public TextfileWindow {
    00039   const ContentInfo *ci;
    00040
    00041   ContentTextfileWindow(TextfileType file_type, const ContentInfo *ci) : TextfileWindow(file_type), ci(ci)
    00042   {
    00043     const char *textfile = this->ci->GetTextfile(file_type);
    00044     this->LoadTextfile(textfile, GetContentInfoSubDir(this->ci->type));
    00045   }
    00046
    00047   StringID GetTypeString() const
    00048   {
    00049     switch (this->ci->type) {
    00050       case CONTENT_TYPE_NEWGRF:        return STR_CONTENT_TYPE_NEWGRF;
    00051       case CONTENT_TYPE_BASE_GRAPHICS: return STR_CONTENT_TYPE_BASE_GRAPHICS;
    00052       case CONTENT_TYPE_BASE_SOUNDS:   return STR_CONTENT_TYPE_BASE_SOUNDS;
    00053       case CONTENT_TYPE_BASE_MUSIC:    return STR_CONTENT_TYPE_BASE_MUSIC;
    00054       case CONTENT_TYPE_AI:            return STR_CONTENT_TYPE_AI;
    00055       case CONTENT_TYPE_AI_LIBRARY:    return STR_CONTENT_TYPE_AI_LIBRARY;
    00056       case CONTENT_TYPE_GAME:          return STR_CONTENT_TYPE_GAME_SCRIPT;
    00057       case CONTENT_TYPE_GAME_LIBRARY:  return STR_CONTENT_TYPE_GS_LIBRARY;
    00058       case CONTENT_TYPE_SCENARIO:      return STR_CONTENT_TYPE_SCENARIO;
    00059       case CONTENT_TYPE_HEIGHTMAP:     return STR_CONTENT_TYPE_HEIGHTMAP;
    00060       default: NOT_REACHED();
    00061     }
    00062   }
    00063
    00064   /* virtual */ void SetStringParameters(int widget) const
    00065   {
    00066     if (widget == WID_TF_CAPTION) {
    00067       SetDParam(0, this->GetTypeString());
    00068       SetDParamStr(1, this->ci->name);
    00069     }
    00070   }
    00071 };
    00072
    00073 void ShowContentTextfileWindow(TextfileType file_type, const ContentInfo *ci)
    00074 {
    00075   DeleteWindowByClass(WC_TEXTFILE);
    00076   new ContentTextfileWindow(file_type, ci);
    00077 }
    00078
    00080 static const NWidgetPart _nested_network_content_download_status_window_widgets[] = {
    00081   NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
    00082   NWidget(WWT_PANEL, COLOUR_GREY, WID_NCDS_BACKGROUND),
    00083     NWidget(NWID_SPACER), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM + 30),
    00084     NWidget(NWID_HORIZONTAL),
    00085       NWidget(NWID_SPACER), SetMinimalSize(125, 0),
    00086       NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCDS_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
    00087       NWidget(NWID_SPACER), SetFill(1, 0),
    00088     EndContainer(),
    00089     NWidget(NWID_SPACER), SetMinimalSize(0, 4),
    00090   EndContainer(),
    00091 };
    00092
    00094 static const WindowDesc _network_content_download_status_window_desc(
    00095   WDP_CENTER, 0, 0,
    00096   WC_NETWORK_STATUS_WINDOW, WC_NONE,
    00097   WDF_MODAL,
    00098   _nested_network_content_download_status_window_widgets, lengthof(_nested_network_content_download_status_window_widgets)
    00099 );
    00100
    00101 BaseNetworkContentDownloadStatusWindow::BaseNetworkContentDownloadStatusWindow(const WindowDesc *desc) :
    00102     cur_id(UINT32_MAX)
    00103 {
    00104   _network_content_client.AddCallback(this);
    00105   _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes);
    00106
    00107   this->InitNested(desc, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD);
    00108 }
    00109
    00110 BaseNetworkContentDownloadStatusWindow::~BaseNetworkContentDownloadStatusWindow()
    00111 {
    00112   _network_content_client.RemoveCallback(this);
    00113 }
    00114
    00115 /* virtual */ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, int widget) const
    00116 {
    00117   if (widget != WID_NCDS_BACKGROUND) return;
    00118
    00119   /* Draw nice progress bar :) */
    00120   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);
    00121
    00122   int y = r.top + 20;
    00123   SetDParam(0, this->downloaded_bytes);
    00124   SetDParam(1, this->total_bytes);
    00125   SetDParam(2, this->downloaded_bytes * 100LL / this->total_bytes);
    00126   DrawString(r.left + 2, r.right - 2, y, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, TC_FROMSTRING, SA_HOR_CENTER);
    00127
    00128   StringID str;
    00129   if (this->downloaded_bytes == this->total_bytes) {
    00130     str = STR_CONTENT_DOWNLOAD_COMPLETE;
    00131   } else if (!StrEmpty(this->name)) {
    00132     SetDParamStr(0, this->name);
    00133     SetDParam(1, this->downloaded_files);
    00134     SetDParam(2, this->total_files);
    00135     str = STR_CONTENT_DOWNLOAD_FILE;
    00136   } else {
    00137     str = STR_CONTENT_DOWNLOAD_INITIALISE;
    00138   }
    00139
    00140   y += FONT_HEIGHT_NORMAL + 5;
    00141   DrawStringMultiLine(r.left + 2, r.right - 2, y, y + FONT_HEIGHT_NORMAL * 2, str, TC_FROMSTRING, SA_CENTER);
    00142 }
    00143
    00144 /* virtual */ void BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(const ContentInfo *ci, int bytes)
    00145 {
    00146   if (ci->id != this->cur_id) {
    00147     strecpy(this->name, ci->filename, lastof(this->name));
    00148     this->cur_id = ci->id;
    00149     this->downloaded_files++;
    00150   }
    00151
    00152   this->downloaded_bytes += bytes;
    00153   this->SetDirty();
    00154 }
    00155
    00156
    00158 struct NetworkContentDownloadStatusWindow : public BaseNetworkContentDownloadStatusWindow {
    00159 private:
    00160   SmallVector<ContentType, 4> receivedTypes;
    00161
    00162 public:
    00167   NetworkContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_network_content_download_status_window_desc)
    00168   {
    00169     this->parent = FindWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
    00170   }
    00171
    00173   ~NetworkContentDownloadStatusWindow()
    00174   {
    00175     TarScanner::Mode mode = TarScanner::NONE;
    00176     for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
    00177       switch (*iter) {
    00178         case CONTENT_TYPE_AI:
    00179         case CONTENT_TYPE_AI_LIBRARY:
    00180           /* AI::Rescan calls the scanner. */
    00181           break;
    00182         case CONTENT_TYPE_GAME:
    00183         case CONTENT_TYPE_GAME_LIBRARY:
    00184           /* Game::Rescan calls the scanner. */
    00185           break;
    00186
    00187         case CONTENT_TYPE_BASE_GRAPHICS:
    00188         case CONTENT_TYPE_BASE_SOUNDS:
    00189         case CONTENT_TYPE_BASE_MUSIC:
    00190           mode |= TarScanner::BASESET;
    00191           break;
    00192
    00193         case CONTENT_TYPE_NEWGRF:
    00194           /* ScanNewGRFFiles calls the scanner. */
    00195           break;
    00196
    00197         case CONTENT_TYPE_SCENARIO:
    00198         case CONTENT_TYPE_HEIGHTMAP:
    00199           mode |= TarScanner::SCENARIO;
    00200           break;
    00201
    00202         default:
    00203           break;
    00204       }
    00205     }
    00206
    00207     TarScanner::DoScan(mode);
    00208
    00209     /* Tell all the backends about what we've downloaded */
    00210     for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
    00211       switch (*iter) {
    00212         case CONTENT_TYPE_AI:
    00213         case CONTENT_TYPE_AI_LIBRARY:
    00214           AI::Rescan();
    00215           break;
    00216
    00217         case CONTENT_TYPE_GAME:
    00218         case CONTENT_TYPE_GAME_LIBRARY:
    00219           Game::Rescan();
    00220           break;
    00221
    00222         case CONTENT_TYPE_BASE_GRAPHICS:
    00223           BaseGraphics::FindSets();
    00224           SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS);
    00225           break;
    00226
    00227         case CONTENT_TYPE_BASE_SOUNDS:
    00228           BaseSounds::FindSets();
    00229           SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS);
    00230           break;
    00231
    00232         case CONTENT_TYPE_BASE_MUSIC:
    00233           BaseMusic::FindSets();
    00234           SetWindowDirty(WC_GAME_OPTIONS, WN_GAME_OPTIONS_GAME_OPTIONS);
    00235           break;
    00236
    00237         case CONTENT_TYPE_NEWGRF:
    00238           ScanNewGRFFiles(NULL);
    00239           break;
    00240
    00241         case CONTENT_TYPE_SCENARIO:
    00242         case CONTENT_TYPE_HEIGHTMAP:
    00243           extern void ScanScenarios();
    00244           ScanScenarios();
    00245           InvalidateWindowData(WC_SAVELOAD, 0, 0);
    00246           break;
    00247
    00248         default:
    00249           break;
    00250       }
    00251     }
    00252
    00253     /* Always invalidate the download window; tell it we are going to be gone */
    00254     InvalidateWindowData(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST, 2);
    00255   }
    00256
    00257   virtual void OnClick(Point pt, int widget, int click_count)
    00258   {
    00259     if (widget == WID_NCDS_CANCELOK) {
    00260       if (this->downloaded_bytes != this->total_bytes) {
    00261         _network_content_client.Close();
    00262         delete this;
    00263       } else {
    00264         /* If downloading succeeded, close the online content window. This will close
    00265          * the current window as well. */
    00266         DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
    00267       }
    00268     }
    00269   }
    00270
    00271   virtual void OnDownloadProgress(const ContentInfo *ci, int bytes)
    00272   {
    00273     BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(ci, bytes);
    00274     this->receivedTypes.Include(ci->type);
    00275
    00276     /* When downloading is finished change cancel in ok */
    00277     if (this->downloaded_bytes == this->total_bytes) {
    00278       this->GetWidget<NWidgetCore>(WID_NCDS_CANCELOK)->widget_data = STR_BUTTON_OK;
    00279     }
    00280   }
    00281 };
    00282
    00284 class NetworkContentListWindow : public Window, ContentCallback {
    00286   typedef GUIList<const ContentInfo *, StringFilter &> GUIContentList;
    00287
    00288   static const uint EDITBOX_MAX_SIZE   =  50;
    00289
    00290   static Listing last_sorting;
    00291   static Filtering last_filtering;
    00292   static GUIContentList::SortFunction * const sorter_funcs[];
    00293   static GUIContentList::FilterFunction * const filter_funcs[];
    00294   GUIContentList content;
    00295   bool auto_select;
    00296   StringFilter string_filter;
    00297   QueryString filter_editbox;
    00298
    00299   const ContentInfo *selected;
    00300   int list_pos;
    00301   uint filesize_sum;
    00302   Scrollbar *vscroll;
    00303
    00305   void OpenExternalSearch()
    00306   {
    00307     extern void OpenBrowser(const char *url);
    00308
    00309     char url[1024];
    00310     const char *last = lastof(url);
    00311
    00312     char *pos = strecpy(url, "http://grfsearch.openttd.org/?", last);
    00313
    00314     if (this->auto_select) {
    00315       pos = strecpy(pos, "do=searchgrfid&q=", last);
    00316
    00317       bool first = true;
    00318       for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
    00319         const ContentInfo *ci = *iter;
    00320         if (ci->state != ContentInfo::DOES_NOT_EXIST) continue;
    00321
    00322         if (!first) pos = strecpy(pos, ",", last);
    00323         first = false;
    00324
    00325         pos += seprintf(pos, last, "%08X", ci->unique_id);
    00326         pos = strecpy(pos, ":", last);
    00327         pos = md5sumToString(pos, last, ci->md5sum);
    00328       }
    00329     } else {
    00330       pos = strecpy(pos, "do=searchtext&q=", last);
    00331
    00332       /* Escape search term */
    00333       for (const char *search = this->filter_editbox.text.buf; *search != '\0'; search++) {
    00334         /* Remove quotes */
    00335         if (*search == '\'' || *search == '"') continue;
    00336
    00337         /* Escape special chars, such as &%,= */
    00338         if (*search < 0x30) {
    00339           pos += seprintf(pos, last, "%%%02X", *search);
    00340         } else if (pos < last) {
    00341           *pos = *search;
    00342           *++pos = '\0';
    00343         }
    00344       }
    00345     }
    00346
    00347     OpenBrowser(url);
    00348   }
    00349
    00353   static void ExternalSearchDisclaimerCallback(Window *w, bool accepted)
    00354   {
    00355     if (accepted) {
    00356       _accepted_external_search = true;
    00357       ((NetworkContentListWindow*)w)->OpenExternalSearch();
    00358     }
    00359   }
    00360
    00365   void BuildContentList()
    00366   {
    00367     if (!this->content.NeedRebuild()) return;
    00368
    00369     /* Create temporary array of games to use for listing */
    00370     this->content.Clear();
    00371
    00372     bool all_available = true;
    00373
    00374     for (ConstContentIterator iter = _network_content_client.Begin(); iter != _network_content_client.End(); iter++) {
    00375       if ((*iter)->state == ContentInfo::DOES_NOT_EXIST) all_available = false;
    00376       *this->content.Append() = *iter;
    00377     }
    00378
    00379     this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL, this->auto_select && all_available);
    00380
    00381     this->FilterContentList();
    00382     this->content.Compact();
    00383     this->content.RebuildDone();
    00384     this->SortContentList();
    00385
    00386     this->vscroll->SetCount(this->content.Length()); // Update the scrollbar
    00387     this->ScrollToSelected();
    00388   }
    00389
    00391   static int CDECL NameSorter(const ContentInfo * const *a, const ContentInfo * const *b)
    00392   {
    00393     return strnatcmp((*a)->name, (*b)->name, true); // Sort by name (natural sorting).
    00394   }
    00395
    00397   static int CDECL TypeSorter(const ContentInfo * const *a, const ContentInfo * const *b)
    00398   {
    00399     int r = 0;
    00400     if ((*a)->type != (*b)->type) {
    00401       char a_str[64];
    00402       char b_str[64];
    00403       GetString(a_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*a)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(a_str));
    00404       GetString(b_str, STR_CONTENT_TYPE_BASE_GRAPHICS + (*b)->type - CONTENT_TYPE_BASE_GRAPHICS, lastof(b_str));
    00405       r = strnatcmp(a_str, b_str);
    00406     }
    00407     if (r == 0) r = NameSorter(a, b);
    00408     return r;
    00409   }
    00410
    00412   static int CDECL StateSorter(const ContentInfo * const *a, const ContentInfo * const *b)
    00413   {
    00414     int r = (*a)->state - (*b)->state;
    00415     if (r == 0) r = TypeSorter(a, b);
    00416     return r;
    00417   }
    00418
    00420   void SortContentList()
    00421   {
    00422     if (!this->content.Sort()) return;
    00423
    00424     for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
    00425       if (*iter == this->selected) {
    00426         this->list_pos = iter - this->content.Begin();
    00427         break;
    00428       }
    00429     }
    00430   }
    00431
    00433   static bool CDECL TagNameFilter(const ContentInfo * const *a, StringFilter &filter)
    00434   {
    00435     filter.ResetState();
    00436     for (int i = 0; i < (*a)->tag_count; i++) {
    00437       filter.AddLine((*a)->tags[i]);
    00438     }
    00439     filter.AddLine((*a)->name);
    00440     return filter.GetState();
    00441   }
    00442
    00444   void FilterContentList()
    00445   {
    00446     if (!this->content.Filter(this->string_filter)) return;
    00447
    00448     /* update list position */
    00449     for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
    00450       if (*iter == this->selected) {
    00451         this->list_pos = iter - this->content.Begin();
    00452         return;
    00453       }
    00454     }
    00455
    00456     /* previously selected item not in list anymore */
    00457     this->selected = NULL;
    00458     this->list_pos = 0;
    00459   }
    00460
    00462   void ScrollToSelected()
    00463   {
    00464     if (this->selected == NULL) return;
    00465
    00466     this->vscroll->ScrollTowards(this->list_pos);
    00467   }
    00468
    00469 public:
    00475   NetworkContentListWindow(const WindowDesc *desc, bool select_all) :
    00476       auto_select(select_all),
    00477       filter_editbox(EDITBOX_MAX_SIZE),
    00478       selected(NULL),
    00479       list_pos(0)
    00480   {
    00481     this->CreateNestedTree(desc);
    00482     this->vscroll = this->GetScrollbar(WID_NCL_SCROLLBAR);
    00483     this->FinishInitNested(desc, WN_NETWORK_WINDOW_CONTENT_LIST);
    00484
    00485     this->GetWidget<NWidgetStacked>(WID_NCL_SEL_ALL_UPDATE)->SetDisplayedPlane(select_all);
    00486
    00487     this->querystrings[WID_NCL_FILTER] = &this->filter_editbox;
    00488     this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
    00489     this->SetFocusedWidget(WID_NCL_FILTER);
    00490     this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL, this->auto_select);
    00491
    00492     _network_content_client.AddCallback(this);
    00493     this->content.SetListing(this->last_sorting);
    00494     this->content.SetFiltering(this->last_filtering);
    00495     this->content.SetSortFuncs(this->sorter_funcs);
    00496     this->content.SetFilterFuncs(this->filter_funcs);
    00497     this->content.ForceRebuild();
    00498     this->FilterContentList();
    00499     this->SortContentList();
    00500     this->InvalidateData();
    00501   }
    00502
    00504   ~NetworkContentListWindow()
    00505   {
    00506     _network_content_client.RemoveCallback(this);
    00507   }
    00508
    00509   virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
    00510   {
    00511     switch (widget) {
    00512       case WID_NCL_FILTER_CAPT:
    00513         *size = maxdim(*size, GetStringBoundingBox(STR_CONTENT_FILTER_TITLE));
    00514         break;
    00515
    00516       case WID_NCL_TYPE: {
    00517         Dimension d = *size;
    00518         for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
    00519           d = maxdim(d, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS));
    00520         }
    00521         size->width = d.width + WD_MATRIX_RIGHT + WD_MATRIX_LEFT;
    00522         break;
    00523       }
    00524
    00525       case WID_NCL_MATRIX:
    00526         resize->height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
    00527         size->height = 10 * resize->height;
    00528         break;
    00529     }
    00530   }
    00531
    00532
    00533   virtual void DrawWidget(const Rect &r, int widget) const
    00534   {
    00535     switch (widget) {
    00536       case WID_NCL_FILTER_CAPT:
    00537         DrawString(r.left, r.right, r.top, STR_CONTENT_FILTER_TITLE, TC_FROMSTRING, SA_RIGHT);
    00538         break;
    00539
    00540       case WID_NCL_DETAILS:
    00541         this->DrawDetails(r);
    00542         break;
    00543
    00544       case WID_NCL_MATRIX:
    00545         this->DrawMatrix(r);
    00546         break;
    00547     }
    00548   }
    00549
    00550   virtual void OnPaint()
    00551   {
    00552     const SortButtonState arrow = this->content.IsDescSortOrder() ? SBS_DOWN : SBS_UP;
    00553
    00554     if (this->content.NeedRebuild()) {
    00555       this->BuildContentList();
    00556     }
    00557
    00558     this->DrawWidgets();
    00559
    00560     switch (this->content.SortType()) {
    00561       case WID_NCL_CHECKBOX - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_CHECKBOX, arrow); break;
    00562       case WID_NCL_TYPE     - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_TYPE,     arrow); break;
    00563       case WID_NCL_NAME     - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_NAME,     arrow); break;
    00564     }
    00565   }
    00566
    00571   void DrawMatrix(const Rect &r) const
    00572   {
    00573     const NWidgetBase *nwi_checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX);
    00574     const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(WID_NCL_NAME);
    00575     const NWidgetBase *nwi_type = this->GetWidget<NWidgetBase>(WID_NCL_TYPE);
    00576
    00577
    00578     /* Fill the matrix with the information */
    00579     int sprite_y_offset = WD_MATRIX_TOP + (FONT_HEIGHT_NORMAL - 10) / 2;
    00580     uint y = r.top;
    00581     int cnt = 0;
    00582     for (ConstContentIterator iter = this->content.Get(this->vscroll->GetPosition()); iter != this->content.End() && cnt < this->vscroll->GetCapacity(); iter++, cnt++) {
    00583       const ContentInfo *ci = *iter;
    00584
    00585       if (ci == this->selected) GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 1, PC_GREY);
    00586
    00587       SpriteID sprite;
    00588       SpriteID pal = PAL_NONE;
    00589       switch (ci->state) {
    00590         case ContentInfo::UNSELECTED:     sprite = SPR_BOX_EMPTY;   break;
    00591         case ContentInfo::SELECTED:       sprite = SPR_BOX_CHECKED; break;
    00592         case ContentInfo::AUTOSELECTED:   sprite = SPR_BOX_CHECKED; break;
    00593         case ContentInfo::ALREADY_HERE:   sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
    00594         case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED;   break;
    00595         default: NOT_REACHED();
    00596       }
    00597       DrawSprite(sprite, pal, nwi_checkbox->pos_x + (pal == PAL_NONE ? 2 : 3), y + sprite_y_offset + (pal == PAL_NONE ? 1 : 0));
    00598
    00599       StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
    00600       DrawString(nwi_type->pos_x, nwi_type->pos_x + nwi_type->current_x - 1, y + WD_MATRIX_TOP, str, TC_BLACK, SA_HOR_CENTER);
    00601
    00602       DrawString(nwi_name->pos_x + WD_FRAMERECT_LEFT, nwi_name->pos_x + nwi_name->current_x - WD_FRAMERECT_RIGHT, y + WD_MATRIX_TOP, ci->name, TC_BLACK);
    00603       y += this->resize.step_height;
    00604     }
    00605   }
    00606
    00611   void DrawDetails(const Rect &r) const
    00612   {
    00613     static const int DETAIL_LEFT         =  5;
    00614     static const int DETAIL_RIGHT        =  5;
    00615     static const int DETAIL_TOP          =  5;
    00616
    00617     /* Height for the title banner */
    00618     int DETAIL_TITLE_HEIGHT = 5 * FONT_HEIGHT_NORMAL;
    00619
    00620     /* Create the nice grayish rectangle at the details top */
    00621     GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + DETAIL_TITLE_HEIGHT, PC_DARK_BLUE);
    00622     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);
    00623
    00624     /* Draw the total download size */
    00625     SetDParam(0, this->filesize_sum);
    00626     DrawString(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_NORMAL, STR_CONTENT_TOTAL_DOWNLOAD_SIZE);
    00627
    00628     if (this->selected == NULL) return;
    00629
    00630     /* And fill the rest of the details when there's information to place there */
    00631     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);
    00632
    00633     /* Also show the total download size, so keep some space from the bottom */
    00634     const uint max_y = r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_WIDE;
    00635     int y = r.top + DETAIL_TITLE_HEIGHT + DETAIL_TOP;
    00636
    00637     if (this->selected->upgrade) {
    00638       SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
    00639       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_UPDATE);
    00640       y += WD_PAR_VSEP_WIDE;
    00641     }
    00642
    00643     SetDParamStr(0, this->selected->name);
    00644     y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_NAME);
    00645
    00646     if (!StrEmpty(this->selected->version)) {
    00647       SetDParamStr(0, this->selected->version);
    00648       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_VERSION);
    00649     }
    00650
    00651     if (!StrEmpty(this->selected->description)) {
    00652       SetDParamStr(0, this->selected->description);
    00653       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DESCRIPTION);
    00654     }
    00655
    00656     if (!StrEmpty(this->selected->url)) {
    00657       SetDParamStr(0, this->selected->url);
    00658       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_URL);
    00659     }
    00660
    00661     SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
    00662     y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TYPE);
    00663
    00664     y += WD_PAR_VSEP_WIDE;
    00665     SetDParam(0, this->selected->filesize);
    00666     y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_FILESIZE);
    00667
    00668     if (this->selected->dependency_count != 0) {
    00669       /* List dependencies */
    00670       char buf[DRAW_STRING_BUFFER] = "";
    00671       char *p = buf;
    00672       for (uint i = 0; i < this->selected->dependency_count; i++) {
    00673         ContentID cid = this->selected->dependencies[i];
    00674
    00675         /* Try to find the dependency */
    00676         ConstContentIterator iter = _network_content_client.Begin();
    00677         for (; iter != _network_content_client.End(); iter++) {
    00678           const ContentInfo *ci = *iter;
    00679           if (ci->id != cid) continue;
    00680
    00681           p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
    00682           break;
    00683         }
    00684       }
    00685       SetDParamStr(0, buf);
    00686       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DEPENDENCIES);
    00687     }
    00688
    00689     if (this->selected->tag_count != 0) {
    00690       /* List all tags */
    00691       char buf[DRAW_STRING_BUFFER] = "";
    00692       char *p = buf;
    00693       for (uint i = 0; i < this->selected->tag_count; i++) {
    00694         p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
    00695       }
    00696       SetDParamStr(0, buf);
    00697       y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TAGS);
    00698     }
    00699
    00700     if (this->selected->IsSelected()) {
    00701       /* When selected show all manually selected content that depends on this */
    00702       ConstContentVector tree;
    00703       _network_content_client.ReverseLookupTreeDependency(tree, this->selected);
    00704
    00705       char buf[DRAW_STRING_BUFFER] = "";
    00706       char *p = buf;
    00707       for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
    00708         const ContentInfo *ci = *iter;
    00709         if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
    00710
    00711         p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
    00712       }
    00713       if (p != buf) {
    00714         SetDParamStr(0, buf);
    00715         y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF);
    00716       }
    00717     }
    00718   }
    00719
    00720   virtual void OnClick(Point pt, int widget, int click_count)
    00721   {
    00722     if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_END) {
    00723       if (this->selected == NULL || this->selected->state != ContentInfo::ALREADY_HERE) return;
    00724
    00725       ShowContentTextfileWindow((TextfileType)(widget - WID_NCL_TEXTFILE), this->selected);
    00726       return;
    00727     }
    00728
    00729     switch (widget) {
    00730       case WID_NCL_MATRIX: {
    00731         uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NCL_MATRIX);
    00732         if (id_v >= this->content.Length()) return; // click out of bounds
    00733
    00734         this->selected = *this->content.Get(id_v);
    00735         this->list_pos = id_v;
    00736
    00737         const NWidgetBase *checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX);
    00738         if (click_count > 1 || IsInsideBS(pt.x, checkbox->pos_x, checkbox->current_x)) {
    00739           _network_content_client.ToggleSelectedState(this->selected);
    00740           this->content.ForceResort();
    00741         }
    00742
    00743         this->InvalidateData();
    00744         break;
    00745       }
    00746
    00747       case WID_NCL_CHECKBOX:
    00748       case WID_NCL_TYPE:
    00749       case WID_NCL_NAME:
    00750         if (this->content.SortType() == widget - WID_NCL_CHECKBOX) {
    00751           this->content.ToggleSortOrder();
    00752           this->list_pos = this->content.Length() - this->list_pos - 1;
    00753         } else {
    00754           this->content.SetSortType(widget - WID_NCL_CHECKBOX);
    00755           this->content.ForceResort();
    00756           this->SortContentList();
    00757         }
    00758         this->ScrollToSelected();
    00759         this->InvalidateData();
    00760         break;
    00761
    00762       case WID_NCL_SELECT_ALL:
    00763         _network_content_client.SelectAll();
    00764         this->InvalidateData();
    00765         break;
    00766
    00767       case WID_NCL_SELECT_UPDATE:
    00768         _network_content_client.SelectUpgrade();
    00769         this->InvalidateData();
    00770         break;
    00771
    00772       case WID_NCL_UNSELECT:
    00773         _network_content_client.UnselectAll();
    00774         this->InvalidateData();
    00775         break;
    00776
    00777       case WID_NCL_CANCEL:
    00778         delete this;
    00779         break;
    00780
    00781       case WID_NCL_OPEN_URL:
    00782         if (this->selected != NULL) {
    00783           extern void OpenBrowser(const char *url);
    00784           OpenBrowser(this->selected->url);
    00785         }
    00786         break;
    00787
    00788       case WID_NCL_DOWNLOAD:
    00789         if (BringWindowToFrontById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD) == NULL) new NetworkContentDownloadStatusWindow();
    00790         break;
    00791
    00792       case WID_NCL_SEARCH_EXTERNAL:
    00793         if (_accepted_external_search) {
    00794           this->OpenExternalSearch();
    00795         } else {
    00796           ShowQuery(STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER_CAPTION, STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER, this, ExternalSearchDisclaimerCallback);
    00797         }
    00798         break;
    00799     }
    00800   }
    00801
    00802   virtual EventState OnKeyPress(uint16 key, uint16 keycode)
    00803   {
    00804     switch (keycode) {
    00805       case WKC_UP:
    00806         /* scroll up by one */
    00807         if (this->list_pos > 0) this->list_pos--;
    00808         break;
    00809       case WKC_DOWN:
    00810         /* scroll down by one */
    00811         if (this->list_pos < (int)this->content.Length() - 1) this->list_pos++;
    00812         break;
    00813       case WKC_PAGEUP:
    00814         /* scroll up a page */
    00815         this->list_pos = (this->list_pos < this->vscroll->GetCapacity()) ? 0 : this->list_pos - this->vscroll->GetCapacity();
    00816         break;
    00817       case WKC_PAGEDOWN:
    00818         /* scroll down a page */
    00819         this->list_pos = min(this->list_pos + this->vscroll->GetCapacity(), (int)this->content.Length() - 1);
    00820         break;
    00821       case WKC_HOME:
    00822         /* jump to beginning */
    00823         this->list_pos = 0;
    00824         break;
    00825       case WKC_END:
    00826         /* jump to end */
    00827         this->list_pos = this->content.Length() - 1;
    00828         break;
    00829
    00830       case WKC_SPACE:
    00831       case WKC_RETURN:
    00832         if (keycode == WKC_RETURN || !IsWidgetFocused(WID_NCL_FILTER)) {
    00833           if (this->selected != NULL) {
    00834             _network_content_client.ToggleSelectedState(this->selected);
    00835             this->content.ForceResort();
    00836             this->InvalidateData();
    00837           }
    00838           return ES_HANDLED;
    00839         }
    00840         /* FALL THROUGH, space is pressed and filter isn't focused. */
    00841
    00842       default:
    00843         return ES_NOT_HANDLED;
    00844     }
    00845
    00846     if (_network_content_client.Length() == 0) return ES_HANDLED;
    00847
    00848     this->selected = *this->content.Get(this->list_pos);
    00849
    00850     /* scroll to the new server if it is outside the current range */
    00851     this->ScrollToSelected();
    00852
    00853     /* redraw window */
    00854     this->InvalidateData();
    00855     return ES_HANDLED;
    00856   }
    00857
    00858   virtual void OnEditboxChanged(int wid)
    00859   {
    00860     if (wid == WID_NCL_FILTER) {
    00861       this->string_filter.SetFilterTerm(this->filter_editbox.text.buf);
    00862       this->content.SetFilterState(!this->string_filter.IsEmpty());
    00863       this->content.ForceRebuild();
    00864       this->InvalidateData();
    00865     }
    00866   }
    00867
    00868   virtual void OnResize()
    00869   {
    00870     this->vscroll->SetCapacityFromWidget(this, WID_NCL_MATRIX);
    00871     this->GetWidget<NWidgetCore>(WID_NCL_MATRIX)->widget_data = (this->vscroll->GetCapacity() << MAT_ROW_START) + (1 << MAT_COL_START);
    00872   }
    00873
    00874   virtual void OnReceiveContentInfo(const ContentInfo *rci)
    00875   {
    00876     if (this->auto_select && !rci->IsSelected()) _network_content_client.ToggleSelectedState(rci);
    00877     this->content.ForceRebuild();
    00878     this->InvalidateData();
    00879   }
    00880
    00881   virtual void OnDownloadComplete(ContentID cid)
    00882   {
    00883     this->content.ForceResort();
    00884     this->InvalidateData();
    00885   }
    00886
    00887   virtual void OnConnect(bool success)
    00888   {
    00889     if (!success) {
    00890       ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT, INVALID_STRING_ID, WL_ERROR);
    00891       delete this;
    00892       return;
    00893     }
    00894
    00895     this->InvalidateData();
    00896   }
    00897
    00903   virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
    00904   {
    00905     if (!gui_scope) return;
    00906     if (this->content.NeedRebuild()) this->BuildContentList();
    00907
    00908     /* To sum all the bytes we intend to download */
    00909     this->filesize_sum = 0;
    00910     bool show_select_all = false;
    00911     bool show_select_upgrade = false;
    00912     for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
    00913       const ContentInfo *ci = *iter;
    00914       switch (ci->state) {
    00915         case ContentInfo::SELECTED:
    00916         case ContentInfo::AUTOSELECTED:
    00917           this->filesize_sum += ci->filesize;
    00918           break;
    00919
    00920         case ContentInfo::UNSELECTED:
    00921           show_select_all = true;
    00922           show_select_upgrade |= ci->upgrade;
    00923           break;
    00924
    00925         default:
    00926           break;
    00927       }
    00928     }
    00929
    00930     /* If data == 2 then the status window caused this OnInvalidate */
    00931     this->SetWidgetDisabledState(WID_NCL_DOWNLOAD, this->filesize_sum == 0 || (FindWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_CONTENT_DOWNLOAD) != NULL && data != 2));
    00932     this->SetWidgetDisabledState(WID_NCL_UNSELECT, this->filesize_sum == 0);
    00933     this->SetWidgetDisabledState(WID_NCL_SELECT_ALL, !show_select_all);
    00934     this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE, !show_select_upgrade);
    00935     this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == NULL || StrEmpty(this->selected->url));
    00936     for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
    00937       this->SetWidgetDisabledState(WID_NCL_TEXTFILE + tft, this->selected == NULL || this->selected->state != ContentInfo::ALREADY_HERE || this->selected->GetTextfile(tft) == NULL);
    00938     }
    00939
    00940     this->GetWidget<NWidgetCore>(WID_NCL_CANCEL)->widget_data = this->filesize_sum == 0 ? STR_AI_SETTINGS_CLOSE : STR_AI_LIST_CANCEL;
    00941   }
    00942 };
    00943
    00944 Listing NetworkContentListWindow::last_sorting = {false, 1};
    00945 Filtering NetworkContentListWindow::last_filtering = {false, 0};
    00946
    00947 NetworkContentListWindow::GUIContentList::SortFunction * const NetworkContentListWindow::sorter_funcs[] = {
    00948   &StateSorter,
    00949   &TypeSorter,
    00950   &NameSorter,
    00951 };
    00952
    00953 NetworkContentListWindow::GUIContentList::FilterFunction * const NetworkContentListWindow::filter_funcs[] = {
    00954   &TagNameFilter,
    00955 };
    00956
    00958 static const NWidgetPart _nested_network_content_list_widgets[] = {
    00959   NWidget(NWID_HORIZONTAL),
    00960     NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
    00961     NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_CONTENT_TITLE, STR_NULL),
    00962   EndContainer(),
    00963   NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NCL_BACKGROUND),
    00964     NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
    00965     NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
    00966       /* Top */
    00967       NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, WID_NCL_FILTER_CAPT), SetFill(1, 0), SetResize(1, 0),
    00968       NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NCL_FILTER), SetFill(1, 0), SetResize(1, 0),
    00969             SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
    00970     EndContainer(),
    00971     NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
    00972     NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
    00973       /* Left side. */
    00974       NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
    00975         NWidget(NWID_HORIZONTAL),
    00976           NWidget(NWID_VERTICAL),
    00977             NWidget(NWID_HORIZONTAL),
    00978               NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CHECKBOX), SetMinimalSize(13, 1), SetDataTip(STR_EMPTY, STR_NULL),
    00979               NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TYPE),
    00980                       SetDataTip(STR_CONTENT_TYPE_CAPTION, STR_CONTENT_TYPE_CAPTION_TOOLTIP),
    00981               NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_NAME), SetResize(1, 0), SetFill(1, 0),
    00982                       SetDataTip(STR_CONTENT_NAME_CAPTION, STR_CONTENT_NAME_CAPTION_TOOLTIP),
    00983             EndContainer(),
    00984             NWidget(WWT_MATRIX, COLOUR_LIGHT_BLUE, WID_NCL_MATRIX), SetResize(1, 14), SetFill(1, 1), SetScrollbar(WID_NCL_SCROLLBAR), SetDataTip(STR_NULL, STR_CONTENT_MATRIX_TOOLTIP),
    00985           EndContainer(),
    00986           NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NCL_SCROLLBAR),
    00987         EndContainer(),
    00988         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, 8, 0),
    00989           NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NCL_SEL_ALL_UPDATE), SetResize(1, 0), SetFill(1, 0),
    00990             NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SELECT_UPDATE), SetResize(1, 0), SetFill(1, 0),
    00991                         SetDataTip(STR_CONTENT_SELECT_UPDATES_CAPTION, STR_CONTENT_SELECT_UPDATES_CAPTION_TOOLTIP),
    00992             NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SELECT_ALL), SetResize(1, 0), SetFill(1, 0),
    00993                         SetDataTip(STR_CONTENT_SELECT_ALL_CAPTION, STR_CONTENT_SELECT_ALL_CAPTION_TOOLTIP),
    00994           EndContainer(),
    00995           NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_UNSELECT), SetResize(1, 0), SetFill(1, 0),
    00996                         SetDataTip(STR_CONTENT_UNSELECT_ALL_CAPTION, STR_CONTENT_UNSELECT_ALL_CAPTION_TOOLTIP),
    00997         EndContainer(),
    00998       EndContainer(),
    00999       /* Right side. */
    01000       NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
    01001         NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NCL_DETAILS), SetResize(1, 1), SetFill(1, 1), EndContainer(),
    01002         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, 8, 0),
    01003           NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
    01004           NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
    01005         EndContainer(),
    01006         NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, 8, 0),
    01007           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),
    01008           NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
    01009         EndContainer(),
    01010       EndContainer(),
    01011     EndContainer(),
    01012     NWidget(NWID_SPACER), SetMinimalSize(0, 7), SetResize(1, 0),
    01013     /* Bottom. */
    01014     NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(8, 8, 8),
    01015       NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SEARCH_EXTERNAL), SetResize(1, 0), SetFill(1, 0),
    01016                     SetDataTip(STR_CONTENT_SEARCH_EXTERNAL, STR_CONTENT_SEARCH_EXTERNAL_TOOLTIP),
    01017       NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(0, 8, 0),
    01018         NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CANCEL), SetResize(1, 0), SetFill(1, 0),
    01019                       SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
    01020         NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
    01021                       SetDataTip(STR_CONTENT_DOWNLOAD_CAPTION, STR_CONTENT_DOWNLOAD_CAPTION_TOOLTIP),
    01022       EndContainer(),
    01023     EndContainer(),
    01024     NWidget(NWID_SPACER), SetMinimalSize(0, 2), SetResize(1, 0),
    01025     /* Resize button. */
    01026     NWidget(NWID_HORIZONTAL),
    01027       NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
    01028       NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE),
    01029     EndContainer(),
    01030   EndContainer(),
    01031 };
    01032
    01034 static const WindowDesc _network_content_list_desc(
    01035   WDP_CENTER, 630, 460,
    01036   WC_NETWORK_WINDOW, WC_NONE,
    01037   0,
    01038   _nested_network_content_list_widgets, lengthof(_nested_network_content_list_widgets)
    01039 );
    01040
    01046 void ShowNetworkContentListWindow(ContentVector *cv, ContentType type)
    01047 {
    01048 #if defined(WITH_ZLIB)
    01049   _network_content_client.Clear();
    01050   if (cv == NULL) {
    01051     _network_content_client.RequestContentList(type);
    01052   } else {
    01053     _network_content_client.RequestContentList(cv, true);
    01054   }
    01055
    01056   DeleteWindowById(WC_NETWORK_WINDOW, WN_NETWORK_WINDOW_CONTENT_LIST);
    01057   new NetworkContentListWindow(&_network_content_list_desc, cv != NULL);
    01058 #else
    01059   ShowErrorMessage(STR_CONTENT_NO_ZLIB, STR_CONTENT_NO_ZLIB_SUB, WL_ERROR);
    01060   /* Connection failed... clean up the mess */
    01061   if (cv != NULL) {
    01062     for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter;
    01063   }
    01064 #endif /* WITH_ZLIB */
    01065 }
    01066
    01067 #endif /* ENABLE_NETWORK */