CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Dialog/PSPMsgDialog.cpp
Views: 1401
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <algorithm>
19
#include "Common/Serialize/Serializer.h"
20
#include "Common/Serialize/SerializeFuncs.h"
21
#include "Common/StringUtils.h"
22
#include "Core/Dialog/PSPMsgDialog.h"
23
#include "Core/Dialog/PSPSaveDialog.h"
24
#include "Core/Util/PPGeDraw.h"
25
#include "Core/HLE/sceCtrl.h"
26
#include "Core/MemMapHelpers.h"
27
#include "Core/Reporting.h"
28
#include "Common/Data/Text/I18n.h"
29
#include "Common/Data/Encoding/Utf8.h"
30
31
static const float FONT_SCALE = 0.65f;
32
33
// These are rough, it seems to take a long time to init, and probably depends on threads.
34
// TODO: This takes like 700ms on a PSP but that's annoyingly long.
35
const static int MSG_INIT_DELAY_US = 300000;
36
const static int MSG_SHUTDOWN_DELAY_US = 26000;
37
38
PSPMsgDialog::PSPMsgDialog(UtilityDialogType type) : PSPDialog(type) {
39
}
40
41
PSPMsgDialog::~PSPMsgDialog() {
42
}
43
44
int PSPMsgDialog::Init(unsigned int paramAddr) {
45
// Ignore if already running
46
if (GetStatus() != SCE_UTILITY_STATUS_NONE) {
47
ERROR_LOG_REPORT(Log::sceUtility, "sceUtilityMsgDialogInitStart: invalid status");
48
return SCE_ERROR_UTILITY_INVALID_STATUS;
49
}
50
51
messageDialogAddr = paramAddr;
52
if (!Memory::IsValidAddress(messageDialogAddr))
53
{
54
return 0;
55
}
56
int size = Memory::Read_U32(paramAddr);
57
memset(&messageDialog,0,sizeof(messageDialog));
58
// Only copy the right size to support different request format
59
Memory::Memcpy(&messageDialog,paramAddr,size);
60
61
// debug info
62
int optionsNotCoded = messageDialog.options & ~SCE_UTILITY_MSGDIALOG_OPTION_SUPPORTED;
63
if(optionsNotCoded)
64
{
65
ERROR_LOG_REPORT(Log::sceUtility, "PSPMsgDialog options not coded : 0x%08x", optionsNotCoded);
66
}
67
68
flag = 0;
69
scrollPos_ = 0.0f;
70
framesUpHeld_ = 0;
71
framesDownHeld_ = 0;
72
73
// Check request invalidity
74
if(messageDialog.type == 0 && !(messageDialog.errorNum & 0x80000000))
75
{
76
flag |= DS_ERROR;
77
messageDialog.result = SCE_UTILITY_MSGDIALOG_ERROR_ERRORCODEINVALID;
78
}
79
else if(size == SCE_UTILITY_MSGDIALOG_SIZE_V2 && messageDialog.type == 1)
80
{
81
unsigned int validOp = SCE_UTILITY_MSGDIALOG_OPTION_TEXTSOUND |
82
SCE_UTILITY_MSGDIALOG_OPTION_YESNO |
83
SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO;
84
if (((messageDialog.options | validOp) ^ validOp) != 0)
85
{
86
flag |= DS_ERROR;
87
messageDialog.result = SCE_UTILITY_MSGDIALOG_ERROR_BADOPTION;
88
}
89
}
90
else if(size == SCE_UTILITY_MSGDIALOG_SIZE_V3)
91
{
92
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO) &&
93
!(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_YESNO))
94
{
95
flag |= DS_ERROR;
96
messageDialog.result = SCE_UTILITY_MSGDIALOG_ERROR_BADOPTION;
97
}
98
if (messageDialog.options & ~SCE_UTILITY_MSGDIALOG_OPTION_SUPPORTED)
99
{
100
flag |= DS_ERROR;
101
messageDialog.result = SCE_UTILITY_MSGDIALOG_ERROR_BADOPTION;
102
}
103
}
104
105
if(flag == 0)
106
{
107
yesnoChoice = 1;
108
if(messageDialog.type == 1)
109
flag |= DS_MSG;
110
if(messageDialog.type == 0)
111
flag |= DS_ERRORMSG;
112
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_YESNO) &&
113
((size == SCE_UTILITY_MSGDIALOG_SIZE_V3) ||
114
(size == SCE_UTILITY_MSGDIALOG_SIZE_V2 && messageDialog.type == 1)))
115
flag |= DS_YESNO;
116
if(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_DEFAULT_NO)
117
{
118
yesnoChoice = 0;
119
flag |= DS_DEFNO;
120
}
121
if((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_OK) && (size == SCE_UTILITY_MSGDIALOG_SIZE_V3))
122
{
123
yesnoChoice = 1;
124
flag |= DS_OK;
125
}
126
if((flag & DS_YESNO) || (flag & DS_OK))
127
flag |= DS_VALIDBUTTON;
128
if(!((messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_NOCANCEL) && (size == SCE_UTILITY_MSGDIALOG_SIZE_V3)))
129
flag |= DS_CANCELBUTTON;
130
if(messageDialog.options & SCE_UTILITY_MSGDIALOG_OPTION_NOSOUND)
131
flag |= DS_NOSOUND;
132
}
133
134
if (flag & DS_ERRORMSG) {
135
FormatErrorCode(messageDialog.errorNum);
136
} else {
137
truncate_cpy(msgText, messageDialog.string);
138
}
139
140
ChangeStatusInit(MSG_INIT_DELAY_US);
141
142
UpdateButtons();
143
InitCommon();
144
StartFade(true);
145
return 0;
146
}
147
148
149
void PSPMsgDialog::FormatErrorCode(uint32_t code) {
150
auto err = GetI18NCategory(I18NCat::DIALOG);
151
152
switch (code) {
153
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN:
154
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataDataBroken", "Save data was corrupt."), code);
155
break;
156
157
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_MS:
158
case SCE_UTILITY_SAVEDATA_ERROR_RW_NO_MEMSTICK:
159
case SCE_UTILITY_SAVEDATA_ERROR_SAVE_NO_MS:
160
case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_MS:
161
case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_MS:
162
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataNoMS", "Memory stick not inserted."), code);
163
break;
164
165
case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA:
166
case SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA:
167
case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA:
168
case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA:
169
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataNoData", "Warning: no save data was found."), code);
170
break;
171
172
case SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL:
173
case SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE:
174
snprintf(msgText, 512, "%s (%08x)", err->T_cstr("MsgErrorSavedataMSFull", "Memory stick full. Check your storage space."), code);
175
break;
176
177
default:
178
snprintf(msgText, 512, "%s %08x", err->T_cstr("MsgErrorCode", "Error code:"), code);
179
}
180
}
181
182
void PSPMsgDialog::DisplayMessage(const std::string &text, bool hasYesNo, bool hasOK) {
183
auto di = GetI18NCategory(I18NCat::DIALOG);
184
185
PPGeStyle buttonStyle = FadedStyle(PPGeAlign::BOX_CENTER, FONT_SCALE);
186
PPGeStyle messageStyle = FadedStyle(PPGeAlign::BOX_HCENTER, FONT_SCALE);
187
188
// Without the scrollbar, we have 390 total pixels.
189
float WRAP_WIDTH = 340.0f;
190
if ((size_t)UTF8StringNonASCIICount(text) >= text.size() / 4) {
191
WRAP_WIDTH = 376.0f;
192
if (text.size() > 12) {
193
messageStyle.scale = 0.6f;
194
}
195
}
196
197
float totalHeight = 0.0f;
198
PPGeMeasureText(nullptr, &totalHeight, text, FONT_SCALE, PPGE_LINE_WRAP_WORD, WRAP_WIDTH);
199
// The PSP normally only shows about 8 lines at a time.
200
// For improved UX, we intentionally show part of the next line.
201
float visibleHeight = std::min(totalHeight, 175.0f);
202
float h2 = visibleHeight / 2.0f;
203
204
float centerY = 135.0f;
205
float sy = centerY - h2 - 15.0f;
206
float ey = centerY + h2 + 20.0f;
207
float buttonY = centerY + h2 + 5.0f;
208
209
auto drawSelectionBoxAndAdjust = [&](float x) {
210
// Box has a fixed size.
211
float w = 15.0f;
212
float h = 8.0f;
213
PPGeDrawRect(x - w, buttonY - h, x + w, buttonY + h, CalcFadedColor(0x6DCFCFCF));
214
215
centerY -= h + 5.0f;
216
sy -= h + 5.0f;
217
ey = buttonY + h * 2.0f + 5.0f;
218
};
219
220
if (hasYesNo) {
221
if (yesnoChoice == 1) {
222
drawSelectionBoxAndAdjust(204.0f);
223
} else {
224
drawSelectionBoxAndAdjust(273.0f);
225
}
226
227
PPGeDrawText(di->T("Yes"), 203.0f, buttonY - 1.0f, buttonStyle);
228
PPGeDrawText(di->T("No"), 272.0f, buttonY - 1.0f, buttonStyle);
229
if (IsButtonPressed(CTRL_LEFT) && yesnoChoice == 0) {
230
yesnoChoice = 1;
231
} else if (IsButtonPressed(CTRL_RIGHT) && yesnoChoice == 1) {
232
yesnoChoice = 0;
233
}
234
buttonY += 8.0f + 5.0f;
235
}
236
237
if (hasOK) {
238
drawSelectionBoxAndAdjust(240.0f);
239
240
PPGeDrawText(di->T("OK"), 239.0f, buttonY - 1.0f, buttonStyle);
241
buttonY += 8.0f + 5.0f;
242
}
243
244
PPGeScissor(0, (int)(centerY - h2 - 2), 480, (int)(centerY + h2 + 2));
245
PPGeDrawTextWrapped(text.c_str(), 240.0f, centerY - h2 - scrollPos_, WRAP_WIDTH, 0, messageStyle);
246
PPGeScissorReset();
247
248
// Do we need a scrollbar?
249
if (visibleHeight < totalHeight) {
250
float scrollSpeed = 5.0f;
251
float scrollMax = totalHeight - visibleHeight;
252
253
float bobHeight = (visibleHeight / totalHeight) * visibleHeight;
254
float bobOffset = (scrollPos_ / scrollMax) * (visibleHeight - bobHeight);
255
float bobY1 = centerY - h2 + bobOffset;
256
PPGeDrawRect(435.0f, bobY1, 440.0f, bobY1 + bobHeight, CalcFadedColor(0xFFCCCCCC));
257
258
auto buttonDown = [this](int btn, int &held) {
259
if (IsButtonPressed(btn)) {
260
held = 0;
261
return true;
262
}
263
return IsButtonHeld(btn, held, 1, 1);
264
};
265
if (buttonDown(CTRL_DOWN, framesDownHeld_) && scrollPos_ < scrollMax) {
266
scrollPos_ = std::min(scrollMax, scrollPos_ + scrollSpeed);
267
}
268
if (buttonDown(CTRL_UP, framesUpHeld_) && scrollPos_ > 0.0f) {
269
scrollPos_ = std::max(0.0f, scrollPos_ - scrollSpeed);
270
}
271
}
272
273
PPGeDrawRect(40.0f, sy, 440.0f, sy + 1.0f, CalcFadedColor(0xFFFFFFFF));
274
PPGeDrawRect(40.0f, ey, 440.0f, ey + 1.0f, CalcFadedColor(0xFFFFFFFF));
275
}
276
277
int PSPMsgDialog::Update(int animSpeed) {
278
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
279
return SCE_ERROR_UTILITY_INVALID_STATUS;
280
}
281
282
if (flag & (DS_ERROR | DS_ABORT)) {
283
ChangeStatus(SCE_UTILITY_STATUS_FINISHED, 0);
284
} else {
285
UpdateButtons();
286
UpdateCommon();
287
UpdateFade(animSpeed);
288
289
StartDraw();
290
// white -> RGB(168,173,189), black -> RGB(129,134,150)
291
// (255 - a) + (x * a / 255) = 173, x * a / 255 = 134
292
// a = 255 - w + b = 158, x = b * 255 / a = ?
293
// but is not drawn using x * a + y * (255 - a) here?
294
//PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x9EF2D8D0));
295
PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0xC0C8B2AC));
296
297
if ((flag & DS_MSG) || (flag & DS_ERRORMSG))
298
DisplayMessage(msgText, (flag & DS_YESNO) != 0, (flag & DS_OK) != 0);
299
300
if (flag & (DS_OK | DS_VALIDBUTTON))
301
DisplayButtons(DS_BUTTON_OK, messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ? messageDialog.okayButton : "");
302
303
if (flag & DS_CANCELBUTTON)
304
DisplayButtons(DS_BUTTON_CANCEL, messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ? messageDialog.cancelButton : "");
305
306
if (IsButtonPressed(cancelButtonFlag) && (flag & DS_CANCELBUTTON))
307
{
308
if(messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V3 ||
309
((messageDialog.common.size == SCE_UTILITY_MSGDIALOG_SIZE_V2) && (flag & DS_YESNO)))
310
messageDialog.buttonPressed = 3;
311
else
312
messageDialog.buttonPressed = 0;
313
StartFade(false);
314
}
315
else if (IsButtonPressed(okButtonFlag) && (flag & DS_VALIDBUTTON))
316
{
317
if (yesnoChoice == 0)
318
{
319
messageDialog.buttonPressed = 2;
320
}
321
else
322
{
323
messageDialog.buttonPressed = 1;
324
}
325
StartFade(false);
326
}
327
328
329
EndDraw();
330
331
messageDialog.result = 0;
332
}
333
334
Memory::Memcpy(messageDialogAddr, &messageDialog, messageDialog.common.size, "MsgDialogParam");
335
return 0;
336
}
337
338
int PSPMsgDialog::Abort() {
339
// Katekyoushi Hitman Reborn! Battle Arena expects this to fail when not running.
340
if (GetStatus() != SCE_UTILITY_STATUS_RUNNING) {
341
return SCE_ERROR_UTILITY_INVALID_STATUS;
342
} else {
343
// Status is not actually changed until Update().
344
flag |= DS_ABORT;
345
return 0;
346
}
347
}
348
349
int PSPMsgDialog::Shutdown(bool force) {
350
if (GetStatus() != SCE_UTILITY_STATUS_FINISHED && !force)
351
return SCE_ERROR_UTILITY_INVALID_STATUS;
352
353
PSPDialog::Shutdown(force);
354
if (!force) {
355
ChangeStatusShutdown(MSG_SHUTDOWN_DELAY_US);
356
}
357
358
return 0;
359
}
360
361
void PSPMsgDialog::DoState(PointerWrap &p)
362
{
363
PSPDialog::DoState(p);
364
365
auto s = p.Section("PSPMsgDialog", 1);
366
if (!s)
367
return;
368
369
Do(p, flag);
370
Do(p, messageDialog);
371
Do(p, messageDialogAddr);
372
DoArray(p, msgText, sizeof(msgText));
373
Do(p, yesnoChoice);
374
375
// We don't save state this, you'll just have to scroll down again.
376
if (p.mode == p.MODE_READ) {
377
scrollPos_ = 0.0f;
378
framesUpHeld_ = 0;
379
framesDownHeld_ = 0;
380
}
381
}
382
383
pspUtilityDialogCommon *PSPMsgDialog::GetCommonParam()
384
{
385
return &messageDialog.common;
386
}
387
388