ctkPathLineEdit.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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.commontk.org/LICENSE
  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. // CTK includes
  26. #include "ctkPathLineEdit.h"
  27. //-----------------------------------------------------------------------------
  28. const char *qt_file_dialog_filter_reg_exp =
  29. "^(.*)\\(([a-zA-Z0-9_.*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
  30. //-----------------------------------------------------------------------------
  31. QStringList filterNameExtensions(const QString& filterName)
  32. {
  33. QRegExp regexp(QString::fromLatin1(qt_file_dialog_filter_reg_exp));
  34. int i = regexp.indexIn(filterName);
  35. if (i < 0)
  36. {
  37. return QStringList();
  38. }
  39. QString f = regexp.cap(2);
  40. return f.split(QLatin1Char(' '), QString::SkipEmptyParts);
  41. }
  42. //-----------------------------------------------------------------------------
  43. QStringList filterNamesExtensions(const QStringList& filterNames)
  44. {
  45. QStringList extensions;
  46. foreach(const QString& filterName, filterNames)
  47. {
  48. extensions << filterNameExtensions(filterName);
  49. }
  50. return extensions;
  51. }
  52. //-----------------------------------------------------------------------------
  53. QString extensionToRegExp(const QString& extension)
  54. {
  55. // typically *.jpg
  56. QRegExp extensionExtractor("\\*\\.(\\w+)");
  57. int pos = extensionExtractor.indexIn(extension);
  58. if (pos < 0)
  59. {
  60. return QString();
  61. }
  62. return ".*\\." + extensionExtractor.cap(1) + "?$";
  63. }
  64. //-----------------------------------------------------------------------------
  65. QRegExp filterNamesToRegExpExtensions(const QStringList& filterNames)
  66. {
  67. QString pattern;
  68. foreach(QString filterName, filterNames)
  69. {
  70. foreach(QString extension, filterNameExtensions(filterName))
  71. {
  72. QString regExpExtension = extensionToRegExp(extension);
  73. if (!regExpExtension.isEmpty())
  74. {
  75. if (pattern.isEmpty())
  76. {
  77. pattern = "(";
  78. }
  79. else
  80. {
  81. pattern += "|";
  82. }
  83. pattern +=regExpExtension;
  84. }
  85. }
  86. }
  87. if (pattern.isEmpty())
  88. {
  89. pattern = ".+";
  90. }
  91. else
  92. {
  93. pattern += ")";
  94. }
  95. return QRegExp(pattern);
  96. }
  97. //-----------------------------------------------------------------------------
  98. class ctkPathLineEditPrivate
  99. {
  100. Q_DECLARE_PUBLIC(ctkPathLineEdit);
  101. protected:
  102. ctkPathLineEdit* const q_ptr;
  103. public:
  104. ctkPathLineEditPrivate(ctkPathLineEdit& object);
  105. void init();
  106. void updateFilter();
  107. QComboBox* ComboBox;
  108. QString Label; //!< used in file dialogs
  109. QStringList NameFilters; //!< Regular expression (in wildcard mode) used to help the user to complete the line
  110. QDir::Filters Filters; //!< Type of path (file, dir...)
  111. bool HasValidInput; //!< boolean that stores the old state of valid input
  112. static QString sCurrentDirectory; //!< Content the last value of the current directory
  113. static int sMaxHistory; //!< Size of the history, if the history is full and a new value is added, the oldest value is dropped
  114. };
  115. QString ctkPathLineEditPrivate::sCurrentDirectory = "";
  116. int ctkPathLineEditPrivate::sMaxHistory = 5;
  117. //-----------------------------------------------------------------------------
  118. ctkPathLineEditPrivate::ctkPathLineEditPrivate(ctkPathLineEdit& object)
  119. :q_ptr(&object)
  120. {
  121. this->ComboBox = 0;
  122. this->HasValidInput = false;
  123. this->Filters = QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Readable;
  124. }
  125. //-----------------------------------------------------------------------------
  126. void ctkPathLineEditPrivate::init()
  127. {
  128. Q_Q(ctkPathLineEdit);
  129. this->ComboBox = new QComboBox(q);
  130. QHBoxLayout* layout = new QHBoxLayout(q);
  131. layout->addWidget(this->ComboBox);
  132. layout->setContentsMargins(0,0,0,0);
  133. this->ComboBox->setEditable(true);
  134. q->setSizePolicy(QSizePolicy(
  135. QSizePolicy::Expanding, QSizePolicy::Fixed,
  136. QSizePolicy::LineEdit));
  137. QObject::connect(this->ComboBox,SIGNAL(editTextChanged(const QString&)),
  138. q, SLOT(setCurrentDirectory(const QString&)));
  139. QObject::connect(this->ComboBox,SIGNAL(editTextChanged(const QString&)),
  140. q, SLOT(updateHasValidInput()));
  141. }
  142. //-----------------------------------------------------------------------------
  143. void ctkPathLineEditPrivate::updateFilter()
  144. {
  145. Q_Q(ctkPathLineEdit);
  146. // help completion for the QComboBox::QLineEdit
  147. QCompleter *newCompleter = new QCompleter(q);
  148. newCompleter->setModel(new QDirModel(
  149. filterNamesExtensions(this->NameFilters),
  150. this->Filters | QDir::NoDotAndDotDot | QDir::AllDirs,
  151. QDir::Name|QDir::DirsLast, newCompleter));
  152. this->ComboBox->setCompleter(newCompleter);
  153. }
  154. //-----------------------------------------------------------------------------
  155. ctkPathLineEdit::ctkPathLineEdit(QWidget *parentWidget)
  156. : QWidget(parentWidget)
  157. , d_ptr(new ctkPathLineEditPrivate(*this))
  158. {
  159. Q_D(ctkPathLineEdit);
  160. d->init();
  161. }
  162. //-----------------------------------------------------------------------------
  163. ctkPathLineEdit::ctkPathLineEdit(const QString& label,
  164. const QStringList& nameFilters,
  165. Filters filters,
  166. QWidget *parentWidget)
  167. : QWidget(parentWidget)
  168. , d_ptr(new ctkPathLineEditPrivate(*this))
  169. {
  170. Q_D(ctkPathLineEdit);
  171. d->init();
  172. this->setLabel(label);
  173. this->setNameFilters(nameFilters);
  174. this->setFilters(filters);
  175. }
  176. //-----------------------------------------------------------------------------
  177. ctkPathLineEdit::~ctkPathLineEdit()
  178. {
  179. }
  180. //-----------------------------------------------------------------------------
  181. void ctkPathLineEdit::setLabel(const QString &label)
  182. {
  183. Q_D(ctkPathLineEdit);
  184. d->Label = label;
  185. }
  186. //-----------------------------------------------------------------------------
  187. const QString& ctkPathLineEdit::label()const
  188. {
  189. Q_D(const ctkPathLineEdit);
  190. return d->Label;
  191. }
  192. //-----------------------------------------------------------------------------
  193. void ctkPathLineEdit::setNameFilters(const QStringList &nameFilters)
  194. {
  195. Q_D(ctkPathLineEdit);
  196. d->NameFilters = nameFilters;
  197. d->updateFilter();
  198. d->ComboBox->lineEdit()->setValidator(
  199. new QRegExpValidator(
  200. filterNamesToRegExpExtensions(d->NameFilters), this));
  201. }
  202. //-----------------------------------------------------------------------------
  203. const QStringList& ctkPathLineEdit::nameFilters()const
  204. {
  205. Q_D(const ctkPathLineEdit);
  206. return d->NameFilters;
  207. }
  208. //-----------------------------------------------------------------------------
  209. void ctkPathLineEdit::setFilters(const Filters &filters)
  210. {
  211. Q_D(ctkPathLineEdit);
  212. d->Filters = QFlags<QDir::Filter>(static_cast<int>(filters));
  213. d->updateFilter();
  214. }
  215. //-----------------------------------------------------------------------------
  216. ctkPathLineEdit::Filters ctkPathLineEdit::filters()const
  217. {
  218. Q_D(const ctkPathLineEdit);
  219. return QFlags<ctkPathLineEdit::Filter>(static_cast<int>(d->Filters));
  220. }
  221. //-----------------------------------------------------------------------------
  222. void ctkPathLineEdit::browse()
  223. {
  224. Q_D(ctkPathLineEdit);
  225. QString path = "";
  226. if ( d->Filters & QDir::Files ) //file
  227. {
  228. if ( d->Filters & QDir::Writable) // load or save
  229. {
  230. path = QFileDialog::getSaveFileName(this,
  231. tr("Select a file to save "),
  232. this->currentPath().isEmpty() ? ctkPathLineEditPrivate::sCurrentDirectory : this->currentPath(),
  233. d->NameFilters.join(";;"));
  234. }
  235. else
  236. {
  237. path = QFileDialog::getOpenFileName(
  238. this,
  239. QString("Open a file"),
  240. this->currentPath().isEmpty()? ctkPathLineEditPrivate::sCurrentDirectory : this->currentPath(),
  241. d->NameFilters.join(";;"));
  242. }
  243. }
  244. else //directory
  245. {
  246. path = QFileDialog::getExistingDirectory(
  247. this,
  248. QString("Select a directory..."),
  249. this->currentPath().isEmpty() ? ctkPathLineEditPrivate::sCurrentDirectory : this->currentPath());
  250. }
  251. if (path.isEmpty())
  252. {
  253. return;
  254. }
  255. this->setCurrentPath(path);
  256. }
  257. //-----------------------------------------------------------------------------
  258. void ctkPathLineEdit::retrieveHistory()
  259. {
  260. Q_D(ctkPathLineEdit);
  261. d->ComboBox->clear();
  262. // fill the combobox using the QSettings
  263. QSettings settings;
  264. QString key = "ctkPathLineEdit/" + this->objectName();
  265. QStringList history = settings.value(key).toStringList();
  266. foreach(QString path, history)
  267. {
  268. d->ComboBox->addItem(path);
  269. if (d->ComboBox->count() >= ctkPathLineEditPrivate::sMaxHistory)
  270. {
  271. break;
  272. }
  273. }
  274. //select the most recent file location
  275. if (this->currentPath().isEmpty())
  276. {
  277. d->ComboBox->setCurrentIndex(0);
  278. }
  279. }
  280. //-----------------------------------------------------------------------------
  281. void ctkPathLineEdit::addCurrentPathToHistory()
  282. {
  283. Q_D(ctkPathLineEdit);
  284. if (this->currentPath().isEmpty())
  285. {
  286. return;
  287. }
  288. QSettings settings;
  289. //keep the same values, add the current value
  290. //if more than m_MaxHistory entrees, drop the oldest.
  291. QString key = "ctkPathLineEdit/" + this->objectName();
  292. QStringList history = settings.value(key).toStringList();
  293. if (history.contains(this->currentPath()))
  294. {
  295. history.removeAll(this->currentPath());
  296. }
  297. history.push_front(this->currentPath());
  298. settings.setValue(key, history);
  299. int index =d->ComboBox->findText(this->currentPath());
  300. if (index >= 0)
  301. {
  302. d->ComboBox->removeItem(index);
  303. }
  304. while (d->ComboBox->count() >= ctkPathLineEditPrivate::sMaxHistory)
  305. {
  306. d->ComboBox->removeItem(d->ComboBox->count() - 1);
  307. }
  308. d->ComboBox->insertItem(0, this->currentPath());
  309. }
  310. //------------------------------------------------------------------------------
  311. void ctkPathLineEdit::setCurrentFileExtension(const QString& extension)
  312. {
  313. QString filename = this->currentPath();
  314. QFileInfo fileInfo(filename);
  315. if (!fileInfo.suffix().isEmpty())
  316. {
  317. filename.replace(fileInfo.suffix(), extension);
  318. }
  319. else
  320. {
  321. filename.append(QString(".") + extension);
  322. }
  323. this->setCurrentPath(filename);
  324. }
  325. //------------------------------------------------------------------------------
  326. QString ctkPathLineEdit::currentPath()const
  327. {
  328. Q_D(const ctkPathLineEdit);
  329. return d->ComboBox->currentText();
  330. }
  331. //------------------------------------------------------------------------------
  332. void ctkPathLineEdit::setCurrentPath(const QString& path)
  333. {
  334. Q_D(ctkPathLineEdit);
  335. d->ComboBox->setEditText(path);
  336. }
  337. //------------------------------------------------------------------------------
  338. void ctkPathLineEdit::setCurrentDirectory(const QString& directory)
  339. {
  340. ctkPathLineEditPrivate::sCurrentDirectory = directory;
  341. }
  342. //------------------------------------------------------------------------------
  343. void ctkPathLineEdit::updateHasValidInput()
  344. {
  345. Q_D(ctkPathLineEdit);
  346. bool oldHasValidInput = d->HasValidInput;
  347. d->HasValidInput = d->ComboBox->lineEdit()->hasAcceptableInput();
  348. if (d->HasValidInput)
  349. {
  350. QFileInfo fileInfo(d->ComboBox->currentText());
  351. ctkPathLineEditPrivate::sCurrentDirectory =
  352. fileInfo.isFile() ? fileInfo.absolutePath() : fileInfo.absoluteFilePath();
  353. emit currentPathChanged(d->ComboBox->currentText());
  354. }
  355. if ( d->HasValidInput != oldHasValidInput)
  356. {
  357. emit validInputChanged(d->HasValidInput);
  358. }
  359. }