diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/network.cpp | 5 | ||||
-rw-r--r-- | src/network/network_client.cpp | 10 | ||||
-rw-r--r-- | src/network/network_gui.cpp | 625 | ||||
-rw-r--r-- | src/network/network_server.cpp | 7 |
4 files changed, 511 insertions, 136 deletions
diff --git a/src/network/network.cpp b/src/network/network.cpp index 97eadb976..1e0838684 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -522,9 +522,10 @@ void ParseGameConnectionString(const char **company, const char **port, char *co /* Register the login */ _network_clients_connected++; - SetWindowDirty(WC_CLIENT_LIST, 0); ServerNetworkGameSocketHandler *cs = new ServerNetworkGameSocketHandler(s); cs->client_address = address; // Save the IP of the client + + InvalidateWindowData(WC_CLIENT_LIST, 0); } /** @@ -713,7 +714,7 @@ void NetworkClientConnectGame(NetworkAddress address, CompanyID join_as, const c static void NetworkInitGameInfo() { if (StrEmpty(_settings_client.network.server_name)) { - seprintf(_settings_client.network.server_name, lastof(_settings_client.network.server_name), "Unnamed Server"); + strecpy(_settings_client.network.server_name, "Unnamed Server", lastof(_settings_client.network.server_name)); } /* The server is a client too */ diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 6156dc486..d5fe64b11 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -647,7 +647,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac ci->client_playas = playas; strecpy(ci->client_name, name, lastof(ci->client_name)); - SetWindowDirty(WC_CLIENT_LIST, 0); + InvalidateWindowData(WC_CLIENT_LIST, 0); return NETWORK_RECV_STATUS_OKAY; } @@ -666,7 +666,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CLIENT_INFO(Pac strecpy(ci->client_name, name, lastof(ci->client_name)); - SetWindowDirty(WC_CLIENT_LIST, 0); + InvalidateWindowData(WC_CLIENT_LIST, 0); return NETWORK_RECV_STATUS_OKAY; } @@ -1043,7 +1043,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack delete ci; } - SetWindowDirty(WC_CLIENT_LIST, 0); + InvalidateWindowData(WC_CLIENT_LIST, 0); return NETWORK_RECV_STATUS_OKAY; } @@ -1062,7 +1062,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p) DEBUG(net, 0, "Unknown client (%d) is leaving the game", client_id); } - SetWindowDirty(WC_CLIENT_LIST, 0); + InvalidateWindowData(WC_CLIENT_LIST, 0); /* If we come here it means we could not locate the client.. strange :s */ return NETWORK_RECV_STATUS_OKAY; @@ -1079,7 +1079,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_JOIN(Packet *p) NetworkTextMessage(NETWORK_ACTION_JOIN, CC_DEFAULT, false, ci->client_name); } - SetWindowDirty(WC_CLIENT_LIST, 0); + InvalidateWindowData(WC_CLIENT_LIST, 0); return NETWORK_RECV_STATUS_OKAY; } diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index dd25ad5bc..fcfeded5a 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -21,6 +21,7 @@ #include "network_udp.h" #include "../window_func.h" #include "../gfx_func.h" +#include "../widgets/dropdown_type.h" #include "../widgets/dropdown_func.h" #include "../querystring_gui.h" #include "../sortlist_type.h" @@ -30,6 +31,8 @@ #include "../map_type.h" #include "../guitimer_func.h" #include "../zoom_func.h" +#include "../sprite.h" +#include "../settings_internal.h" #include "../widgets/network_widget.h" @@ -38,21 +41,24 @@ #include "../stringfilter_type.h" -#include "../safeguards.h" - #ifdef __EMSCRIPTEN__ # include <emscripten.h> #endif +#include <map> + +#include "../safeguards.h" + static void ShowNetworkStartServerWindow(); static void ShowNetworkLobbyWindow(NetworkGameList *ngl); /** - * Advertisement options in the start server window + * Visibility of the server. Public servers advertise, where private servers + * do not. */ -static const StringID _connection_types_dropdown[] = { - STR_NETWORK_START_SERVER_UNADVERTISED, - STR_NETWORK_START_SERVER_ADVERTISED, +static const StringID _server_visibility_dropdown[] = { + STR_NETWORK_SERVER_VISIBILITY_PRIVATE, + STR_NETWORK_SERVER_VISIBILITY_PUBLIC, INVALID_STRING_ID }; @@ -985,7 +991,7 @@ struct NetworkStartServerWindow : public Window { { switch (widget) { case WID_NSS_CONNTYPE_BTN: - SetDParam(0, _connection_types_dropdown[_settings_client.network.server_advertise]); + SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]); break; case WID_NSS_CLIENTS_TXT: @@ -1006,7 +1012,7 @@ struct NetworkStartServerWindow : public Window { { switch (widget) { case WID_NSS_CONNTYPE_BTN: - *size = maxdim(GetStringBoundingBox(_connection_types_dropdown[0]), GetStringBoundingBox(_connection_types_dropdown[1])); + *size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1])); size->width += padding.width; size->height += padding.height; break; @@ -1036,7 +1042,7 @@ struct NetworkStartServerWindow : public Window { break; case WID_NSS_CONNTYPE_BTN: // Connection type - ShowDropDownMenu(this, _connection_types_dropdown, _settings_client.network.server_advertise, WID_NSS_CONNTYPE_BTN, 0, 0); // do it for widget WID_NSS_CONNTYPE_BTN + ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_NSS_CONNTYPE_BTN, 0, 0); // do it for widget WID_NSS_CONNTYPE_BTN break; case WID_NSS_CLIENTS_BTND: case WID_NSS_CLIENTS_BTNU: // Click on up/down button for number of clients @@ -1175,8 +1181,8 @@ static const NWidgetPart _nested_network_start_server_window_widgets[] = { NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(10, 6, 10), NWidget(NWID_VERTICAL), SetPIP(0, 1, 0), - NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_ADVERTISED_LABEL, STR_NULL), - NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_BTN), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_START_SERVER_ADVERTISED_TOOLTIP), + NWidget(WWT_TEXT, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_LABEL), SetFill(1, 0), SetDataTip(STR_NETWORK_START_SERVER_VISIBILITY_LABEL, STR_NULL), + NWidget(WWT_DROPDOWN, COLOUR_LIGHT_BLUE, WID_NSS_CONNTYPE_BTN), SetFill(1, 0), SetDataTip(STR_BLACK_STRING, STR_NETWORK_START_SERVER_VISIBILITY_TOOLTIP), EndContainer(), NWidget(NWID_VERTICAL), SetPIP(0, 1, 0), NWidget(NWID_SPACER), SetFill(1, 1), @@ -1592,21 +1598,6 @@ static void ClientList_Ban(const NetworkClientInfo *ci) NetworkServerKickOrBanIP(ci->client_id, true, nullptr); } -static void ClientList_SpeakToClient(const NetworkClientInfo *ci) -{ - ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, ci->client_id); -} - -static void ClientList_SpeakToCompany(const NetworkClientInfo *ci) -{ - ShowNetworkChatQueryWindow(DESTTYPE_TEAM, ci->client_playas); -} - -static void ClientList_SpeakToAll(const NetworkClientInfo *ci) -{ - ShowNetworkChatQueryWindow(DESTTYPE_BROADCAST, 0); -} - /** Popup selection window to chose an action to perform */ struct NetworkClientListPopupWindow : Window { /** Container for actions that can be executed. */ @@ -1639,15 +1630,6 @@ struct NetworkClientListPopupWindow : Window { const NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id); - if (_network_own_client_id != ci->client_id) { - this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_CLIENT, &ClientList_SpeakToClient); - } - - if (Company::IsValidID(ci->client_playas) || ci->client_playas == COMPANY_SPECTATOR) { - this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_COMPANY, &ClientList_SpeakToCompany); - } - this->AddAction(STR_NETWORK_CLIENTLIST_SPEAK_TO_ALL, &ClientList_SpeakToAll); - /* A server can kick clients (but not himself). */ if (_network_server && _network_own_client_id != ci->client_id) { this->AddAction(STR_NETWORK_CLIENTLIST_KICK, &ClientList_Kick); @@ -1671,7 +1653,7 @@ struct NetworkClientListPopupWindow : Window { } d.height *= (uint)this->actions.size(); - d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT; + d.width += WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT + 4 + 4; // Give the list a bit of padding on both sides. d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; *size = d; } @@ -1690,7 +1672,7 @@ struct NetworkClientListPopupWindow : Window { colour = TC_BLACK; } - DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, action.name, colour); + DrawString(r.left + WD_FRAMERECT_LEFT + 4, r.right - WD_FRAMERECT_RIGHT - 4, y, action.name, colour); y += FONT_HEIGHT_NORMAL; } } @@ -1731,166 +1713,553 @@ static void PopupClientList(ClientID client_id, int x, int y) static const NWidgetPart _nested_client_list_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_CLOSEBOX, COLOUR_GREY), - NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_COMPANY_LIST_CLIENT_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_GREY), NWidget(WWT_STICKYBOX, COLOUR_GREY), EndContainer(), - NWidget(WWT_PANEL, COLOUR_GREY, WID_CL_PANEL), SetMinimalSize(250, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM), SetResize(1, 1), EndContainer(), + NWidget(WWT_PANEL, COLOUR_GREY), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_CL_SERVER_SELECTOR), + NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER, STR_NULL), SetPadding(4, 4, 0, 4), SetPIP(0, 2, 0), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_NAME, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_SERVER_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_SERVER_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_SERVER_NAME_EDIT_TOOLTIP), + EndContainer(), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(20, 0), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_CL_SERVER_VISIBILITY), SetDataTip(STR_BLACK_STRING, STR_NETWORK_CLIENT_LIST_SERVER_VISIBILITY_TOOLTIP), + EndContainer(), + EndContainer(), + EndContainer(), + NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER, STR_NULL), SetPadding(4, 4, 4, 4), SetPIP(0, 2, 0), + NWidget(NWID_HORIZONTAL), SetPIP(0, 3, 0), + NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalTextLines(1, 0), SetDataTip(STR_NETWORK_CLIENT_LIST_PLAYER_NAME, STR_NULL), + NWidget(NWID_SPACER), SetMinimalSize(20, 0), + NWidget(WWT_TEXT, COLOUR_GREY, WID_CL_CLIENT_NAME), SetFill(1, 0), SetMinimalTextLines(1, 0), SetResize(1, 0), SetDataTip(STR_BLACK_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_TOOLTIP), SetAlignment(SA_VERT_CENTER | SA_RIGHT), + NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_CL_CLIENT_NAME_EDIT), SetMinimalSize(12, 14), SetDataTip(SPR_RENAME, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_EDIT_TOOLTIP), + EndContainer(), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_CL_MATRIX), SetMinimalSize(180, 0), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_CL_SCROLLBAR), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_CL_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_GREY), + EndContainer(), + EndContainer(), + EndContainer(), }; static WindowDesc _client_list_desc( - WDP_AUTO, "list_clients", 0, 0, + WDP_AUTO, "list_clients", 220, 300, WC_CLIENT_LIST, WC_NONE, 0, _nested_client_list_widgets, lengthof(_nested_client_list_widgets) ); /** + * Button shown for either a company or client in the client-list. + * + * These buttons are dynamic and strongly depends on which company/client + * what buttons are available. This class allows dynamically creating them + * as the current Widget system does not. + */ +class ButtonCommon { +public: + SpriteID sprite; ///< The sprite to use on the button. + StringID tooltip; ///< The tooltip of the button. + Colours colour; ///< The colour of the button. + bool disabled; ///< Is the button disabled? + uint height; ///< Calculated height of the button. + uint width; ///< Calculated width of the button. + + ButtonCommon(SpriteID sprite, StringID tooltip, Colours colour) : + sprite(sprite), + tooltip(tooltip), + colour(colour), + disabled(false) + { + Dimension d = GetSpriteSize(sprite); + this->height = d.height + ScaleGUITrad(WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM); + this->width = d.width + ScaleGUITrad(WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT); + } + virtual ~ButtonCommon() {} + + /** + * OnClick handler for when the button is pressed. + */ + virtual void OnClick(struct NetworkClientListWindow *w, Point pt) = 0; +}; + +/** + * Template version of Button, with callback support. + */ +template<typename T> +class Button : public ButtonCommon { +private: + typedef void (*ButtonCallback)(struct NetworkClientListWindow *w, Point pt, T id); ///< Callback function to call on click. + T id; ///< ID this button belongs to. + ButtonCallback proc; ///< Callback proc to call when button is pressed. + +public: + Button(SpriteID sprite, StringID tooltip, Colours colour, T id, ButtonCallback proc) : + ButtonCommon(sprite, tooltip, colour), + id(id), + proc(proc) + { + assert(proc != nullptr); + } + + void OnClick(struct NetworkClientListWindow *w, Point pt) override + { + if (this->disabled) return; + + this->proc(w, pt, this->id); + } +}; + +using CompanyButton = Button<CompanyID>; +using ClientButton = Button<ClientID>; + +/** * Main handle for clientlist */ struct NetworkClientListWindow : Window { - int selected_item; +private: + ClientListWidgets query_widget; ///< During a query this tracks what widget caused the query. + CompanyID join_company; ///< During query for company password, this stores what company we wanted to join. - uint server_client_width; - uint line_height; + Scrollbar *vscroll; ///< Vertical scrollbar of this window. + uint line_height; ///< Current lineheight of each entry in the matrix. + uint line_count; ///< Amount of lines in the matrix. - Dimension icon_size; + std::map<uint, std::vector<std::unique_ptr<ButtonCommon>>> buttons; ///< Per line which buttons are available. - NetworkClientListWindow(WindowDesc *desc, WindowNumber window_number) : - Window(desc), - selected_item(-1) + static const int CLIENT_OFFSET_LEFT = 12; ///< Offset of client entries compared to company entries. + + /** + * Chat button on a Company is clicked. + * @param w The instance of this window. + * @param pt The point where this button was clicked. + * @param company_id The company this button was assigned to. + */ + static void OnClickCompanyChat(NetworkClientListWindow *w, Point pt, CompanyID company_id) { - this->InitNested(window_number); + ShowNetworkChatQueryWindow(DESTTYPE_TEAM, company_id); + } + + /** + * Join button on a Company is clicked. + * @param w The instance of this window. + * @param pt The point where this button was clicked. + * @param company_id The company this button was assigned to. + */ + static void OnClickCompanyJoin(NetworkClientListWindow *w, Point pt, CompanyID company_id) + { + if (_network_server) { + NetworkServerDoMove(CLIENT_ID_SERVER, company_id); + MarkWholeScreenDirty(); + } else if (NetworkCompanyIsPassworded(company_id)) { + w->query_widget = WID_CL_COMPANY_JOIN; + w->join_company = company_id; + ShowQueryString(STR_EMPTY, STR_NETWORK_NEED_COMPANY_PASSWORD_CAPTION, NETWORK_PASSWORD_LENGTH, w, CS_ALPHANUMERAL, QSF_PASSWORD); + } else { + NetworkClientRequestMove(company_id); + } } /** - * Finds the amount of clients and set the height correct + * Admin button on a Client is clicked. + * @param w The instance of this window. + * @param pt The point where this button was clicked. + * @param client_id The client this button was assigned to. */ - bool CheckClientListHeight() + static void OnClickClientAdmin(NetworkClientListWindow *w, Point pt, ClientID client_id) { - int num = 0; + PopupClientList(client_id, pt.x + w->left, pt.y + w->top); + } - /* Should be replaced with a loop through all clients */ + /** + * Chat button on a Client is clicked. + * @param w The instance of this window. + * @param pt The point where this button was clicked. + * @param client_id The client this button was assigned to. + */ + static void OnClickClientChat(NetworkClientListWindow *w, Point pt, ClientID client_id) + { + ShowNetworkChatQueryWindow(DESTTYPE_CLIENT, client_id); + } + + /** + * Part of RebuildList() to create the information for a single company. + * @param company_id The company to build the list for. + * @param own_ci The NetworkClientInfo of the client itself. + */ + void RebuildListCompany(CompanyID company_id, const NetworkClientInfo *own_ci) + { + ButtonCommon *chat_button = new CompanyButton(SPR_CHAT, company_id == COMPANY_SPECTATOR ? STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP : STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyChat); + + this->buttons[line_count].emplace_back(chat_button); + if (own_ci->client_playas != company_id) this->buttons[line_count].emplace_back(new CompanyButton(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin)); + + this->line_count += 1; + + bool has_players = false; for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) { - if (ci->client_playas != COMPANY_INACTIVE_CLIENT) num++; + if (ci->client_playas != company_id) continue; + has_players = true; + + if (_network_own_client_id != ci->client_id) this->buttons[line_count].emplace_back(new ClientButton(SPR_CHAT, STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP, COLOUR_ORANGE, ci->client_id, &NetworkClientListWindow::OnClickClientChat)); + if (_network_server && _network_own_client_id != ci->client_id) this->buttons[line_count].emplace_back(new ClientButton(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_TOOLTIP, COLOUR_RED, ci->client_id, &NetworkClientListWindow::OnClickClientAdmin)); + + this->line_count += 1; + } + + chat_button->disabled = !has_players; + } + + /** + * Rebuild the list, meaning: calculate the lines needed and what buttons go on which line. + */ + void RebuildList() + { + const NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id); + + this->buttons.clear(); + this->line_count = 0; + + /* Companies */ + for (const Company *c : Company::Iterate()) { + this->RebuildListCompany(c->index, own_ci); } - num *= this->line_height; + /* Spectators */ + this->RebuildListCompany(COMPANY_SPECTATOR, own_ci); + + this->vscroll->SetCount(this->line_count); + } + + /** + * Get the button at a specific point on the WID_CL_MATRIX. + * @param pt The point to look for a button. + * @return The button or a nullptr if there was none. + */ + ButtonCommon *GetButtonAtPoint(Point pt) + { + uint index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CL_MATRIX); + NWidgetBase *widget_matrix = this->GetWidget<NWidgetBase>(WID_CL_MATRIX); - int diff = (num + WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM) - (this->GetWidget<NWidgetBase>(WID_CL_PANEL)->current_y); - /* If height is changed */ - if (diff != 0) { - ResizeWindow(this, 0, diff, false); - return false; + bool rtl = _current_text_dir == TD_RTL; + uint x = rtl ? (uint)widget_matrix->pos_x + WD_FRAMERECT_LEFT : widget_matrix->current_x - WD_FRAMERECT_RIGHT; + + /* Find the buttons for this row. */ + auto button_find = this->buttons.find(index); + if (button_find == this->buttons.end()) return nullptr; + + /* Check if we want to display a tooltip for any of the buttons. */ + for (auto &button : button_find->second) { + uint left = rtl ? x : x - button->width; + uint right = rtl ? x + button->width : x; + + if (IsInsideMM(pt.x, left, right)) { + return button.get(); + } + + int width = button->width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT; + x += rtl ? width : -width; } - return true; + + return nullptr; + } + +public: + NetworkClientListWindow(WindowDesc *desc, WindowNumber window_number) : + Window(desc) + { + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_CL_SCROLLBAR); + this->OnInvalidateData(); + this->FinishInitNested(window_number); + } + + void OnInvalidateData(int data = 0, bool gui_scope = true) override + { + this->RebuildList(); + + /* Currently server information is not sync'd to clients, so we cannot show it on clients. */ + this->GetWidget<NWidgetStacked>(WID_CL_SERVER_SELECTOR)->SetDisplayedPlane(_network_server ? 0 : SZSP_HORIZONTAL); } void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) override { - if (widget != WID_CL_PANEL) return; + switch (widget) { + case WID_CL_SERVER_VISIBILITY: + *size = maxdim(GetStringBoundingBox(_server_visibility_dropdown[0]), GetStringBoundingBox(_server_visibility_dropdown[1])); + size->width += padding.width; + size->height += padding.height; + break; - this->server_client_width = std::max(GetStringBoundingBox(STR_NETWORK_SERVER).width, GetStringBoundingBox(STR_NETWORK_CLIENT).width) + WD_FRAMERECT_RIGHT; - this->icon_size = GetSpriteSize(SPR_COMPANY_ICON); - this->line_height = std::max(this->icon_size.height + 2U, (uint)FONT_HEIGHT_NORMAL); + case WID_CL_MATRIX: { + uint height = std::max({GetSpriteSize(SPR_COMPANY_ICON).height, GetSpriteSize(SPR_JOIN).height, GetSpriteSize(SPR_ADMIN).height, GetSpriteSize(SPR_CHAT).height}); + height += ScaleGUITrad(WD_FRAMERECT_TOP) + ScaleGUITrad(WD_FRAMERECT_BOTTOM); + this->line_height = std::max(height, (uint)FONT_HEIGHT_NORMAL) + ScaleGUITrad(WD_MATRIX_TOP + WD_MATRIX_BOTTOM); - uint width = 100; // Default width - for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) { - width = std::max(width, GetStringBoundingBox(ci->client_name).width); + resize->width = 1; + resize->height = this->line_height; + fill->height = this->line_height; + size->height = std::max(size->height, 5 * this->line_height); + break; + } } + } - size->width = WD_FRAMERECT_LEFT + this->server_client_width + this->icon_size.width + WD_FRAMERECT_LEFT + width + WD_FRAMERECT_RIGHT; + void OnResize() override + { + this->vscroll->SetCapacityFromWidget(this, WID_CL_MATRIX); } - void OnPaint() override + void SetStringParameters(int widget) const override { - /* Check if we need to reset the height */ - if (!this->CheckClientListHeight()) return; + switch (widget) { + case WID_CL_SERVER_NAME: + SetDParamStr(0, _settings_client.network.server_name); + break; - this->DrawWidgets(); + case WID_CL_SERVER_VISIBILITY: + SetDParam(0, _server_visibility_dropdown[_settings_client.network.server_advertise]); + break; + + case WID_CL_CLIENT_NAME: + SetDParamStr(0, _settings_client.network.client_name); + break; + } } - void DrawWidget(const Rect &r, int widget) const override + void OnClick(Point pt, int widget, int click_count) override { - if (widget != WID_CL_PANEL) return; + switch (widget) { + case WID_CL_SERVER_NAME_EDIT: + if (!_network_server) break; - bool rtl = _current_text_dir == TD_RTL; - int icon_offset = (this->line_height - icon_size.height) / 2; - int text_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2; + this->query_widget = WID_CL_SERVER_NAME_EDIT; + SetDParamStr(0, _settings_client.network.server_name); + ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_CLIENT_LIST_SERVER_NAME_QUERY_CAPTION, NETWORK_NAME_LENGTH, this, CS_ALPHANUMERAL, QSF_LEN_IN_CHARS); + break; - uint y = r.top + WD_FRAMERECT_TOP; - uint left = r.left + WD_FRAMERECT_LEFT; - uint right = r.right - WD_FRAMERECT_RIGHT; - uint type_icon_width = this->server_client_width + this->icon_size.width + WD_FRAMERECT_LEFT; + case WID_CL_CLIENT_NAME_EDIT: + this->query_widget = WID_CL_CLIENT_NAME_EDIT; + SetDParamStr(0, _settings_client.network.client_name); + ShowQueryString(STR_JUST_RAW_STRING, STR_NETWORK_CLIENT_LIST_PLAYER_NAME_QUERY_CAPTION, NETWORK_CLIENT_NAME_LENGTH, this, CS_ALPHANUMERAL, QSF_LEN_IN_CHARS); + break; + case WID_CL_SERVER_VISIBILITY: + if (!_network_server) break; - uint type_left = rtl ? right - this->server_client_width : left; - uint type_right = rtl ? right : left + this->server_client_width - 1; - uint icon_left = rtl ? right - type_icon_width + WD_FRAMERECT_LEFT : left + this->server_client_width; - uint name_left = rtl ? left : left + type_icon_width; - uint name_right = rtl ? right - type_icon_width : right; + ShowDropDownMenu(this, _server_visibility_dropdown, _settings_client.network.server_advertise, WID_CL_SERVER_VISIBILITY, 0, 0); + break; - int i = 0; - for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) { - TextColour colour; - if (this->selected_item == i++) { // Selected item, highlight it - GfxFillRect(r.left + 1, y, r.right - 1, y + this->line_height - 1, PC_BLACK); - colour = TC_WHITE; - } else { - colour = TC_BLACK; + case WID_CL_MATRIX: { + ButtonCommon *button = this->GetButtonAtPoint(pt); + if (button == nullptr) break; + + button->OnClick(this, pt); + break; } + } + } - if (ci->client_id == CLIENT_ID_SERVER) { - DrawString(type_left, type_right, y + text_offset, STR_NETWORK_SERVER, colour); - } else { - DrawString(type_left, type_right, y + text_offset, STR_NETWORK_CLIENT, colour); + bool OnTooltip(Point pt, int widget, TooltipCloseCondition close_cond) override + { + switch (widget) { + case WID_CL_MATRIX: { + ButtonCommon *button = this->GetButtonAtPoint(pt); + if (button == nullptr) return false; + + GuiShowTooltips(this, button->tooltip, 0, nullptr, close_cond); + return true; + }; + } + + return false; + } + + void OnDropdownSelect(int widget, int index) override + { + switch (widget) { + case WID_CL_SERVER_VISIBILITY: + if (!_network_server) break; + + _settings_client.network.server_advertise = (index != 0); + break; + + default: + NOT_REACHED(); + } + + this->SetDirty(); + } + + void OnQueryTextFinished(char *str) override + { + if (str == nullptr) return; + + switch (this->query_widget) { + default: NOT_REACHED(); + + case WID_CL_SERVER_NAME_EDIT: { + if (!_network_server) break; + + uint index; + GetSettingFromName("network.server_name", &index); + SetSettingValue(index, StrEmpty(str) ? "Unnamed Server" : str); + this->InvalidateData(); + break; } - /* Filter out spectators */ - if (Company::IsValidID(ci->client_playas)) DrawCompanyIcon(ci->client_playas, icon_left, y + icon_offset); + case WID_CL_CLIENT_NAME_EDIT: { + if (!NetworkValidateClientName(str)) break; - DrawString(name_left, name_right, y + text_offset, ci->client_name, colour); + uint index; + GetSettingFromName("network.client_name", &index); + SetSettingValue(index, str); + this->InvalidateData(); + break; + } - y += line_height; + case WID_CL_COMPANY_JOIN: + NetworkClientRequestMove(this->join_company, str); + break; } } - void OnClick(Point pt, int widget, int click_count) override + /** + * Draw the buttons for a single line in the matrix. + * + * The x-position in RTL is the most left or otherwise the most right pixel + * we can draw the buttons from. + * + * @param x The x-position to start with the buttons. Updated during this function. + * @param y The y-position to start with the buttons. + * @param buttons The buttons to draw. + */ + void DrawButtons(uint &x, uint y, const std::vector<std::unique_ptr<ButtonCommon>> &buttons) const { - /* Show the popup with option */ - if (this->selected_item != -1) { - int client_no = this->selected_item; - for (NetworkClientInfo *ci : NetworkClientInfo::Iterate()) { - if (client_no == 0) { - PopupClientList(ci->client_id, pt.x + this->left, pt.y + this->top); - break; - } - client_no--; + for (auto &button : buttons) { + bool rtl = _current_text_dir == TD_RTL; + + uint left = rtl ? x : x - button->width; + uint right = rtl ? x + button->width : x; + + int offset = std::max(0, ((int)(this->line_height + 1) - (int)button->height) / 2); + + DrawFrameRect(left, y + offset, right, y + offset + button->height, button->colour, FR_NONE); + DrawSprite(button->sprite, PAL_NONE, left + ScaleGUITrad(WD_FRAMERECT_LEFT), y + offset + ScaleGUITrad(WD_FRAMERECT_TOP)); + if (button->disabled) { + GfxFillRect(left + 1, y + offset + 1, right - 1, y + offset + button->height - 1, _colour_gradient[button->colour & 0xF][2], FILLRECT_CHECKER); } + + int width = button->width + WD_FRAMERECT_LEFT + WD_FRAMERECT_RIGHT; + x += rtl ? width : -width; } } - void OnMouseOver(Point pt, int widget) override + /** + * Draw a company and its clients on the matrix. + * @param c The company to draw. + * @param left The most left pixel of the line. + * @param right The most right pixel of the line. + * @param top The top of the first line. + * @param line The Nth line we are drawing. Updated during this function. + */ + void DrawCompany(const Company *c, uint left, uint right, uint top, uint &line) const { - /* -1 means we left the current window */ - if (pt.y == -1) { - this->selected_item = -1; - this->SetDirty(); - return; + bool rtl = _current_text_dir == TD_RTL; + int text_y_offset = std::max(0, ((int)(this->line_height + 1) - (int)FONT_HEIGHT_NORMAL) / 2) + WD_MATRIX_BOTTOM; + + Dimension d = GetSpriteSize(SPR_COMPANY_ICON); + int offset = std::max(0, ((int)(this->line_height + 1) - (int)d.height) / 2); + + uint text_left = left + (rtl ? (uint)WD_FRAMERECT_LEFT : d.width + 8); + uint text_right = right - (rtl ? d.width + 8 : (uint)WD_FRAMERECT_RIGHT); + + uint line_start = this->vscroll->GetPosition(); + uint line_end = line_start + this->vscroll->GetCapacity(); + + uint y = top + (this->line_height * (line - line_start)); + + /* Draw the company line (if in range of scrollbar). */ + if (IsInsideMM(line, line_start, line_end)) { + uint x = rtl ? text_left : text_right; + + /* If there are buttons for this company, draw them. */ + auto button_find = this->buttons.find(line); + if (button_find != this->buttons.end()) { + this->DrawButtons(x, y, button_find->second); + } + + if (c == nullptr) { + DrawSprite(SPR_COMPANY_ICON, PALETTE_TO_GREY, rtl ? right - d.width - 4 : left + 4, y + offset); + DrawString(rtl ? x : text_left, rtl ? text_right : x, y + text_y_offset, STR_NETWORK_CLIENT_LIST_SPECTATORS, TC_SILVER); + } else { + DrawCompanyIcon(c->index, rtl ? right - d.width - 4 : left + 4, y + offset); + + SetDParam(0, c->index); + SetDParam(1, c->index); + DrawString(rtl ? x : text_left, rtl ? text_right : x, y + text_y_offset, STR_COMPANY_NAME, TC_SILVER); + } } - /* Find the new selected item (if any) */ - pt.y -= this->GetWidget<NWidgetBase>(WID_CL_PANEL)->pos_y; - int item = -1; - if (IsInsideMM(pt.y, WD_FRAMERECT_TOP, this->GetWidget<NWidgetBase>(WID_CL_PANEL)->current_y - WD_FRAMERECT_BOTTOM)) { - item = (pt.y - WD_FRAMERECT_TOP) / this->line_height; + y += this->line_height; + line++; + + for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) { + if (c != nullptr && ci->client_playas != c->index) continue; + if (c == nullptr && ci->client_playas != COMPANY_SPECTATOR) continue; + + /* Draw the player line (if in range of scrollbar). */ + if (IsInsideMM(line, line_start, line_end)) { + uint x = rtl ? text_left : text_right; + + /* If there are buttons for this client, draw them. */ + auto button_find = this->buttons.find(line); + if (button_find != this->buttons.end()) { + this->DrawButtons(x, y, button_find->second); + } + + StringID client_string = STR_JUST_RAW_STRING; + + if (ci->client_id == CLIENT_ID_SERVER) { + client_string = STR_NETWORK_CLIENT_LIST_PLAYER_HOST; + } + if (ci->client_id == _network_own_client_id) { + client_string = STR_NETWORK_CLIENT_LIST_PLAYER_SELF; + } + + SetDParamStr(0, ci->client_name); + DrawString(rtl ? x : text_left + CLIENT_OFFSET_LEFT, rtl ? text_right - CLIENT_OFFSET_LEFT : x, y + text_y_offset, client_string, TC_BLACK); + } + + y += this->line_height; + line++; } + } + + void DrawWidget(const Rect &r, int widget) const override + { + switch (widget) { + case WID_CL_MATRIX: { + uint line = 0; - /* It did not change.. no update! */ - if (item == this->selected_item) return; - this->selected_item = item; + for (const Company *c : Company::Iterate()) { + this->DrawCompany(c, r.left, r.right, r.top, line); + } + /* Specators */ + this->DrawCompany(nullptr, r.left, r.right, r.top, line); - /* Repaint */ - this->SetDirty(); + break; + } + } } }; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index a0d1a0066..746077abd 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -288,13 +288,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta _network_clients_connected--; DeleteWindowById(WC_CLIENT_LIST_POPUP, this->client_id); - SetWindowDirty(WC_CLIENT_LIST, 0); this->SendPackets(true); delete this->GetInfo(); delete this; + InvalidateWindowData(WC_CLIENT_LIST, 0); + return status; } @@ -1043,6 +1044,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_MAP_OK(Packet * this->GetClientName(client_name, lastof(client_name)); NetworkTextMessage(NETWORK_ACTION_JOIN, CC_DEFAULT, false, client_name, nullptr, this->client_id); + InvalidateWindowData(WC_CLIENT_LIST, 0); /* Mark the client as pre-active, and wait for an ACK * so we know he is done loading and in sync with us */ @@ -2061,6 +2063,9 @@ void NetworkServerDoMove(ClientID client_id, CompanyID company_id) NetworkAction action = (company_id == COMPANY_SPECTATOR) ? NETWORK_ACTION_COMPANY_SPECTATOR : NETWORK_ACTION_COMPANY_JOIN; NetworkServerSendChat(action, DESTTYPE_BROADCAST, 0, "", client_id, company_id + 1); + + InvalidateWindowClassesData(WC_CLIENT_LIST_POPUP); + InvalidateWindowData(WC_CLIENT_LIST, 0); } /** |