feat(filemanager): add reorderable folder tabs and stable window menus
parent
6da810db92
commit
cc2b984d41
@ -0,0 +1,258 @@
|
|||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import FishUI 1.0 as FishUI
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: control
|
||||||
|
property var tabs: []
|
||||||
|
property int currentIndex: 0
|
||||||
|
property int dragIndex: -1
|
||||||
|
property int dropIndex: -1
|
||||||
|
property real pointerX: 0
|
||||||
|
property string dragTitle: ""
|
||||||
|
signal selected(int index)
|
||||||
|
signal closeRequested(int index)
|
||||||
|
signal newRequested()
|
||||||
|
signal duplicateRequested(string path)
|
||||||
|
signal moveRequested(int from, int to)
|
||||||
|
|
||||||
|
color: FishUI.Theme.secondBackgroundColor
|
||||||
|
readonly property color separatorColor: Qt.rgba(FishUI.Theme.textColor.r,
|
||||||
|
FishUI.Theme.textColor.g, FishUI.Theme.textColor.b, 0.10)
|
||||||
|
readonly property color hoverColor: Qt.rgba(FishUI.Theme.textColor.r,
|
||||||
|
FishUI.Theme.textColor.g, FishUI.Theme.textColor.b, 0.05)
|
||||||
|
|
||||||
|
function updateDrop(x) {
|
||||||
|
pointerX = x
|
||||||
|
dropIndex = Math.max(0, Math.min(tabs.length - 1,
|
||||||
|
Math.floor((x - list.x + list.contentX) / list.tabWidth)))
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
x: list.x
|
||||||
|
y: list.y
|
||||||
|
width: list.width
|
||||||
|
height: list.height
|
||||||
|
radius: height / 2
|
||||||
|
color: FishUI.Theme.alternateBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: list
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 6
|
||||||
|
anchors.rightMargin: 40
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.bottomMargin: 4
|
||||||
|
orientation: ListView.Horizontal
|
||||||
|
layoutDirection: Qt.LeftToRight
|
||||||
|
clip: true
|
||||||
|
model: control.tabs
|
||||||
|
currentIndex: control.currentIndex
|
||||||
|
readonly property real tabWidth: Math.max(140, width / Math.max(1, count))
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (control.dragIndex < 0)
|
||||||
|
positionViewAtIndex(currentIndex, ListView.Contain)
|
||||||
|
}
|
||||||
|
onCountChanged: Qt.callLater(function() { list.positionViewAtIndex(list.currentIndex, ListView.Contain) })
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
id: tab
|
||||||
|
objectName: "folderTab" + index
|
||||||
|
width: list.tabWidth
|
||||||
|
height: list.height
|
||||||
|
property bool selected: index === control.currentIndex
|
||||||
|
opacity: control.dragIndex === index ? 0.25 : 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
radius: height / 2
|
||||||
|
color: tab.selected ? (FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.secondBackgroundColor, 2)
|
||||||
|
: FishUI.Theme.secondBackgroundColor)
|
||||||
|
: mouse.containsMouse ? control.hoverColor : "transparent"
|
||||||
|
layer.enabled: tab.selected
|
||||||
|
layer.effect: DropShadow {
|
||||||
|
transparentBorder: true
|
||||||
|
radius: 3
|
||||||
|
samples: 6
|
||||||
|
horizontalOffset: 0
|
||||||
|
verticalOffset: 1
|
||||||
|
color: Qt.rgba(0, 0, 0, FishUI.Theme.darkMode ? 0.18 : 0.12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
preventStealing: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||||
|
property real pressX: 0
|
||||||
|
property bool didDrag: false
|
||||||
|
onPressed: function(event) {
|
||||||
|
didDrag = false
|
||||||
|
pressX = mapToItem(control, event.x, event.y).x
|
||||||
|
if (event.button === Qt.LeftButton)
|
||||||
|
control.selected(index)
|
||||||
|
}
|
||||||
|
onPositionChanged: function(event) {
|
||||||
|
if (!(pressedButtons & Qt.LeftButton))
|
||||||
|
return
|
||||||
|
var x = mapToItem(control, event.x, event.y).x
|
||||||
|
if (!didDrag && Math.abs(x - pressX) > 8) {
|
||||||
|
didDrag = true
|
||||||
|
control.dragIndex = index
|
||||||
|
control.dragTitle = modelData.tabTitle
|
||||||
|
}
|
||||||
|
if (didDrag)
|
||||||
|
control.updateDrop(x)
|
||||||
|
}
|
||||||
|
onReleased: {
|
||||||
|
if (didDrag) {
|
||||||
|
var from = control.dragIndex
|
||||||
|
var to = control.dropIndex
|
||||||
|
control.dragIndex = -1
|
||||||
|
control.dropIndex = -1
|
||||||
|
control.moveRequested(from, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCanceled: {
|
||||||
|
control.dragIndex = -1
|
||||||
|
control.dropIndex = -1
|
||||||
|
}
|
||||||
|
onClicked: function(event) {
|
||||||
|
if (didDrag)
|
||||||
|
return
|
||||||
|
if (event.button === Qt.MiddleButton)
|
||||||
|
control.closeRequested(index)
|
||||||
|
else if (event.button === Qt.RightButton)
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 28
|
||||||
|
anchors.rightMargin: 28
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: modelData.tabTitle
|
||||||
|
font.pixelSize: 12
|
||||||
|
font.weight: tab.selected ? Font.Medium : Font.Normal
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: FishUI.Theme.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: closeButton
|
||||||
|
objectName: "tabCloseButton"
|
||||||
|
width: 20
|
||||||
|
height: 20
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 6
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
opacity: tab.selected || mouse.containsMouse || hovered ? 1 : 0
|
||||||
|
Accessible.name: qsTr("Close Tab")
|
||||||
|
onClicked: control.closeRequested(index)
|
||||||
|
contentItem: Item {
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 10
|
||||||
|
height: 1.2
|
||||||
|
rotation: 45
|
||||||
|
color: FishUI.Theme.textColor
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 10
|
||||||
|
height: 1.2
|
||||||
|
rotation: -45
|
||||||
|
color: FishUI.Theme.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: closeButton.down ? control.separatorColor
|
||||||
|
: closeButton.hovered ? control.hoverColor : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FishUI.DesktopMenu {
|
||||||
|
id: menu
|
||||||
|
FishUI.MenuItem {
|
||||||
|
text: qsTr("Open In New Tab")
|
||||||
|
onTriggered: control.duplicateRequested(modelData.currentUrl)
|
||||||
|
}
|
||||||
|
FishUI.MenuItem {
|
||||||
|
text: qsTr("Close Tab")
|
||||||
|
onTriggered: control.closeRequested(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: control.dragIndex >= 0
|
||||||
|
x: Math.max(list.x, Math.min(list.x + list.width - width, control.pointerX - width / 2))
|
||||||
|
y: 4
|
||||||
|
width: Math.min(list.tabWidth - 2, list.width)
|
||||||
|
height: list.height - 2
|
||||||
|
radius: height / 2
|
||||||
|
color: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.secondBackgroundColor, 2)
|
||||||
|
: FishUI.Theme.secondBackgroundColor
|
||||||
|
opacity: 0.95
|
||||||
|
Label {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: control.dragTitle
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: FishUI.Theme.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: control.dragIndex >= 0 && control.dropIndex !== control.dragIndex
|
||||||
|
x: Math.max(list.x, Math.min(list.x + list.width - width,
|
||||||
|
list.x + (control.dropIndex + (control.dropIndex > control.dragIndex ? 1 : 0)) * list.tabWidth - list.contentX))
|
||||||
|
y: 6
|
||||||
|
width: 2
|
||||||
|
height: control.height - 12
|
||||||
|
radius: 1
|
||||||
|
color: FishUI.Theme.highlightColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 30
|
||||||
|
repeat: true
|
||||||
|
running: control.dragIndex >= 0
|
||||||
|
onTriggered: {
|
||||||
|
var delta = control.pointerX < list.x + 24 ? -8
|
||||||
|
: control.pointerX > list.x + list.width - 24 ? 8 : 0
|
||||||
|
list.contentX = Math.max(0, Math.min(Math.max(0, list.contentWidth - list.width), list.contentX + delta))
|
||||||
|
control.updateDrop(control.pointerX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: addButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 6
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
text: "+"
|
||||||
|
font.pixelSize: 20
|
||||||
|
Accessible.name: qsTr("New Tab")
|
||||||
|
onClicked: control.newRequested()
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: addButton.hovered ? control.hoverColor : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
#include "application.h"
|
||||||
|
#include "qmltypes.h"
|
||||||
|
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QTemporaryDir config;
|
||||||
|
qputenv("XDG_CONFIG_HOME", config.path().toUtf8());
|
||||||
|
CutefishFM::registerQmlTypes();
|
||||||
|
CutefishFM::initResources();
|
||||||
|
Application app(argc, argv);
|
||||||
|
QWindow helper;
|
||||||
|
helper.show();
|
||||||
|
bool closedLastWindow = false;
|
||||||
|
|
||||||
|
auto browserWindows = [] {
|
||||||
|
QList<QWindow *> windows;
|
||||||
|
for (auto *window : QGuiApplication::topLevelWindows()) {
|
||||||
|
if (window->isVisible() && window->property("tabs").isValid())
|
||||||
|
windows.append(window);
|
||||||
|
}
|
||||||
|
return windows;
|
||||||
|
};
|
||||||
|
|
||||||
|
QTimer::singleShot(0, &app, [&] {
|
||||||
|
app.openFiles({config.path(), config.path()});
|
||||||
|
const auto windows = browserWindows();
|
||||||
|
if (windows.size() != 2) {
|
||||||
|
app.exit(2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
windows.first()->close();
|
||||||
|
QTimer::singleShot(100, &app, [&] {
|
||||||
|
const auto remaining = browserWindows();
|
||||||
|
if (remaining.size() != 1) {
|
||||||
|
app.exit(3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closedLastWindow = true;
|
||||||
|
remaining.first()->close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
QTimer::singleShot(5000, &app, [&] { app.exit(4); });
|
||||||
|
const int result = app.exec();
|
||||||
|
return closedLastWindow ? result : 5;
|
||||||
|
}
|
||||||
@ -0,0 +1,167 @@
|
|||||||
|
#include "qmltypes.h"
|
||||||
|
#include "model/foldermodel.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QQmlExpression>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
#include <QtTest>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class TabsTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void navigationAndLifetime()
|
||||||
|
{
|
||||||
|
QTemporaryDir folders;
|
||||||
|
QVERIFY(folders.isValid());
|
||||||
|
QDir dir(folders.path());
|
||||||
|
QVERIFY(dir.mkdir("first"));
|
||||||
|
QVERIFY(dir.mkdir("second"));
|
||||||
|
|
||||||
|
QQmlApplicationEngine engine;
|
||||||
|
CutefishFM::registerImageProviders(&engine);
|
||||||
|
engine.rootContext()->setContextProperty("arg", folders.path());
|
||||||
|
engine.load(QUrl("qrc:/qml/main.qml"));
|
||||||
|
QVERIFY(!engine.rootObjects().isEmpty());
|
||||||
|
QObject *window = engine.rootObjects().first();
|
||||||
|
QObject *menuBar = window->findChild<QObject *>("applicationMenuBar");
|
||||||
|
QVERIFY(menuBar);
|
||||||
|
QSignalSpy menuWindowChanges(menuBar, SIGNAL(windowChanged()));
|
||||||
|
QVERIFY(menuWindowChanges.isValid());
|
||||||
|
auto evaluate = [&](const QString &code) {
|
||||||
|
QQmlExpression expression(engine.rootContext(), window, code);
|
||||||
|
const QVariant result = expression.evaluate();
|
||||||
|
if (expression.hasError())
|
||||||
|
qWarning() << expression.error();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
auto activePage = [&]() {
|
||||||
|
return window->property("_folderPage").value<QObject *>();
|
||||||
|
};
|
||||||
|
auto modelFor = [](QObject *page) {
|
||||||
|
return qobject_cast<FolderModel *>(page->property("model").value<QObject *>());
|
||||||
|
};
|
||||||
|
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 1);
|
||||||
|
auto *bar = window->findChild<QQuickItem *>("folderTabBar");
|
||||||
|
QVERIFY(bar);
|
||||||
|
QVERIFY(!bar->isVisible());
|
||||||
|
QCOMPARE(bar->height(), 0);
|
||||||
|
QObject *firstPage = activePage();
|
||||||
|
QVERIFY(firstPage);
|
||||||
|
FolderModel *firstModel = modelFor(firstPage);
|
||||||
|
QVERIFY(firstModel);
|
||||||
|
QTRY_COMPARE(firstModel->rowCount(), 2);
|
||||||
|
const QString initialUrl = firstModel->url();
|
||||||
|
firstModel->setUrl(QUrl::fromLocalFile(dir.filePath("first")).toString());
|
||||||
|
QTRY_VERIFY(firstModel->canGoBack());
|
||||||
|
const QString firstUrl = firstModel->url();
|
||||||
|
|
||||||
|
QVERIFY(QMetaObject::invokeMethod(window, "openTab", Q_ARG(QVariant, dir.filePath("second"))));
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 2);
|
||||||
|
QCOMPARE(window->property("currentTab").toInt(), 1);
|
||||||
|
QVERIFY(bar->isVisible());
|
||||||
|
QCOMPARE(bar->height(), 38);
|
||||||
|
QObject *secondPage = activePage();
|
||||||
|
QVERIFY(secondPage != firstPage);
|
||||||
|
QVERIFY(!firstPage->property("visible").toBool());
|
||||||
|
QVERIFY(secondPage->property("visible").toBool());
|
||||||
|
QCOMPARE(secondPage->property("tabTitle").toString(), QStringLiteral("second"));
|
||||||
|
FolderModel *secondModel = modelFor(secondPage);
|
||||||
|
QVERIFY(secondModel);
|
||||||
|
const QString secondUrl = secondModel->url();
|
||||||
|
|
||||||
|
window->setProperty("currentTab", 0);
|
||||||
|
QCOMPARE(activePage(), firstPage);
|
||||||
|
QCOMPARE(firstModel->url(), firstUrl);
|
||||||
|
QVERIFY(firstModel->canGoBack());
|
||||||
|
firstModel->goBack();
|
||||||
|
QTRY_COMPARE(firstModel->url(), initialUrl);
|
||||||
|
QCOMPARE(secondModel->url(), secondUrl);
|
||||||
|
|
||||||
|
QTRY_COMPARE(firstModel->rowCount(), 2);
|
||||||
|
firstModel->selectAll();
|
||||||
|
firstModel->prepareContextMenu();
|
||||||
|
QVERIFY(firstModel->action("openInNewTab")->isVisible());
|
||||||
|
firstModel->openInNewTab();
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 4);
|
||||||
|
QCOMPARE(firstModel->selectionCount(), 2);
|
||||||
|
|
||||||
|
QPointer<QObject> closing = activePage();
|
||||||
|
QVERIFY(QMetaObject::invokeMethod(window, "closeTab", Q_ARG(QVariant, 3)));
|
||||||
|
QTRY_VERIFY(closing.isNull());
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 3);
|
||||||
|
evaluate("closeTab(2); closeTab(1)");
|
||||||
|
QCOMPARE(activePage(), firstPage);
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 1);
|
||||||
|
QVERIFY(!bar->isVisible());
|
||||||
|
QCOMPARE(bar->height(), 0);
|
||||||
|
|
||||||
|
auto *quickWindow = qobject_cast<QQuickWindow *>(window);
|
||||||
|
QVERIFY(quickWindow);
|
||||||
|
QTest::keyClick(quickWindow, Qt::Key_T, Qt::ControlModifier);
|
||||||
|
QTRY_COMPARE(evaluate("tabs.length").toInt(), 2);
|
||||||
|
QTest::keyClick(quickWindow, Qt::Key_Tab, Qt::ControlModifier);
|
||||||
|
QTRY_COMPARE(window->property("currentTab").toInt(), 0);
|
||||||
|
QTest::keyClick(quickWindow, Qt::Key_W, Qt::ControlModifier);
|
||||||
|
QTRY_COMPARE(evaluate("tabs.length").toInt(), 1);
|
||||||
|
|
||||||
|
QVERIFY(QMetaObject::invokeMethod(window, "openTab", Q_ARG(QVariant, dir.filePath("second"))));
|
||||||
|
QObject *draggedPage = activePage();
|
||||||
|
std::function<QQuickItem *(QQuickItem *, const QString &)> findItem;
|
||||||
|
findItem = [&](QQuickItem *parent, const QString &name) -> QQuickItem * {
|
||||||
|
if (parent->objectName() == name)
|
||||||
|
return parent;
|
||||||
|
for (auto *child : parent->childItems()) {
|
||||||
|
if (auto *found = findItem(child, name))
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
QTRY_VERIFY(findItem(bar, "folderTab1"));
|
||||||
|
auto *draggedTab = findItem(bar, "folderTab1");
|
||||||
|
auto *targetTab = findItem(bar, "folderTab0");
|
||||||
|
QVERIFY(targetTab);
|
||||||
|
auto *closeButton = draggedTab->findChild<QQuickItem *>("tabCloseButton");
|
||||||
|
QVERIFY(closeButton);
|
||||||
|
QVERIFY(closeButton->x() > draggedTab->width() / 2);
|
||||||
|
const QPoint from = draggedTab->mapToScene(QPointF(draggedTab->width() / 2, draggedTab->height() / 2)).toPoint();
|
||||||
|
const QPoint to = targetTab->mapToScene(QPointF(targetTab->width() / 2, targetTab->height() / 2)).toPoint();
|
||||||
|
QTest::mousePress(quickWindow, Qt::LeftButton, Qt::NoModifier, from);
|
||||||
|
QTest::mouseMove(quickWindow, to, 30);
|
||||||
|
QTRY_COMPARE(bar->property("dragIndex").toInt(), 1);
|
||||||
|
QTest::mouseRelease(quickWindow, Qt::LeftButton, Qt::NoModifier, to);
|
||||||
|
QTRY_COMPARE(window->property("currentTab").toInt(), 0);
|
||||||
|
QCOMPARE(activePage(), draggedPage);
|
||||||
|
QCOMPARE(evaluate("tabs.length").toInt(), 2);
|
||||||
|
QCOMPARE(menuWindowChanges.count(), 0);
|
||||||
|
QCOMPARE(window->findChildren<QObject *>("applicationMenuBar").count(), 1);
|
||||||
|
if (qEnvironmentVariableIsSet("FILEMANAGER_TEST_SCREENSHOT")) {
|
||||||
|
// The software renderer cannot render FishUI's rounded-window shader mask.
|
||||||
|
window->setProperty("windowRadius", 0);
|
||||||
|
QTest::qWait(150);
|
||||||
|
QVERIFY(quickWindow->grabWindow().save(qEnvironmentVariable("FILEMANAGER_TEST_SCREENSHOT")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
QTemporaryDir config;
|
||||||
|
qputenv("XDG_CONFIG_HOME", config.path().toUtf8());
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
app.setOrganizationName("cutefish-tests");
|
||||||
|
app.setApplicationName("tabs");
|
||||||
|
CutefishFM::registerQmlTypes();
|
||||||
|
CutefishFM::initResources();
|
||||||
|
TabsTest test;
|
||||||
|
return QTest::qExec(&test, argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "tst_tabs.moc"
|
||||||
Loading…
Reference in New Issue