Files @ r15899:3bbe04d427d2
Branch filter:

Location: cpp/openttd-patchpack/source/src/statusbar_gui.cpp - annotation

rubidium
(svn r20593) -Fix: (rlongago, r20547): long ago the service interval was int16, after which is got converted to Date except in the order backup. Much later I copied the savegame snippets from a vehicle and applied that on the order backup. Presto, reading/writing 32 bits (of Date) into 16 bits of ancient style service interval. That would then "spoil" the name pointer and that eventually crashes OpenTTD as it's likely to be an invalid pointer.
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r9111:983de9c5a848
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r10208:ef8fcc3dc4ca
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r10208:ef8fcc3dc4ca
r9148:4c9b8a45d519
r8976:8660ab19d0e6
r10208:ef8fcc3dc4ca
r8976:8660ab19d0e6
r15514:7e3c6c9e07c2
r9117:e2b5a638a7c1
r9248:afa00db99401
r14248:a9050881acd7
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11879:4ecbaf7e5a53
r8976:8660ab19d0e6
r9199:7d9724de3af0
r9199:7d9724de3af0
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11246:b5f39f0521f0
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11879:4ecbaf7e5a53
r8976:8660ab19d0e6
r11881:e67abbaf98b1
r11881:e67abbaf98b1
r11881:e67abbaf98b1
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11881:e67abbaf98b1
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11881:e67abbaf98b1
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r11557:32a6000a8201
r11557:32a6000a8201
r11557:32a6000a8201
r11557:32a6000a8201
r11557:32a6000a8201
r11557:32a6000a8201
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r8976:8660ab19d0e6
r15175:66e0817dc450
r15175:66e0817dc450
r15175:66e0817dc450
r15175:66e0817dc450
r10646:b3b2c1ecae0d
r13174:19a8d87f3e31
r9250:54df676211ea
r9250:54df676211ea
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r8976:8660ab19d0e6
r13174:19a8d87f3e31
r9250:54df676211ea
r9250:54df676211ea
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r9250:54df676211ea
r9250:54df676211ea
r9273:0df9c11598cc
r13174:19a8d87f3e31
r9250:54df676211ea
r13695:e0bf1a35834a
r13428:b65c72efe5d8
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r15608:7b580ec7448a
r15608:7b580ec7448a
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13727:9069b5049dbd
r13428:b65c72efe5d8
r13428:b65c72efe5d8
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r15608:7b580ec7448a
r15608:7b580ec7448a
r9250:54df676211ea
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r15390:44d506cb3529
r14044:0ebf93bd0871
r13174:19a8d87f3e31
r14044:0ebf93bd0871
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r15390:44d506cb3529
r13174:19a8d87f3e31
r8976:8660ab19d0e6
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r13174:19a8d87f3e31
r9250:54df676211ea
r9250:54df676211ea
r8976:8660ab19d0e6
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r11881:e67abbaf98b1
r10646:b3b2c1ecae0d
r10640:67fddb68ce12
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r10640:67fddb68ce12
r9250:54df676211ea
r9250:54df676211ea
r8976:8660ab19d0e6
r14399:3f819c0da60a
r9250:54df676211ea
r9250:54df676211ea
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r9250:54df676211ea
r11847:235c011992a8
r8976:8660ab19d0e6
r11881:e67abbaf98b1
r11881:e67abbaf98b1
r13024:48c81d0b078a
r9250:54df676211ea
r9250:54df676211ea
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r10646:b3b2c1ecae0d
r13024:48c81d0b078a
r9250:54df676211ea
r8976:8660ab19d0e6
r9250:54df676211ea
r8976:8660ab19d0e6
r11558:a3c1f30208d3
r11558:a3c1f30208d3
r11558:a3c1f30208d3
r11725:57bc99fdc1bc
r11558:a3c1f30208d3
r11558:a3c1f30208d3
r11558:a3c1f30208d3
r11558:a3c1f30208d3
r13428:b65c72efe5d8
r13786:6205ecbdf2fa
r8976:8660ab19d0e6
r13739:747ed1f003e3
r13554:d1964ead02ee
r11368:058349c3a02c
r8976:8660ab19d0e6
r9248:afa00db99401
r9248:afa00db99401
r9248:afa00db99401
r9248:afa00db99401
r9248:afa00db99401
r9250:54df676211ea
r11881:e67abbaf98b1
r9248:afa00db99401
r9248:afa00db99401
r8976:8660ab19d0e6
r8976:8660ab19d0e6
r9250:54df676211ea
r8976:8660ab19d0e6
/* $Id$ */

/*
 * This file is part of OpenTTD.
 * 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.
 * 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.
 * 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/>.
 */

/** @file statusbar_gui.cpp The GUI for the bottom status bar. */

#include "stdafx.h"
#include "openttd.h"
#include "date_func.h"
#include "gfx_func.h"
#include "news_func.h"
#include "company_func.h"
#include "string_func.h"
#include "strings_func.h"
#include "company_base.h"
#include "tilehighlight_func.h"
#include "news_gui.h"
#include "company_gui.h"
#include "window_gui.h"
#include "saveload/saveload.h"
#include "window_func.h"
#include "statusbar_gui.h"
#include "core/geometry_func.hpp"

#include "table/strings.h"
#include "table/sprites.h"

static bool DrawScrollingStatusText(const NewsItem *ni, int scroll_pos, int left, int right, int top, int bottom)
{
	CopyInDParam(0, ni->params, lengthof(ni->params));
	StringID str = ni->string_id;

	char buf[512];
	GetString(buf, str, lastof(buf));
	const char *s = buf;

	char buffer[256];
	char *d = buffer;
	const char *last = lastof(buffer);

	for (;;) {
		WChar c = Utf8Consume(&s);
		if (c == 0) {
			break;
		} else if (c == '\n') {
			if (d + 4 >= last) break;
			d[0] = d[1] = d[2] = d[3] = ' ';
			d += 4;
		} else if (IsPrintable(c)) {
			if (d + Utf8CharLen(c) >= last) break;
			d += Utf8Encode(d, c);
		}
	}
	*d = '\0';

	DrawPixelInfo tmp_dpi;
	if (!FillDrawPixelInfo(&tmp_dpi, left, top, right - left, bottom)) return true;

	int width = GetStringBoundingBox(buffer).width;
	int pos = (_dynlang.text_dir == TD_RTL) ? (scroll_pos - width) : (right - scroll_pos - left);

	DrawPixelInfo *old_dpi = _cur_dpi;
	_cur_dpi = &tmp_dpi;
	DrawString(pos, INT16_MAX, 0, buffer, TC_LIGHT_BLUE, SA_LEFT | SA_FORCE);
	_cur_dpi = old_dpi;

	return (_dynlang.text_dir == TD_RTL) ? (pos < right - left) : (pos + width > 0);
}

enum StatusbarWidget {
	SBW_LEFT,   ///< left part of the statusbar; date is shown there
	SBW_MIDDLE, ///< middle part; current news or company name or *** SAVING *** or *** PAUSED ***
	SBW_RIGHT,  ///< right part; bank balance
};

struct StatusBarWindow : Window {
	bool saving;
	int ticker_scroll;
	int reminder_timeout;

	static const int TICKER_STOP    = 1640; ///< scrolling is finished when counter reaches this value
	static const int REMINDER_START =   91; ///< initial value of the reminder counter (right dot on the right)
	static const int REMINDER_STOP  =    0; ///< reminder disappears when counter reaches this value
	static const int COUNTER_STEP   =    2; ///< this is subtracted from active counters every tick

	StatusBarWindow(const WindowDesc *desc) : Window()
	{
		CLRBITS(this->flags4, WF_WHITE_BORDER_MASK);
		this->ticker_scroll    =   TICKER_STOP;
		this->reminder_timeout = REMINDER_STOP;

		this->InitNested(desc);
	}

	virtual Point OnInitialPosition(const WindowDesc *desc, int16 sm_width, int16 sm_height, int window_number)
	{
		Point pt = { (_screen.width - max(sm_width, desc->default_width)) / 2, _screen.height - sm_height };
		return pt;
	}

	virtual void OnPaint()
	{
		this->DrawWidgets();
	}

	virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
	{
		Dimension d;
		switch (widget) {
			case SBW_LEFT:
				SetDParam(0, MAX_YEAR * DAYS_IN_YEAR);
				d = GetStringBoundingBox(STR_WHITE_DATE_LONG);
				break;

			case SBW_RIGHT: {
				int64 max_money = UINT32_MAX;
				const Company *c;
				FOR_ALL_COMPANIES(c) max_money = max<int64>(c->money, max_money);
				SetDParam(0, 100LL * max_money);
				d = GetStringBoundingBox(STR_COMPANY_MONEY);
				break;
			}

			default:
				return;
		}

		d.width += padding.width;
		d.height += padding.height;
		*size = maxdim(d, *size);
	}

	virtual void DrawWidget(const Rect &r, int widget) const
	{
		switch (widget) {
			case SBW_LEFT:
				/* Draw the date */
				SetDParam(0, _date);
				DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, (_pause_mode || _settings_client.gui.status_long_date) ? STR_WHITE_DATE_LONG : STR_WHITE_DATE_SHORT, TC_FROMSTRING, SA_HOR_CENTER);
				break;

			case SBW_RIGHT: {
				/* Draw company money, if any */
				const Company *c = Company::GetIfValid(_local_company);
				if (c != NULL) {
					SetDParam(0, c->money);
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_COMPANY_MONEY, TC_FROMSTRING, SA_HOR_CENTER);
				}
				break;
			}

			case SBW_MIDDLE:
				/* Draw status bar */
				if (this->saving) { // true when saving is active
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_STATUSBAR_SAVING_GAME, TC_FROMSTRING, SA_HOR_CENTER);
				} else if (_do_autosave) {
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_STATUSBAR_AUTOSAVE, TC_FROMSTRING, SA_HOR_CENTER);
				} else if (_pause_mode != PM_UNPAUSED) {
					DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_STATUSBAR_PAUSED, TC_FROMSTRING, SA_HOR_CENTER);
				} else if (this->ticker_scroll < TICKER_STOP && FindWindowById(WC_NEWS_WINDOW, 0) == NULL && _statusbar_news_item != NULL && _statusbar_news_item->string_id != 0) {
					/* Draw the scrolling news text */
					if (!DrawScrollingStatusText(_statusbar_news_item, this->ticker_scroll, r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, r.bottom)) {
						InvalidateWindowData(WC_STATUS_BAR, 0, SBI_NEWS_DELETED);
						if (Company::IsValidID(_local_company)) {
							/* This is the default text */
							SetDParam(0, _local_company);
							DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_STATUSBAR_COMPANY_NAME, TC_FROMSTRING, SA_HOR_CENTER);
						}
					}
				} else {
					if (Company::IsValidID(_local_company)) {
						/* This is the default text */
						SetDParam(0, _local_company);
						DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, STR_STATUSBAR_COMPANY_NAME, TC_FROMSTRING, SA_HOR_CENTER);
					}
				}

				if (this->reminder_timeout > 0) DrawSprite(SPR_BLOT, PALETTE_TO_RED, r.right - WD_FRAMERECT_RIGHT - 10, r.top + WD_FRAMERECT_TOP + 1);
				break;
		}
	}

	virtual void OnInvalidateData(int data)
	{
		switch (data) {
			default: NOT_REACHED();
			case SBI_SAVELOAD_START:  this->saving = true;  break;
			case SBI_SAVELOAD_FINISH: this->saving = false; break;
			case SBI_SHOW_TICKER:     this->ticker_scroll = 0; break;
			case SBI_SHOW_REMINDER:   this->reminder_timeout = REMINDER_START; break;
			case SBI_NEWS_DELETED:
				this->ticker_scroll    =   TICKER_STOP; // reset ticker ...
				this->reminder_timeout = REMINDER_STOP; // ... and reminder
				break;
		}
	}

	virtual void OnClick(Point pt, int widget, int click_count)
	{
		switch (widget) {
			case SBW_MIDDLE: ShowLastNewsMessage(); break;
			case SBW_RIGHT:  if (_local_company != COMPANY_SPECTATOR) ShowCompanyFinances(_local_company); break;
			default: ResetObjectToPlace();
		}
	}

	virtual void OnTick()
	{
		if (_pause_mode != PM_UNPAUSED) return;

		if (this->ticker_scroll < TICKER_STOP) { // Scrolling text
			this->ticker_scroll += COUNTER_STEP;
			this->SetWidgetDirty(SBW_MIDDLE);
		}

		if (this->reminder_timeout > REMINDER_STOP) { // Red blot to show there are new unread newsmessages
			this->reminder_timeout -= COUNTER_STEP;
		} else if (this->reminder_timeout < REMINDER_STOP) {
			this->reminder_timeout = REMINDER_STOP;
			this->SetWidgetDirty(SBW_MIDDLE);
		}
	}
};

static const NWidgetPart _nested_main_status_widgets[] = {
	NWidget(NWID_HORIZONTAL),
		NWidget(WWT_PANEL, COLOUR_GREY, SBW_LEFT), SetMinimalSize(140, 12), EndContainer(),
		NWidget(WWT_PUSHBTN, COLOUR_GREY, SBW_MIDDLE), SetMinimalSize(40, 12), SetDataTip(0x0, STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS), SetResize(1, 0),
		NWidget(WWT_PUSHBTN, COLOUR_GREY, SBW_RIGHT), SetMinimalSize(140, 12),
	EndContainer(),
};

static const WindowDesc _main_status_desc(
	WDP_MANUAL, 640, 12,
	WC_STATUS_BAR, WC_NONE,
	WDF_UNCLICK_BUTTONS | WDF_NO_FOCUS,
	_nested_main_status_widgets, lengthof(_nested_main_status_widgets)
);

/**
 * Checks whether the news ticker is currently being used.
 */
bool IsNewsTickerShown()
{
	const StatusBarWindow *w = dynamic_cast<StatusBarWindow*>(FindWindowById(WC_STATUS_BAR, 0));
	return w != NULL && w->ticker_scroll < StatusBarWindow::TICKER_STOP;
}

void ShowStatusBar()
{
	new StatusBarWindow(&_main_status_desc);
}