1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "bspf.hxx"
19#include "Dialog.hxx"
20#include "FBSurface.hxx"
21#include "Font.hxx"
22#include "GuiObject.hxx"
23#include "OSystem.hxx"
24#include "Widget.hxx"
25#include "TabWidget.hxx"
26
27// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
28TabWidget::TabWidget(GuiObject* boss, const GUI::Font& font,
29 int x, int y, int w, int h)
30 : Widget(boss, font, x, y, w, h),
31 CommandSender(boss),
32 _tabWidth(40),
33 _activeTab(-1),
34 _firstTime(true)
35{
36 _id = 0; // For dialogs with multiple tab widgets, they should specifically
37 // call ::setID to differentiate among them
38 _flags = Widget::FLAG_ENABLED | Widget::FLAG_CLEARBG;
39 _bgcolor = kDlgColor;
40 _bgcolorhi = kDlgColor;
41 _textcolor = kTextColor;
42 _textcolorhi = kTextColor;
43
44 _tabHeight = font.getLineHeight() + 4;
45}
46
47// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
48TabWidget::~TabWidget()
49{
50 for(auto& tab: _tabs)
51 {
52 delete tab.firstWidget;
53 tab.firstWidget = nullptr;
54 // _tabs[i].parentWidget is deleted elsewhere
55 }
56 _tabs.clear();
57}
58
59// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
60int TabWidget::getChildY() const
61{
62 return getAbsY() + _tabHeight;
63}
64
65// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
66int TabWidget::addTab(const string& title, int tabWidth)
67{
68 // Add a new tab page
69 int newWidth = _font.getStringWidth(title) + 2 * kTabPadding;
70
71 if(tabWidth == AUTO_WIDTH)
72 _tabs.push_back(Tab(title, newWidth));
73 else
74 _tabs.push_back(Tab(title, tabWidth));
75 int numTabs = int(_tabs.size());
76
77 // Determine the new tab width
78 int fixedWidth = 0, fixedTabs = 0;
79 for(int i = 0; i < int(_tabs.size()); ++i)
80 {
81 if(_tabs[i].tabWidth != NO_WIDTH)
82 {
83 fixedWidth += _tabs[i].tabWidth;
84 fixedTabs++;
85 }
86 }
87
88 if(tabWidth == NO_WIDTH)
89 if(_tabWidth < newWidth)
90 _tabWidth = newWidth;
91
92 if(numTabs - fixedTabs)
93 {
94 int maxWidth = (_w - kTabLeftOffset - fixedWidth) / (numTabs - fixedTabs) - kTabLeftOffset;
95 if(_tabWidth > maxWidth)
96 _tabWidth = maxWidth;
97 }
98
99 // Activate the new tab
100 setActiveTab(numTabs - 1);
101
102 return _activeTab;
103}
104
105// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
106void TabWidget::setActiveTab(int tabID, bool show)
107{
108 assert(0 <= tabID && tabID < int(_tabs.size()));
109
110 if (_activeTab != -1)
111 {
112 // Exchange the widget lists, and switch to the new tab
113 _tabs[_activeTab].firstWidget = _firstWidget;
114 }
115
116 _activeTab = tabID;
117 _firstWidget = _tabs[tabID].firstWidget;
118
119 // Let parent know about the tab change
120 if(show)
121 sendCommand(TabWidget::kTabChangedCmd, _activeTab, _id);
122}
123
124#if 0 // FIXME
125// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
126void TabWidget::disableTab(int tabID)
127{
128 assert(0 <= tabID && tabID < int(_tabs.size()));
129
130 _tabs[tabID].enabled = false;
131 // TODO - also disable all widgets belonging to this tab
132}
133#endif
134
135// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136void TabWidget::updateActiveTab()
137{
138 if(_activeTab < 0)
139 return;
140
141 if(_tabs[_activeTab].parentWidget)
142 _tabs[_activeTab].parentWidget->loadConfig();
143
144 setDirty();
145
146 // Redraw focused areas
147 _boss->redrawFocus(); // TJ: Does nothing!
148}
149
150// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
151void TabWidget::activateTabs()
152{
153 for(uInt32 i = 0; i <_tabs.size(); ++i)
154 sendCommand(TabWidget::kTabChangedCmd, i-1, _id);
155}
156
157// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
158void TabWidget::cycleTab(int direction)
159{
160 int tabID = _activeTab;
161
162 // Don't do anything if no tabs have been defined
163 if(tabID == -1)
164 return;
165
166 if(direction == -1) // Go to the previous tab, wrap around at beginning
167 {
168 tabID--;
169 if(tabID == -1)
170 tabID = int(_tabs.size()) - 1;
171 }
172 else if(direction == 1) // Go to the next tab, wrap around at end
173 {
174 tabID++;
175 if(tabID == int(_tabs.size()))
176 tabID = 0;
177 }
178
179 // Finally, select the active tab
180 setActiveTab(tabID, true);
181 updateActiveTab();
182}
183
184// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
185void TabWidget::setParentWidget(int tabID, Widget* parent)
186{
187 assert(0 <= tabID && tabID < int(_tabs.size()));
188 _tabs[tabID].parentWidget = parent;
189}
190
191// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
192void TabWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount)
193{
194 assert(y < _tabHeight);
195
196 // Determine which tab was clicked
197 int tabID = -1;
198 x -= kTabLeftOffset;
199
200 for(int i = 0; i < int(_tabs.size()); ++i)
201 {
202 int tabWidth = _tabs[i].tabWidth ? _tabs[i].tabWidth : _tabWidth;
203 if(x >= 0 && x < tabWidth)
204 {
205 tabID = i;
206 break;
207 }
208 x -= (tabWidth + kTabSpacing);
209 }
210
211 // If a tab was clicked, switch to that pane
212 if (tabID >= 0)
213 {
214 setActiveTab(tabID, true);
215 updateActiveTab();
216 }
217}
218
219// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
220void TabWidget::handleMouseEntered()
221{
222 setFlags(Widget::FLAG_HILITED);
223 setDirty();
224}
225
226// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
227void TabWidget::handleMouseLeft()
228{
229 clearFlags(Widget::FLAG_HILITED);
230 setDirty();
231}
232
233// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
234void TabWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
235{
236 // Command is not inspected; simply forward it to the caller
237 sendCommand(cmd, data, _id);
238}
239
240// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
241bool TabWidget::handleEvent(Event::Type event)
242{
243 bool handled = false;
244
245 switch (event)
246 {
247 case Event::UIRight:
248 case Event::UIPgDown:
249 cycleTab(1);
250 handled = true;
251 break;
252 case Event::UILeft:
253 case Event::UIPgUp:
254 cycleTab(-1);
255 handled = true;
256 break;
257 default:
258 break;
259 }
260 return handled;
261}
262
263// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
264void TabWidget::loadConfig()
265{
266 if(_firstTime)
267 {
268 setActiveTab(_activeTab, true);
269 _firstTime = false;
270 }
271
272 updateActiveTab();
273}
274
275// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
276void TabWidget::drawWidget(bool hilite)
277{
278 // The tab widget is strange in that it acts as both a widget (obviously)
279 // and a dialog (it contains other widgets). Because of the latter,
280 // it must assume responsibility for refreshing all its children.
281 Widget::setDirtyInChain(_tabs[_activeTab].firstWidget);
282
283 FBSurface& s = dialog().surface();
284 bool onTop = _boss->dialog().isOnTop();
285
286 // Iterate over all tabs and draw them
287 int i, x = _x + kTabLeftOffset;
288 for (i = 0; i < int(_tabs.size()); ++i)
289 {
290 int tabWidth = _tabs[i].tabWidth ? _tabs[i].tabWidth : _tabWidth;
291 ColorId fontcolor = _tabs[i].enabled && onTop? kTextColor : kColor;
292 int yOffset = (i == _activeTab) ? 0 : 1;
293 s.fillRect(x, _y + 1, tabWidth, _tabHeight - 1,
294 (i == _activeTab)
295 ? onTop ? kDlgColor : kBGColorLo
296 : onTop ? kBGColorHi : kDlgColor); // ? kWidColor : kDlgColor
297 s.drawString(_font, _tabs[i].title, x + kTabPadding + yOffset,
298 _y + yOffset + (_tabHeight - _fontHeight - 1),
299 tabWidth - 2 * kTabPadding, fontcolor, TextAlign::Center);
300 if(i == _activeTab)
301 {
302 s.hLine(x, _y, x + tabWidth - 1, onTop ? kWidColor : kDlgColor);
303 s.vLine(x + tabWidth, _y + 1, _y + _tabHeight - 1, onTop ? kBGColorLo : kColor);
304 }
305 else
306 s.hLine(x, _y + _tabHeight, x + tabWidth, onTop ? kWidColor : kDlgColor);
307
308 x += tabWidth + kTabSpacing;
309 }
310
311 // fill empty right space
312 s.hLine(x - kTabSpacing + 1, _y + _tabHeight, _x + _w - 1, onTop ? kWidColor : kDlgColor);
313 s.hLine(_x, _y + _h - 1, _x + _w - 1, onTop ? kBGColorLo : kColor);
314}
315
316// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
317Widget* TabWidget::findWidget(int x, int y)
318{
319 if (y < _tabHeight)
320 {
321 // Click was in the tab area
322 return this;
323 }
324 else
325 {
326 // Iterate over all child widgets and find the one which was clicked
327 return Widget::findWidgetInChain(_firstWidget, x, y - _tabHeight);
328 }
329}
330