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