1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2018-2021 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING.  If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 // This file implements a tab bar derived from QTabBar with a contextmenu
27 // and possibility to close a tab via double-left or middle mouse click.
28 
29 #if defined (HAVE_CONFIG_H)
30 #  include "config.h"
31 #endif
32 
33 #include "tab-bar.h"
34 
35 namespace octave
36 {
tab_bar(QWidget * p)37   tab_bar::tab_bar (QWidget *p)
38     : QTabBar (p), m_context_menu (new QMenu (this))
39   { }
40 
~tab_bar(void)41   tab_bar::~tab_bar (void)
42   {
43     delete m_context_menu;
44   }
45 
46   // slots for tab navigation
switch_left_tab(void)47   void tab_bar::switch_left_tab (void)
48   {
49     switch_tab (-1);
50   }
51 
switch_right_tab(void)52   void tab_bar::switch_right_tab (void)
53   {
54     switch_tab (1);
55   }
56 
move_tab_left(void)57   void tab_bar::move_tab_left (void)
58   {
59 #if defined (HAVE_QTABWIDGET_SETMOVABLE)
60     switch_tab (-1, true);
61 #endif
62   }
63 
move_tab_right(void)64   void tab_bar::move_tab_right (void)
65   {
66 #if defined (HAVE_QTABWIDGET_SETMOVABLE)
67     switch_tab (1, true);
68 #endif
69   }
70 
switch_tab(int direction,bool movetab)71   void tab_bar::switch_tab (int direction, bool movetab)
72   {
73     int tabs = count ();
74 
75     if (tabs < 2)
76       return;
77 
78     int old_pos = currentIndex ();
79     int new_pos = currentIndex () + direction;
80 
81     if (new_pos < 0 || new_pos >= tabs)
82       new_pos = new_pos - direction*tabs;
83 
84     if (movetab)
85       {
86 #if defined (HAVE_QTABWIDGET_SETMOVABLE)
87         moveTab (old_pos, new_pos);
88         setCurrentIndex (old_pos);
89         setCurrentIndex (new_pos);
90 #endif
91       }
92     else
93       setCurrentIndex (new_pos);
94   }
95 
sort_tabs_alph(void)96   void tab_bar::sort_tabs_alph (void)
97   {
98     QString current_title = tabText (currentIndex ());
99     int tab_with_focus = 0;
100 
101     // Get all tab title and sort
102     QStringList tab_texts;
103 
104     for (int i = 0; i < count (); i++)
105       tab_texts.append (tabText (i));
106 
107     tab_texts.sort ();
108 
109     // Move tab into the order of the generated string list
110     for (int title = 0; title < tab_texts.count (); title++)
111       {
112         // Target tab is same as place of title in QStringList.
113         // Find index of next title in string list, leaving out the
114         // tabs (or titles) that were already moved.
115         for (int tab = title; tab < count (); tab++)
116           {
117             if (tabText (tab) == tab_texts.at (title))
118               {
119                 // Index of next tile found, so move tab into next position
120                 moveTab (tab, title);
121 
122                 if (tab_texts.at (title) == current_title)
123                   tab_with_focus = title;
124 
125                 break;
126               }
127           }
128       }
129 
130     setCurrentIndex (tab_with_focus);
131   }
132 
133   // Reimplement mouse event for filtering out the desired mouse clicks
mousePressEvent(QMouseEvent * me)134   void tab_bar::mousePressEvent (QMouseEvent *me)
135   {
136     QPoint click_pos;
137     int clicked_idx = -1;
138 
139     // detect the tab where the click occurred
140     for (int i = 0; i < count (); i++)
141       {
142         click_pos = mapToGlobal (me->pos ());
143         if (tabRect (i).contains (mapFromGlobal (click_pos)))
144           {
145             clicked_idx = i;
146             break;
147           }
148       }
149 
150     // If a tab was clicked
151     if (clicked_idx >= 0)
152       {
153         int current_idx = currentIndex ();
154         int current_count = count ();
155 
156         // detect the mouse click
157         if ((me->type () == QEvent::MouseButtonDblClick
158              && me->button() == Qt::LeftButton)
159             || (me->type () != QEvent::MouseButtonDblClick
160                 && me->button() == Qt::MidButton))
161           {
162             // Middle click or double click -> close the tab
163             // Make the clicked tab the current one and close it
164             setCurrentIndex (clicked_idx);
165             emit close_current_tab_signal (true);
166             // Was the closed tab before or after the previously current tab?
167             // According to the result, use previous index or reduce it by one
168             if (current_idx - clicked_idx > 0)
169               setCurrentIndex (current_idx - 1);
170             else if (current_idx - clicked_idx < 0)
171               setCurrentIndex (current_idx);
172           }
173         else if (me->type () != QEvent::MouseButtonDblClick
174                  && me->button() == Qt::RightButton)
175           {
176             // Right click, show context menu
177             setCurrentIndex (clicked_idx);
178             if (! m_context_menu->exec (click_pos))
179               {
180                 // No action selected, back to previous tab
181                 setCurrentIndex (current_idx);
182               }
183             else if (count () < current_count)
184               {
185                 // A tab was closed:
186                 // Was the possibly only closed tab before or after the
187                 // previously current tab? According to the result, use previous
188                 // index or reduce it by one.  Also prevent using a too large
189                 // index if other or all files were closed.
190                 int new_idx = count () - 1;
191                 if (new_idx > 0)
192                   {
193                     if (current_idx - clicked_idx > 0)
194                       new_idx = current_idx - 1;
195                     else if (current_idx - clicked_idx < 0)
196                       new_idx = current_idx;
197                   }
198                 if (new_idx >= 0)
199                   setCurrentIndex (new_idx);
200               }
201           }
202         else
203           {
204             // regular handling of the mouse event
205             QTabBar::mousePressEvent (me);
206           }
207       }
208     else
209       {
210         // regular handling of the mouse event
211         QTabBar::mousePressEvent (me);
212       }
213   }
214 }
215