VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UIFilePathSelector.cpp@ 76553

Last change on this file since 76553 was 76553, checked in by vboxsync, 5 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.5 KB
Line 
1/* $Id: UIFilePathSelector.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIFilePathSelector class implementation.
4 */
5
6/*
7 * Copyright (C) 2008-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifdef VBOX_WITH_PRECOMPILED_HEADERS
19# include <precomp.h>
20#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
21
22/* Qt includes: */
23# include <QAction>
24# include <QApplication>
25# include <QClipboard>
26# include <QDir>
27# include <QFocusEvent>
28# include <QHBoxLayout>
29# include <QLineEdit>
30# ifdef VBOX_WS_WIN
31# include <QListView>
32# endif
33
34/* GUI includes: */
35# include "QIFileDialog.h"
36# include "QILabel.h"
37# include "QILineEdit.h"
38# include "QIToolButton.h"
39# include "VBoxGlobal.h"
40# include "UIIconPool.h"
41# include "UIFilePathSelector.h"
42
43/* Other VBox includes: */
44# include <iprt/assert.h>
45
46#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
47
48
49/** Returns first position of difference between passed strings. */
50static int differFrom(const QString &str1, const QString &str2)
51{
52 if (str1 == str2)
53 return -1;
54
55 int iMinLength = qMin(str1.size(), str2.size());
56 int iIndex = 0;
57 for (iIndex = 0; iIndex < iMinLength; ++iIndex)
58 if (str1[iIndex] != str2[iIndex])
59 break;
60 return iIndex;
61}
62
63UIFilePathSelector::UIFilePathSelector(QWidget *pParent /* = 0 */)
64 : QIWithRetranslateUI<QIComboBox>(pParent)
65 , m_enmMode(Mode_Folder)
66 , m_strHomeDir(QDir::current().absolutePath())
67 , m_fEditable(true)
68 , m_fModified(false)
69 , m_fEditableMode(false)
70 , m_fMouseAwaited(false)
71 , m_fToolTipOverriden(false)
72 , m_pCopyAction(new QAction(this))
73{
74#ifdef VBOX_WS_WIN
75 // WORKAROUND:
76 // On at least Windows host there is a bug with
77 // the QListView which doesn't take into account
78 // the item size change caused by assigning item's
79 // icon of another size or unassigning icon at all.
80 if (view()->inherits("QListView"))
81 qobject_cast<QListView*>(view())->setUniformItemSizes(true);
82#endif /* VBOX_WS_WIN */
83
84 /* Populate items: */
85 insertItem(PathId, "");
86 insertItem(SelectId, "");
87 insertItem(ResetId, "");
88
89 /* Attaching known icons: */
90 setItemIcon(SelectId, UIIconPool::iconSet(":/select_file_16px.png"));
91 setItemIcon(ResetId, UIIconPool::iconSet(":/eraser_16px.png"));
92
93 /* Setup context menu: */
94 addAction(m_pCopyAction);
95 m_pCopyAction->setShortcut(QKeySequence(QKeySequence::Copy));
96 m_pCopyAction->setShortcutContext(Qt::WidgetShortcut);
97
98 /* Initial setup: */
99 setInsertPolicy(QComboBox::NoInsert);
100 setContextMenuPolicy(Qt::ActionsContextMenu);
101 setMinimumWidth(200);
102
103 /* Setup connections: */
104 connect(this, SIGNAL(activated(int)), this, SLOT(onActivated(int)));
105 connect(m_pCopyAction, SIGNAL(triggered(bool)), this, SLOT(copyToClipboard()));
106
107 /* Editable by default: */
108 setEditable(true);
109
110 /* Applying language settings: */
111 retranslateUi();
112}
113
114void UIFilePathSelector::setMode(Mode enmMode)
115{
116 m_enmMode = enmMode;
117
118 retranslateUi();
119}
120
121void UIFilePathSelector::setEditable(bool fEditable)
122{
123 m_fEditable = fEditable;
124
125 if (m_fEditable)
126 {
127 QIComboBox::setEditable(true);
128
129 /* Install combo-box event-filter: */
130 Assert(comboBox());
131 comboBox()->installEventFilter(this);
132
133 /* Install line-edit connection/event-filter: */
134 Assert(lineEdit());
135 connect(lineEdit(), SIGNAL(textEdited(const QString &)),
136 this, SLOT(onTextEdited(const QString &)));
137 lineEdit()->installEventFilter(this);
138 }
139 else
140 {
141 if (lineEdit())
142 {
143 /* Remove line-edit event-filter/connection: */
144 lineEdit()->removeEventFilter(this);
145 disconnect(lineEdit(), SIGNAL(textEdited(const QString &)),
146 this, SLOT(onTextEdited(const QString &)));
147 }
148 if (comboBox())
149 {
150 /* Remove combo-box event-filter: */
151 comboBox()->removeEventFilter(this);
152 }
153 QIComboBox::setEditable(false);
154 }
155}
156
157void UIFilePathSelector::setResetEnabled(bool fEnabled)
158{
159 if (!fEnabled && count() - 1 == ResetId)
160 removeItem(ResetId);
161 else if (fEnabled && count() - 1 == ResetId - 1)
162 {
163 insertItem(ResetId, "");
164 setItemIcon(ResetId, UIIconPool::iconSet(":/eraser_16px.png"));
165 }
166 retranslateUi();
167}
168
169void UIFilePathSelector::setToolTip(const QString &strToolTip)
170{
171 /* Call to base-class: */
172 QIComboBox::setToolTip(strToolTip);
173
174 /* Remember if the tool-tip overriden: */
175 m_fToolTipOverriden = !toolTip().isEmpty();
176}
177
178void UIFilePathSelector::setDefaultPath(const QString &strDefaultPath)
179{
180 if (m_strDefaultPath == strDefaultPath)
181 return;
182 m_strDefaultPath = strDefaultPath;
183 if (currentIndex() == ResetId)
184 setPath(m_strDefaultPath);
185}
186
187const QString& UIFilePathSelector::defaultPath() const
188{
189 return m_strDefaultPath;
190}
191
192void UIFilePathSelector::setPath(const QString &strPath, bool fRefreshText /* = true */)
193{
194 m_strPath = strPath.isEmpty() ? QString::null :
195 QDir::toNativeSeparators(strPath);
196 if (fRefreshText)
197 refreshText();
198}
199
200bool UIFilePathSelector::eventFilter(QObject *pObject, QEvent *pEvent)
201{
202 /* If the object is private combo-box: */
203 if (pObject == comboBox())
204 {
205 /* Handle focus events related to private child: */
206 switch (pEvent->type())
207 {
208 case QEvent::FocusIn: focusInEvent(static_cast<QFocusEvent*>(pEvent)); break;
209 case QEvent::FocusOut: focusOutEvent(static_cast<QFocusEvent*>(pEvent)); break;
210 default: break;
211 }
212 }
213
214 /* If the object is private line-edit: */
215 if (pObject == lineEdit())
216 {
217 if (m_fMouseAwaited && (pEvent->type() == QEvent::MouseButtonPress))
218 QMetaObject::invokeMethod(this, "refreshText", Qt::QueuedConnection);
219 }
220
221 /* Call to base-class: */
222 return QIWithRetranslateUI<QIComboBox>::eventFilter(pObject, pEvent);
223}
224
225void UIFilePathSelector::resizeEvent(QResizeEvent *pEvent)
226{
227 QIWithRetranslateUI<QIComboBox>::resizeEvent(pEvent);
228 refreshText();
229}
230
231void UIFilePathSelector::focusInEvent(QFocusEvent *pEvent)
232{
233 if (isPathSelected())
234 {
235 if (m_fEditable)
236 m_fEditableMode = true;
237 if (pEvent->reason() == Qt::MouseFocusReason)
238 m_fMouseAwaited = true;
239 else
240 refreshText();
241 }
242 QIWithRetranslateUI<QIComboBox>::focusInEvent(pEvent);
243}
244
245void UIFilePathSelector::focusOutEvent(QFocusEvent *pEvent)
246{
247 if (isPathSelected())
248 {
249 m_fEditableMode = false;
250 refreshText();
251 }
252 QIWithRetranslateUI<QIComboBox>::focusOutEvent(pEvent);
253}
254
255void UIFilePathSelector::retranslateUi()
256{
257 /* Retranslate copy action: */
258 m_pCopyAction->setText(tr("&Copy"));
259
260 /* Retranslate 'select' item: */
261 setItemText(SelectId, tr("Other..."));
262
263 /* Retranslate 'reset' item: */
264 if (count() - 1 == ResetId)
265 setItemText(ResetId, tr("Reset"));
266
267 /* Set tool-tips of the above two items based on the mode: */
268 switch (m_enmMode)
269 {
270 case Mode_Folder:
271 setItemData(SelectId,
272 tr("Displays a window to select a different folder."),
273 Qt::ToolTipRole);
274 setItemData(ResetId,
275 tr("Resets the folder path to the default value."),
276 Qt::ToolTipRole);
277 break;
278 case Mode_File_Open:
279 case Mode_File_Save:
280 setItemData(SelectId,
281 tr("Displays a window to select a different file."),
282 Qt::ToolTipRole);
283 setItemData(ResetId,
284 tr("Resets the file path to the default value."),
285 Qt::ToolTipRole);
286 break;
287 default:
288 AssertFailedBreak();
289 }
290
291 /* If selector is NOT focused => we interpret the "nothing selected"
292 * item depending on "reset to default" feature state: */
293 if (isResetEnabled())
294 {
295 /* If "reset to default" is enabled: */
296 m_strNoneText = tr("<reset to default>");
297 m_strNoneToolTip = tr("The actual default path value will be displayed after "
298 "accepting the changes and opening this window again.");
299 }
300 else
301 {
302 /* If "reset to default" is NOT enabled: */
303 m_strNoneText = tr("<not selected>");
304 m_strNoneToolTip = tr("Please use the <b>Other...</b> item from the drop-down "
305 "list to select a path.");
306 }
307
308 /* But if selector is focused => tool-tip depends on the mode only: */
309 switch (m_enmMode)
310 {
311 case Mode_Folder:
312 m_strNoneToolTipFocused = tr("Holds the folder path.");
313 break;
314 case Mode_File_Open:
315 case Mode_File_Save:
316 m_strNoneToolTipFocused = tr("Holds the file path.");
317 break;
318 default:
319 AssertFailedBreak();
320 }
321
322 /* Finally, retranslate current item: */
323 refreshText();
324}
325
326void UIFilePathSelector::onActivated(int iIndex)
327{
328 switch (iIndex)
329 {
330 case SelectId:
331 {
332 selectPath();
333 break;
334 }
335 case ResetId:
336 {
337 if (m_strDefaultPath.isEmpty())
338 changePath(QString::null);
339 else
340 changePath(m_strDefaultPath);
341 break;
342 }
343 default:
344 break;
345 }
346 setCurrentIndex(PathId);
347 setFocus();
348}
349
350void UIFilePathSelector::onTextEdited(const QString &strPath)
351{
352 changePath(strPath, false /* refresh text? */);
353}
354
355void UIFilePathSelector::copyToClipboard()
356{
357 QString text(fullPath());
358 /* Copy the current text to the selection and global clipboard. */
359 if (QApplication::clipboard()->supportsSelection())
360 QApplication::clipboard()->setText(text, QClipboard::Selection);
361 QApplication::clipboard()->setText(text, QClipboard::Clipboard);
362}
363
364void UIFilePathSelector::changePath(const QString &strPath,
365 bool fRefreshText /* = true */)
366{
367 const QString strOldPath = m_strPath;
368 setPath(strPath, fRefreshText);
369 if (!m_fModified && m_strPath != strOldPath)
370 m_fModified = true;
371 emit pathChanged(strPath);
372}
373
374void UIFilePathSelector::selectPath()
375{
376 /* Prepare initial directory: */
377 QString strInitDir;
378 /* If something already chosen: */
379 if (!m_strPath.isEmpty())
380 {
381 /* If that is just a single file/folder (object) name: */
382 const QString strObjectName = QFileInfo(m_strPath).fileName();
383 if (strObjectName == m_strPath)
384 {
385 /* Use the home directory: */
386 strInitDir = m_strHomeDir;
387 }
388 /* If that is full file/folder (object) path: */
389 else
390 {
391 /* Use the first existing dir of m_strPath: */
392 strInitDir = QIFileDialog::getFirstExistingDir(m_strPath);
393 }
394 /* Finally, append object name itself: */
395 strInitDir = QDir(strInitDir).absoluteFilePath(strObjectName);
396 }
397 /* Use the home directory by default: */
398 if (strInitDir.isNull())
399 strInitDir = m_strHomeDir;
400
401 /* Open the choose-file/folder dialog: */
402 QString strSelPath;
403 switch (m_enmMode)
404 {
405 case Mode_File_Open:
406 strSelPath = QIFileDialog::getOpenFileName(strInitDir, m_strFileDialogFilters, parentWidget(), m_strFileDialogTitle); break;
407 case Mode_File_Save:
408 {
409 strSelPath = QIFileDialog::getSaveFileName(strInitDir, m_strFileDialogFilters, parentWidget(), m_strFileDialogTitle);
410 if (!strSelPath.isEmpty() && QFileInfo(strSelPath).suffix().isEmpty())
411 {
412 if (m_strFileDialogDefaultSaveExtension.isEmpty())
413 strSelPath = QString("%1").arg(strSelPath);
414 else
415 strSelPath = QString("%1.%2").arg(strSelPath).arg(m_strFileDialogDefaultSaveExtension);
416 }
417 break;
418 }
419 case Mode_Folder:
420 strSelPath = QIFileDialog::getExistingDirectory(strInitDir, parentWidget(), m_strFileDialogTitle); break;
421 }
422
423 /* Do nothing if nothing chosen: */
424 if (strSelPath.isNull())
425 return;
426
427 /* Wipe out excessive slashes: */
428 strSelPath.remove(QRegExp("[\\\\/]$"));
429
430 /* Apply chosen path: */
431 changePath(strSelPath);
432}
433
434QIcon UIFilePathSelector::defaultIcon() const
435{
436 if (m_enmMode == Mode_Folder)
437 return vboxGlobal().icon(QFileIconProvider::Folder);
438 else
439 return vboxGlobal().icon(QFileIconProvider::File);
440}
441
442QString UIFilePathSelector::fullPath(bool fAbsolute /* = true */) const
443{
444 if (m_strPath.isNull())
445 return m_strPath;
446
447 QString strResult;
448 switch (m_enmMode)
449 {
450 case Mode_Folder:
451 strResult = fAbsolute ? QDir(m_strPath).absolutePath() :
452 QDir(m_strPath).path();
453 break;
454 case Mode_File_Open:
455 case Mode_File_Save:
456 strResult = fAbsolute ? QFileInfo(m_strPath).absoluteFilePath() :
457 QFileInfo(m_strPath).filePath();
458 break;
459 default:
460 AssertFailedBreak();
461 }
462 return QDir::toNativeSeparators(strResult);
463}
464
465QString UIFilePathSelector::shrinkText(int iWidth) const
466{
467 QString strFullText(fullPath(false));
468 if (strFullText.isEmpty())
469 return strFullText;
470
471 int iOldSize = fontMetrics().width(strFullText);
472 int iIndentSize = fontMetrics().width("x...x");
473
474 /* Compress text: */
475 int iStart = 0;
476 int iFinish = 0;
477 int iPosition = 0;
478 int iTextWidth = 0;
479 do {
480 iTextWidth = fontMetrics().width(strFullText);
481 if (iTextWidth + iIndentSize > iWidth)
482 {
483 iStart = 0;
484 iFinish = strFullText.length();
485
486 /* Selecting remove position: */
487 QRegExp regExp("([\\\\/][^\\\\^/]+[\\\\/]?$)");
488 int iNewFinish = regExp.indexIn(strFullText);
489 if (iNewFinish != -1)
490 iFinish = iNewFinish;
491 iPosition = (iFinish - iStart) / 2;
492
493 if (iPosition == iFinish)
494 break;
495
496 strFullText.remove(iPosition, 1);
497 }
498 } while (iTextWidth + iIndentSize > iWidth);
499
500 strFullText.insert(iPosition, "...");
501 int newSize = fontMetrics().width(strFullText);
502
503 return newSize < iOldSize ? strFullText : fullPath(false);
504}
505
506void UIFilePathSelector::refreshText()
507{
508 if (m_fEditable && m_fEditableMode)
509 {
510 /* Cursor positioning variables: */
511 int iCurPos = -1;
512 int iDiffPos = -1;
513 int iFromRight = -1;
514
515 if (m_fMouseAwaited)
516 {
517 /* Store the cursor position: */
518 iCurPos = lineEdit()->cursorPosition();
519 iDiffPos = differFrom(lineEdit()->text(), m_strPath);
520 iFromRight = lineEdit()->text().size() - iCurPos;
521 }
522
523 /* In editable mode there should be no any icon
524 * and text have be corresponding real stored path
525 * which can be absolute or relative. */
526 if (lineEdit()->text() != m_strPath)
527 setItemText(PathId, m_strPath);
528 setItemIcon(PathId, QIcon());
529
530 /* Set the tool-tip: */
531 if (!m_fToolTipOverriden)
532 QIComboBox::setToolTip(m_strNoneToolTipFocused);
533 setItemData(PathId, toolTip(), Qt::ToolTipRole);
534
535 if (m_fMouseAwaited)
536 {
537 m_fMouseAwaited = false;
538
539 /* Restore the position to the right of dots: */
540 if (iDiffPos != -1 && iCurPos >= iDiffPos + 3)
541 lineEdit()->setCursorPosition(lineEdit()->text().size() -
542 iFromRight);
543 /* Restore the position to the center of text: */
544 else if (iDiffPos != -1 && iCurPos > iDiffPos)
545 lineEdit()->setCursorPosition(lineEdit()->text().size() / 2);
546 /* Restore the position to the left of dots: */
547 else
548 lineEdit()->setCursorPosition(iCurPos);
549 }
550 }
551 else if (m_strPath.isNull())
552 {
553 /* If we are not in editable mode and no path is
554 * stored here - show the translated 'none' string. */
555 if (itemText(PathId) != m_strNoneText)
556 {
557 setItemText(PathId, m_strNoneText);
558 setItemIcon(PathId, QIcon());
559
560 /* Set the tool-tip: */
561 if (!m_fToolTipOverriden)
562 QIComboBox::setToolTip(m_strNoneToolTip);
563 setItemData(PathId, toolTip(), Qt::ToolTipRole);
564 }
565 }
566 else
567 {
568 /* Compress text in combobox: */
569 QStyleOptionComboBox options;
570 options.initFrom(this);
571 QRect rect = QApplication::style()->subControlRect(
572 QStyle::CC_ComboBox, &options, QStyle::SC_ComboBoxEditField);
573 setItemText(PathId, shrinkText(rect.width() - iconSize().width()));
574
575 /* Attach corresponding icon: */
576 setItemIcon(PathId, QFileInfo(m_strPath).exists() ?
577 vboxGlobal().icon(QFileInfo(m_strPath)) :
578 defaultIcon());
579
580 /* Set the tool-tip: */
581 if (!m_fToolTipOverriden)
582 QIComboBox::setToolTip(fullPath());
583 setItemData(PathId, toolTip(), Qt::ToolTipRole);
584 }
585}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use