VirtualBox

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

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

FE/Qt: bugref:8938. Changing connection syntax under the widget subfolder.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use