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