Pārlūkot izejas kodu

Add more controls to ctkPathLineEdit

Add browse button
Control visibility of history button (use QComboBox or QLineEdit)
Improved auto sizeHint
Better support for history QSettings
Issue #14
Julien Finet 13 gadi atpakaļ
vecāks
revīzija
0de5ad082c
2 mainītis faili ar 294 papildinājumiem un 39 dzēšanām
  1. 230 23
      Libs/Widgets/ctkPathLineEdit.cpp
  2. 64 16
      Libs/Widgets/ctkPathLineEdit.h

+ 230 - 23
Libs/Widgets/ctkPathLineEdit.cpp

@@ -29,6 +29,8 @@
 #include <QRegExp>
 #include <QRegExpValidator>
 #include <QSettings>
+#include <QStyleOptionComboBox>
+#include <QToolButton>
 
 // CTK includes
 #include "ctkPathLineEdit.h"
@@ -45,9 +47,17 @@ protected:
 public:
   ctkPathLineEditPrivate(ctkPathLineEdit& object);
   void init();
+  QSize sizeHint(const QString& text)const;
   void updateFilter();
 
+  void createPathLineEditWidget(bool useComboBox);
+  QString settingKey()const;
+
+  QLineEdit*            LineEdit;
   QComboBox*            ComboBox;
+  QToolButton*          BrowseButton;       //!< "..." button
+
+  int                   MinimumContentsLength;
 
   QString               Label;              //!< used in file dialogs
   QStringList           NameFilters;        //!< Regular expression (in wildcard mode) used to help the user to complete the line
@@ -59,6 +69,7 @@ public:
 #endif
 
   bool                  HasValidInput;      //!< boolean that stores the old state of valid input
+  QString               SettingKey;
 
   static QString        sCurrentDirectory;   //!< Content the last value of the current directory
   static int            sMaxHistory;     //!< Size of the history, if the history is full and a new value is added, the oldest value is dropped
@@ -71,7 +82,10 @@ int ctkPathLineEditPrivate::sMaxHistory = 5;
 ctkPathLineEditPrivate::ctkPathLineEditPrivate(ctkPathLineEdit& object)
   :q_ptr(&object)
 {
+  this->LineEdit = 0;
   this->ComboBox = 0;
+  this->BrowseButton = 0;
+  this->MinimumContentsLength = 17;
   this->HasValidInput = false;
   this->Filters = QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Readable;
 }
@@ -80,20 +94,106 @@ ctkPathLineEditPrivate::ctkPathLineEditPrivate(ctkPathLineEdit& object)
 void ctkPathLineEditPrivate::init()
 {
   Q_Q(ctkPathLineEdit);
-  this->ComboBox = new QComboBox(q);
+
   QHBoxLayout* layout = new QHBoxLayout(q);
-  layout->addWidget(this->ComboBox);
   layout->setContentsMargins(0,0,0,0);
+  layout->setSpacing(0); // no space between the combobx and button
+
+  this->createPathLineEditWidget(true);
+
+  this->BrowseButton = new QToolButton(q);
+  this->BrowseButton->setText("...");
+  // Don't vertically stretch the path line edit unnecessary
+  this->BrowseButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored));
+  this->BrowseButton->setToolTip(q->tr("Open a dialog"));
+
+  QObject::connect(this->BrowseButton,SIGNAL(clicked()),
+                   q, SLOT(browse()));
+
+  layout->addWidget(this->BrowseButton);
 
-  this->ComboBox->setEditable(true);
   q->setSizePolicy(QSizePolicy(
                      QSizePolicy::Expanding, QSizePolicy::Fixed,
                      QSizePolicy::LineEdit));
+}
+
+//------------------------------------------------------------------------------
+void ctkPathLineEditPrivate::createPathLineEditWidget(bool useComboBox)
+{
+  Q_Q(ctkPathLineEdit);
+
+  QString path = q->currentPath();
 
-  QObject::connect(this->ComboBox,SIGNAL(editTextChanged(QString)),
+  if (useComboBox)
+    {
+    this->ComboBox = new QComboBox(q);
+    this->ComboBox->setEditable(true);
+    this->ComboBox->setInsertPolicy(QComboBox::NoInsert);
+    this->LineEdit = this->ComboBox->lineEdit();
+    }
+  else
+    {
+    this->ComboBox = 0;
+    this->LineEdit = new QLineEdit(q);
+    }
+
+  if (q->layout() && q->layout()->itemAt(0))
+    {
+    delete q->layout()->itemAt(0)->widget();
+    }
+  qobject_cast<QHBoxLayout*>(q->layout())->insertWidget(
+    0,
+    this->ComboBox ? qobject_cast<QWidget*>(this->ComboBox) :
+    qobject_cast<QWidget*>(this->LineEdit));
+
+  this->updateFilter();
+  q->retrieveHistory();
+  q->setCurrentPath(path);
+
+  QObject::connect(this->LineEdit, SIGNAL(textChanged(QString)),
                    q, SLOT(setCurrentDirectory(QString)));
-  QObject::connect(this->ComboBox,SIGNAL(editTextChanged(QString)),
+  QObject::connect(this->LineEdit, SIGNAL(textChanged(QString)),
                    q, SLOT(updateHasValidInput()));
+  q->updateGeometry();
+}
+
+//------------------------------------------------------------------------------
+QSize ctkPathLineEditPrivate::sizeHint(const QString& text)const
+{
+  Q_Q(const ctkPathLineEdit);
+  int frame = 0;
+  if (this->ComboBox)
+    {
+    QStyleOptionComboBox option;
+    int arrowWidth = this->ComboBox->style()->subControlRect(
+      QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxArrow, this->ComboBox).width()
+      + (this->ComboBox->hasFrame() ? 2 : 0);
+    frame = 2 * (this->ComboBox->hasFrame() ? 3 : 0)
+      + arrowWidth
+      + 1; // for mac style, not sure why
+    }
+  else
+    {
+    QStyleOptionFrame option;
+    int frameWidth = this->LineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, q);
+    int horizontalMargin = 2; // QLineEditPrivate::horizontalMargin
+    // See QLineEdit::sizeHint
+    frame = 2 * frameWidth
+      + this->LineEdit->textMargins().left()
+      + this->LineEdit->textMargins().right()
+      + this->LineEdit->contentsMargins().left()
+      + this->LineEdit->contentsMargins().right()
+      + 2 * horizontalMargin;
+    }
+  int browseWidth = 0;
+  if (q->showBrowseButton())
+    {
+    browseWidth = this->BrowseButton->minimumSizeHint().width();
+    }
+  int textWidth = this->LineEdit->fontMetrics().width(text);
+  int height = (this->ComboBox ? this->ComboBox->minimumSizeHint() :
+                this->LineEdit->minimumSizeHint()).height();
+  return QSize(frame + textWidth + browseWidth, height);
 }
 
 //-----------------------------------------------------------------------------
@@ -106,7 +206,20 @@ void ctkPathLineEditPrivate::updateFilter()
                            ctk::nameFiltersToExtensions(this->NameFilters),
                            this->Filters | QDir::NoDotAndDotDot | QDir::AllDirs,
                            QDir::Name|QDir::DirsLast, newCompleter));
-  this->ComboBox->setCompleter(newCompleter);
+  this->LineEdit->setCompleter(newCompleter);
+
+  // don't accept invalid path
+  QRegExpValidator* validator = new QRegExpValidator(
+    ctk::nameFiltersToRegExp(this->NameFilters), q);
+  this->LineEdit->setValidator(validator);
+}
+
+//-----------------------------------------------------------------------------
+QString ctkPathLineEditPrivate::settingKey()const
+{
+  Q_Q(const ctkPathLineEdit);
+  return QString("ctkPathLineEdit/") +
+    (this->SettingKey.isEmpty() ? q->objectName() : this->SettingKey);
 }
 
 //-----------------------------------------------------------------------------
@@ -162,9 +275,6 @@ void ctkPathLineEdit::setNameFilters(const QStringList &nameFilters)
   Q_D(ctkPathLineEdit);
   d->NameFilters = nameFilters;
   d->updateFilter();
-  d->ComboBox->lineEdit()->setValidator(
-    new QRegExpValidator(
-      ctk::nameFiltersToRegExp(d->NameFilters), this));
 }
 
 //-----------------------------------------------------------------------------
@@ -273,12 +383,18 @@ void ctkPathLineEdit::browse()
 void ctkPathLineEdit::retrieveHistory()
 {
   Q_D(ctkPathLineEdit);
+  if (d->ComboBox == 0)
+    {
+    return;
+    }
+  QString path = this->currentPath();
+  bool wasBlocking = this->blockSignals(true);
   d->ComboBox->clear();
   // fill the combobox using the QSettings
   QSettings settings;
-  QString key = "ctkPathLineEdit/" + this->objectName();
-  QStringList history = settings.value(key).toStringList();
-  foreach(QString path, history)
+  QString key = d->settingKey();
+  const QStringList history = settings.value(key).toStringList();
+  foreach(const QString& path, history)
     {
     d->ComboBox->addItem(path);
     if (d->ComboBox->count() >= ctkPathLineEditPrivate::sMaxHistory)
@@ -286,25 +402,32 @@ void ctkPathLineEdit::retrieveHistory()
       break;
       }
     }
-  //select the most recent file location
-  if (this->currentPath().isEmpty())
+  // Restore path or select the most recent file location if none set.
+  if (path.isEmpty())
     {
+    this->blockSignals(wasBlocking);
     d->ComboBox->setCurrentIndex(0);
     }
+  else
+    {
+    this->setCurrentPath(path);
+    this->blockSignals(wasBlocking);
+    }
 }
 
 //-----------------------------------------------------------------------------
 void ctkPathLineEdit::addCurrentPathToHistory()
 {
   Q_D(ctkPathLineEdit);
-  if (this->currentPath().isEmpty())
+  if (d->ComboBox == 0 ||
+      this->currentPath().isEmpty())
     {
     return;
     }
   QSettings settings;
   //keep the same values, add the current value
   //if more than m_MaxHistory entrees, drop the oldest.
-  QString key = "ctkPathLineEdit/" + this->objectName();
+  QString key = d->settingKey();
   QStringList history = settings.value(key).toStringList();
   if (history.contains(this->currentPath()))
     {
@@ -312,7 +435,7 @@ void ctkPathLineEdit::addCurrentPathToHistory()
     }
   history.push_front(this->currentPath());
   settings.setValue(key, history);
-  int index =d->ComboBox->findText(this->currentPath());
+  int index = d->ComboBox->findText(this->currentPath());
   if (index >= 0)
     {
     d->ComboBox->removeItem(index);
@@ -352,14 +475,14 @@ QComboBox* ctkPathLineEdit::comboBox() const
 QString ctkPathLineEdit::currentPath()const
 {
   Q_D(const ctkPathLineEdit);
-  return d->ComboBox->currentText();
+  return d->LineEdit ? d->LineEdit->text() : QString();
 }
 
 //------------------------------------------------------------------------------
 void ctkPathLineEdit::setCurrentPath(const QString& path)
 {
   Q_D(ctkPathLineEdit);
-  d->ComboBox->setEditText(path);
+  d->LineEdit->setText(path);
 }
 
 //------------------------------------------------------------------------------
@@ -374,16 +497,100 @@ void ctkPathLineEdit::updateHasValidInput()
   Q_D(ctkPathLineEdit);
 
   bool oldHasValidInput = d->HasValidInput;
-  d->HasValidInput = d->ComboBox->lineEdit()->hasAcceptableInput();
+  d->HasValidInput = d->LineEdit->hasAcceptableInput();
   if (d->HasValidInput)
     {
-    QFileInfo fileInfo(d->ComboBox->currentText());
+    QFileInfo fileInfo(this->currentPath());
     ctkPathLineEditPrivate::sCurrentDirectory =
       fileInfo.isFile() ? fileInfo.absolutePath() : fileInfo.absoluteFilePath();
-    emit currentPathChanged(d->ComboBox->currentText());
+    emit currentPathChanged(this->currentPath());
     }
-  if ( d->HasValidInput != oldHasValidInput)
+  if (d->HasValidInput != oldHasValidInput)
     {
     emit validInputChanged(d->HasValidInput);
     }
+  this->updateGeometry();
+}
+
+//------------------------------------------------------------------------------
+QString ctkPathLineEdit::settingKey()const
+{
+  Q_D(const ctkPathLineEdit);
+  return d->SettingKey;
+}
+
+//------------------------------------------------------------------------------
+void ctkPathLineEdit::setSettingKey(const QString& key)
+{
+  Q_D(ctkPathLineEdit);
+  d->SettingKey = key;
+  this->retrieveHistory();
+}
+
+//------------------------------------------------------------------------------
+bool ctkPathLineEdit::showBrowseButton()const
+{
+  Q_D(const ctkPathLineEdit);
+  return d->BrowseButton->isVisibleTo(const_cast<ctkPathLineEdit*>(this));
+}
+
+//------------------------------------------------------------------------------
+void ctkPathLineEdit::setShowBrowseButton(bool visible)
+{
+  Q_D(ctkPathLineEdit);
+  d->BrowseButton->setVisible(visible);
+}
+
+//------------------------------------------------------------------------------
+bool ctkPathLineEdit::showHistoryButton()const
+{
+  Q_D(const ctkPathLineEdit);
+  return d->ComboBox ? true: false;
+}
+
+//------------------------------------------------------------------------------
+void ctkPathLineEdit::setShowHistoryButton(bool visible)
+{
+  Q_D(ctkPathLineEdit);
+  d->createPathLineEditWidget(visible);
+}
+
+//------------------------------------------------------------------------------
+int ctkPathLineEdit::minimumContentsLength()const
+{
+  Q_D(const ctkPathLineEdit);
+  return d->MinimumContentsLength;
+}
+
+//------------------------------------------------------------------------------
+void ctkPathLineEdit::setMinimumContentsLength(int length)
+{
+  Q_D(ctkPathLineEdit);
+  d->MinimumContentsLength = length;
+  this->updateGeometry();
+}
+
+//------------------------------------------------------------------------------
+QSize ctkPathLineEdit::minimumSizeHint()const
+{
+  Q_D(const ctkPathLineEdit);
+  QString fileName = QString('/') + QFileInfo(this->currentPath()).fileName();
+  if (fileName.size() < d->MinimumContentsLength)
+    {
+    fileName = QString("x").repeated(d->MinimumContentsLength);
+    }
+  QSize hint = d->sizeHint(fileName);
+  return hint;
+}
+
+//------------------------------------------------------------------------------
+QSize ctkPathLineEdit::sizeHint()const
+{
+  Q_D(const ctkPathLineEdit);
+  QString path = this->currentPath();
+  if (path.size() < d->MinimumContentsLength)
+    {
+    path = QString("x").repeated(d->MinimumContentsLength);
+    }
+  return d->sizeHint(path);
 }

+ 64 - 16
Libs/Widgets/ctkPathLineEdit.h

@@ -46,15 +46,15 @@ MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #define __ctkPathLineEdit_h
 
 // Qt includes
-#include <QWidget>
 #include <QDir>
+#include <QWidget>
 class QComboBox;
 
 // CTK includes
 #include "ctkWidgetsExport.h"
 class ctkPathLineEditPrivate;
 
-/** 
+/**
  * \ingroup Widgets
  * \brief Advanced line edit to select file or directory
 */
@@ -77,6 +77,33 @@ class CTK_WIDGETS_EXPORT ctkPathLineEdit: public QWidget
   Q_FLAGS(Option Options);
 #endif
 
+  /// This property controls the key used to search the settings for recorded
+  /// paths.
+  /// If multiple path line edits share the same key, their history is then
+  /// shared.
+  /// If an empty key string is given, the object name is used as key.
+  /// Setting the key automatically retrieve the history from settings
+  /// Empty by default.
+  /// \sa retrieveHistory(), addCurrentPathToHistory(), showHistoryButton
+  Q_PROPERTY(QString settingKey READ settingKey WRITE setSettingKey )
+
+  /// This property controls whether the browse ("...") button is visible or
+  /// not. Clicking on the button calls opens a dialog to select the current path.
+  /// True by default
+  /// \sa browse()
+  Q_PROPERTY(bool showBrowseButton READ showBrowseButton WRITE setShowBrowseButton);
+
+  /// This property controls whether the history button (arrow button that opens
+  /// the history menu) is visible or not.
+  /// True by default.
+  /// \sa retrieveHistory(), addCurrentPathToHistory(), settingKey
+  Q_PROPERTY(bool showHistoryButton READ showHistoryButton WRITE setShowHistoryButton);
+
+  /// This property holds the minimum number of characters that should fit into
+  /// the path line edit.
+  /// The default value is 17.
+  Q_PROPERTY(int minimumContentsLength READ minimumContentsLength WRITE setMinimumContentsLength)
+
 public:
   enum Filter { Dirs        = 0x001,
                 Files       = 0x002,
@@ -152,14 +179,33 @@ public:
   const Options& options()const;
 #endif
 
-  /** Change the current extension of the edit line.
-   *  If there is no extension yet, set it
-  */
+  /// Change the current extension of the edit line.
+  ///  If there is no extension yet, set it
   void setCurrentFileExtension(const QString& extension);
 
+  QString settingKey()const;
+  void setSettingKey(const QString& key);
+
+  bool showBrowseButton()const;
+  void setShowBrowseButton(bool visible);
+
+  bool showHistoryButton()const;
+  void setShowHistoryButton(bool visible);
+
+  int minimumContentsLength()const;
+  void setMinimumContentsLength(int lenght);
+
   /// Return the combo box internally used by the path line edit
   QComboBox* comboBox() const;
 
+  /// The width returned, in pixels, is the length of the file name (with no
+  /// path) if any. Otherwise, it's enough for 15 to 20 characters.
+  virtual QSize minimumSizeHint()const;
+
+  /// The width returned, in pixels, is the entire length of the current path
+  /// if any. Otherwise, it's enough for 15 to 20 characters.
+  virtual QSize sizeHint()const;
+
 Q_SIGNALS:
   /** the signal is emit when the state of hasValidInput changed
   */
@@ -170,20 +216,22 @@ Q_SIGNALS:
 public Q_SLOTS:
   void setCurrentPath(const QString& path);
 
-  /** Open a QFileDialog to select a file or directory and set current text to it
-   *  You would probably connect a browse push button like this:
-   *  connect(myPushButton,SIGNAL(clicked()),myPathLineEdit,SLOT(browse()))
-  */
+  /// Open a QFileDialog to select a file or directory and set current text to it
+  /// You would probably connect a browse push button like this:
+  /// connect(myPushButton,SIGNAL(clicked()),myPathLineEdit,SLOT(browse()))
+  /// As a conveniency, such button is provided by default via the browseButton
+  /// \sa showBrowseButton
   void browse();
 
-  /** Load the history of the paths. To be restored the inputs must have been saved by
-   *  saveCurrentPathInHistory().
-  */
-
+  /// Load the history of the paths that have been saved in the application
+  /// settings with addCurrentPathToHistory().
+  /// The history is identified using \a settingKey
+  /// \sa addCurrentPathToHistory(), showHistoryButton, settingKey
   void retrieveHistory();
-  /** Save the current value (this->currentPath()) into the history. That value will be retrieved
-   *  next time the retrieveHistory()
-  */
+
+  /// Save the current value (this->currentPath()) into the history. That value
+  ///  will be retrieved next time retrieveHistory() is called.
+  /// \sa retrieveHistory(), showHistoryButton, settingKey
   void addCurrentPathToHistory();
 
 protected Q_SLOTS: