ctkPathLineEdit.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QComboBox>
  16. #include <QCompleter>
  17. #include <QDebug>
  18. #include <QDirModel>
  19. #include <QFileDialog>
  20. #include <QHBoxLayout>
  21. #include <QLineEdit>
  22. #include <QRegExp>
  23. #include <QRegExpValidator>
  24. #include <QSettings>
  25. #include <QStyleOptionComboBox>
  26. #include <QToolButton>
  27. // CTK includes
  28. #include "ctkPathLineEdit.h"
  29. #include "ctkUtils.h"
  30. //-----------------------------------------------------------------------------
  31. class ctkPathLineEditPrivate
  32. {
  33. Q_DECLARE_PUBLIC(ctkPathLineEdit);
  34. protected:
  35. ctkPathLineEdit* const q_ptr;
  36. public:
  37. ctkPathLineEditPrivate(ctkPathLineEdit& object);
  38. void init();
  39. QSize sizeHint(const QString& text)const;
  40. void updateFilter();
  41. void createPathLineEditWidget(bool useComboBox);
  42. QString settingKey()const;
  43. QLineEdit* LineEdit;
  44. QComboBox* ComboBox;
  45. QToolButton* BrowseButton; //!< "..." button
  46. int MinimumContentsLength;
  47. QString Label; //!< used in file dialogs
  48. QStringList NameFilters; //!< Regular expression (in wildcard mode) used to help the user to complete the line
  49. QDir::Filters Filters; //!< Type of path (file, dir...)
  50. #ifdef USE_QFILEDIALOG_OPTIONS
  51. QFileDialog::Options DialogOptions;
  52. #else
  53. ctkPathLineEdit::Options DialogOptions;
  54. #endif
  55. bool HasValidInput; //!< boolean that stores the old state of valid input
  56. QString SettingKey;
  57. static QString sCurrentDirectory; //!< Content the last value of the current directory
  58. static int sMaxHistory; //!< Size of the history, if the history is full and a new value is added, the oldest value is dropped
  59. };
  60. QString ctkPathLineEditPrivate::sCurrentDirectory = "";
  61. int ctkPathLineEditPrivate::sMaxHistory = 5;
  62. //-----------------------------------------------------------------------------
  63. ctkPathLineEditPrivate::ctkPathLineEditPrivate(ctkPathLineEdit& object)
  64. :q_ptr(&object)
  65. {
  66. this->LineEdit = 0;
  67. this->ComboBox = 0;
  68. this->BrowseButton = 0;
  69. this->MinimumContentsLength = 17;
  70. this->HasValidInput = false;
  71. this->Filters = QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Readable;
  72. }
  73. //-----------------------------------------------------------------------------
  74. void ctkPathLineEditPrivate::init()
  75. {
  76. Q_Q(ctkPathLineEdit);
  77. QHBoxLayout* layout = new QHBoxLayout(q);
  78. layout->setContentsMargins(0,0,0,0);
  79. layout->setSpacing(0); // no space between the combobx and button
  80. this->createPathLineEditWidget(true);
  81. this->BrowseButton = new QToolButton(q);
  82. this->BrowseButton->setText("...");
  83. // Don't vertically stretch the path line edit unnecessary
  84. this->BrowseButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored));
  85. this->BrowseButton->setToolTip(q->tr("Open a dialog"));
  86. QObject::connect(this->BrowseButton,SIGNAL(clicked()),
  87. q, SLOT(browse()));
  88. layout->addWidget(this->BrowseButton);
  89. q->setSizePolicy(QSizePolicy(
  90. QSizePolicy::Expanding, QSizePolicy::Fixed,
  91. QSizePolicy::LineEdit));
  92. }
  93. //------------------------------------------------------------------------------
  94. void ctkPathLineEditPrivate::createPathLineEditWidget(bool useComboBox)
  95. {
  96. Q_Q(ctkPathLineEdit);
  97. QString path = q->currentPath();
  98. if (useComboBox)
  99. {
  100. this->ComboBox = new QComboBox(q);
  101. this->ComboBox->setEditable(true);
  102. this->ComboBox->setInsertPolicy(QComboBox::NoInsert);
  103. this->LineEdit = this->ComboBox->lineEdit();
  104. }
  105. else
  106. {
  107. this->ComboBox = 0;
  108. this->LineEdit = new QLineEdit(q);
  109. }
  110. if (q->layout() && q->layout()->itemAt(0))
  111. {
  112. delete q->layout()->itemAt(0)->widget();
  113. }
  114. qobject_cast<QHBoxLayout*>(q->layout())->insertWidget(
  115. 0,
  116. this->ComboBox ? qobject_cast<QWidget*>(this->ComboBox) :
  117. qobject_cast<QWidget*>(this->LineEdit));
  118. this->updateFilter();
  119. q->retrieveHistory();
  120. q->setCurrentPath(path);
  121. QObject::connect(this->LineEdit, SIGNAL(textChanged(QString)),
  122. q, SLOT(setCurrentDirectory(QString)));
  123. QObject::connect(this->LineEdit, SIGNAL(textChanged(QString)),
  124. q, SLOT(updateHasValidInput()));
  125. q->updateGeometry();
  126. }
  127. //------------------------------------------------------------------------------
  128. QSize ctkPathLineEditPrivate::sizeHint(const QString& text)const
  129. {
  130. Q_Q(const ctkPathLineEdit);
  131. int frame = 0;
  132. if (this->ComboBox)
  133. {
  134. QStyleOptionComboBox option;
  135. int arrowWidth = this->ComboBox->style()->subControlRect(
  136. QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxArrow, this->ComboBox).width()
  137. + (this->ComboBox->hasFrame() ? 2 : 0);
  138. frame = 2 * (this->ComboBox->hasFrame() ? 3 : 0)
  139. + arrowWidth
  140. + 1; // for mac style, not sure why
  141. }
  142. else
  143. {
  144. QStyleOptionFrame option;
  145. int frameWidth = this->LineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, q);
  146. int horizontalMargin = 2; // QLineEditPrivate::horizontalMargin
  147. // See QLineEdit::sizeHint
  148. frame = 2 * frameWidth
  149. + this->LineEdit->textMargins().left()
  150. + this->LineEdit->textMargins().right()
  151. + this->LineEdit->contentsMargins().left()
  152. + this->LineEdit->contentsMargins().right()
  153. + 2 * horizontalMargin;
  154. }
  155. int browseWidth = 0;
  156. if (q->showBrowseButton())
  157. {
  158. browseWidth = this->BrowseButton->minimumSizeHint().width();
  159. }
  160. int textWidth = this->LineEdit->fontMetrics().width(text);
  161. int height = (this->ComboBox ? this->ComboBox->minimumSizeHint() :
  162. this->LineEdit->minimumSizeHint()).height();
  163. return QSize(frame + textWidth + browseWidth, height);
  164. }
  165. //-----------------------------------------------------------------------------
  166. void ctkPathLineEditPrivate::updateFilter()
  167. {
  168. Q_Q(ctkPathLineEdit);
  169. // help completion for the QComboBox::QLineEdit
  170. QCompleter *newCompleter = new QCompleter(q);
  171. newCompleter->setModel(new QDirModel(
  172. ctk::nameFiltersToExtensions(this->NameFilters),
  173. this->Filters | QDir::NoDotAndDotDot | QDir::AllDirs,
  174. QDir::Name|QDir::DirsLast, newCompleter));
  175. this->LineEdit->setCompleter(newCompleter);
  176. // don't accept invalid path
  177. QRegExpValidator* validator = new QRegExpValidator(
  178. ctk::nameFiltersToRegExp(this->NameFilters), q);
  179. this->LineEdit->setValidator(validator);
  180. }
  181. //-----------------------------------------------------------------------------
  182. QString ctkPathLineEditPrivate::settingKey()const
  183. {
  184. Q_Q(const ctkPathLineEdit);
  185. return QString("ctkPathLineEdit/") +
  186. (this->SettingKey.isEmpty() ? q->objectName() : this->SettingKey);
  187. }
  188. //-----------------------------------------------------------------------------
  189. ctkPathLineEdit::ctkPathLineEdit(QWidget *parentWidget)
  190. : QWidget(parentWidget)
  191. , d_ptr(new ctkPathLineEditPrivate(*this))
  192. {
  193. Q_D(ctkPathLineEdit);
  194. d->init();
  195. this->setNameFilters(nameFilters());
  196. this->setFilters(filters());
  197. }
  198. //-----------------------------------------------------------------------------
  199. ctkPathLineEdit::ctkPathLineEdit(const QString& label,
  200. const QStringList& nameFilters,
  201. Filters filters,
  202. QWidget *parentWidget)
  203. : QWidget(parentWidget)
  204. , d_ptr(new ctkPathLineEditPrivate(*this))
  205. {
  206. Q_D(ctkPathLineEdit);
  207. d->init();
  208. this->setLabel(label);
  209. this->setNameFilters(nameFilters);
  210. this->setFilters(filters);
  211. }
  212. //-----------------------------------------------------------------------------
  213. ctkPathLineEdit::~ctkPathLineEdit()
  214. {
  215. }
  216. //-----------------------------------------------------------------------------
  217. void ctkPathLineEdit::setLabel(const QString &label)
  218. {
  219. Q_D(ctkPathLineEdit);
  220. d->Label = label;
  221. }
  222. //-----------------------------------------------------------------------------
  223. const QString& ctkPathLineEdit::label()const
  224. {
  225. Q_D(const ctkPathLineEdit);
  226. return d->Label;
  227. }
  228. //-----------------------------------------------------------------------------
  229. void ctkPathLineEdit::setNameFilters(const QStringList &nameFilters)
  230. {
  231. Q_D(ctkPathLineEdit);
  232. d->NameFilters = nameFilters;
  233. d->updateFilter();
  234. }
  235. //-----------------------------------------------------------------------------
  236. const QStringList& ctkPathLineEdit::nameFilters()const
  237. {
  238. Q_D(const ctkPathLineEdit);
  239. return d->NameFilters;
  240. }
  241. //-----------------------------------------------------------------------------
  242. void ctkPathLineEdit::setFilters(const Filters &filters)
  243. {
  244. Q_D(ctkPathLineEdit);
  245. d->Filters = QFlags<QDir::Filter>(static_cast<int>(filters));
  246. d->updateFilter();
  247. }
  248. //-----------------------------------------------------------------------------
  249. ctkPathLineEdit::Filters ctkPathLineEdit::filters()const
  250. {
  251. Q_D(const ctkPathLineEdit);
  252. return QFlags<ctkPathLineEdit::Filter>(static_cast<int>(d->Filters));
  253. }
  254. //-----------------------------------------------------------------------------
  255. #ifdef USE_QFILEDIALOG_OPTIONS
  256. void ctkPathLineEdit::setOptions(const QFileDialog::Options& dialogOptions)
  257. #else
  258. void ctkPathLineEdit::setOptions(const Options& dialogOptions)
  259. #endif
  260. {
  261. Q_D(ctkPathLineEdit);
  262. d->DialogOptions = dialogOptions;
  263. }
  264. //-----------------------------------------------------------------------------
  265. #ifdef USE_QFILEDIALOG_OPTIONS
  266. const QFileDialog::Options& ctkPathLineEdit::options()const
  267. #else
  268. const ctkPathLineEdit::Options& ctkPathLineEdit::options()const
  269. #endif
  270. {
  271. Q_D(const ctkPathLineEdit);
  272. return d->DialogOptions;
  273. }
  274. //-----------------------------------------------------------------------------
  275. void ctkPathLineEdit::browse()
  276. {
  277. Q_D(ctkPathLineEdit);
  278. QString path = "";
  279. if ( d->Filters & QDir::Files ) //file
  280. {
  281. if ( d->Filters & QDir::Writable) // load or save
  282. {
  283. path = QFileDialog::getSaveFileName(
  284. this,
  285. tr("Select a file to save "),
  286. this->currentPath().isEmpty() ? ctkPathLineEditPrivate::sCurrentDirectory :
  287. this->currentPath(),
  288. d->NameFilters.join(";;"),
  289. 0,
  290. #ifdef USE_QFILEDIALOG_OPTIONS
  291. d->DialogOptions);
  292. #else
  293. QFlags<QFileDialog::Option>(int(d->DialogOptions)));
  294. #endif
  295. }
  296. else
  297. {
  298. path = QFileDialog::getOpenFileName(
  299. this,
  300. QString("Open a file"),
  301. this->currentPath().isEmpty()? ctkPathLineEditPrivate::sCurrentDirectory :
  302. this->currentPath(),
  303. d->NameFilters.join(";;"),
  304. 0,
  305. #ifdef USE_QFILEDIALOG_OPTIONS
  306. d->DialogOptions);
  307. #else
  308. QFlags<QFileDialog::Option>(int(d->DialogOptions)));
  309. #endif
  310. }
  311. }
  312. else //directory
  313. {
  314. path = QFileDialog::getExistingDirectory(
  315. this,
  316. QString("Select a directory..."),
  317. this->currentPath().isEmpty() ? ctkPathLineEditPrivate::sCurrentDirectory :
  318. this->currentPath(),
  319. #ifdef USE_QFILEDIALOG_OPTIONS
  320. d->DialogOptions);
  321. #else
  322. QFlags<QFileDialog::Option>(int(d->DialogOptions)));
  323. #endif
  324. }
  325. if (path.isEmpty())
  326. {
  327. return;
  328. }
  329. this->setCurrentPath(path);
  330. }
  331. //-----------------------------------------------------------------------------
  332. void ctkPathLineEdit::retrieveHistory()
  333. {
  334. Q_D(ctkPathLineEdit);
  335. if (d->ComboBox == 0)
  336. {
  337. return;
  338. }
  339. QString path = this->currentPath();
  340. bool wasBlocking = this->blockSignals(true);
  341. d->ComboBox->clear();
  342. // fill the combobox using the QSettings
  343. QSettings settings;
  344. QString key = d->settingKey();
  345. const QStringList history = settings.value(key).toStringList();
  346. foreach(const QString& path, history)
  347. {
  348. d->ComboBox->addItem(path);
  349. if (d->ComboBox->count() >= ctkPathLineEditPrivate::sMaxHistory)
  350. {
  351. break;
  352. }
  353. }
  354. // Restore path or select the most recent file location if none set.
  355. if (path.isEmpty())
  356. {
  357. this->blockSignals(wasBlocking);
  358. d->ComboBox->setCurrentIndex(0);
  359. }
  360. else
  361. {
  362. this->setCurrentPath(path);
  363. this->blockSignals(wasBlocking);
  364. }
  365. }
  366. //-----------------------------------------------------------------------------
  367. void ctkPathLineEdit::addCurrentPathToHistory()
  368. {
  369. Q_D(ctkPathLineEdit);
  370. if (d->ComboBox == 0 ||
  371. this->currentPath().isEmpty())
  372. {
  373. return;
  374. }
  375. QSettings settings;
  376. //keep the same values, add the current value
  377. //if more than m_MaxHistory entrees, drop the oldest.
  378. QString key = d->settingKey();
  379. QStringList history = settings.value(key).toStringList();
  380. if (history.contains(this->currentPath()))
  381. {
  382. history.removeAll(this->currentPath());
  383. }
  384. history.push_front(this->currentPath());
  385. settings.setValue(key, history);
  386. int index = d->ComboBox->findText(this->currentPath());
  387. if (index >= 0)
  388. {
  389. d->ComboBox->removeItem(index);
  390. }
  391. while (d->ComboBox->count() >= ctkPathLineEditPrivate::sMaxHistory)
  392. {
  393. d->ComboBox->removeItem(d->ComboBox->count() - 1);
  394. }
  395. d->ComboBox->insertItem(0, this->currentPath());
  396. }
  397. //------------------------------------------------------------------------------
  398. void ctkPathLineEdit::setCurrentFileExtension(const QString& extension)
  399. {
  400. QString filename = this->currentPath();
  401. QFileInfo fileInfo(filename);
  402. if (!fileInfo.suffix().isEmpty())
  403. {
  404. filename.replace(fileInfo.suffix(), extension);
  405. }
  406. else
  407. {
  408. filename.append(QString(".") + extension);
  409. }
  410. this->setCurrentPath(filename);
  411. }
  412. //------------------------------------------------------------------------------
  413. QComboBox* ctkPathLineEdit::comboBox() const
  414. {
  415. Q_D(const ctkPathLineEdit);
  416. return d->ComboBox;
  417. }
  418. //------------------------------------------------------------------------------
  419. QString ctkPathLineEdit::currentPath()const
  420. {
  421. Q_D(const ctkPathLineEdit);
  422. return d->LineEdit ? d->LineEdit->text() : QString();
  423. }
  424. //------------------------------------------------------------------------------
  425. void ctkPathLineEdit::setCurrentPath(const QString& path)
  426. {
  427. Q_D(ctkPathLineEdit);
  428. d->LineEdit->setText(path);
  429. }
  430. //------------------------------------------------------------------------------
  431. void ctkPathLineEdit::setCurrentDirectory(const QString& directory)
  432. {
  433. ctkPathLineEditPrivate::sCurrentDirectory = directory;
  434. }
  435. //------------------------------------------------------------------------------
  436. void ctkPathLineEdit::updateHasValidInput()
  437. {
  438. Q_D(ctkPathLineEdit);
  439. bool oldHasValidInput = d->HasValidInput;
  440. d->HasValidInput = d->LineEdit->hasAcceptableInput();
  441. if (d->HasValidInput)
  442. {
  443. QFileInfo fileInfo(this->currentPath());
  444. ctkPathLineEditPrivate::sCurrentDirectory =
  445. fileInfo.isFile() ? fileInfo.absolutePath() : fileInfo.absoluteFilePath();
  446. emit currentPathChanged(this->currentPath());
  447. }
  448. if (d->HasValidInput != oldHasValidInput)
  449. {
  450. emit validInputChanged(d->HasValidInput);
  451. }
  452. this->updateGeometry();
  453. }
  454. //------------------------------------------------------------------------------
  455. QString ctkPathLineEdit::settingKey()const
  456. {
  457. Q_D(const ctkPathLineEdit);
  458. return d->SettingKey;
  459. }
  460. //------------------------------------------------------------------------------
  461. void ctkPathLineEdit::setSettingKey(const QString& key)
  462. {
  463. Q_D(ctkPathLineEdit);
  464. d->SettingKey = key;
  465. this->retrieveHistory();
  466. }
  467. //------------------------------------------------------------------------------
  468. bool ctkPathLineEdit::showBrowseButton()const
  469. {
  470. Q_D(const ctkPathLineEdit);
  471. return d->BrowseButton->isVisibleTo(const_cast<ctkPathLineEdit*>(this));
  472. }
  473. //------------------------------------------------------------------------------
  474. void ctkPathLineEdit::setShowBrowseButton(bool visible)
  475. {
  476. Q_D(ctkPathLineEdit);
  477. d->BrowseButton->setVisible(visible);
  478. }
  479. //------------------------------------------------------------------------------
  480. bool ctkPathLineEdit::showHistoryButton()const
  481. {
  482. Q_D(const ctkPathLineEdit);
  483. return d->ComboBox ? true: false;
  484. }
  485. //------------------------------------------------------------------------------
  486. void ctkPathLineEdit::setShowHistoryButton(bool visible)
  487. {
  488. Q_D(ctkPathLineEdit);
  489. d->createPathLineEditWidget(visible);
  490. }
  491. //------------------------------------------------------------------------------
  492. int ctkPathLineEdit::minimumContentsLength()const
  493. {
  494. Q_D(const ctkPathLineEdit);
  495. return d->MinimumContentsLength;
  496. }
  497. //------------------------------------------------------------------------------
  498. void ctkPathLineEdit::setMinimumContentsLength(int length)
  499. {
  500. Q_D(ctkPathLineEdit);
  501. d->MinimumContentsLength = length;
  502. this->updateGeometry();
  503. }
  504. //------------------------------------------------------------------------------
  505. QSize ctkPathLineEdit::minimumSizeHint()const
  506. {
  507. Q_D(const ctkPathLineEdit);
  508. QString fileName = QString('/') + QFileInfo(this->currentPath()).fileName();
  509. if (fileName.size() < d->MinimumContentsLength)
  510. {
  511. fileName = QString("x").repeated(d->MinimumContentsLength);
  512. }
  513. QSize hint = d->sizeHint(fileName);
  514. return hint;
  515. }
  516. //------------------------------------------------------------------------------
  517. QSize ctkPathLineEdit::sizeHint()const
  518. {
  519. Q_D(const ctkPathLineEdit);
  520. QString path = this->currentPath();
  521. if (path.size() < d->MinimumContentsLength)
  522. {
  523. path = QString("x").repeated(d->MinimumContentsLength);
  524. }
  525. return d->sizeHint(path);
  526. }