File indexing completed on 2024-04-14 05:40:00
0001 /***************************************************************************** 0002 * Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com> * 0003 * Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU Lesser General Public License as * 0007 * published by the Free Software Foundation; either version 2.1 of the * 0008 * License, or (at your option) version 3, or any later version accepted * 0009 * by the membership of KDE e.V. (or its successor approved by the * 0010 * membership of KDE e.V.), which shall act as a proxy defined in * 0011 * Section 6 of version 3 of the license. * 0012 * * 0013 * This program is distributed in the hope that it will be useful, * 0014 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 0016 * Lesser General Public License for more details. * 0017 * * 0018 * You should have received a copy of the GNU Lesser General Public * 0019 * License along with this library. If not, * 0020 * see <http://www.gnu.org/licenses/>. * 0021 *****************************************************************************/ 0022 0023 #include "tab.h" 0024 0025 #include <qtcurve-utils/gtkprops.h> 0026 0027 #include <vector> 0028 #include <unordered_map> 0029 #include <tuple> 0030 0031 namespace QtCurve { 0032 namespace Tab { 0033 0034 struct Info { 0035 int id; 0036 std::vector<QtcRect> rects; 0037 Info(GtkWidget *notebook); 0038 }; 0039 0040 Info::Info(GtkWidget *notebook) 0041 : id(-1), 0042 rects(gtk_notebook_get_n_pages((GtkNotebook*)notebook), 0043 qtcRect(0, 0, -1, -1)) 0044 { 0045 } 0046 0047 class TabMap: public std::unordered_map<GtkWidget*, Info> { 0048 public: 0049 // TODO Use constructor inheritance when we drop gcc 4.7 support 0050 TabMap() 0051 : std::unordered_map<GtkWidget*, Info>() 0052 {} 0053 Info* 0054 lookup(GtkWidget *hash, bool create=false) 0055 { 0056 auto it = find(hash); 0057 if (it != end()) { 0058 return &it->second; 0059 } else if (!create) { 0060 return nullptr; 0061 } 0062 return &(emplace(std::piecewise_construct, std::forward_as_tuple(hash), 0063 std::forward_as_tuple(hash)).first->second); 0064 } 0065 }; 0066 0067 static TabMap tabMap; 0068 0069 static Info* 0070 widgetFindTab(GtkWidget *widget) 0071 { 0072 if (GTK_IS_NOTEBOOK(widget)) 0073 return tabMap.lookup(widget); 0074 return nullptr; 0075 } 0076 0077 static void 0078 cleanup(GtkWidget *widget) 0079 { 0080 if (widget) { 0081 GtkWidgetProps props(widget); 0082 props->tabDestroy.disconn(); 0083 props->tabUnrealize.disconn(); 0084 props->tabStyleSet.disconn(); 0085 props->tabMotion.disconn(); 0086 props->tabLeave.disconn(); 0087 props->tabPageAdded.disconn(); 0088 props->tabHacked = true; 0089 tabMap.erase(widget); 0090 } 0091 } 0092 0093 static gboolean 0094 styleSet(GtkWidget *widget, GtkStyle*, void*) 0095 { 0096 cleanup(widget); 0097 return false; 0098 } 0099 0100 static gboolean 0101 destroy(GtkWidget *widget, GdkEvent*, void*) 0102 { 0103 cleanup(widget); 0104 return false; 0105 } 0106 0107 static void 0108 setHovered(Info *tab, GtkWidget *widget, int index) 0109 { 0110 if (tab->id != index) { 0111 QtcRect updateRect = {0, 0, -1, -1}; 0112 tab->id = index; 0113 for (auto &rect: tab->rects) { 0114 Rect::union_(&rect, &updateRect, &updateRect); 0115 } 0116 gtk_widget_queue_draw_area(widget, updateRect.x - 4, updateRect.y - 4, 0117 updateRect.width + 8, updateRect.height + 8); 0118 } 0119 } 0120 0121 static gboolean 0122 motion(GtkWidget *widget, GdkEventMotion*, void*) 0123 { 0124 Info *tab = widgetFindTab(widget); 0125 if (tab) { 0126 int px; 0127 int py; 0128 gdk_window_get_pointer(gtk_widget_get_window(widget), &px, &py, nullptr); 0129 0130 for (size_t i = 0;i < tab->rects.size();i++) { 0131 auto &rect = tab->rects[i]; 0132 if (rect.x <= px && rect.y <= py && rect.x + rect.width > px && 0133 rect.y + rect.height > py) { 0134 setHovered(tab, widget, i); 0135 return false; 0136 } 0137 } 0138 setHovered(tab, widget, -1); 0139 } 0140 return false; 0141 } 0142 0143 static gboolean 0144 leave(GtkWidget *widget, GdkEventCrossing*, void*) 0145 { 0146 Info *prevTab = widgetFindTab(widget); 0147 0148 if (prevTab && prevTab->id >= 0) { 0149 prevTab->id = -1; 0150 gtk_widget_queue_draw(widget); 0151 } 0152 return false; 0153 } 0154 0155 static void 0156 unregisterChild(GtkWidget *widget) 0157 { 0158 GtkWidgetProps props(widget); 0159 if (widget && props->tabChildHacked) { 0160 props->tabChildDestroy.disconn(); 0161 props->tabChildStyleSet.disconn(); 0162 props->tabChildEnter.disconn(); 0163 props->tabChildLeave.disconn(); 0164 if (GTK_IS_CONTAINER(widget)) { 0165 props->tabChildAdd.disconn(); 0166 } 0167 props->tabChildHacked = false; 0168 } 0169 } 0170 0171 static void updateChildren(GtkWidget *widget); 0172 0173 static gboolean 0174 childMotion(GtkWidget *widget, GdkEventMotion *event, void *user_data) 0175 { 0176 motion((GtkWidget*)user_data, event, widget); 0177 return false; 0178 } 0179 0180 static gboolean 0181 childDestroy(GtkWidget *widget, GdkEventCrossing*, void*) 0182 { 0183 unregisterChild(widget); 0184 return false; 0185 } 0186 0187 static gboolean 0188 childStyleSet(GtkWidget *widget, GdkEventCrossing*, void*) 0189 { 0190 unregisterChild(widget); 0191 return false; 0192 } 0193 0194 static gboolean 0195 childAdd(GtkWidget*, GdkEventCrossing *, void *data) 0196 { 0197 updateChildren((GtkWidget*)data); 0198 return false; 0199 } 0200 0201 static void 0202 registerChild(GtkWidget *notebook, GtkWidget *widget) 0203 { 0204 GtkWidgetProps props(widget); 0205 if (widget && !props->tabChildHacked) { 0206 props->tabChildHacked = true; 0207 props->tabChildDestroy.conn("destroy", childDestroy, notebook); 0208 props->tabChildStyleSet.conn("style-set", childStyleSet, notebook); 0209 props->tabChildEnter.conn("enter-notify-event", childMotion, notebook); 0210 props->tabChildLeave.conn("leave-notify-event", childMotion, notebook); 0211 if (GTK_IS_CONTAINER(widget)) { 0212 props->tabChildAdd.conn("add", childAdd, notebook); 0213 GList *children = gtk_container_get_children(GTK_CONTAINER(widget)); 0214 for (GList *child = children;child;child = g_list_next(child)) { 0215 registerChild(notebook, GTK_WIDGET(child->data)); 0216 } 0217 if (children) { 0218 g_list_free(children); 0219 } 0220 } 0221 } 0222 } 0223 0224 static void 0225 updateChildren(GtkWidget *widget) 0226 { 0227 if (widget && GTK_IS_NOTEBOOK(widget)) { 0228 GtkNotebook *notebook = GTK_NOTEBOOK(widget); 0229 int numPages = gtk_notebook_get_n_pages(notebook); 0230 for (int i = 0;i < numPages;i++) { 0231 registerChild( 0232 widget, gtk_notebook_get_tab_label( 0233 notebook, gtk_notebook_get_nth_page(notebook, i))); 0234 } 0235 } 0236 } 0237 0238 static gboolean 0239 pageAdded(GtkWidget *widget, GdkEventCrossing*, void*) 0240 { 0241 updateChildren(widget); 0242 return false; 0243 } 0244 0245 int 0246 currentHoveredIndex(GtkWidget *widget) 0247 { 0248 Info *tab = widgetFindTab(widget); 0249 return tab ? tab->id : -1; 0250 } 0251 0252 void 0253 setup(GtkWidget *widget) 0254 { 0255 GtkWidgetProps props(widget); 0256 if (widget && !props->tabHacked) { 0257 props->tabHacked = true; 0258 tabMap.lookup(widget, true); 0259 props->tabDestroy.conn("destroy-event", destroy); 0260 props->tabUnrealize.conn("unrealize", destroy); 0261 props->tabStyleSet.conn("style-set", styleSet); 0262 props->tabMotion.conn("motion-notify-event", motion); 0263 props->tabLeave.conn("leave-notify-event", leave); 0264 props->tabPageAdded.conn("page-added", pageAdded); 0265 updateChildren(widget); 0266 } 0267 } 0268 0269 void 0270 updateRect(GtkWidget *widget, int tabIndex, int x, int y, int width, int height) 0271 { 0272 Info *tab = widgetFindTab(widget); 0273 0274 if (tab && tabIndex >= 0) { 0275 if (tabIndex >= (int)tab->rects.size()) { 0276 tab->rects.resize(tabIndex + 8, qtcRect(0, 0, -1, -1)); 0277 } 0278 tab->rects[tabIndex].x = x; 0279 tab->rects[tabIndex].y = y; 0280 tab->rects[tabIndex].width = width; 0281 tab->rects[tabIndex].height = height; 0282 } 0283 } 0284 0285 bool 0286 isLabel(GtkNotebook *notebook, GtkWidget *widget) 0287 { 0288 int numPages = gtk_notebook_get_n_pages(notebook); 0289 for (int i = 0;i < numPages;++i) { 0290 if (gtk_notebook_get_tab_label( 0291 notebook, gtk_notebook_get_nth_page(notebook, i)) == widget) { 0292 return true; 0293 } 0294 } 0295 return false; 0296 } 0297 0298 QtcRect 0299 getTabbarRect(GtkNotebook *notebook) 0300 { 0301 QtcRect rect = {0, 0, -1, -1}; 0302 QtcRect empty = rect; 0303 QtcRect pageAllocation; 0304 unsigned int borderWidth; 0305 int pageIndex; 0306 GtkWidget *page; 0307 GList *children = nullptr; 0308 // check tab visibility 0309 if (!(gtk_notebook_get_show_tabs(notebook) && 0310 (children = gtk_container_get_children(GTK_CONTAINER(notebook))))) { 0311 return empty; 0312 } 0313 g_list_free(children); 0314 // get full rect 0315 rect = Widget::getAllocation(GTK_WIDGET(notebook)); 0316 0317 // adjust to account for borderwidth 0318 borderWidth = gtk_container_get_border_width(GTK_CONTAINER(notebook)); 0319 0320 rect.x += borderWidth; 0321 rect.y += borderWidth; 0322 rect.height -= 2 * borderWidth; 0323 rect.width -= 2 * borderWidth; 0324 0325 // get current page 0326 pageIndex = gtk_notebook_get_current_page(notebook); 0327 0328 if (!(pageIndex >= 0 && pageIndex < gtk_notebook_get_n_pages(notebook))) { 0329 return empty; 0330 } 0331 page = gtk_notebook_get_nth_page(notebook, pageIndex); 0332 if (!page) { 0333 return empty; 0334 } 0335 0336 // removes page allocated size from rect, based on tabwidget orientation 0337 pageAllocation = Widget::getAllocation(page); 0338 switch (gtk_notebook_get_tab_pos(notebook)) { 0339 case GTK_POS_BOTTOM: 0340 rect.y += pageAllocation.height; 0341 rect.height -= pageAllocation.height; 0342 break; 0343 case GTK_POS_TOP: 0344 rect.height -= pageAllocation.height; 0345 break; 0346 case GTK_POS_RIGHT: 0347 rect.x += pageAllocation.width; 0348 rect.width -= pageAllocation.width; 0349 break; 0350 case GTK_POS_LEFT: 0351 rect.width -= pageAllocation.width; 0352 break; 0353 } 0354 return rect; 0355 } 0356 0357 bool 0358 hasVisibleArrows(GtkNotebook *notebook) 0359 { 0360 if (gtk_notebook_get_show_tabs(notebook)) { 0361 int numPages = gtk_notebook_get_n_pages(notebook); 0362 for (int i = 0;i < numPages;i++) { 0363 GtkWidget *label = gtk_notebook_get_tab_label( 0364 notebook, gtk_notebook_get_nth_page(notebook, i)); 0365 #if GTK_CHECK_VERSION(2, 20, 0) 0366 if (label && !gtk_widget_get_mapped(label)) { 0367 return true; 0368 } 0369 #else 0370 if (label && !GTK_WIDGET_MAPPED(label)) { 0371 return true; 0372 } 0373 #endif 0374 } 0375 } 0376 return false; 0377 } 0378 0379 } 0380 }