From 7d550846ed7ff649c1a538b87cf3682e98d610fd Mon Sep 17 00:00:00 2001 From: arkpar Date: Mon, 10 Aug 2015 15:41:24 +0200 Subject: [PATCH] custom file dialog to work around qt 5.5 bug --- mix/CMakeLists.txt | 5 + mix/FileIo.cpp | 8 + mix/FileIo.h | 5 + mix/MixApplication.cpp | 3 + mix/MixClient.cpp | 2 +- mix/osx.qrc | 5 + mix/qml.qrc | 1 + mix/qml/Application.qml | 6 +- mix/qml/MacFileDialog.qml | 298 +++++++++++++++++++++++++++++++++++ mix/qml/NewProjectDialog.qml | 2 +- mix/qml/PackagingStep.qml | 3 +- mix/qml/QFileDialog.qml | 5 + mix/qml/StateDialog.qml | 2 +- 13 files changed, 339 insertions(+), 6 deletions(-) create mode 100644 mix/osx.qrc create mode 100644 mix/qml/MacFileDialog.qml create mode 100644 mix/qml/QFileDialog.qml diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 44cd9c12a..874656f89 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -20,6 +20,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND NOT (CMAKE_CXX_COMPILER_VER set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-inconsistent-missing-override") endif () +#TODO: remove once qt 5.5.1 is out +if (APPLE) + qt5_add_resources(UI_RESOURCES osx.qrc) +endif() + find_package (Qt5WebEngine) if (APPLE) # TODO: remove indirect dependencies once macdeployqt is fixed diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 1c9faa896..525715d5a 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -224,3 +224,11 @@ void FileIo::deleteFile(QString const& _path) QFile file(pathFromUrl(_path)); file.remove(); } + +QUrl FileIo::pathFolder(QString const& _path) +{ + QFileInfo info(_path); + if (info.exists() && info.isDir()) + return QUrl::fromLocalFile(_path); + return QUrl::fromLocalFile(QFileInfo(_path).absolutePath()); +} diff --git a/mix/FileIo.h b/mix/FileIo.h index eb5bc6cad..0db399a73 100644 --- a/mix/FileIo.h +++ b/mix/FileIo.h @@ -71,6 +71,11 @@ public: /// delete a directory Q_INVOKABLE void deleteDir(QString const& _url); + //TODO: remove once qt 5.5.1 is out + Q_INVOKABLE QString urlToPath(QUrl const& _url) { return _url.toLocalFile(); } + Q_INVOKABLE QUrl pathToUrl(QString const& _path) { return QUrl::fromLocalFile(_path); } + Q_INVOKABLE QUrl pathFolder(QString const& _path); + private: QString getHomePath() const; QString pathFromUrl(QString const& _url); diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index d81a4f6b2..4972b6cf5 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -41,6 +41,9 @@ extern int qInitResources_js(); using namespace dev::mix; + + + ApplicationService::ApplicationService() { #ifdef ETH_HAVE_WEBENGINE diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 4f4139701..89b749629 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -284,7 +284,7 @@ void MixClient::mine() RLPStream header; h.streamRLP(header); m_postMine.sealBlock(header.out()); - bc().import(m_postMine.blockData(), m_stateDB, ImportRequirements::Everything & ~ImportRequirements::ValidSeal); + bc().import(m_postMine.blockData(), m_stateDB, (ImportRequirements::Everything & ~ImportRequirements::ValidSeal) != 0); m_postMine.sync(bc()); m_preMine = m_postMine; } diff --git a/mix/osx.qrc b/mix/osx.qrc new file mode 100644 index 000000000..ef302c2d5 --- /dev/null +++ b/mix/osx.qrc @@ -0,0 +1,5 @@ + + + qml/MacFileDialog.qml + + diff --git a/mix/qml.qrc b/mix/qml.qrc index c1901c220..50795d08b 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -34,6 +34,7 @@ qml/QStringTypeView.qml qml/QVariableDeclaration.qml qml/QVariableDefinition.qml + qml/QFileDialog.qml qml/SourceSansProBold.qml qml/SourceSansProLight.qml qml/SourceSansProRegular.qml diff --git a/mix/qml/Application.qml b/mix/qml/Application.qml index 188d87be9..b05673ce8 100644 --- a/mix/qml/Application.qml +++ b/mix/qml/Application.qml @@ -281,11 +281,12 @@ ApplicationWindow { onTriggered: openProjectFileDialog.open() } - FileDialog { + QFileDialog { id: openProjectFileDialog visible: false title: qsTr("Open a Project") selectFolder: true + selectExisting: true onAccepted: { var path = openProjectFileDialog.fileUrl.toString(); path += "/"; @@ -333,11 +334,12 @@ ApplicationWindow { onTriggered: addExistingFileDialog.open() } - FileDialog { + QFileDialog { id: addExistingFileDialog visible: false title: qsTr("Add a File") selectFolder: false + selectExisting: true onAccepted: { var paths = addExistingFileDialog.fileUrls; projectModel.addExistingFiles(paths); diff --git a/mix/qml/MacFileDialog.qml b/mix/qml/MacFileDialog.qml new file mode 100644 index 000000000..f1995588f --- /dev/null +++ b/mix/qml/MacFileDialog.qml @@ -0,0 +1,298 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Private 1.0 as ControlsPrivate +import QtQuick.Dialogs 1.2 +import QtQuick.Dialogs.Private 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 +import Qt.labs.folderlistmodel 2.1 +import Qt.labs.settings 1.0 + +AbstractDialog { + + id: root + + property string folder: view.model.folder + property var nameFilters: [] + property bool selectFolder: false + property bool selectExisting: true + property int selectedNameFilterIndex: -1 + property var selectedNameFilterExtensions: [] + property string selection: "" + property alias fileUrl: root.selection + + + function selectNameFilter(text) { + } + + function clearSelection(text) { + selection = ""; + } + + function addSelection(text) { + selection = text; + } + + onVisibleChanged: { + if (visible) { + view.needsWidthAdjustment = true + view.selection.clear() + view.focus = true + } + } + + Component.onCompleted: { + folder = fileIo.pathToUrl(fileIo.homePath); + view.model.nameFilters = root.selectedNameFilterExtensions + filterField.currentIndex = root.selectedNameFilterIndex + root.favoriteFolders = settings.favoriteFolders + } + + Component.onDestruction: { + settings.favoriteFolders = root.favoriteFolders + } + + property Settings settings: Settings { + category: "QQControlsFileDialog" + property alias width: root.width + property alias height: root.height + property variant favoriteFolders: [] + } + + property bool showFocusHighlight: false + property SystemPalette palette: SystemPalette { } + property var favoriteFolders: [] + + function dirDown(path) { + view.selection.clear() + root.folder = "file://" + path + } + function dirUp() { + view.selection.clear() + if (view.model.parentFolder != "") + root.folder = view.model.parentFolder + } + function acceptSelection() { + // transfer the view's selections to QQuickFileDialog + clearSelection() + if (selectFolder && view.selection.count === 0) + addSelection(folder) + else { + view.selection.forEach(function(idx) { + if (view.model.isFolder(idx)) { + if (selectFolder) + addSelection(view.model.get(idx, "fileURL")) + } else { + if (!selectFolder) + addSelection(view.model.get(idx, "fileURL")) + } + }) + } + accept() + } + + property Action dirUpAction: Action { + text: "\ue810" + shortcut: "Ctrl+U" + onTriggered: dirUp() + tooltip: qsTr("Go up to the folder containing this one") + } + + Rectangle { + id: window + implicitWidth: Math.min(root.__maximumDimension, Math.max(Screen.pixelDensity * 100, view.implicitWidth)) + implicitHeight: Math.min(root.__maximumDimension, Screen.pixelDensity * 80) + color: root.palette.window + + Binding { + target: view.model + property: "folder" + value: root.folder + } + Binding { + target: currentPathField + property: "text" + value: fileIo.urlToPath(root.folder) + } + Keys.onPressed: { + event.accepted = true + switch (event.key) { + case Qt.Key_Back: + case Qt.Key_Escape: + reject() + break + default: + event.accepted = false + break + } + } + Keys.forwardTo: [view.flickableItem] + + + TableView { + id: view + sortIndicatorVisible: true + width: parent.width + anchors.top: titleBar.bottom + anchors.bottom: bottomBar.top + + property bool needsWidthAdjustment: true + selectionMode: root.selectMultiple ? + (ControlsPrivate.Settings.hasTouchScreen ? SelectionMode.MultiSelection : SelectionMode.ExtendedSelection) : + SelectionMode.SingleSelection + onRowCountChanged: if (needsWidthAdjustment && rowCount > 0) { + resizeColumnsToContents() + needsWidthAdjustment = false + } + model: FolderListModel { + showFiles: !root.selectFolder + nameFilters: root.selectedNameFilterExtensions + sortField: (view.sortIndicatorColumn === 0 ? FolderListModel.Name : + (view.sortIndicatorColumn === 1 ? FolderListModel.Type : + (view.sortIndicatorColumn === 2 ? FolderListModel.Size : FolderListModel.LastModified))) + sortReversed: view.sortIndicatorOrder === Qt.DescendingOrder + } + + onActivated: if (view.focus) { + if (view.selection.count > 0 && view.model.isFolder(row)) { + dirDown(view.model.get(row, "filePath")) + } else { + root.acceptSelection() + } + } + onClicked: currentPathField.text = view.model.get(row, "filePath") + + + TableViewColumn { + id: fileNameColumn + role: "fileName" + title: qsTr("Filename") + delegate: Item { + implicitWidth: pathText.implicitWidth + pathText.anchors.leftMargin + pathText.anchors.rightMargin + + Text { + id: fileIcon + width: height + verticalAlignment: Text.AlignVCenter + font.family: iconFont.name + property alias unicode: fileIcon.text + FontLoader { id: iconFont; source: "qrc:/QtQuick/Dialogs/qml/icons.ttf"; onNameChanged: console.log("custom font" + name) } + x: 4 + height: parent.height - 2 + unicode: view.model.isFolder(styleData.row) ? "\ue804" : "\ue802" + } + Text { + id: pathText + text: styleData.value + anchors { + left: parent.left + right: parent.right + leftMargin: 36 + 6 + rightMargin: 4 + verticalCenter: parent.verticalCenter + } + color: styleData.textColor + elide: Text.ElideRight + renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering + } + } + } + TableViewColumn { + role: "fileSuffix" + title: qsTr("Type", "file type (extension)") + // TODO should not need to create a whole new component just to customize the text value + // something like textFormat: function(text) { return view.model.get(styleData.row, "fileIsDir") ? "folder" : text } + delegate: Item { + implicitWidth: sizeText.implicitWidth + sizeText.anchors.leftMargin + sizeText.anchors.rightMargin + Text { + id: sizeText + text: view.model.get(styleData.row, "fileIsDir") ? "folder" : styleData.value + anchors { + left: parent.left + right: parent.right + leftMargin: 4 + rightMargin: 4 + verticalCenter: parent.verticalCenter + } + color: styleData.textColor + elide: Text.ElideRight + renderType: ControlsPrivate.Settings.isMobile ? Text.QtRendering : Text.NativeRendering + } + } + } + TableViewColumn { + role: "fileSize" + title: qsTr("Size", "file size") + horizontalAlignment: Text.AlignRight + } + TableViewColumn { id: modifiedColumn; role: "fileModified" ; title: qsTr("Modified", "last-modified time") } + TableViewColumn { id: accessedColumn; role: "fileAccessed" ; title: qsTr("Accessed", "last-accessed time") } + } + + ToolBar { + id: titleBar + RowLayout { + anchors.fill: parent + ToolButton { + action: dirUpAction + //style: IconButtonStyle { } + Layout.maximumWidth: height * 1.5 + } + TextField { + id: currentPathField + Layout.fillWidth: true + function doAccept() { + root.clearSelection() + if (root.addSelection(fileIo.pathToUrl(text))) + root.accept() + else + root.folder = fileIo.pathFolder(text) + } + onAccepted: doAccept() + } + } + } + Item { + id: bottomBar + width: parent.width + height: buttonRow.height + buttonRow.spacing * 2 + anchors.bottom: parent.bottom + + Row { + id: buttonRow + anchors.right: parent.right + anchors.rightMargin: spacing + anchors.verticalCenter: parent.verticalCenter + spacing: 4 + ComboBox { + id: filterField + model: root.nameFilters + visible: !selectFolder + width: bottomBar.width - cancelButton.width - okButton.width - parent.spacing * 6 + anchors.verticalCenter: parent.verticalCenter + onCurrentTextChanged: { + root.selectNameFilter(currentText) + view.model.nameFilters = root.selectedNameFilterExtensions + } + } + Button { + id: cancelButton + text: qsTr("Cancel") + onClicked: root.reject() + } + Button { + id: okButton + text: root.selectFolder ? qsTr("Choose") : (selectExisting ? qsTr("Open") : qsTr("Save")) + onClicked: { + if (view.model.isFolder(view.currentIndex) && !selectFolder) + dirDown(view.model.get(view.currentIndex, "filePath")) + else if (!(root.selectExisting)) + currentPathField.doAccept() + else + root.acceptSelection() + } + } + } + } + } +} diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 77b6c513a..ab35d5c5e 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -105,7 +105,7 @@ Item } - FileDialog { + QFileDialog { id: createProjectFileDialog visible: false title: qsTr("Please choose a path for the project") diff --git a/mix/qml/PackagingStep.qml b/mix/qml/PackagingStep.qml index 26b5c8540..8ee383f56 100644 --- a/mix/qml/PackagingStep.qml +++ b/mix/qml/PackagingStep.qml @@ -27,11 +27,12 @@ Rectangle { visible = true } - FileDialog { + QFileDialog { id: ressourcesFolder visible: false title: qsTr("Please choose a path") selectFolder: true + selectExisting: true property variant target onAccepted: { var u = ressourcesFolder.fileUrl.toString(); diff --git a/mix/qml/QFileDialog.qml b/mix/qml/QFileDialog.qml new file mode 100644 index 000000000..5ce2ad41c --- /dev/null +++ b/mix/qml/QFileDialog.qml @@ -0,0 +1,5 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 + +FileDialog { } diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index cd19751b6..a2d384b3d 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -124,7 +124,7 @@ Dialog { importJsonFileDialog.open() } } - FileDialog { + QFileDialog { id: importJsonFileDialog visible: false title: qsTr("Select State File")