changeset 29467:80457383d5e1

allow rotated tabs in the file editor (bug #60276) * gui-preferences-ed.h: define new preference names and default values * file-editor.cc (notice_settings): set rotation mode for tabs, compute height and width depending on rotation, set style sheet with desired sizes, omit close button in case of rotation * settings-dialog.cc (settings_dialog): initials new items with the values from the settings file; (write_changed_settings): write current values into the settings file * settings-dialog.ui: new preferences for tab rotation and max. tab width * tab-bar.cc (set_rotated): new function for settings the rotation state; (tabSizeHint): reimplemented size hint allowing rotated tabs; (paintEvent): reimplemented paint event allowing rotated tabs * tab-bar.h new functions set_rotated, tabSizeHint, and paintEvent, new class variable m_rotated
author Torsten Lilge <ttl-octave@mailbox.org>
date Sun, 28 Mar 2021 22:55:08 +0200
parents 7c8a70e4daad
children 46def32e6806
files libgui/src/gui-preferences-ed.h libgui/src/m-editor/file-editor.cc libgui/src/settings-dialog.cc libgui/src/settings-dialog.ui libgui/src/tab-bar.cc libgui/src/tab-bar.h
diffstat 6 files changed, 182 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/gui-preferences-ed.h	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/gui-preferences-ed.h	Sun Mar 28 22:55:08 2021 +0200
@@ -204,6 +204,12 @@
 const gui_pref
 ed_tab_position ("editor/tab_position", QVariant (QTabWidget::North));
 
+const gui_pref
+ed_tabs_rotated ("editor/tabs_rotated", QVariant (false));
+
+const gui_pref
+ed_tabs_max_width ("editor/tabs_max_width", QVariant (0));
+
 // File handling
 
 const gui_pref
--- a/libgui/src/m-editor/file-editor.cc	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/m-editor/file-editor.cc	Sun Mar 28 22:55:08 2021 +0200
@@ -1238,26 +1238,24 @@
     int icon_size = st->pixelMetric (global_icon_sizes[size_idx]);
     m_tool_bar->setIconSize (QSize (icon_size, icon_size));
 
-    // Tab position
+    // Tab position and rotation
     QTabWidget::TabPosition pos
       = static_cast<QTabWidget::TabPosition> (settings->value (ed_tab_position).toInt ());
+    bool rotated = settings->value (ed_tabs_rotated).toBool ();
 
     m_tab_widget->setTabPosition (pos);
 
-    // Update style sheet properties depending on position
-    QString width_str ("width");
-    QString height_str ("height");
-    if (pos == QTabWidget::West || pos == QTabWidget::East)
-      {
-        width_str = QString ("height");
-        height_str = QString ("width");
-      }
-
-    // Min and max width for full path titles
-    int tab_width_min = settings->value (ed_notebook_tab_width_min)
-                        .toInt ();
-    int tab_width_max = settings->value (ed_notebook_tab_width_max)
-                        .toInt ();
+    if (rotated)
+      m_tab_widget->setTabsClosable (false);  // No close buttons
+      // FIXME: close buttons can not be correctly placed in rotated tabs
+
+    // Get the tab bar and set the rotation
+    int rotation = rotated;
+    if (pos == QTabWidget::West)
+      rotation = -rotation;
+
+    tab_bar *bar = m_tab_widget->get_tab_bar ();
+    bar->set_rotated (rotation);
 
     // Get suitable height of a tab related to font and icon size
     int height = 1.5*QFontMetrics (m_tab_widget->font ()).height ();
@@ -1265,13 +1263,32 @@
     if (is > height)
       height = is;
 
-    // Style sheet for tab height
-    QString style_sheet = QString ("QTabBar::tab {max-" + height_str + ": %1px;}")
-                          .arg (height);
-
-    // Style sheet for tab height together with width
+    // Calculate possibly limited width and set the elide mode
+    int chars = settings->value (ed_tabs_max_width).toInt ();
+    int width = 9999;
+    if (chars > 0)
+      width = chars * QFontMetrics (m_tab_widget->font ()).averageCharWidth ();
+
+    // Get tab bar size properties for style sheet depending on rotation
+    QString width_str ("width");
+    QString height_str ("height");
+    if ((pos == QTabWidget::West) || (pos == QTabWidget::East))
+      {
+        width_str = QString ("height");
+        height_str = QString ("width");
+      }
+
+    QString style_sheet
+        = QString ("QTabBar::tab {max-" + height_str + ": %1px;\n"
+                                 "max-" + width_str + ": %2px; }")
+                          .arg (height).arg (width);
+
+    // Style sheet for tab height together with width when full path is shown
     if (settings->value (ed_long_window_title).toBool ())
       {
+        // Min and max width for full path titles
+        int tab_width_min = settings->value (ed_notebook_tab_width_min).toInt ();
+        int tab_width_max = settings->value (ed_notebook_tab_width_max).toInt ();
         style_sheet = QString ("QTabBar::tab "
                                " {max-" + height_str + ": %1px;"
                                "  min-" + width_str + ": %2px;"
@@ -1279,21 +1296,21 @@
                       .arg (height).arg (tab_width_min).arg (tab_width_max);
         m_tab_widget->setElideMode (Qt::ElideLeft);
       }
-    else
-      {
-        m_tab_widget->setElideMode (Qt::ElideNone);
-      }
 
 #if defined (Q_OS_MAC)
     // FIXME: This is a workaround for missing tab close buttons on MacOS
     // in several Qt versions (https://bugreports.qt.io/browse/QTBUG-61092)
-    QString close_button_css
-      ("QTabBar::close-button"
-       "  { width: 6px; image: url(:/actions/icons/widget-close.png);}\n"
-       "QTabBar::close-button:hover"
-       "  { background-color: #cccccc; }");
-
-    style_sheet = style_sheet + close_button_css;
+    if (! rotated)
+      {
+        QString close_button_css_mac (
+            "QTabBar::close-button"
+            "  { width: 6px; image: url(:/actions/icons/widget-close.png);"
+            "    subcontrol-position: button; }\n"
+            "QTabBar::close-button:hover"
+            "  { background-color: #cccccc; }");
+
+        style_sheet = style_sheet + close_button_css_mac;
+      }
 #endif
 
     m_tab_widget->setStyleSheet (style_sheet);
--- a/libgui/src/settings-dialog.cc	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/settings-dialog.cc	Sun Mar 28 22:55:08 2021 +0200
@@ -311,6 +311,9 @@
     editor_combox_tab_pos->setCurrentIndex
       (settings->value (ed_tab_position).toInt ());
 
+    editor_cb_tabs_rotated->setChecked (settings->value (ed_tabs_rotated).toBool ());
+    editor_sb_tabs_max_width->setValue (settings->value (ed_tabs_max_width).toInt ());
+
     int selected_comment_string, selected_uncomment_string;
 
     if (settings->contains (ed_comment_str.key))   // new version (radio buttons)
@@ -961,6 +964,8 @@
     settings->setValue (ed_default_eol_mode.key, combo_eol_mode->currentIndex ());
 
     settings->setValue (ed_tab_position.key, editor_combox_tab_pos->currentIndex ());
+    settings->setValue (ed_tabs_rotated.key, editor_cb_tabs_rotated->isChecked ());
+    settings->setValue (ed_tabs_max_width.key, editor_sb_tabs_max_width->value ());
 
     // Comment strings
     int rb_uncomment = 0;
--- a/libgui/src/settings-dialog.ui	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/settings-dialog.ui	Sun Mar 28 22:55:08 2021 +0200
@@ -32,7 +32,7 @@
       </size>
      </property>
      <property name="currentIndex">
-      <number>0</number>
+      <number>2</number>
      </property>
      <widget class="QWidget" name="tab_general">
       <property name="enabled">
@@ -863,7 +863,7 @@
             <x>0</x>
             <y>0</y>
             <width>1021</width>
-            <height>1529</height>
+            <height>1530</height>
            </rect>
           </property>
           <layout class="QVBoxLayout" name="verticalLayout_16">
@@ -1225,6 +1225,32 @@
                     <property name="orientation">
                      <enum>Qt::Horizontal</enum>
                     </property>
+                    <property name="sizeType">
+                     <enum>QSizePolicy::Expanding</enum>
+                    </property>
+                    <property name="sizeHint" stdset="0">
+                     <size>
+                      <width>20</width>
+                      <height>20</height>
+                     </size>
+                    </property>
+                   </spacer>
+                  </item>
+                  <item>
+                   <widget class="QCheckBox" name="editor_cb_tabs_rotated">
+                    <property name="toolTip">
+                     <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Roate tabs: Vertical when at top or bottom and horizontal when left or right. The close button is not shown in rotated tabs.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                    </property>
+                    <property name="text">
+                     <string>Rotated tabs</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item>
+                   <spacer name="horizontalSpacer_33">
+                    <property name="orientation">
+                     <enum>Qt::Horizontal</enum>
+                    </property>
                     <property name="sizeHint" stdset="0">
                      <size>
                       <width>40</width>
@@ -1238,7 +1264,7 @@
                 <item row="0" column="0">
                  <widget class="QLabel" name="label_30">
                   <property name="text">
-                   <string>Tab position</string>
+                   <string>Position</string>
                   </property>
                  </widget>
                 </item>
@@ -1342,6 +1368,40 @@
                   </property>
                  </spacer>
                 </item>
+                <item row="0" column="3">
+                 <layout class="QHBoxLayout" name="horizontalLayout_15">
+                  <item>
+                   <widget class="QLabel" name="label_3">
+                    <property name="text">
+                     <string>Max. tab width in chars (0: no limit)</string>
+                    </property>
+                   </widget>
+                  </item>
+                  <item>
+                   <widget class="QSpinBox" name="editor_sb_tabs_max_width">
+                    <property name="toolTip">
+                     <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Max. width of a tab in characters (average char. width). Especially useful for rotated tabs.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+                    </property>
+                    <property name="maximum">
+                     <number>64</number>
+                    </property>
+                   </widget>
+                  </item>
+                  <item>
+                   <spacer name="horizontalSpacer_38">
+                    <property name="orientation">
+                     <enum>Qt::Horizontal</enum>
+                    </property>
+                    <property name="sizeHint" stdset="0">
+                     <size>
+                      <width>40</width>
+                      <height>20</height>
+                     </size>
+                    </property>
+                   </spacer>
+                  </item>
+                 </layout>
+                </item>
                </layout>
               </item>
              </layout>
--- a/libgui/src/tab-bar.cc	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/tab-bar.cc	Sun Mar 28 22:55:08 2021 +0200
@@ -34,12 +34,21 @@
 
 namespace octave
 {
+  //
+  // Reimplemented QTabbar
+  //
+
   tab_bar::tab_bar (QWidget *p)
     : QTabBar (p), m_context_menu (new QMenu (this))
   { }
 
   tab_bar::~tab_bar (void) { }
 
+  void tab_bar::set_rotated (int rotated)
+  {
+    m_rotated = rotated;
+  }
+
   // slots for tab navigation
   void tab_bar::switch_left_tab (void)
   {
@@ -121,6 +130,48 @@
     setCurrentIndex (tab_with_focus);
   }
 
+  // Reimplemented size hint allowing rotated tabs
+  QSize tab_bar::tabSizeHint (int idx) const
+  {
+    QSize s = QTabBar::tabSizeHint (idx);
+    if (m_rotated)
+      s.transpose();
+
+    return s;
+  }
+
+  // Reimplemented paint event allowing rotated tabs
+  void tab_bar::paintEvent(QPaintEvent *e)
+  {
+    // Just process the original event if not rotated
+    if (! m_rotated)
+      return QTabBar::paintEvent (e);
+
+    // Process the event for rotated tabs
+    QStylePainter painter (this);
+    QStyleOptionTab opt;
+
+    for (int idx = 0; idx < count(); idx++)
+      {
+        initStyleOption (&opt, idx);
+        painter.drawControl (QStyle::CE_TabBarTabShape, opt);
+        painter.save ();
+
+        QSize s = opt.rect.size();
+        s.transpose();
+        QRect rect (QPoint (), s);
+        rect.moveCenter (opt.rect.center ());
+        opt.rect = rect;
+
+        QPoint p = tabRect (idx).center ();
+        painter.translate (p);
+        painter.rotate (-m_rotated*90);
+        painter.translate (-p);
+        painter.drawControl (QStyle::CE_TabBarTabLabel, opt);
+        painter.restore ();
+      }
+  }
+
   // Reimplement mouse event for filtering out the desired mouse clicks
   void tab_bar::mousePressEvent (QMouseEvent *me)
   {
--- a/libgui/src/tab-bar.h	Sun Mar 28 15:35:05 2021 -0400
+++ b/libgui/src/tab-bar.h	Sun Mar 28 22:55:08 2021 +0200
@@ -31,11 +31,15 @@
 
 #include <QMenu>
 #include <QMouseEvent>
+#include <QSize>
+#include <QStyleOptionTab>
+#include <QStylePainter>
 #include <QTabBar>
 
 namespace octave
 {
-  // Subclassed QTabBar for usable tab-bar and reimplemented mouse event
+  // Subclassed QTabBar for usable tab-bar, rotated tabs and
+  // reimplemented mouse event
 
   class tab_bar : public QTabBar
   {
@@ -47,7 +51,9 @@
 
     ~tab_bar (void);
 
+    void set_rotated (int rotated);
     QMenu *get_context_menu (void) { return m_context_menu; };
+    QSize tabSizeHint (int idx) const;
 
   signals:
 
@@ -67,6 +73,7 @@
 
   protected:
 
+    void paintEvent(QPaintEvent *e);
     void mousePressEvent(QMouseEvent *event);
 
   private:
@@ -75,6 +82,7 @@
 
     QMenu *m_context_menu;
     QList <QAction *> m_ctx_actions;
+    int m_rotated;
   };
 }