network_content_gui.cpp

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