From 3663b3ea3203671a2ed86980aefe5f05202ebe40 Mon Sep 17 00:00:00 2001
From: Bill Somerville <bill@classdesign.com>
Date: Fri, 27 Nov 2020 01:14:12 +0000
Subject: [PATCH] MultiGeometryWidget template class abstracts window geometry
 changes

---
 widgets/MultiGeometryWidget.hpp |  92 ++++++++++++++++++++++++++++
 widgets/mainwindow.cpp          | 103 ++++++++++++++++++++++----------
 widgets/mainwindow.h            |   9 ++-
 widgets/mainwindow.ui           |  36 +----------
 4 files changed, 171 insertions(+), 69 deletions(-)
 create mode 100644 widgets/MultiGeometryWidget.hpp

diff --git a/widgets/MultiGeometryWidget.hpp b/widgets/MultiGeometryWidget.hpp
new file mode 100644
index 000000000..a9386189c
--- /dev/null
+++ b/widgets/MultiGeometryWidget.hpp
@@ -0,0 +1,92 @@
+#ifndef MULTI_GEOMETRY_WIDGET_HPP__
+#define MULTI_GEOMETRY_WIDGET_HPP__
+
+#include <cstddef>
+#include <utility>
+#include <array>
+
+#include <QWidget>
+#include <QByteArray>
+#include <QEvent>
+
+//
+// Class MultiGeometryWidget<N, Widget> - Decorate a QWidget type with
+// 																				switchable geometries
+//
+// The abstract base class imbues a Qt Widget type with N alternative
+// geometries. Sub-classes can initialise the currently selected
+// geometry and the initial geometries using geometries. To switch
+// geometry call select_geometry(n) which saves the current geometry
+// and switches to the n'th saved geometry.
+//
+template<std::size_t N, typename Widget=QWidget>
+class MultiGeometryWidget
+  : public Widget
+{
+public:
+  template<typename... Args>
+  explicit MultiGeometryWidget (Args&&... args)
+    : Widget {std::forward<Args> (args)...}
+    , current_geometry_ {0}
+  {
+  }
+
+  void geometries (std::size_t current
+                   , std::array<QByteArray, N> const& the_geometries = std::array<QByteArray, N> {})
+  {
+    Q_ASSERT (current < the_geometries.size ());
+    saved_geometries_ = the_geometries;
+    current_geometry_ = current;
+    Widget::restoreGeometry (saved_geometries_[current_geometry_]);
+  }
+
+  std::array<QByteArray, N> const& geometries () const {return saved_geometries_;}
+  std::size_t current () {return current_geometry_;}
+
+  // Call this to select a new geometry denoted by the 'n' argument,
+  // any actual layout changes should be made in the implementation of
+  // the change_layout operation below.
+  void select_geometry (std::size_t n)
+  {
+    Q_ASSERT (n < N);
+    auto geometry = Widget::saveGeometry ();
+    change_layout (n);
+    saved_geometries_[current_geometry_] = geometry;
+    current_geometry_ = n;
+
+    // Defer restoration of the window geometry until the layour
+    // request event has been processed, this is necessary otherwise
+    // the final geometry may be affected by widgets not shown in the
+    // new layout.
+    desired_geometry_ = saved_geometries_[n];
+  }
+
+protected:
+  virtual ~MultiGeometryWidget () {}
+
+private:
+  // Override this operation to implement any layout changes for the
+  // geometry specified by the argument 'n'.
+  virtual void change_layout (std::size_t n) = 0;
+
+  bool event (QEvent * e) override
+  {
+    auto ret = Widget::event (e);
+    if (QEvent::LayoutRequest == e->type ()
+        && desired_geometry_.size ())
+      {
+        // Restore the new desired geometry and flag that we have done
+        // so by clearing the desired_geometry_ member variable.
+        QByteArray geometry;
+        std::swap (geometry, desired_geometry_);
+        Widget::restoreGeometry (geometry);
+      }
+    return ret;
+  }
+
+  std::size_t current_geometry_;
+  std::array<QByteArray, N> saved_geometries_;
+  QByteArray desired_geometry_;
+};
+
+#endif
diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp
index 44a1af29e..eafa76545 100644
--- a/widgets/mainwindow.cpp
+++ b/widgets/mainwindow.cpp
@@ -236,7 +236,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
                        MultiSettings * multi_settings, QSharedMemory *shdmem,
                        unsigned downSampleFactor,
                        QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) :
-  QMainWindow(parent),
+  MultiGeometryWidget {parent},
   m_env {env},
   m_network_manager {this},
   m_valid {true},
@@ -1022,10 +1022,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
   }
 
   ui->pbBestSP->setVisible(m_mode=="FT4");
-  if(!ui->cbMenus->isChecked()) {
-    ui->cbMenus->setChecked(true);
-    ui->cbMenus->setChecked(false);
-  }
+
 // this must be the last statement of constructor
   if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
 }
@@ -1094,8 +1091,33 @@ MainWindow::~MainWindow()
 void MainWindow::writeSettings()
 {
   m_settings->beginGroup("MainWindow");
-  m_settings->setValue ("geometry", saveGeometry ());
-  m_settings->setValue ("geometryNoControls", m_geometryNoControls);
+  if (ui->actionSWL_Mode->isChecked ())
+    {
+      m_settings->setValue ("SWLView", true);
+      m_settings->setValue ("ShowMenus", ui->cbMenus->isChecked ());
+      m_settings->setValue ("geometry", geometries ()[0]);
+      m_settings->setValue ("SWLModeGeometry", saveGeometry ());
+      m_settings->setValue ("geometryNoControls", geometries ()[2]);
+    }
+  else
+    {
+      if (ui->cbMenus->isChecked())
+        {
+          m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ());
+          m_settings->setValue ("ShowMenus", true);
+          m_settings->setValue ("geometry", saveGeometry ());
+          m_settings->setValue ("SWLModeGeometry", geometries ()[1]);
+          m_settings->setValue ("geometryNoControls", geometries ()[2]);
+        }
+      else
+        {
+          m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ());
+          m_settings->setValue ("ShowMenus", false);
+          m_settings->setValue ("geometry", geometries ()[0]);
+          m_settings->setValue ("SWLModeGeometry", geometries ()[1]);
+          m_settings->setValue ("geometryNoControls", saveGeometry ());
+        }
+    }
   m_settings->setValue ("state", saveState ());
   m_settings->setValue("MRUdir", m_path);
   m_settings->setValue("TxFirst",m_txFirst);
@@ -1105,7 +1127,6 @@ void MainWindow::writeSettings()
   m_settings->setValue ("MsgAvgDisplayed", m_msgAvgWidget && m_msgAvgWidget->isVisible ());
   m_settings->setValue ("FoxLogDisplayed", m_foxLogWindow && m_foxLogWindow->isVisible ());
   m_settings->setValue ("ContestLogDisplayed", m_contestLogWindow && m_contestLogWindow->isVisible ());
-  m_settings->setValue("ShowMenus",ui->cbMenus->isChecked());
   m_settings->setValue("CallFirst",ui->cbFirst->isChecked());
   m_settings->setValue("HoundSort",ui->comboBoxHoundSort->currentIndex());
   m_settings->setValue("FoxNlist",ui->sbNlist->value());
@@ -1159,9 +1180,8 @@ void MainWindow::writeSettings()
   m_settings->setValue("pwrBandTuneMemory",m_pwrBandTuneMemory);
   m_settings->setValue ("FT8AP", ui->actionEnable_AP_FT8->isChecked ());
   m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ());
-  m_settings->setValue("SplitterState",ui->splitter->saveState());
+  m_settings->setValue("SplitterState",ui->decodes_splitter->saveState());
   m_settings->setValue("Blanker",ui->sbNB->value());
-  m_settings->setValue ("SWLView", ui->actionSWL_Mode->isChecked ());
 
   {
     QList<QVariant> coeffs;     // suitable for QSettings
@@ -1180,8 +1200,17 @@ void MainWindow::readSettings()
   ui->cbAutoSeq->setVisible(false);
   ui->cbFirst->setVisible(false);
   m_settings->beginGroup("MainWindow");
-  restoreGeometry (m_settings->value ("geometry", saveGeometry ()).toByteArray ());
-  m_geometryNoControls = m_settings->value ("geometryNoControls",saveGeometry()).toByteArray();
+  std::array<QByteArray, 3> the_geometries;
+  the_geometries[0] = m_settings->value ("geometry", saveGeometry ()).toByteArray ();
+  the_geometries[1] = m_settings->value ("SWLModeGeometry", saveGeometry ()).toByteArray ();
+  the_geometries[2] = m_settings->value ("geometryNoControls", saveGeometry ()).toByteArray ();
+  auto SWL_mode = m_settings->value ("SWLView", false).toBool ();
+  auto show_menus = m_settings->value ("ShowMenus", true).toBool ();
+  ui->actionSWL_Mode->setChecked (SWL_mode);
+  ui->cbMenus->setChecked (show_menus);
+  auto current_view_mode = SWL_mode ? 1 : show_menus ? 0 : 2;
+  change_layout (current_view_mode);
+  geometries (current_view_mode, the_geometries);
   restoreState (m_settings->value ("state", saveState ()).toByteArray ());
   ui->dxCallEntry->setText (m_settings->value ("DXcall", QString {}).toString ());
   ui->dxGridEntry->setText (m_settings->value ("DXgrid", QString {}).toString ());
@@ -1191,7 +1220,6 @@ void MainWindow::readSettings()
   auto displayMsgAvg = m_settings->value ("MsgAvgDisplayed", false).toBool ();
   auto displayFoxLog = m_settings->value ("FoxLogDisplayed", false).toBool ();
   auto displayContestLog = m_settings->value ("ContestLogDisplayed", false).toBool ();
-  ui->cbMenus->setChecked(m_settings->value("ShowMenus",true).toBool());
   ui->cbFirst->setChecked(m_settings->value("CallFirst",true).toBool());
   ui->comboBoxHoundSort->setCurrentIndex(m_settings->value("HoundSort",3).toInt());
   ui->sbNlist->setValue(m_settings->value("FoxNlist",12).toInt());
@@ -1265,10 +1293,8 @@ void MainWindow::readSettings()
   m_pwrBandTuneMemory=m_settings->value("pwrBandTuneMemory").toHash();
   ui->actionEnable_AP_FT8->setChecked (m_settings->value ("FT8AP", false).toBool());
   ui->actionEnable_AP_JT65->setChecked (m_settings->value ("JT65AP", false).toBool());
-  ui->splitter->restoreState(m_settings->value("SplitterState").toByteArray());
+  ui->decodes_splitter->restoreState(m_settings->value("SplitterState").toByteArray());
   ui->sbNB->setValue(m_settings->value("Blanker",0).toInt());
-  ui->actionSWL_Mode->setChecked (m_settings->value ("SWLView", false).toBool ());
-  on_actionSWL_Mode_triggered (ui->actionSWL_Mode->isChecked ());
   {
     auto const& coeffs = m_settings->value ("PhaseEqualizationCoefficients"
                                             , QList<QVariant> {0., 0., 0., 0., 0.}).toList ();
@@ -2568,34 +2594,47 @@ void MainWindow::on_actionCopyright_Notice_triggered()
   MessageBox::warning_message(this, message);
 }
 
+// Implement the MultiGeometryWidget::change_layout() operation.
+void MainWindow::change_layout (std::size_t n)
+{
+  switch (n)
+    {
+    case 1:                     // SWL view
+      ui->menuBar->show ();
+      ui->lower_panel_widget->hide ();
+      trim_view (false);        // ensure we can switch back
+      break;
+
+    case 2:                     // hide menus view
+      ui->menuBar->hide ();
+      ui->lower_panel_widget->show ();
+      trim_view (true);
+      break;
+
+    default:                    // normal view
+      ui->menuBar->setVisible (ui->cbMenus->isChecked ());
+      ui->lower_panel_widget->show ();
+      trim_view (!ui->cbMenus->isChecked ());
+      break;
+    }
+}
+
 void MainWindow::on_actionSWL_Mode_triggered (bool checked)
 {
-  ui->lower_panel_widget->setVisible (!checked);
-  if (checked)
-    {
-      hideMenus (false);        // make sure we can be turned off
-    }
+  select_geometry (checked ? 1 : ui->cbMenus->isChecked () ? 0 : 2);
 }
 
 // This allows the window to shrink by removing certain things
 // and reducing space used by controls
-void MainWindow::hideMenus(bool checked)
+void MainWindow::trim_view (bool checked)
 {
   int spacing = checked ? 1 : 6;
   if (checked) {
       statusBar ()->removeWidget (&auto_tx_label);
-      minimumSize().setHeight(450);
-      minimumSize().setWidth(700);
-      restoreGeometry(m_geometryNoControls);
-      updateGeometry();
   } else {
-      m_geometryNoControls = saveGeometry();
       statusBar ()->addWidget(&auto_tx_label);
-      minimumSize().setHeight(520);
-      minimumSize().setWidth(770);
   }
-  ui->menuBar->setVisible(!checked);
-  if(m_mode!="FreqCal" and m_mode!="WSPR" and m_mode!="FST4W") {
+  if (m_mode != "FreqCal" && m_mode != "WSPR" && m_mode != "FST4W") {
     ui->lh_decodes_title_label->setVisible(!checked);
     ui->rh_decodes_title_label->setVisible(!checked);
   }
@@ -8437,7 +8476,7 @@ void MainWindow::update_watchdog_label ()
 
 void MainWindow::on_cbMenus_toggled(bool b)
 {
-  hideMenus(!b);
+  select_geometry (!b ? 2 : ui->actionSWL_Mode->isChecked () ? 1 : 0);
 }
 
 void MainWindow::on_cbCQonly_toggled(bool)
diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h
index c51a418de..dd53e8837 100644
--- a/widgets/mainwindow.h
+++ b/widgets/mainwindow.h
@@ -26,6 +26,7 @@
 #include <QFuture>
 #include <QFutureWatcher>
 
+#include "MultiGeometryWidget.hpp"
 #include "NonInheritingProcess.hpp"
 #include "Audio/AudioDevice.hpp"
 #include "commons.h"
@@ -94,7 +95,8 @@ class MultiSettings;
 class EqualizationToolsDialog;
 class DecodedText;
 
-class MainWindow : public QMainWindow
+class MainWindow
+  : public MultiGeometryWidget<3, QMainWindow>
 {
   Q_OBJECT;
 
@@ -130,7 +132,8 @@ public slots:
   void msgAvgDecode2();
   void fastPick(int x0, int x1, int y);
 
-protected:
+private:
+  void change_layout (std::size_t) override;
   void keyPressEvent (QKeyEvent *) override;
   void closeEvent(QCloseEvent *) override;
   void childEvent(QChildEvent *) override;
@@ -353,7 +356,7 @@ private:
   void astroUpdate ();
   void writeAllTxt(QString message);
   void auto_sequence (DecodedText const& message, unsigned start_tolerance, unsigned stop_tolerance);
-  void hideMenus(bool b);
+  void trim_view (bool b);
   void foxTest();
   void setColorHighlighting();
   void chkFT4();
diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui
index f4f1ed750..61affd377 100644
--- a/widgets/mainwindow.ui
+++ b/widgets/mainwindow.ui
@@ -2,20 +2,6 @@
 <ui version="4.0">
  <class>MainWindow</class>
  <widget class="QMainWindow" name="MainWindow">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>692</width>
-    <height>498</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Ignored" vsizetype="Preferred">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
   <property name="windowTitle">
    <string>WSJT-X   by K1JT</string>
   </property>
@@ -25,7 +11,7 @@
   <widget class="QWidget" name="centralWidget">
    <layout class="QVBoxLayout" name="verticalLayout_6" stretch="0,0">
     <item>
-     <widget class="QSplitter" name="splitter">
+     <widget class="QSplitter" name="decodes_splitter">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
@@ -148,18 +134,6 @@
         </item>
         <item>
          <widget class="DisplayText" name="decodedTextBrowser">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-            <horstretch>0</horstretch>
-            <verstretch>10</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>200</width>
-            <height>100</height>
-           </size>
-          </property>
           <property name="frameShape">
            <enum>QFrame::StyledPanel</enum>
           </property>
@@ -307,12 +281,6 @@
           <property name="enabled">
            <bool>true</bool>
           </property>
-          <property name="minimumSize">
-           <size>
-            <width>200</width>
-            <height>100</height>
-           </size>
-          </property>
           <property name="verticalScrollBarPolicy">
            <enum>Qt::ScrollBarAlwaysOn</enum>
           </property>
@@ -2857,7 +2825,7 @@ Double-click to reset to the standard 73 message</string>
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>692</width>
+     <width>834</width>
      <height>21</height>
     </rect>
    </property>