1/*
2 * Copyright 2017 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Brian Hill
7 */
8
9
10#include "TaskTimer.h"
11
12#include <Application.h>
13#include <Catalog.h>
14
15#include "constants.h"
16
17#undef B_TRANSLATION_CONTEXT
18#define B_TRANSLATION_CONTEXT "TaskTimer"
19
20static int32 sAlertStackCount = 0;
21
22
23TaskTimer::TaskTimer(const BMessenger& target, Task* owner)
24	:
25	BLooper(),
26	fTimeoutMicroSeconds(kTimerTimeoutSeconds * 1000000),
27	fTimerIsRunning(false),
28	fReplyTarget(target),
29	fMessageRunner(NULL),
30	fTimeoutMessage(TASK_TIMEOUT),
31	fTimeoutAlert(NULL),
32	fOwner(owner)
33{
34	Run();
35
36	// Messenger for the Message Runner to use to send its message to the timer
37	fMessenger.SetTo(this);
38	// Invoker for the Alerts to use to send their messages to the timer
39	fTimeoutAlertInvoker.SetMessage(
40		new BMessage(TIMEOUT_ALERT_BUTTON_SELECTION));
41	fTimeoutAlertInvoker.SetTarget(this);
42}
43
44
45TaskTimer::~TaskTimer()
46{
47	if (fTimeoutAlert) {
48		fTimeoutAlert->Lock();
49		fTimeoutAlert->Quit();
50	}
51	if (fMessageRunner)
52		fMessageRunner->SetCount(0);
53}
54
55
56bool
57TaskTimer::QuitRequested()
58{
59	return true;
60}
61
62
63void
64TaskTimer::MessageReceived(BMessage* message)
65{
66	switch (message->what)
67	{
68		case TASK_TIMEOUT:
69		{
70			fMessageRunner = NULL;
71			if (fTimerIsRunning) {
72				BString text(B_TRANSLATE_COMMENT("The task for repository"
73					" %name% is taking a long time to complete.",
74					"Alert message.  Do not translate %name%"));
75				BString nameString("\"");
76				nameString.Append(fRepositoryName).Append("\"");
77				text.ReplaceFirst("%name%", nameString);
78				fTimeoutAlert = new BAlert("timeout", text,
79					B_TRANSLATE_COMMENT("Keep trying", "Button label"),
80					B_TRANSLATE_COMMENT("Cancel task", "Button label"),
81					NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
82				fTimeoutAlert->SetShortcut(0, B_ESCAPE);
83				// Calculate the position to correctly stack this alert
84				BRect windowFrame = be_app->WindowAt(0)->Frame();
85				int32 stackPos = _NextAlertStackCount();
86				float xPos = windowFrame.left
87					+ windowFrame.Width()/2 + stackPos * kTimerAlertOffset;
88				float yPos = windowFrame.top
89					+ (stackPos + 1) * kTimerAlertOffset;
90				fTimeoutAlert->Go(&fTimeoutAlertInvoker);
91				xPos -= fTimeoutAlert->Frame().Width()/2;
92					// The correct frame for the alert is not available until
93					// after Go is called
94				fTimeoutAlert->MoveTo(xPos, yPos);
95			}
96			break;
97		}
98
99		case TIMEOUT_ALERT_BUTTON_SELECTION:
100		{
101			fTimeoutAlert = NULL;
102			// Timeout alert was invoked by user and timer still has not
103			// been stopped
104			if (fTimerIsRunning) {
105				// find which button was pressed
106				int32 selection = -1;
107				message->FindInt32("which", &selection);
108				if (selection == 1) {
109					BMessage reply(TASK_KILL_REQUEST);
110					reply.AddPointer(key_taskptr, fOwner);
111					fReplyTarget.SendMessage(&reply);
112				} else if (selection == 0) {
113					// Create new timer
114					fMessageRunner = new BMessageRunner(fMessenger,
115						&fTimeoutMessage, kTimerRetrySeconds * 1000000, 1);
116				}
117			}
118			break;
119		}
120	}
121}
122
123
124void
125TaskTimer::Start(const char* name)
126{
127	fTimerIsRunning = true;
128	fRepositoryName.SetTo(name);
129
130	// Create a message runner that will send a TASK_TIMEOUT message if the
131	// timer is not stopped
132	if (fMessageRunner == NULL) {
133		fMessageRunner = new BMessageRunner(fMessenger, &fTimeoutMessage,
134			fTimeoutMicroSeconds, 1);
135	}
136	else
137		fMessageRunner->SetInterval(fTimeoutMicroSeconds);
138}
139
140
141void
142TaskTimer::Stop(const char* name)
143{
144	fTimerIsRunning = false;
145
146	// Reset max timeout so we can reuse the runner at the next Start call
147	if (fMessageRunner != NULL)
148		fMessageRunner->SetInterval(LLONG_MAX);
149
150	// If timeout alert is showing replace it
151	if (fTimeoutAlert) {
152		// Remove current alert
153		BRect frame = fTimeoutAlert->Frame();
154		fTimeoutAlert->Quit();
155		fTimeoutAlert = NULL;
156
157		// Display new alert that won't send a message
158		BString text(B_TRANSLATE_COMMENT("Good news! The task for repository "
159			"%name% completed.", "Alert message.  Do not translate %name%"));
160		BString nameString("\"");
161		nameString.Append(name).Append("\"");
162		text.ReplaceFirst("%name%", nameString);
163		BAlert* newAlert = new BAlert("timeout", text, kOKLabel, NULL, NULL,
164			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
165		newAlert->SetShortcut(0, B_ESCAPE);
166		newAlert->MoveTo(frame.left, frame.top);
167		newAlert->Go(NULL);
168	}
169}
170
171
172int32
173TaskTimer::_NextAlertStackCount()
174{
175	if (sAlertStackCount > 9)
176		sAlertStackCount = 0;
177	return sAlertStackCount++;
178}
179