OpenTTD
network_content_gui.cpp
Go to the documentation of this file.
1 /* $Id: network_content_gui.cpp 27469 2015-12-10 19:58:33Z zuu $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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  */
9 
12 #if defined(ENABLE_NETWORK)
13 #include "../stdafx.h"
14 #include "../strings_func.h"
15 #include "../gfx_func.h"
16 #include "../window_func.h"
17 #include "../error.h"
18 #include "../ai/ai.hpp"
19 #include "../game/game.hpp"
20 #include "../base_media_base.h"
21 #include "../sortlist_type.h"
22 #include "../stringfilter_type.h"
23 #include "../querystring_gui.h"
24 #include "../core/geometry_func.hpp"
25 #include "../textfile_gui.h"
26 #include "network_content_gui.h"
27 
28 
29 #include "table/strings.h"
30 #include "../table/sprites.h"
31 
32 #include <bitset>
33 
34 #include "../safeguards.h"
35 
36 
38 static bool _accepted_external_search = false;
39 
40 
43  const ContentInfo *ci;
44 
46  {
47  const char *textfile = this->ci->GetTextfile(file_type);
48  this->LoadTextfile(textfile, GetContentInfoSubDir(this->ci->type));
49  }
50 
51  StringID GetTypeString() const
52  {
53  switch (this->ci->type) {
54  case CONTENT_TYPE_NEWGRF: return STR_CONTENT_TYPE_NEWGRF;
55  case CONTENT_TYPE_BASE_GRAPHICS: return STR_CONTENT_TYPE_BASE_GRAPHICS;
56  case CONTENT_TYPE_BASE_SOUNDS: return STR_CONTENT_TYPE_BASE_SOUNDS;
57  case CONTENT_TYPE_BASE_MUSIC: return STR_CONTENT_TYPE_BASE_MUSIC;
58  case CONTENT_TYPE_AI: return STR_CONTENT_TYPE_AI;
59  case CONTENT_TYPE_AI_LIBRARY: return STR_CONTENT_TYPE_AI_LIBRARY;
60  case CONTENT_TYPE_GAME: return STR_CONTENT_TYPE_GAME_SCRIPT;
61  case CONTENT_TYPE_GAME_LIBRARY: return STR_CONTENT_TYPE_GS_LIBRARY;
62  case CONTENT_TYPE_SCENARIO: return STR_CONTENT_TYPE_SCENARIO;
63  case CONTENT_TYPE_HEIGHTMAP: return STR_CONTENT_TYPE_HEIGHTMAP;
64  default: NOT_REACHED();
65  }
66  }
67 
68  /* virtual */ void SetStringParameters(int widget) const
69  {
70  if (widget == WID_TF_CAPTION) {
71  SetDParam(0, this->GetTypeString());
72  SetDParamStr(1, this->ci->name);
73  }
74  }
75 };
76 
77 void ShowContentTextfileWindow(TextfileType file_type, const ContentInfo *ci)
78 {
80  new ContentTextfileWindow(file_type, ci);
81 }
82 
85  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
86  NWidget(WWT_PANEL, COLOUR_GREY, WID_NCDS_BACKGROUND),
90  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCDS_CANCELOK), SetMinimalSize(101, 12), SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
91  NWidget(NWID_SPACER), SetFill(1, 0),
92  EndContainer(),
94  EndContainer(),
95 };
96 
99  WDP_CENTER, NULL, 0, 0,
101  WDF_MODAL,
102  _nested_network_content_download_status_window_widgets, lengthof(_nested_network_content_download_status_window_widgets)
103 );
104 
106  Window(desc), cur_id(UINT32_MAX)
107 {
110 
112 }
113 
115 {
117 }
118 
119 /* virtual */ void BaseNetworkContentDownloadStatusWindow::DrawWidget(const Rect &r, int widget) const
120 {
121  if (widget != WID_NCDS_BACKGROUND) return;
122 
123  /* Draw nice progress bar :) */
124  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);
125 
126  int y = r.top + 20;
127  SetDParam(0, this->downloaded_bytes);
128  SetDParam(1, this->total_bytes);
129  SetDParam(2, this->downloaded_bytes * 100LL / this->total_bytes);
130  DrawString(r.left + 2, r.right - 2, y, STR_CONTENT_DOWNLOAD_PROGRESS_SIZE, TC_FROMSTRING, SA_HOR_CENTER);
131 
132  StringID str;
133  if (this->downloaded_bytes == this->total_bytes) {
134  str = STR_CONTENT_DOWNLOAD_COMPLETE;
135  } else if (!StrEmpty(this->name)) {
136  SetDParamStr(0, this->name);
137  SetDParam(1, this->downloaded_files);
138  SetDParam(2, this->total_files);
139  str = STR_CONTENT_DOWNLOAD_FILE;
140  } else {
141  str = STR_CONTENT_DOWNLOAD_INITIALISE;
142  }
143 
144  y += FONT_HEIGHT_NORMAL + 5;
145  DrawStringMultiLine(r.left + 2, r.right - 2, y, y + FONT_HEIGHT_NORMAL * 2, str, TC_FROMSTRING, SA_CENTER);
146 }
147 
148 /* virtual */ void BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(const ContentInfo *ci, int bytes)
149 {
150  if (ci->id != this->cur_id) {
151  strecpy(this->name, ci->filename, lastof(this->name));
152  this->cur_id = ci->id;
153  this->downloaded_files++;
154  }
155 
156  this->downloaded_bytes += bytes;
157  this->SetDirty();
158 }
159 
160 
163 private:
165 
166 public:
172  {
174  }
175 
178  {
180  for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
181  switch (*iter) {
182  case CONTENT_TYPE_AI:
184  /* AI::Rescan calls the scanner. */
185  break;
186  case CONTENT_TYPE_GAME:
188  /* Game::Rescan calls the scanner. */
189  break;
190 
194  mode |= TarScanner::BASESET;
195  break;
196 
197  case CONTENT_TYPE_NEWGRF:
198  /* ScanNewGRFFiles calls the scanner. */
199  break;
200 
203  mode |= TarScanner::SCENARIO;
204  break;
205 
206  default:
207  break;
208  }
209  }
210 
211  TarScanner::DoScan(mode);
212 
213  /* Tell all the backends about what we've downloaded */
214  for (ContentType *iter = this->receivedTypes.Begin(); iter != this->receivedTypes.End(); iter++) {
215  switch (*iter) {
216  case CONTENT_TYPE_AI:
218  AI::Rescan();
219  break;
220 
221  case CONTENT_TYPE_GAME:
223  Game::Rescan();
224  break;
225 
229  break;
230 
234  break;
235 
239  break;
240 
241  case CONTENT_TYPE_NEWGRF:
242  ScanNewGRFFiles(NULL);
243  break;
244 
247  extern void ScanScenarios();
248  ScanScenarios();
250  break;
251 
252  default:
253  break;
254  }
255  }
256 
257  /* Always invalidate the download window; tell it we are going to be gone */
259  }
260 
261  virtual void OnClick(Point pt, int widget, int click_count)
262  {
263  if (widget == WID_NCDS_CANCELOK) {
264  if (this->downloaded_bytes != this->total_bytes) {
266  delete this;
267  } else {
268  /* If downloading succeeded, close the online content window. This will close
269  * the current window as well. */
271  }
272  }
273  }
274 
275  virtual void OnDownloadProgress(const ContentInfo *ci, int bytes)
276  {
277  BaseNetworkContentDownloadStatusWindow::OnDownloadProgress(ci, bytes);
278  this->receivedTypes.Include(ci->type);
279 
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;
283  }
284  }
285 };
286 
290  std::bitset<CONTENT_TYPE_END> types;
291 };
292 
297 };
298 
303 
304  static const uint EDITBOX_MAX_SIZE = 50;
305 
311  bool auto_select;
315 
317  int list_pos;
320 
322 
325  {
326  extern void OpenBrowser(const char *url);
327 
328  char url[1024];
329  const char *last = lastof(url);
330 
331  char *pos = strecpy(url, "http://grfsearch.openttd.org/?", last);
332 
333  if (this->auto_select) {
334  pos = strecpy(pos, "do=searchgrfid&q=", last);
335 
336  bool first = true;
337  for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
338  const ContentInfo *ci = *iter;
339  if (ci->state != ContentInfo::DOES_NOT_EXIST) continue;
340 
341  if (!first) pos = strecpy(pos, ",", last);
342  first = false;
343 
344  pos += seprintf(pos, last, "%08X", ci->unique_id);
345  pos = strecpy(pos, ":", last);
346  pos = md5sumToString(pos, last, ci->md5sum);
347  }
348  } else {
349  pos = strecpy(pos, "do=searchtext&q=", last);
350 
351  /* Escape search term */
352  for (const char *search = this->filter_editbox.text.buf; *search != '\0'; search++) {
353  /* Remove quotes */
354  if (*search == '\'' || *search == '"') continue;
355 
356  /* Escape special chars, such as &%,= */
357  if (*search < 0x30) {
358  pos += seprintf(pos, last, "%%%02X", *search);
359  } else if (pos < last) {
360  *pos = *search;
361  *++pos = '\0';
362  }
363  }
364  }
365 
366  OpenBrowser(url);
367  }
368 
372  static void ExternalSearchDisclaimerCallback(Window *w, bool accepted)
373  {
374  if (accepted) {
376  ((NetworkContentListWindow*)w)->OpenExternalSearch();
377  }
378  }
379 
385  {
386  if (!this->content.NeedRebuild()) return;
387 
388  /* Create temporary array of games to use for listing */
389  this->content.Clear();
390 
391  bool all_available = true;
392 
394  if ((*iter)->state == ContentInfo::DOES_NOT_EXIST) all_available = false;
395  *this->content.Append() = *iter;
396  }
397 
398  this->SetWidgetDisabledState(WID_NCL_SEARCH_EXTERNAL, this->auto_select && all_available);
399 
400  this->FilterContentList();
401  this->content.Compact();
402  this->content.RebuildDone();
403  this->SortContentList();
404 
405  this->vscroll->SetCount(this->content.Length()); // Update the scrollbar
406  this->ScrollToSelected();
407  }
408 
410  static int CDECL NameSorter(const ContentInfo * const *a, const ContentInfo * const *b)
411  {
412  return strnatcmp((*a)->name, (*b)->name, true); // Sort by name (natural sorting).
413  }
414 
416  static int CDECL TypeSorter(const ContentInfo * const *a, const ContentInfo * const *b)
417  {
418  int r = 0;
419  if ((*a)->type != (*b)->type) {
420  r = strnatcmp(content_type_strs[(*a)->type], content_type_strs[(*b)->type]);
421  }
422  if (r == 0) r = NameSorter(a, b);
423  return r;
424  }
425 
427  static int CDECL StateSorter(const ContentInfo * const *a, const ContentInfo * const *b)
428  {
429  int r = (*a)->state - (*b)->state;
430  if (r == 0) r = TypeSorter(a, b);
431  return r;
432  }
433 
436  {
437  if (!this->content.Sort()) return;
438 
439  for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
440  if (*iter == this->selected) {
441  this->list_pos = iter - this->content.Begin();
442  break;
443  }
444  }
445  }
446 
448  static bool CDECL TagNameFilter(const ContentInfo * const *a, ContentListFilterData &filter)
449  {
450  filter.string_filter.ResetState();
451  for (int i = 0; i < (*a)->tag_count; i++) {
452  filter.string_filter.AddLine((*a)->tags[i]);
453  }
454  filter.string_filter.AddLine((*a)->name);
455  return filter.string_filter.GetState();
456  }
457 
459  static bool CDECL TypeOrSelectedFilter(const ContentInfo * const *a, ContentListFilterData &filter)
460  {
461  if (filter.types.none()) return true;
462  if (filter.types[(*a)->type]) return true;
463  return ((*a)->state == ContentInfo::SELECTED || (*a)->state == ContentInfo::AUTOSELECTED);
464  }
465 
468  {
469  /* Apply filters. */
470  bool changed = false;
471  if (!this->filter_data.string_filter.IsEmpty()) {
473  changed |= this->content.Filter(this->filter_data);
474  }
475  if (this->filter_data.types.any()) {
477  changed |= this->content.Filter(this->filter_data);
478  }
479  if (!changed) return;
480 
481  /* update list position */
482  for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
483  if (*iter == this->selected) {
484  this->list_pos = iter - this->content.Begin();
485  return;
486  }
487  }
488 
489  /* previously selected item not in list anymore */
490  this->selected = NULL;
491  this->list_pos = 0;
492  }
493 
499  {
500  Filtering old_params = this->content.GetFiltering();
501  bool new_state = !this->filter_data.string_filter.IsEmpty() || this->filter_data.types.any();
502  if (new_state != old_params.state) {
503  this->content.SetFilterState(new_state);
504  }
505  return new_state != old_params.state;
506  }
507 
510  {
511  if (this->selected == NULL) return;
512 
513  this->vscroll->ScrollTowards(this->list_pos);
514  }
515 
516  friend void BuildContentTypeStringList();
517 public:
527  NetworkContentListWindow(WindowDesc *desc, bool select_all, const std::bitset<CONTENT_TYPE_END> &types) :
528  Window(desc),
529  auto_select(select_all),
531  selected(NULL),
532  list_pos(0)
533  {
534  this->checkbox_size = maxdim(maxdim(GetSpriteSize(SPR_BOX_EMPTY), GetSpriteSize(SPR_BOX_CHECKED)), GetSpriteSize(SPR_BLOT));
535 
536  this->CreateNestedTree();
537  this->vscroll = this->GetScrollbar(WID_NCL_SCROLLBAR);
539 
540  this->GetWidget<NWidgetStacked>(WID_NCL_SEL_ALL_UPDATE)->SetDisplayedPlane(select_all);
541 
546  this->filter_data.types = types;
547 
549  this->content.SetListing(this->last_sorting);
550  this->content.SetFiltering(this->last_filtering);
551  this->content.SetSortFuncs(this->sorter_funcs);
552  this->content.SetFilterFuncs(this->filter_funcs);
553  this->UpdateFilterState();
554  this->content.ForceRebuild();
555  this->FilterContentList();
556  this->SortContentList();
557  this->InvalidateData();
558  }
559 
562  {
564  }
565 
566  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
567  {
568  switch (widget) {
569  case WID_NCL_FILTER_CAPT:
570  *size = maxdim(*size, GetStringBoundingBox(STR_CONTENT_FILTER_TITLE));
571  break;
572 
573  case WID_NCL_CHECKBOX:
574  size->width = this->checkbox_size.width + WD_MATRIX_RIGHT + WD_MATRIX_LEFT;
575  break;
576 
577  case WID_NCL_TYPE: {
578  Dimension d = *size;
579  for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
580  d = maxdim(d, GetStringBoundingBox(STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS));
581  }
582  size->width = d.width + WD_MATRIX_RIGHT + WD_MATRIX_LEFT;
583  break;
584  }
585 
586  case WID_NCL_MATRIX:
587  resize->height = max(this->checkbox_size.height, (uint)FONT_HEIGHT_NORMAL) + WD_MATRIX_TOP + WD_MATRIX_BOTTOM;
588  size->height = 10 * resize->height;
589  break;
590  }
591  }
592 
593 
594  virtual void DrawWidget(const Rect &r, int widget) const
595  {
596  switch (widget) {
597  case WID_NCL_FILTER_CAPT:
598  DrawString(r.left, r.right, r.top, STR_CONTENT_FILTER_TITLE, TC_FROMSTRING, SA_RIGHT);
599  break;
600 
601  case WID_NCL_DETAILS:
602  this->DrawDetails(r);
603  break;
604 
605  case WID_NCL_MATRIX:
606  this->DrawMatrix(r);
607  break;
608  }
609  }
610 
611  virtual void OnPaint()
612  {
613  const SortButtonState arrow = this->content.IsDescSortOrder() ? SBS_DOWN : SBS_UP;
614 
615  if (this->content.NeedRebuild()) {
616  this->BuildContentList();
617  }
618 
619  this->DrawWidgets();
620 
621  switch (this->content.SortType()) {
623  case WID_NCL_TYPE - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_TYPE, arrow); break;
624  case WID_NCL_NAME - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_NAME, arrow); break;
625  }
626  }
627 
632  void DrawMatrix(const Rect &r) const
633  {
634  const NWidgetBase *nwi_checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX);
635  const NWidgetBase *nwi_name = this->GetWidget<NWidgetBase>(WID_NCL_NAME);
636  const NWidgetBase *nwi_type = this->GetWidget<NWidgetBase>(WID_NCL_TYPE);
637 
638  int line_height = max(this->checkbox_size.height, (uint)FONT_HEIGHT_NORMAL);
639 
640  /* Fill the matrix with the information */
641  int sprite_y_offset = WD_MATRIX_TOP + (line_height - this->checkbox_size.height) / 2 - 1;
642  int text_y_offset = WD_MATRIX_TOP + (line_height - FONT_HEIGHT_NORMAL) / 2;
643  uint y = r.top;
644  int cnt = 0;
645  for (ConstContentIterator iter = this->content.Get(this->vscroll->GetPosition()); iter != this->content.End() && cnt < this->vscroll->GetCapacity(); iter++, cnt++) {
646  const ContentInfo *ci = *iter;
647 
648  if (ci == this->selected) GfxFillRect(r.left + 1, y + 1, r.right - 1, y + this->resize.step_height - 1, PC_GREY);
649 
650  SpriteID sprite;
651  SpriteID pal = PAL_NONE;
652  switch (ci->state) {
653  case ContentInfo::UNSELECTED: sprite = SPR_BOX_EMPTY; break;
654  case ContentInfo::SELECTED: sprite = SPR_BOX_CHECKED; break;
655  case ContentInfo::AUTOSELECTED: sprite = SPR_BOX_CHECKED; break;
656  case ContentInfo::ALREADY_HERE: sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
657  case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED; break;
658  default: NOT_REACHED();
659  }
660  DrawSprite(sprite, pal, nwi_checkbox->pos_x + (pal == PAL_NONE ? 2 : 3), y + sprite_y_offset + (pal == PAL_NONE ? 1 : 0));
661 
662  StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
663  DrawString(nwi_type->pos_x, nwi_type->pos_x + nwi_type->current_x - 1, y + text_y_offset, str, TC_BLACK, SA_HOR_CENTER);
664 
665  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);
666  y += this->resize.step_height;
667  }
668  }
669 
674  void DrawDetails(const Rect &r) const
675  {
676  static const int DETAIL_LEFT = 5;
677  static const int DETAIL_RIGHT = 5;
678  static const int DETAIL_TOP = 5;
679 
680  /* Height for the title banner */
681  int DETAIL_TITLE_HEIGHT = 5 * FONT_HEIGHT_NORMAL;
682 
683  /* Create the nice grayish rectangle at the details top */
684  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.top + DETAIL_TITLE_HEIGHT, PC_DARK_BLUE);
685  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);
686 
687  /* Draw the total download size */
688  SetDParam(0, this->filesize_sum);
689  DrawString(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_NORMAL, STR_CONTENT_TOTAL_DOWNLOAD_SIZE);
690 
691  if (this->selected == NULL) return;
692 
693  /* And fill the rest of the details when there's information to place there */
694  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);
695 
696  /* Also show the total download size, so keep some space from the bottom */
697  const uint max_y = r.bottom - FONT_HEIGHT_NORMAL - WD_PAR_VSEP_WIDE;
698  int y = r.top + DETAIL_TITLE_HEIGHT + DETAIL_TOP;
699 
700  if (this->selected->upgrade) {
701  SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
702  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_UPDATE);
703  y += WD_PAR_VSEP_WIDE;
704  }
705 
706  SetDParamStr(0, this->selected->name);
707  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_NAME);
708 
709  if (!StrEmpty(this->selected->version)) {
710  SetDParamStr(0, this->selected->version);
711  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_VERSION);
712  }
713 
714  if (!StrEmpty(this->selected->description)) {
715  SetDParamStr(0, this->selected->description);
716  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DESCRIPTION);
717  }
718 
719  if (!StrEmpty(this->selected->url)) {
720  SetDParamStr(0, this->selected->url);
721  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_URL);
722  }
723 
724  SetDParam(0, STR_CONTENT_TYPE_BASE_GRAPHICS + this->selected->type - CONTENT_TYPE_BASE_GRAPHICS);
725  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TYPE);
726 
727  y += WD_PAR_VSEP_WIDE;
728  SetDParam(0, this->selected->filesize);
729  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_FILESIZE);
730 
731  if (this->selected->dependency_count != 0) {
732  /* List dependencies */
733  char buf[DRAW_STRING_BUFFER] = "";
734  char *p = buf;
735  for (uint i = 0; i < this->selected->dependency_count; i++) {
736  ContentID cid = this->selected->dependencies[i];
737 
738  /* Try to find the dependency */
740  for (; iter != _network_content_client.End(); iter++) {
741  const ContentInfo *ci = *iter;
742  if (ci->id != cid) continue;
743 
744  p += seprintf(p, lastof(buf), p == buf ? "%s" : ", %s", (*iter)->name);
745  break;
746  }
747  }
748  SetDParamStr(0, buf);
749  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_DEPENDENCIES);
750  }
751 
752  if (this->selected->tag_count != 0) {
753  /* List all tags */
754  char buf[DRAW_STRING_BUFFER] = "";
755  char *p = buf;
756  for (uint i = 0; i < this->selected->tag_count; i++) {
757  p += seprintf(p, lastof(buf), i == 0 ? "%s" : ", %s", this->selected->tags[i]);
758  }
759  SetDParamStr(0, buf);
760  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_TAGS);
761  }
762 
763  if (this->selected->IsSelected()) {
764  /* When selected show all manually selected content that depends on this */
765  ConstContentVector tree;
767 
768  char buf[DRAW_STRING_BUFFER] = "";
769  char *p = buf;
770  for (ConstContentIterator iter = tree.Begin(); iter != tree.End(); iter++) {
771  const ContentInfo *ci = *iter;
772  if (ci == this->selected || ci->state != ContentInfo::SELECTED) continue;
773 
774  p += seprintf(p, lastof(buf), buf == p ? "%s" : ", %s", ci->name);
775  }
776  if (p != buf) {
777  SetDParamStr(0, buf);
778  y = DrawStringMultiLine(r.left + DETAIL_LEFT, r.right - DETAIL_RIGHT, y, max_y, STR_CONTENT_DETAIL_SELECTED_BECAUSE_OF);
779  }
780  }
781  }
782 
783  virtual void OnClick(Point pt, int widget, int click_count)
784  {
785  if (widget >= WID_NCL_TEXTFILE && widget < WID_NCL_TEXTFILE + TFT_END) {
786  if (this->selected == NULL || this->selected->state != ContentInfo::ALREADY_HERE) return;
787 
788  ShowContentTextfileWindow((TextfileType)(widget - WID_NCL_TEXTFILE), this->selected);
789  return;
790  }
791 
792  switch (widget) {
793  case WID_NCL_MATRIX: {
794  uint id_v = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_NCL_MATRIX);
795  if (id_v >= this->content.Length()) return; // click out of bounds
796 
797  this->selected = *this->content.Get(id_v);
798  this->list_pos = id_v;
799 
800  const NWidgetBase *checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX);
801  if (click_count > 1 || IsInsideBS(pt.x, checkbox->pos_x, checkbox->current_x)) {
803  this->content.ForceResort();
804  }
805 
806  if (this->filter_data.types.any()) {
807  this->content.ForceRebuild();
808  }
809 
810  this->InvalidateData();
811  break;
812  }
813 
814  case WID_NCL_CHECKBOX:
815  case WID_NCL_TYPE:
816  case WID_NCL_NAME:
817  if (this->content.SortType() == widget - WID_NCL_CHECKBOX) {
818  this->content.ToggleSortOrder();
819  if (this->content.Length() > 0) this->list_pos = this->content.Length() - this->list_pos - 1;
820  } else {
821  this->content.SetSortType(widget - WID_NCL_CHECKBOX);
822  this->content.ForceResort();
823  this->SortContentList();
824  }
825  this->ScrollToSelected();
826  this->InvalidateData();
827  break;
828 
829  case WID_NCL_SELECT_ALL:
831  this->InvalidateData();
832  break;
833 
836  this->InvalidateData();
837  break;
838 
839  case WID_NCL_UNSELECT:
841  this->InvalidateData();
842  break;
843 
844  case WID_NCL_CANCEL:
845  delete this;
846  break;
847 
848  case WID_NCL_OPEN_URL:
849  if (this->selected != NULL) {
850  extern void OpenBrowser(const char *url);
851  OpenBrowser(this->selected->url);
852  }
853  break;
854 
855  case WID_NCL_DOWNLOAD:
857  break;
858 
861  this->OpenExternalSearch();
862  } else {
863  ShowQuery(STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER_CAPTION, STR_CONTENT_SEARCH_EXTERNAL_DISCLAIMER, this, ExternalSearchDisclaimerCallback);
864  }
865  break;
866  }
867  }
868 
869  virtual EventState OnKeyPress(WChar key, uint16 keycode)
870  {
871  switch (keycode) {
872  case WKC_UP:
873  /* scroll up by one */
874  if (this->list_pos > 0) this->list_pos--;
875  break;
876  case WKC_DOWN:
877  /* scroll down by one */
878  if (this->list_pos < (int)this->content.Length() - 1) this->list_pos++;
879  break;
880  case WKC_PAGEUP:
881  /* scroll up a page */
882  this->list_pos = (this->list_pos < this->vscroll->GetCapacity()) ? 0 : this->list_pos - this->vscroll->GetCapacity();
883  break;
884  case WKC_PAGEDOWN:
885  /* scroll down a page */
886  this->list_pos = min(this->list_pos + this->vscroll->GetCapacity(), (int)this->content.Length() - 1);
887  break;
888  case WKC_HOME:
889  /* jump to beginning */
890  this->list_pos = 0;
891  break;
892  case WKC_END:
893  /* jump to end */
894  this->list_pos = this->content.Length() - 1;
895  break;
896 
897  case WKC_SPACE:
898  case WKC_RETURN:
899  if (keycode == WKC_RETURN || !IsWidgetFocused(WID_NCL_FILTER)) {
900  if (this->selected != NULL) {
902  this->content.ForceResort();
903  this->InvalidateData();
904  }
905  if (this->filter_data.types.any()) {
906  this->content.ForceRebuild();
907  this->InvalidateData();
908  }
909  return ES_HANDLED;
910  }
911  /* FALL THROUGH, space is pressed and filter is focused. */
912 
913  default:
914  return ES_NOT_HANDLED;
915  }
916 
917  if (this->content.Length() == 0) {
918  this->list_pos = 0; // above stuff may result in "-1".
919  if (this->UpdateFilterState()) {
920  this->content.ForceRebuild();
921  this->InvalidateData();
922  }
923  return ES_HANDLED;
924  }
925 
926  this->selected = *this->content.Get(this->list_pos);
927 
928  if (this->UpdateFilterState()) {
929  this->content.ForceRebuild();
930  } else {
931  /* Scroll to the new content if it is outside the current range. */
932  this->ScrollToSelected();
933  }
934 
935  /* redraw window */
936  this->InvalidateData();
937  return ES_HANDLED;
938  }
939 
940  virtual void OnEditboxChanged(int wid)
941  {
942  if (wid == WID_NCL_FILTER) {
944  this->UpdateFilterState();
945  this->content.ForceRebuild();
946  this->InvalidateData();
947  }
948  }
949 
950  virtual void OnResize()
951  {
953  }
954 
955  virtual void OnReceiveContentInfo(const ContentInfo *rci)
956  {
958  this->content.ForceRebuild();
959  this->InvalidateData();
960  }
961 
962  virtual void OnDownloadComplete(ContentID cid)
963  {
964  this->content.ForceResort();
965  this->InvalidateData();
966  }
967 
968  virtual void OnConnect(bool success)
969  {
970  if (!success) {
971  ShowErrorMessage(STR_CONTENT_ERROR_COULD_NOT_CONNECT, INVALID_STRING_ID, WL_ERROR);
972  delete this;
973  return;
974  }
975 
976  this->InvalidateData();
977  }
978 
984  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
985  {
986  if (!gui_scope) return;
987  if (this->content.NeedRebuild()) this->BuildContentList();
988 
989  /* To sum all the bytes we intend to download */
990  this->filesize_sum = 0;
991  bool show_select_all = false;
992  bool show_select_upgrade = false;
993  for (ConstContentIterator iter = this->content.Begin(); iter != this->content.End(); iter++) {
994  const ContentInfo *ci = *iter;
995  switch (ci->state) {
998  this->filesize_sum += ci->filesize;
999  break;
1000 
1002  show_select_all = true;
1003  show_select_upgrade |= ci->upgrade;
1004  break;
1005 
1006  default:
1007  break;
1008  }
1009  }
1010 
1011  /* If data == 2 then the status window caused this OnInvalidate */
1014  this->SetWidgetDisabledState(WID_NCL_SELECT_ALL, !show_select_all);
1015  this->SetWidgetDisabledState(WID_NCL_SELECT_UPDATE, !show_select_upgrade);
1016  this->SetWidgetDisabledState(WID_NCL_OPEN_URL, this->selected == NULL || StrEmpty(this->selected->url));
1017  for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
1018  this->SetWidgetDisabledState(WID_NCL_TEXTFILE + tft, this->selected == NULL || this->selected->state != ContentInfo::ALREADY_HERE || this->selected->GetTextfile(tft) == NULL);
1019  }
1020 
1021  this->GetWidget<NWidgetCore>(WID_NCL_CANCEL)->widget_data = this->filesize_sum == 0 ? STR_AI_SETTINGS_CLOSE : STR_AI_LIST_CANCEL;
1022  }
1023 };
1024 
1027 
1029  &StateSorter,
1030  &TypeSorter,
1031  &NameSorter,
1032 };
1033 
1035  &TagNameFilter,
1036  &TypeOrSelectedFilter,
1037 };
1038 
1040 
1045 {
1046  for (int i = CONTENT_TYPE_BEGIN; i < CONTENT_TYPE_END; i++) {
1047  GetString(NetworkContentListWindow::content_type_strs[i], STR_CONTENT_TYPE_BASE_GRAPHICS + i - CONTENT_TYPE_BASE_GRAPHICS, lastof(NetworkContentListWindow::content_type_strs[i]));
1048  }
1049 }
1050 
1054  NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE),
1055  NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_CONTENT_TITLE, STR_NULL),
1056  NWidget(WWT_DEFSIZEBOX, COLOUR_LIGHT_BLUE),
1057  EndContainer(),
1058  NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NCL_BACKGROUND),
1061  /* Top */
1062  NWidget(WWT_EMPTY, COLOUR_LIGHT_BLUE, WID_NCL_FILTER_CAPT), SetFill(1, 0), SetResize(1, 0),
1063  NWidget(WWT_EDITBOX, COLOUR_LIGHT_BLUE, WID_NCL_FILTER), SetFill(1, 0), SetResize(1, 0),
1064  SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP),
1065  EndContainer(),
1068  /* Left side. */
1069  NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
1073  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CHECKBOX), SetMinimalSize(13, 1), SetDataTip(STR_EMPTY, STR_NULL),
1074  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TYPE),
1075  SetDataTip(STR_CONTENT_TYPE_CAPTION, STR_CONTENT_TYPE_CAPTION_TOOLTIP),
1076  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_NAME), SetResize(1, 0), SetFill(1, 0),
1077  SetDataTip(STR_CONTENT_NAME_CAPTION, STR_CONTENT_NAME_CAPTION_TOOLTIP),
1078  EndContainer(),
1079  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),
1080  EndContainer(),
1081  NWidget(NWID_VSCROLLBAR, COLOUR_LIGHT_BLUE, WID_NCL_SCROLLBAR),
1082  EndContainer(),
1084  NWidget(NWID_SELECTION, INVALID_COLOUR, WID_NCL_SEL_ALL_UPDATE), SetResize(1, 0), SetFill(1, 0),
1085  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SELECT_UPDATE), SetResize(1, 0), SetFill(1, 0),
1086  SetDataTip(STR_CONTENT_SELECT_UPDATES_CAPTION, STR_CONTENT_SELECT_UPDATES_CAPTION_TOOLTIP),
1087  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SELECT_ALL), SetResize(1, 0), SetFill(1, 0),
1088  SetDataTip(STR_CONTENT_SELECT_ALL_CAPTION, STR_CONTENT_SELECT_ALL_CAPTION_TOOLTIP),
1089  EndContainer(),
1090  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_UNSELECT), SetResize(1, 0), SetFill(1, 0),
1091  SetDataTip(STR_CONTENT_UNSELECT_ALL_CAPTION, STR_CONTENT_UNSELECT_ALL_CAPTION_TOOLTIP),
1092  EndContainer(),
1093  EndContainer(),
1094  /* Right side. */
1095  NWidget(NWID_VERTICAL), SetPIP(0, 4, 0),
1096  NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE, WID_NCL_DETAILS), SetResize(1, 1), SetFill(1, 1), EndContainer(),
1098  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
1099  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
1100  EndContainer(),
1102  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),
1103  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
1104  EndContainer(),
1105  EndContainer(),
1106  EndContainer(),
1108  /* Bottom. */
1110  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_SEARCH_EXTERNAL), SetResize(1, 0), SetFill(1, 0),
1111  SetDataTip(STR_CONTENT_SEARCH_EXTERNAL, STR_CONTENT_SEARCH_EXTERNAL_TOOLTIP),
1113  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_CANCEL), SetResize(1, 0), SetFill(1, 0),
1114  SetDataTip(STR_BUTTON_CANCEL, STR_NULL),
1115  NWidget(WWT_PUSHTXTBTN, COLOUR_WHITE, WID_NCL_DOWNLOAD), SetResize(1, 0), SetFill(1, 0),
1116  SetDataTip(STR_CONTENT_DOWNLOAD_CAPTION, STR_CONTENT_DOWNLOAD_CAPTION_TOOLTIP),
1117  EndContainer(),
1118  EndContainer(),
1120  /* Resize button. */
1122  NWidget(NWID_SPACER), SetFill(1, 0), SetResize(1, 0),
1123  NWidget(WWT_RESIZEBOX, COLOUR_LIGHT_BLUE),
1124  EndContainer(),
1125  EndContainer(),
1126 };
1127 
1130  WDP_CENTER, "list_content", 630, 460,
1132  0,
1133  _nested_network_content_list_widgets, lengthof(_nested_network_content_list_widgets)
1134 );
1135 
1144 {
1145 #if defined(WITH_ZLIB)
1146  std::bitset<CONTENT_TYPE_END> types;
1148  if (cv == NULL) {
1149  assert(type1 != CONTENT_TYPE_END || type2 == CONTENT_TYPE_END);
1150  assert(type1 == CONTENT_TYPE_END || type1 != type2);
1153 
1154  if (type1 != CONTENT_TYPE_END) types[type1] = true;
1155  if (type2 != CONTENT_TYPE_END) types[type2] = true;
1156  } else {
1158  }
1159 
1161  new NetworkContentListWindow(&_network_content_list_desc, cv != NULL, types);
1162 #else
1163  ShowErrorMessage(STR_CONTENT_NO_ZLIB, STR_CONTENT_NO_ZLIB_SUB, WL_ERROR);
1164  /* Connection failed... clean up the mess */
1165  if (cv != NULL) {
1166  for (ContentIterator iter = cv->Begin(); iter != cv->End(); iter++) delete *iter;
1167  }
1168 #endif /* WITH_ZLIB */
1169 }
1170 
1171 #endif /* ENABLE_NETWORK */