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