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 }