ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

tab.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "common/util.h"
00024 #include "gui/widgets/tab.h"
00025 #include "gui/gui-manager.h"
00026 
00027 #include "gui/ThemeEval.h"
00028 
00029 namespace GUI {
00030 
00031 enum {
00032     kCmdLeft  = 'LEFT',
00033     kCmdRight = 'RGHT'
00034 };
00035 
00036 static const int kTabTitleSpacing = 2 * 5;
00037 
00038 TabWidget::TabWidget(GuiObject *boss, int x, int y, int w, int h)
00039     : Widget(boss, x, y, w, h), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
00040     init();
00041 }
00042 
00043 TabWidget::TabWidget(GuiObject *boss, const String &name)
00044     : Widget(boss, name), _bodyBackgroundType(GUI::ThemeEngine::kDialogBackgroundDefault) {
00045     init();
00046 }
00047 
00048 void TabWidget::init() {
00049     setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE);
00050     _type = kTabWidget;
00051     _activeTab = -1;
00052     _firstVisibleTab = 0;
00053     _lastVisibleTab = 0;
00054     _navButtonsVisible = false;
00055 
00056     _minTabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width");
00057     _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height");
00058     _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top");
00059 
00060     _bodyTP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Top");
00061     _bodyBP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Bottom");
00062     _bodyLP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Left");
00063     _bodyRP = g_gui.xmlEval()->getVar("Globals.TabWidget.Body.Padding.Right");
00064 
00065     _butRP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Right", 0);
00066     _butTP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Top", 0);
00067     _butW = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Width", 10);
00068     _butH = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Height", 10);
00069 
00070     int x = _w - _butRP - _butW * 2 - 2;
00071     int y = _butTP - _tabHeight;
00072 
00073     String leftArrow = g_gui.useRTL() ? ">" : "<";
00074     String rightArrow = g_gui.useRTL() ? "<" : ">";
00075 
00076     _navLeft = new ButtonWidget(this, x, y, _butW, _butH, leftArrow, nullptr, kCmdLeft);
00077     _navRight = new ButtonWidget(this, x + _butW + 2, y, _butW, _butH, rightArrow, nullptr, kCmdRight);
00078 
00079     _navLeft->setEnabled(false);
00080     _navRight->setEnabled(true);
00081 
00082     _lastRead = -1;
00083 }
00084 
00085 TabWidget::~TabWidget() {
00086     // If widgets were added or removed in the current tab, without tabs
00087     // having been switched using setActiveTab() afterward, then the
00088     // firstWidget in the _tabs list for the active tab may not be up to
00089     // date. So update it now.
00090     if (_activeTab != -1)
00091         _tabs[_activeTab].firstWidget = _firstWidget;
00092     _firstWidget = nullptr;
00093     for (uint i = 0; i < _tabs.size(); ++i) {
00094         delete _tabs[i].firstWidget;
00095         _tabs[i].firstWidget = nullptr;
00096     }
00097     _tabs.clear();
00098     delete _navRight;
00099 }
00100 
00101 int16 TabWidget::getChildY() const {
00102     // NOTE: if you change that, make sure to do the same
00103     // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp)
00104     return getAbsY() + _tabHeight;
00105 }
00106 
00107 uint16 TabWidget::getHeight() const {
00108     // NOTE: if you change that, make sure to do the same
00109     // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp)
00110     // NOTE: this height is used for clipping, so it *includes*
00111     // tabs, because it starts from getAbsY(), not getChildY()
00112     return _h + _tabHeight;
00113 }
00114 
00115 int TabWidget::addTab(const String &title, const String &dialogName) {
00116     // Add a new tab page
00117     Tab newTab;
00118     newTab.title = title;
00119     newTab.dialogName = dialogName;
00120     newTab.firstWidget = nullptr;
00121 
00122     // Determine the new tab width
00123     int newWidth = g_gui.getStringWidth(title) + kTabTitleSpacing;
00124     if (newWidth < _minTabWidth)
00125         newWidth = _minTabWidth;
00126     newTab._tabWidth = newWidth;
00127 
00128     _tabs.push_back(newTab);
00129 
00130     int numTabs = _tabs.size();
00131 
00132     // Activate the new tab
00133     setActiveTab(numTabs - 1);
00134 
00135     return _activeTab;
00136 }
00137 
00138 void TabWidget::removeTab(int tabID) {
00139     assert(0 <= tabID && tabID < (int)_tabs.size());
00140 
00141     // Deactivate the tab if it's currently the active one
00142     if (tabID == _activeTab) {
00143         _tabs[tabID].firstWidget = _firstWidget;
00144         releaseFocus();
00145         _firstWidget = nullptr;
00146     }
00147 
00148     // Dispose the widgets in that tab and then the tab itself
00149     delete _tabs[tabID].firstWidget;
00150     _tabs.remove_at(tabID);
00151 
00152     // Adjust _firstVisibleTab if necessary
00153     if (_firstVisibleTab >= (int)_tabs.size()) {
00154         _firstVisibleTab = MAX(0, (int)_tabs.size() - 1);
00155     }
00156 
00157     // The active tab was removed, so select a new active one (if any remains)
00158     if (tabID == _activeTab) {
00159         _activeTab = -1;
00160         if (tabID >= (int)_tabs.size())
00161             tabID = _tabs.size() - 1;
00162         if (tabID >= 0)
00163             setActiveTab(tabID);
00164     }
00165 
00166     // Finally trigger a redraw
00167     g_gui.scheduleTopDialogRedraw();
00168 }
00169 
00170 void TabWidget::setActiveTab(int tabID) {
00171     assert(0 <= tabID && tabID < (int)_tabs.size());
00172     if (_activeTab != tabID) {
00173         // Exchange the widget lists, and switch to the new tab
00174         if (_activeTab != -1) {
00175             _tabs[_activeTab].firstWidget = _firstWidget;
00176             releaseFocus();
00177         }
00178         _activeTab = tabID;
00179         _firstWidget = _tabs[tabID].firstWidget;
00180 
00181         // Also ensure the tab is visible in the tab bar
00182         if (_firstVisibleTab > tabID)
00183             setFirstVisible(tabID, true);
00184         while (_lastVisibleTab < tabID)
00185             setFirstVisible(_firstVisibleTab + 1, false);
00186 
00187         g_gui.scheduleTopDialogRedraw();
00188     }
00189 }
00190 
00191 
00192 void TabWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
00193     Widget::handleCommand(sender, cmd, data);
00194 
00195     switch (cmd) {
00196     case kCmdLeft:
00197         if (!_navRight->isEnabled()) {
00198             _navRight->setEnabled(true);
00199         }
00200 
00201         if (_firstVisibleTab > 0) {
00202             setFirstVisible(_firstVisibleTab - 1);
00203         }
00204         if (_firstVisibleTab == 0) {
00205             _navLeft->setEnabled(false);
00206         }
00207         break;
00208 
00209     case kCmdRight:
00210         if (!_navLeft->isEnabled()) {
00211             _navLeft->setEnabled(true);
00212         }
00213 
00214         if (_lastVisibleTab + 1 < (int)_tabs.size()) {
00215             setFirstVisible(_firstVisibleTab + 1, false);
00216         }
00217         if (_lastVisibleTab + 1 == (int)_tabs.size()) {
00218             _navRight->setEnabled(false);
00219         }
00220         break;
00221 
00222     default:
00223         break;
00224     }
00225 }
00226 
00227 void TabWidget::handleMouseDown(int x, int y, int button, int clickCount) {
00228     assert(y < _tabHeight);
00229 
00230     if (x < 0)
00231         return;
00232 
00233     // Determine which tab was clicked
00234     int tabID;
00235     for (tabID = _firstVisibleTab; tabID <= _lastVisibleTab; ++tabID) {
00236         x -= _tabs[tabID]._tabWidth;
00237         if (x < 0)
00238             break;
00239     }
00240 
00241     // If a tab was clicked, switch to that pane
00242     if (tabID <= _lastVisibleTab)
00243         setActiveTab(tabID);
00244 }
00245 
00246 void TabWidget::handleMouseMoved(int x, int y, int button) {
00247     if (y < 0 || y >= _tabHeight)
00248         return;
00249 
00250     if (x < 0)
00251         return;
00252 
00253     // Determine which tab the mouse is on
00254     int tabID;
00255     for (tabID = _firstVisibleTab; tabID <= _lastVisibleTab; ++tabID) {
00256         x -= _tabs[tabID]._tabWidth;
00257         if (x < 0)
00258             break;
00259     }
00260 
00261     if (tabID <= _lastVisibleTab) {
00262         if (tabID != _lastRead) {
00263             read(_tabs[tabID].title);
00264             _lastRead = tabID;
00265         }
00266     }
00267     else
00268         _lastRead = -1;
00269 }
00270 
00271 bool TabWidget::handleKeyDown(Common::KeyState state) {
00272     if (state.hasFlags(Common::KBD_SHIFT) && state.keycode == Common::KEYCODE_TAB)
00273         adjustTabs(kTabBackwards);
00274     else if (state.keycode == Common::KEYCODE_TAB)
00275         adjustTabs(kTabForwards);
00276 
00277     return Widget::handleKeyDown(state);
00278 }
00279 
00280 void TabWidget::adjustTabs(int value) {
00281     // Determine which tab is next
00282     int tabID = _activeTab + value;
00283     int lastVis = _lastVisibleTab;
00284 
00285     if (tabID >= (int)_tabs.size())
00286         tabID = 0;
00287     else if (tabID < 0)
00288         tabID = ((int)_tabs.size() - 1);
00289 
00290     setActiveTab(tabID);
00291 
00292     if (_navButtonsVisible) {
00293         if (lastVis != _lastVisibleTab) {
00294             _navLeft->setEnabled(true);
00295             _navRight->setEnabled(true);
00296         }
00297 
00298         if (_firstVisibleTab == 0)
00299             _navLeft->setEnabled(false);
00300         else if (_lastVisibleTab == (int)_tabs.size() - 1)
00301             _navRight->setEnabled(false);
00302     }
00303 }
00304 
00305 int TabWidget::getFirstVisible() const {
00306     return _firstVisibleTab;
00307 }
00308 
00309 void TabWidget::setFirstVisible(int tabID, bool adjustIfRoom) {
00310     assert(0 <= tabID && tabID < (int)_tabs.size());
00311     _firstVisibleTab = tabID;
00312 
00313     computeLastVisibleTab(adjustIfRoom);
00314 
00315     g_gui.scheduleTopDialogRedraw(); // TODO: Necessary?
00316 }
00317 
00318 void TabWidget::reflowLayout() {
00319     Widget::reflowLayout();
00320 
00321     // NOTE: if you change that, make sure to do the same
00322     // changes in the ThemeLayoutTabWidget (gui/ThemeLayout.cpp)
00323     _tabHeight = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Height");
00324     _minTabWidth = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Width");
00325     _titleVPad = g_gui.xmlEval()->getVar("Globals.TabWidget.Tab.Padding.Top");
00326 
00327     _butRP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Right", 0);
00328     _butTP = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Padding.Top", 0);
00329     _butW = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Width", 10);
00330     _butH = g_gui.xmlEval()->getVar("Globals.TabWidget.NavButton.Height", 10);
00331 
00332     // If widgets were added or removed in the current tab, without tabs
00333     // having been switched using setActiveTab() afterward, then the
00334     // firstWidget in the _tabs list for the active tab may not be up to
00335     // date. So update it now.
00336     if (_activeTab != -1)
00337         _tabs[_activeTab].firstWidget = _firstWidget;
00338 
00339     for (uint i = 0; i < _tabs.size(); ++i) {
00340         if (!_tabs[i].dialogName.empty()) {
00341             g_gui.xmlEval()->reflowDialogLayout(_tabs[i].dialogName, _tabs[i].firstWidget);
00342         }
00343 
00344         Widget *w = _tabs[i].firstWidget;
00345         while (w) {
00346             w->reflowLayout();
00347             w = w->next();
00348         }
00349     }
00350 
00351     for (uint i = 0; i < _tabs.size(); ++i) {
00352         // Determine the new tab width
00353         int newWidth = g_gui.getStringWidth(_tabs[i].title) + kTabTitleSpacing;
00354         if (newWidth < _minTabWidth)
00355             newWidth = _minTabWidth;
00356         _tabs[i]._tabWidth = newWidth;
00357     }
00358 
00359     // See how many tabs fit on screen.
00360     // We do this in a loop, because it will change if we need to
00361     // add left/right scroll buttons, if we scroll left to use free
00362     // space on the right, or a combination of those.
00363     _navButtonsVisible = _firstVisibleTab > 0;
00364     do {
00365         computeLastVisibleTab(true);
00366 
00367         if (_firstVisibleTab > 0 || _lastVisibleTab + 1 < (int)_tabs.size()) {
00368             if (!_navButtonsVisible)
00369                 _navButtonsVisible = true;
00370             else
00371                 break;
00372         } else {
00373             if (_navButtonsVisible)
00374                 _navButtonsVisible = false;
00375             else
00376                 break;
00377         }
00378     } while (true);
00379 
00380     int x = _w - _butRP - _butW * 2 - 2;
00381     int y = _butTP - _tabHeight;
00382     _navLeft->resize(x, y, _butW, _butH);
00383     _navRight->resize(x + _butW + 2, y, _butW, _butH);
00384 }
00385 
00386 void TabWidget::drawWidget() {
00387     Common::Array<Common::String> tabs;
00388     Common::Array<int> widths;
00389     for (int i = _firstVisibleTab; i <= _lastVisibleTab; ++i) {
00390         tabs.push_back(_tabs[i].title);
00391         widths.push_back(_tabs[i]._tabWidth);
00392     }
00393 
00394     g_gui.theme()->drawDialogBackground(
00395             Common::Rect(_x + _bodyLP, _y + _bodyTP, _x + _w - _bodyRP, _y + _h - _bodyBP + _tabHeight),
00396             _bodyBackgroundType);
00397 
00398     g_gui.theme()->drawTab(Common::Rect(_x, _y, _x + _w, _y + _h), _tabHeight, widths, tabs,
00399                 _activeTab - _firstVisibleTab, (g_gui.useRTL() && _useRTL));
00400 }
00401 
00402 void TabWidget::draw() {
00403     Widget::draw();
00404 
00405     if (_navButtonsVisible) {
00406         _navLeft->draw();
00407         _navRight->draw();
00408     }
00409 }
00410 
00411 void TabWidget::markAsDirty() {
00412     Widget::markAsDirty();
00413 
00414     if (_navButtonsVisible) {
00415         _navLeft->markAsDirty();
00416         _navRight->markAsDirty();
00417     }
00418 }
00419 
00420 bool TabWidget::containsWidget(Widget *w) const {
00421     if (w == _navLeft || w == _navRight || _navLeft->containsWidget(w) || _navRight->containsWidget(w))
00422         return true;
00423     return containsWidgetInChain(_firstWidget, w);
00424 }
00425 
00426 
00427 Widget *TabWidget::findWidget(int x, int y) {
00428     if (y < _tabHeight) {
00429         if (_navButtonsVisible) {
00430             if (y >= _butTP && y < _butTP + _butH) {
00431                 if (x >= _w - _butRP - _butW * 2 - 2 && x < _w - _butRP - _butW - 2)
00432                     return _navLeft;
00433                 if (x >= _w - _butRP - _butW &&  x < _w - _butRP)
00434                     return _navRight;
00435             }
00436         }
00437 
00438         // Click was in the tab area
00439         return this;
00440     } else {
00441         // Iterate over all child widgets and find the one which was clicked
00442         return Widget::findWidgetInChain(_firstWidget, x, y - _tabHeight);
00443     }
00444 }
00445 
00446 void TabWidget::computeLastVisibleTab(bool adjustFirstIfRoom) {
00447     int availableWidth = _w;
00448     if (_navButtonsVisible)
00449         availableWidth -= 2 + _butW * 2 + _butRP;
00450 
00451     _lastVisibleTab = _tabs.size() - 1;
00452     for (int i = _firstVisibleTab; i < (int)_tabs.size(); ++i) {
00453         if (_tabs[i]._tabWidth > availableWidth) {
00454             if (i > _firstVisibleTab)
00455                 _lastVisibleTab = i - 1;
00456             else
00457                 _lastVisibleTab = _firstVisibleTab; // Always show 1
00458             break;
00459         }
00460         availableWidth -= _tabs[i]._tabWidth;
00461     }
00462 
00463     if (adjustFirstIfRoom) {
00464         // If possible, scroll to fit if there's unused space to the right
00465         while (_firstVisibleTab > 0 && _tabs[_firstVisibleTab-1]._tabWidth <= availableWidth) {
00466             availableWidth -= _tabs[_firstVisibleTab-1]._tabWidth;
00467             _firstVisibleTab--;
00468         }
00469     }
00470 }
00471 
00472 } // End of namespace GUI


Generated on Sat Jun 27 2020 05:00:53 for ResidualVM by doxygen 1.7.1
curved edge   curved edge