├── .gitignore ├── LICENSE ├── README.md ├── app └── SQLiteEditor │ ├── SQLiteEditor.pro │ ├── dbthread.cpp │ ├── dbthread.h │ ├── deployment.pri │ ├── generate-qrc.sh │ ├── main.cpp │ ├── qml.qrc │ ├── qml │ ├── img │ │ └── icon-tables.png │ ├── main.qml │ ├── utils │ │ ├── AccentBottom.qml │ │ ├── AccentLeft.qml │ │ ├── AccentRight.qml │ │ ├── AccentTop.qml │ │ ├── ActionSheet.qml │ │ ├── AsyncImage.qml │ │ ├── BaseButtonTheme.qml │ │ ├── BaseIcon.qml │ │ ├── BaseTabBarPage.qml │ │ ├── BaseWindow.qml │ │ ├── Blurtangle.qml │ │ ├── ClickGuard.qml │ │ ├── Config.qml │ │ ├── Fill.qml │ │ ├── GestureArea.qml │ │ ├── HorizontalSpacer.qml │ │ ├── Log.qml │ │ ├── Model.qml │ │ ├── Platform.qml │ │ ├── PlatformiOS.qml │ │ ├── RootItem.qml │ │ ├── SafeLoader.qml │ │ ├── TabBarButton.qml │ │ ├── TabBarController.qml │ │ ├── Theme.qml │ │ └── VerticalSpacer.qml │ └── views │ │ ├── AppWindow.qml │ │ ├── Header.qml │ │ ├── Label.qml │ │ ├── Theme.qml │ │ └── YosemiteButton.qml │ ├── sqlite.cpp │ └── sqlite.h └── readme-misc └── screenshot-01.png /.gitignore: -------------------------------------------------------------------------------- 1 | build-* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Niraj Desai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SQLite Editor 2 | 3 | 4 | The goal of this project is to build an easy-to-use, yet feature-filled SQLite tables / contents editor. This is built in Qt \ QML. 5 | 6 | 7 | ![image](readme-misc/screenshot-01.png) -------------------------------------------------------------------------------- /app/SQLiteEditor/SQLiteEditor.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick sql core widgets 4 | 5 | CONFIG += c++11 6 | 7 | HEADERS += dbthread.h \ 8 | sqlite.h 9 | 10 | SOURCES += main.cpp \ 11 | dbthread.cpp \ 12 | sqlite.cpp 13 | 14 | RESOURCES += qml.qrc 15 | 16 | # Additional import path used to resolve QML modules in Qt Creator's code model 17 | QML_IMPORT_PATH = 18 | 19 | # Default rules for deployment. 20 | include(deployment.pri) 21 | -------------------------------------------------------------------------------- /app/SQLiteEditor/dbthread.cpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2017 Niraj Desai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include "dbthread.h" 24 | 25 | #define DATABASE_DRIVER "QSQLITE" 26 | 27 | class DbWorker : public QObject 28 | { 29 | Q_OBJECT 30 | 31 | public: 32 | DbWorker( QObject* parent = 0, QString databasePath = ""); 33 | ~DbWorker(); 34 | 35 | public slots: 36 | void slotExecute( const QString& query ); 37 | 38 | signals: 39 | void results( const QList& records ); 40 | 41 | private: 42 | QSqlDatabase m_database; 43 | }; 44 | 45 | // 46 | 47 | DbWorker::DbWorker( QObject* parent, QString databasePath ) 48 | : QObject( parent ) 49 | { 50 | QUuid uuid = QUuid::createUuid(); 51 | m_database = QSqlDatabase::addDatabase(DATABASE_DRIVER, uuid.toString()); 52 | m_database.setDatabaseName(databasePath); 53 | 54 | if (!m_database.open()) 55 | { 56 | qDebug() << "Unable to connect to database, giving up:" << m_database.lastError().text(); 57 | return; 58 | } 59 | } 60 | 61 | DbWorker::~DbWorker() 62 | { 63 | m_database.close(); 64 | } 65 | 66 | void DbWorker::slotExecute( const QString& query ) 67 | { 68 | qDebug() << __PRETTY_FUNCTION__; 69 | QList recs; 70 | QSqlQuery sql(query, m_database); 71 | while(sql.next()) 72 | { 73 | recs.push_back(sql.record()); 74 | } 75 | qDebug() << Q_FUNC_INFO << "query is\"" << query << "\"and records count is" << recs.count(); 76 | emit results(recs); 77 | } 78 | 79 | // 80 | 81 | DbThread::DbThread(QObject *parent, QString databaseFilePath) : 82 | QThread(parent) 83 | { 84 | m_databaseFilePath = databaseFilePath; 85 | } 86 | 87 | DbThread::~DbThread() 88 | { 89 | delete m_worker; 90 | } 91 | 92 | void DbThread::execute(const QString &query) 93 | { 94 | qDebug() << __PRETTY_FUNCTION__; 95 | emit executefwd(query); 96 | } 97 | 98 | void DbThread::run() 99 | { 100 | emit ready(false); 101 | emit progress("DbThread is starting..one moment please.."); 102 | 103 | m_worker = new DbWorker(0, m_databaseFilePath); 104 | 105 | connect( this, SIGNAL( executefwd( const QString& ) ), 106 | m_worker, SLOT( slotExecute( const QString& ) ) ); 107 | 108 | qRegisterMetaType< QList >( "QList" ); 109 | 110 | connect( m_worker, SIGNAL( results( const QList& ) ), 111 | this, SIGNAL( results( const QList& ) ) ); 112 | 113 | emit ready(true); 114 | 115 | exec(); 116 | 117 | } 118 | 119 | #include "dbthread.moc" 120 | -------------------------------------------------------------------------------- /app/SQLiteEditor/dbthread.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2017 Niraj Desai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | class DbWorker; 38 | 39 | class DbThread : public QThread 40 | { 41 | Q_OBJECT 42 | public: 43 | explicit DbThread(QObject *parent = 0, QString databaseFilePath = ""); 44 | ~DbThread(); 45 | 46 | void execute( const QString& query ); 47 | 48 | signals: 49 | void progress( const QString& msg ); 50 | void ready(bool); 51 | void results( const QList& records ); 52 | 53 | protected: 54 | void run(); 55 | 56 | signals: 57 | void executefwd( const QString& query ); 58 | 59 | private: 60 | DbWorker* m_worker; 61 | QString m_databaseFilePath; 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /app/SQLiteEditor/deployment.pri: -------------------------------------------------------------------------------- 1 | android-no-sdk { 2 | target.path = /data/user/qt 3 | export(target.path) 4 | INSTALLS += target 5 | } else:android { 6 | x86 { 7 | target.path = /libs/x86 8 | } else: armeabi-v7a { 9 | target.path = /libs/armeabi-v7a 10 | } else { 11 | target.path = /libs/armeabi 12 | } 13 | export(target.path) 14 | INSTALLS += target 15 | } else:unix { 16 | isEmpty(target.path) { 17 | qnx { 18 | target.path = /tmp/$${TARGET}/bin 19 | } else { 20 | target.path = /opt/$${TARGET}/bin 21 | } 22 | export(target.path) 23 | } 24 | INSTALLS += target 25 | } 26 | 27 | export(INSTALLS) 28 | -------------------------------------------------------------------------------- /app/SQLiteEditor/generate-qrc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generate qrc 4 | 5 | QMLBASEDIR=qml 6 | TMPQRC=.qml.qrc 7 | QRC=qml.qrc 8 | 9 | # 10 | # 11 | # main_ios.qml 12 | # home.qml 13 | # dev.qml 14 | # img/triangular.png 15 | # 16 | # 17 | 18 | { 19 | echo "" 20 | echo -ne '\t' 21 | echo "" 22 | for i in `find ${QMLBASEDIR} -type f | grep -v .DS_Store`; 23 | do 24 | echo -ne '\t\t' 25 | echo "${i}" 26 | done; 27 | echo -ne '\t' 28 | echo "" 29 | echo "" 30 | } >> ${TMPQRC} 31 | 32 | 33 | mv ${TMPQRC} ${QRC} 34 | cat ${QRC} 35 | -------------------------------------------------------------------------------- /app/SQLiteEditor/main.cpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2017 Niraj Desai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "sqlite.h" 29 | 30 | class Utility : public QObject { 31 | Q_OBJECT 32 | public: 33 | explicit Utility(QObject* parent = 0) 34 | { 35 | Q_UNUSED(parent) 36 | } 37 | 38 | Q_INVOKABLE void saveTextToClipboard(QString text) 39 | { 40 | QClipboard *clipboard = QApplication::clipboard(); 41 | clipboard->setText(text); 42 | } 43 | }; 44 | 45 | int main(int argc, char *argv[]) 46 | { 47 | QApplication app(argc, argv); 48 | 49 | QQmlApplicationEngine engine; 50 | 51 | qmlRegisterType("st.app", 1, 0, "SQLite"); 52 | 53 | Utility utility; 54 | engine.rootContext()->setContextProperty("$", &utility); 55 | engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); 56 | 57 | return app.exec(); 58 | } 59 | 60 | #include "main.moc" 61 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/img/icon-tables.png 4 | qml/main.qml 5 | qml/utils/AccentBottom.qml 6 | qml/utils/AccentLeft.qml 7 | qml/utils/AccentRight.qml 8 | qml/utils/AccentTop.qml 9 | qml/utils/ActionSheet.qml 10 | qml/utils/AsyncImage.qml 11 | qml/utils/BaseButtonTheme.qml 12 | qml/utils/BaseIcon.qml 13 | qml/utils/BaseTabBarPage.qml 14 | qml/utils/BaseWindow.qml 15 | qml/utils/Blurtangle.qml 16 | qml/utils/ClickGuard.qml 17 | qml/utils/Config.qml 18 | qml/utils/Fill.qml 19 | qml/utils/GestureArea.qml 20 | qml/utils/HorizontalSpacer.qml 21 | qml/utils/Log.qml 22 | qml/utils/Model.qml 23 | qml/utils/Platform.qml 24 | qml/utils/PlatformiOS.qml 25 | qml/utils/RootItem.qml 26 | qml/utils/SafeLoader.qml 27 | qml/utils/TabBarButton.qml 28 | qml/utils/TabBarController.qml 29 | qml/utils/Theme.qml 30 | qml/utils/VerticalSpacer.qml 31 | qml/views/Label.qml 32 | qml/views/Header.qml 33 | qml/views/AppWindow.qml 34 | qml/views/Theme.qml 35 | qml/views/YosemiteButton.qml 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/img/icon-tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ndesai/sqlite-editor-qtqml/c928970862348c8cd291374503e2a3620bcc2f8d/app/SQLiteEditor/qml/img/icon-tables.png -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Dialogs 1.0 4 | import "views" as Views 5 | import "utils" as Utils 6 | import QtQuick.Controls.Styles 1.2 7 | import st.app 1.0 as AppStreet 8 | 9 | 10 | Views.AppWindow { 11 | id: superRoot 12 | 13 | property QtObject queries : QtObject { 14 | readonly property string tableView : "SELECT * FROM sqlite_master WHERE type = 'table' OR type = 'view' ORDER BY type" 15 | property string fat : "SELECT * FROM Track tra JOIN (SELECT * FROM Album alb JOIN Artist art ON art.ArtistId = alb.ArtistId) albart ON tra.AlbumId = albart.AlbumId" 16 | } 17 | 18 | AppStreet.SQLite { 19 | id: _SQLite 20 | databasePath: superRoot.activeDatabase 21 | onResultsReady: { 22 | console.log("ready = " + query) 23 | console.log("results", Object.keys(results)) 24 | if(query == queries.tableView) 25 | { 26 | _ListView_Tables.model = results 27 | } else 28 | { 29 | _TableView.prepareAndSetModel(results) 30 | } 31 | } 32 | onDatabaseOpened: { 33 | executeQuery(queries.tableView) 34 | } 35 | } 36 | 37 | // GUI 38 | Views.Header { 39 | id: _Header 40 | z: theme.z.header 41 | } 42 | 43 | Rectangle { 44 | id: _Rectangle_Navigation 45 | 46 | anchors.top: _Header.bottom 47 | anchors.bottom: parent.bottom 48 | 49 | width: 72 50 | color: theme.asphalt 51 | 52 | ListView { 53 | id: _ListView_Navigation 54 | 55 | anchors.fill: parent 56 | 57 | interactive: false 58 | 59 | model: [ 60 | { 61 | image : "img/icon-tables.png" 62 | } 63 | ] 64 | 65 | delegate: Rectangle { 66 | width: ListView.view.width 67 | height: width 68 | color: ListView.view.currentIndex === index ? Qt.darker(theme.asphalt) : theme.asphalt 69 | Utils.BaseIcon { 70 | anchors.centerIn: parent 71 | width: 32 72 | source: modelData.image 73 | color: theme.lightblue 74 | } 75 | } 76 | } 77 | } 78 | 79 | Rectangle { 80 | id: _Rectangle_Tables 81 | anchors.top: _Header.bottom 82 | anchors.bottom: parent.bottom 83 | anchors.left: _Rectangle_Navigation.right 84 | width: 200 85 | color: theme.white 86 | 87 | Utils.AccentRight { 88 | MouseArea { 89 | anchors.fill: parent 90 | cursorShape: Qt.SizeHorCursor 91 | property int _x : 0 92 | onPressed: { 93 | _x = mouse.x 94 | } 95 | onPositionChanged: { 96 | _Rectangle_Tables.width = Math.max(200, _Rectangle_Tables.width + mouse.x - _x) 97 | } 98 | } 99 | color: "transparent" 100 | width: 4 101 | z: 3 102 | } 103 | 104 | Utils.Blurtangle { 105 | id: _Item_TablesHeader 106 | anchors.top: parent.top 107 | height: 30 108 | width: parent.width 109 | attachTo: _ListView_Tables 110 | color: "#ffffff" 111 | 112 | Utils.AccentBottom { 113 | color: theme.headerAccent 114 | } 115 | 116 | Views.Label { 117 | anchors.fill: parent 118 | anchors.margins: 8 119 | text: qsTr("Tables") 120 | font.capitalization: Font.AllUppercase 121 | font.bold: true 122 | font.pixelSize: 12 123 | } 124 | z: 2 125 | } 126 | 127 | ListView { 128 | id: _ListView_Tables 129 | 130 | anchors.top: parent.top 131 | anchors.left: parent.left 132 | anchors.right: parent.right 133 | anchors.bottom: parent.bottom 134 | 135 | currentIndex: -1 136 | z: 1 137 | 138 | function getAllTableRowCounts() 139 | { 140 | 141 | } 142 | 143 | onCurrentIndexChanged: { 144 | 145 | _TextArea_Query.text = "SELECT * FROM " + currentItem.dataModel.name 146 | _Button_Query.clicked() 147 | } 148 | 149 | header: Item { 150 | width: 1 151 | height: _Item_TablesHeader.height 152 | } 153 | 154 | 155 | delegate: Rectangle { 156 | property variant dataModel : modelData || {} 157 | width: ListView.view.width 158 | height: 30 159 | color: ListView.view.currentIndex === index ? theme.dirtywhite : "transparent" 160 | Row { 161 | anchors.left: parent.left 162 | anchors.leftMargin: 15 163 | anchors.right: _Rectangle_Count.left 164 | anchors.rightMargin: 10 165 | anchors.verticalCenter: parent.verticalCenter 166 | spacing: 6 167 | Rectangle { 168 | width: 12 169 | height: 12 170 | radius: 6 171 | color: theme.blue 172 | anchors.verticalCenter: _Text_TableName.verticalCenter 173 | } 174 | Views.Label { 175 | id: _Text_TableName 176 | height: 20 177 | text: modelData.name 178 | } 179 | } 180 | Rectangle { 181 | id: _Rectangle_Count 182 | anchors.right: parent.right 183 | anchors.rightMargin: 10 184 | anchors.verticalCenter: parent.verticalCenter 185 | width: Math.max(12, _Text_TableRowCount.paintedWidth + 10) 186 | height: 12 187 | radius: 6 188 | color: theme.lightgray 189 | Views.Label { 190 | id: _Text_TableRowCount 191 | anchors.centerIn: parent 192 | font.pixelSize: 10 193 | font.bold: true 194 | text: modelData.count || "" 195 | color: theme.text 196 | } 197 | visible: _Text_TableRowCount.text !== "" 198 | } 199 | MouseArea { 200 | anchors.fill: parent 201 | onClicked: parent.ListView.view.currentIndex = index 202 | } 203 | } 204 | } 205 | } 206 | 207 | Item { 208 | id: _Item_Container 209 | anchors.top: _Header.bottom 210 | anchors.left: _Rectangle_Tables.right 211 | anchors.right: _Item_RowDetail.left 212 | anchors.bottom: parent.bottom 213 | 214 | TableView { 215 | id: _TableView 216 | property string tableViewColumnBuilder : "import QtQuick 2.3; import QtQuick.Controls 1.0; import QtQuick.Layouts 1.0; TableViewColumn { role: \"%roleName%\"; title: role; width: 100 }" 217 | anchors.top: parent.top 218 | anchors.left: parent.left 219 | anchors.right: parent.right 220 | anchors.bottom: _TextArea_Query.top 221 | 222 | onClicked: { 223 | _TextArea_RowDetail.text = JSON.stringify(_TableView.model[row], null, 2) 224 | } 225 | 226 | onDoubleClicked: { 227 | $.saveTextToClipboard(JSON.stringify(_TableView.model[row], null, 2)) 228 | } 229 | 230 | function prepareAndSetModel(result) 231 | { 232 | if(result.length === 0) 233 | { 234 | _TableView.model = [] 235 | return 236 | } 237 | 238 | var columns = Object.keys(result[0]) 239 | 240 | for(var i = _TableView.columnCount - 1; i >= 0; i--) 241 | { 242 | _TableView.removeColumn(i) 243 | } 244 | 245 | for(var j = 0; j < columns.length; j++) 246 | { 247 | var c = Qt.createQmlObject(_TableView.tableViewColumnBuilder.replace(/%roleName%/g, columns[j]), _TableView, "") 248 | _TableView.addColumn(c) 249 | } 250 | 251 | _TableView.model = result 252 | 253 | _TableView.resizeColumnsToContents() 254 | } 255 | 256 | style: TableViewStyle { 257 | id: _TableViewStyle 258 | backgroundColor: theme.white 259 | alternateBackgroundColor: theme.superlightgray 260 | textColor: theme.text 261 | highlightedTextColor: theme.black 262 | corner: Item { } 263 | frame: Item { } 264 | rowDelegate: Rectangle { 265 | height: 40 266 | color: styleData.alternate ? _TableViewStyle.alternateBackgroundColor : _TableViewStyle.backgroundColor 267 | Utils.AccentBottom { 268 | color: theme.headerAccent 269 | opacity: 0.3 270 | } 271 | } 272 | headerDelegate: Rectangle { 273 | color: theme.white 274 | height: 20 275 | clip: true 276 | Utils.AccentBottom { 277 | color: theme.headerAccent 278 | } 279 | Utils.AccentRight { 280 | color: theme.headerAccent 281 | opacity: 0.3 282 | } 283 | Text { 284 | width: parent.width 285 | anchors.left: parent.left 286 | anchors.leftMargin: 4 287 | anchors.verticalCenter: parent.verticalCenter 288 | font.bold: true 289 | font.pixelSize: 10 290 | elide: Text.ElideRight 291 | color: theme.text 292 | text: String(styleData.value) 293 | } 294 | } 295 | } 296 | 297 | itemDelegate: Item { 298 | width: 100 299 | clip: true 300 | 301 | Utils.AccentRight { 302 | color: theme.headerAccent 303 | opacity: 0.3 304 | } 305 | 306 | Views.Label { 307 | id: _Text 308 | anchors.left: parent.left 309 | anchors.leftMargin: 8 310 | anchors.verticalCenter: parent.verticalCenter 311 | color: styleData.textColor 312 | elide: Text.ElideRight 313 | wrapMode: Text.NoWrap 314 | width: parent.width 315 | text: String(styleData.value) 316 | } 317 | } 318 | } 319 | 320 | TextArea { 321 | id: _TextArea_Query 322 | anchors.horizontalCenterOffset: 0 323 | anchors.horizontalCenter: parent.horizontalCenter 324 | anchors.bottom: parent.bottom 325 | width: parent.width 326 | height: 150 327 | font.family: "Courier" 328 | style: TextAreaStyle { 329 | frame: Item { } 330 | } 331 | 332 | Button { 333 | id: _Button_Query 334 | anchors.right: parent.right 335 | anchors.rightMargin: 15 336 | anchors.bottom: parent.bottom 337 | anchors.bottomMargin: 15 338 | text: qsTr("Execute Query") 339 | onClicked: { 340 | _SQLite.executeQuery(_TextArea_Query.text) 341 | } 342 | } 343 | } 344 | } 345 | 346 | Item { 347 | id: _Item_RowDetail 348 | width: 400 349 | anchors.top: _Header.bottom 350 | anchors.bottom: parent.bottom 351 | anchors.right: parent.right 352 | 353 | TextArea { 354 | id: _TextArea_RowDetail 355 | anchors.fill: parent 356 | style: TextAreaStyle { 357 | frame: Item { } 358 | } 359 | font.family: "Courier" 360 | } 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/AccentBottom.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Rectangle { 4 | height: 1 5 | anchors.left: parent.left; anchors.right: parent.right 6 | anchors.bottom: parent.bottom 7 | color: "red" 8 | smooth: true 9 | } 10 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/AccentLeft.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Rectangle { 4 | anchors.top: parent.top 5 | anchors.bottom: parent.bottom 6 | anchors.left: parent.left 7 | width: 1 8 | color: "red" 9 | } 10 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/AccentRight.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Rectangle { 4 | anchors.top: parent.top 5 | anchors.bottom: parent.bottom 6 | anchors.right: parent.right 7 | width: 1 8 | color: "red" 9 | } 10 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/AccentTop.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Rectangle { 4 | height: 1 5 | anchors.left: parent.left; anchors.right: parent.right 6 | anchors.top: parent.top 7 | color: "red" 8 | smooth: true 9 | } 10 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/ActionSheet.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import "../views" as Views 3 | 4 | 5 | Item { 6 | id: root 7 | 8 | signal itemClicked(variant itemObject) 9 | property alias model : _Repeater.model 10 | 11 | 12 | function open() 13 | { 14 | _StateGroup_Selector.state = "open" 15 | } 16 | 17 | function close() 18 | { 19 | _StateGroup_Selector.state = "" 20 | } 21 | 22 | anchors.fill: parent 23 | 24 | Rectangle { 25 | id: _Fulltone 26 | anchors.fill: parent 27 | color: "#222222" 28 | opacity: 0.0 29 | visible: opacity>0 30 | ClickGuard { 31 | onClicked: { 32 | root.close() 33 | } 34 | } 35 | } 36 | 37 | Item { 38 | id: _Item_SelectorPanel 39 | anchors.left: parent.left 40 | anchors.right: parent.right 41 | anchors.rightMargin: __theme.dp(16) 42 | anchors.leftMargin: anchors.rightMargin 43 | anchors.bottom: parent.bottom 44 | height: __theme.dp(300) 45 | Rectangle { 46 | id: _Choose 47 | radius: 5 48 | anchors.left: parent.left 49 | anchors.right: parent.right 50 | anchors.bottom: _Cancel.top 51 | anchors.bottomMargin: __theme.dp(16) 52 | height: _Column.height 53 | Column { 54 | id: _Column 55 | width: parent.width 56 | height: childrenRect.height 57 | Repeater { 58 | id: _Repeater 59 | Item { 60 | anchors.left: parent.left 61 | anchors.right: parent.right 62 | height: __theme.dp(87) 63 | Views.Label { 64 | anchors.fill: parent 65 | verticalAlignment: Text.AlignVCenter 66 | horizontalAlignment: Text.AlignHCenter 67 | text: modelData.text 68 | } 69 | ClickGuard { 70 | onClicked: { 71 | log.jsonDump(root, modelData) 72 | root.close() 73 | root.itemClicked(modelData) 74 | } 75 | } 76 | Rectangle { 77 | color: "#dddddd" 78 | anchors.left: parent.left 79 | anchors.right: parent.right 80 | height: 1 81 | anchors.bottom: parent.bottom 82 | visible: index !== root.model.length 83 | } 84 | } 85 | } 86 | } 87 | } 88 | Rectangle { 89 | id: _Cancel 90 | radius: __theme.dp(5) 91 | anchors.left: parent.left 92 | anchors.right: parent.right 93 | anchors.bottom: parent.bottom 94 | anchors.bottomMargin: __theme.dp(17) 95 | height: __theme.dp(88) 96 | Views.Label { 97 | anchors.fill: parent 98 | verticalAlignment: Text.AlignVCenter 99 | horizontalAlignment: Text.AlignHCenter 100 | text: qsTr("Cancel") 101 | font.weight: Font.DemiBold 102 | } 103 | } 104 | visible: false 105 | anchors.bottomMargin: -1*height 106 | StateGroup { 107 | id: _StateGroup_Selector 108 | states: [ 109 | State { 110 | name: "open" 111 | PropertyChanges { 112 | target: _Item_SelectorPanel 113 | anchors.bottomMargin: 0 114 | visible: true 115 | } 116 | PropertyChanges { 117 | target: _Fulltone 118 | opacity: 0.25 119 | } 120 | } 121 | ] 122 | transitions: [ 123 | Transition { 124 | from: "" 125 | to: "open" 126 | SequentialAnimation { 127 | PropertyAction { 128 | targets: [_Item_SelectorPanel, _Fulltone]; 129 | property: "visible" 130 | } 131 | ParallelAnimation { 132 | NumberAnimation { 133 | target: _Fulltone 134 | property: "opacity" 135 | duration: 200 136 | easing.type: Easing.OutCubic 137 | } 138 | NumberAnimation { 139 | target: _Item_SelectorPanel 140 | property: "anchors.bottomMargin" 141 | duration: 250 142 | easing.type: Easing.OutCubic 143 | } 144 | } 145 | } 146 | }, 147 | Transition { 148 | from: "open" 149 | to: "" 150 | SequentialAnimation { 151 | ParallelAnimation { 152 | NumberAnimation { 153 | target: _Fulltone 154 | property: "opacity" 155 | duration: 150 156 | easing.type: Easing.OutCubic 157 | } 158 | NumberAnimation { 159 | target: _Item_SelectorPanel 160 | property: "anchors.bottomMargin" 161 | duration: 200 162 | easing.type: Easing.OutCubic 163 | } 164 | } 165 | PropertyAction { 166 | targets: [_Item_SelectorPanel, _Fulltone]; 167 | property: "visible" 168 | } 169 | } 170 | } 171 | ] 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/AsyncImage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Image { 4 | property double finalOpacity : 1.0 5 | property bool ready : status===Image.Ready 6 | cache: false; 7 | asynchronous: true 8 | opacity: 0 9 | onStatusChanged: { 10 | if(status == Image.Ready) 11 | { 12 | opacity = finalOpacity; 13 | } 14 | } 15 | Behavior on opacity { NumberAnimation{ duration: 150; } } 16 | } 17 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/BaseButtonTheme.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | QtObject { 4 | id: root 5 | property color backgroundDefaultColor : "#000000" 6 | property color backgroundPressedColor : backgroundDefaultColor 7 | property color backgroundActiveColor : backgroundDefaultColor 8 | property color iconDefaultColor : "#ffffff" 9 | property color iconPressedColor : iconDefaultColor 10 | property color iconActiveColor : iconDefaultColor 11 | 12 | property color borderColor : "#111111" 13 | 14 | property int colorAnimationDuration : 50 15 | } 16 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/BaseIcon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtGraphicalEffects 1.0 as QGE 3 | 4 | Item { 5 | property alias source: _Image.source 6 | property alias color : _ColorOverlay.color 7 | // anchors.centerIn: parent 8 | width: 44 9 | height: _Image.height 10 | layer.smooth: true 11 | layer.enabled: true 12 | Image { 13 | id: _Image 14 | anchors.left: parent.left 15 | anchors.right: parent.right 16 | fillMode: Image.PreserveAspectFit 17 | sourceSize.width: 2*width; sourceSize.height: 2*height 18 | smooth: true 19 | visible: false 20 | } 21 | QGE.ColorOverlay { 22 | id: _ColorOverlay 23 | anchors.fill: parent 24 | source: _Image 25 | color: "#000000" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/BaseTabBarPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Loader { 4 | id: root 5 | signal opened 6 | signal closed 7 | 8 | property alias controller : _Connections.target 9 | property bool loadAsNeeded : false 10 | property bool loaded : false 11 | property variant contentComponent 12 | 13 | anchors.fill: parent 14 | visible: false 15 | 16 | ClickGuard { } 17 | 18 | Connections { 19 | id: _Connections 20 | target: null 21 | ignoreUnknownSignals: true 22 | onHideAllPages: { 23 | console.log("hide all pages") 24 | root.closed() 25 | root.visible = false 26 | } 27 | } 28 | 29 | onLoaded: { 30 | loaded = true 31 | _show() 32 | } 33 | 34 | function show() 35 | { 36 | if(!loaded && loadAsNeeded) 37 | { 38 | // load the component 39 | if(contentComponent) 40 | sourceComponent = contentComponent 41 | } else 42 | { 43 | _show() 44 | } 45 | } 46 | 47 | function _show() 48 | { 49 | root.opened() 50 | visible = true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/BaseWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.1 3 | 4 | Window { 5 | property bool showFills : false 6 | property variant activeObject 7 | visible: true 8 | } 9 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Blurtangle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtGraphicalEffects 1.0 3 | Item { 4 | id: root 5 | property color color: "#222222" 6 | property Item attachTo 7 | property alias blurOffset: _ShaderEffectSource.yOffset 8 | property alias radius: _Rectangle.radius 9 | Rectangle { 10 | id: _Rectangle 11 | anchors.fill: _FastBlur 12 | color: root.color 13 | } 14 | FastBlur { 15 | id: _FastBlur 16 | height: root.height 17 | width: root.width 18 | radius: 100 19 | opacity: 0.30 20 | source: ShaderEffectSource { 21 | id: _ShaderEffectSource 22 | property int yOffset : 0 23 | sourceItem: root.attachTo ? root.attachTo : _Dummy 24 | sourceRect: Qt.rect(0,yOffset,root.width,_FastBlur.height) 25 | } 26 | } 27 | Item { 28 | id: _Dummy 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/ClickGuard.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | MouseArea { anchors.fill: parent } 4 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Config.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | id: root 5 | 6 | property string packageName : "st.app.vegguide" 7 | property string identifier : "VegGuide" 8 | property string version : "0.0.1" 9 | property string email : "support+vegguide@app.st" 10 | 11 | property url apiTest : "http://appstreet.local/vg/test.json" 12 | property url apiTestEngland : "http://www.vegguide.org/search/by-lat-long/51.5033630,-0.1276250" 13 | 14 | property string latitude : _Location.latitude 15 | property string longitude : _Location.longitude 16 | 17 | property url apiNearby : "http://www.vegguide.org/search/by-lat-long/%lat,%long".replace(/%lat/g, latitude).replace(/%long/g, longitude) 18 | 19 | 20 | property variant apiRegion : { 21 | "url" : "http://localhost/vg/all.json", 22 | "headers" : { 23 | "Type" : "application/vnd.vegguide.org-regions+json" 24 | } 25 | } 26 | 27 | property url apiRecent : "http://appstreet.local/vg/recent.xml" 28 | 29 | // https://developers.google.com/maps/documentation/ios/urlscheme 30 | // https://developer.apple.com/library/ios/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html 31 | property variant maps_ios : [ 32 | "comgooglemaps://?q=", 33 | "http://maps.apple.com/?q=" 34 | ] 35 | 36 | property variant maps_android : [ 37 | "geo:$coordinates$", 38 | ] 39 | 40 | property variant maps : maps_ios 41 | 42 | function openMaps(address) 43 | { 44 | console.log("openMaps") 45 | console.log("address = " + address) 46 | /// TODO: Check for reachability 47 | // If network is available, use the address, 48 | // if not, use the lat/long coordinates 49 | for(var i = 0; i < maps.length; i++) 50 | { 51 | if(Qt.openUrlExternally(maps[i]+""+address)) 52 | break; 53 | } 54 | } 55 | 56 | StateGroup { 57 | states: [ 58 | State { 59 | when: Qt.platform.os === "ios" 60 | PropertyChanges { 61 | target: root 62 | apiRecent: "http://www.vegguide.org/site/recent.rss" 63 | apiTest: apiTestEngland 64 | apiRegion: { 65 | "url" : "http://www.vegguide.org/", 66 | "headers" : { 67 | "Type" : "application/vnd.vegguide.org-regions+json" 68 | } 69 | } 70 | } 71 | } 72 | ] 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Fill.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | id: root 5 | anchors.fill: parent 6 | color: "#00FEAA" 7 | opacity: 0.5 8 | visible: superRoot.showFills 9 | border { width: this === superRoot.activeObject ? 4 : 0; color: "#ffffff" } 10 | Rectangle { 11 | anchors.fill: parent 12 | anchors.margins: 4 13 | color: "transparent" 14 | border { width: root.border.width; color: "#000000" } 15 | } 16 | MouseArea { 17 | anchors.fill: parent 18 | onClicked: superRoot.activeObject = root 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/GestureArea.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | MouseArea { 4 | id: root 5 | property int acceptableSwipeDistance : 25; 6 | 7 | signal swipeLeft; 8 | signal swipeRight; 9 | signal swipeUp; 10 | signal swipeDown; 11 | signal tap; 12 | 13 | signal press; 14 | signal release; 15 | 16 | 17 | // private 18 | property int pressPosX; 19 | property int pressPosY; 20 | property int releasePosX; 21 | property int releasePosY; 22 | 23 | onPressed: { 24 | root.press(); 25 | pressPosX = mouse.x; 26 | pressPosY = mouse.y; 27 | } 28 | onReleased: { 29 | root.release(); 30 | releasePosX = mouse.x 31 | releasePosY = mouse.y 32 | var deltaX = releasePosX - pressPosX; 33 | var deltaY = releasePosY - pressPosY; 34 | if(deltaX < -1*acceptableSwipeDistance) 35 | { 36 | root.swipeLeft(); 37 | } 38 | else if(deltaX > acceptableSwipeDistance) 39 | { 40 | root.swipeRight(); 41 | } else if(deltaY < -1*acceptableSwipeDistance) 42 | { 43 | root.swipeUp(); 44 | } else if(deltaY > acceptableSwipeDistance) 45 | { 46 | root.swipeDown(); 47 | } 48 | else 49 | { 50 | root.tap(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/HorizontalSpacer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | Item { 3 | height: parent.height 4 | width: 10 5 | Fill { color: "black" } 6 | } 7 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Log.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | QtObject { 4 | 5 | function notice(rootObject, message) 6 | { 7 | console.log(message) 8 | } 9 | 10 | function jsonDump(rootObject, jsonObject) 11 | { 12 | console.log(JSON.stringify(jsonObject, null, 2)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Model.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import st.app.models 1.0 as Models 3 | 4 | Models.SQLiteDatabase { 5 | id: root 6 | source: "" 7 | 8 | // API data 9 | property int apiStatus : Loader.Null 10 | 11 | property variant entries 12 | 13 | function reload() 14 | { 15 | apiStatus = Loader.Loading 16 | 17 | if(Qt.platform.os === "ios") 18 | { 19 | webRequest(config.apiNearby, function(response, request, requestUrl) { 20 | if(response) 21 | { 22 | apiStatus = Loader.Ready 23 | entries = response.entries 24 | } 25 | }) 26 | } 27 | else 28 | { 29 | webRequest(config.apiTest, function(response, request, requestUrl) { 30 | if(response) 31 | { 32 | apiStatus = Loader.Ready 33 | entries = response.entries 34 | } 35 | }) 36 | } 37 | } 38 | 39 | function load(urlObject, callback) 40 | { 41 | webRequest(urlObject.url, callback) 42 | } 43 | 44 | // Temporary model retriever 45 | 46 | function webRequest(requestUrl, callback){ 47 | console.log("url="+requestUrl) 48 | var request = new XMLHttpRequest(); 49 | request.onreadystatechange = function() { 50 | var response; 51 | if(request.readyState === XMLHttpRequest.DONE) { 52 | if(request.status === 200) { 53 | response = JSON.parse(request.responseText); 54 | } else { 55 | console.log("Server: " + request.status + "- " + request.statusText); 56 | apiStatus = Loader.Error 57 | response = "" 58 | } 59 | callback(response, request, requestUrl) 60 | } 61 | } 62 | request.open("GET", requestUrl, true); // only async supported 63 | request.setRequestHeader("Accept", "application/json") 64 | request.setRequestHeader("User-Agent", [config.identifier, config.version, config.packageName, config.email].join(" ")) 65 | request.send(); 66 | } 67 | 68 | // Component.onCompleted: 69 | } 70 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Platform.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | SafeLoader { 4 | id: root 5 | states: [ 6 | State { 7 | when: Qt.platform.os === "ios" 8 | PropertyChanges { 9 | target: root 10 | source: "PlatformiOS.qml" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/PlatformiOS.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import st.app.platform 1.0 3 | PlatformiOS { 4 | // signal applicationDidBecomeActive(); 5 | // signal applicationWillResignActive(); 6 | // signal applicationDidEnterBackground(); 7 | // signal applicationWillEnterForeground(); 8 | // signal applicationDidFinishLaunching(); 9 | // signal applicationDidReceiveMemoryWarning(); 10 | // signal applicationWillTerminate(); 11 | statusBarStyle: PlatformiOS.StatusBarStyleLightContent 12 | function setStatusBarStyleLight() 13 | { 14 | statusBarStyle = PlatformiOS.StatusBarStyleLightContent 15 | } 16 | function setStatusBarStyleDefault() 17 | { 18 | statusBarStyle = PlatformiOS.StatusBarStyleDefault 19 | } 20 | function hideStatusBar() 21 | { 22 | setStatusBarVisible(false) 23 | } 24 | function showStatusBar() 25 | { 26 | setStatusBarVisible(true) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/RootItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Item { 4 | property bool showFills : false 5 | property variant activeObject 6 | } 7 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/SafeLoader.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Loader { 4 | signal callSuccess(string fn) 5 | signal callFailed(string fn) 6 | property bool isReady : status === Loader.Ready && item 7 | function call(fn) 8 | { 9 | console.log("call - " + fn) 10 | if(status === Loader.Ready 11 | && item 12 | && typeof item[fn] !== "undefined") 13 | { 14 | item[fn]() 15 | callSuccess(fn) 16 | } else 17 | { 18 | callFailed(fn) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/TabBarButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | import "../utils" as Utils 3 | Rectangle { 4 | id: _root 5 | 6 | signal clicked 7 | property alias icon : _BaseIcon.source 8 | property alias iconWidth : _BaseIcon.width 9 | property alias iconObject : _BaseIcon 10 | readonly property bool isActive : root.activeButton === this 11 | 12 | property variant theme : root.theme 13 | 14 | width: 120 15 | height: 100 16 | border { width: 1; color: theme.borderColor } 17 | color: !isActive ? (!_MouseArea.pressed ? 18 | theme.backgroundDefaultColor : 19 | theme.backgroundPressedColor) : theme.backgroundActiveColor 20 | 21 | Utils.BaseIcon { 22 | id: _BaseIcon 23 | anchors.centerIn: parent 24 | width: 54 25 | color: !isActive ? (!_MouseArea.pressed ? 26 | theme.iconDefaultColor : 27 | theme.iconPressedColor) : theme.iconActiveColor 28 | transformOrigin: Item.Center 29 | Fill { } 30 | } 31 | 32 | MouseArea { 33 | id: _MouseArea 34 | anchors.fill: parent 35 | onClicked: _root.clicked() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/TabBarController.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import "../views/" as Views 3 | import "../utils/" as Utils 4 | Item { 5 | id: root 6 | 7 | signal tabClicked(variant tabObject) 8 | signal hideAllPages 9 | 10 | property bool enabled : true 11 | property variant tabBarModel : [] 12 | property variant activeButton : _Repeater_TabBar.count > 0 ? _Repeater_TabBar.itemAt(0) : { } 13 | 14 | property alias theme : _BaseButtonTheme 15 | 16 | Utils.BaseButtonTheme { 17 | id: _BaseButtonTheme 18 | backgroundDefaultColor: "#f3f3f3" 19 | backgroundPressedColor: "#e0e0e0" 20 | backgroundActiveColor: "#81c343" 21 | iconDefaultColor: "#222222" 22 | iconPressedColor: "#111111" 23 | iconActiveColor: "#ffffff" 24 | borderColor: "transparent" 25 | } 26 | 27 | Row { 28 | id: _Row_TabBar 29 | anchors.left: parent.left 30 | anchors.right: parent.right 31 | anchors.bottom: parent.bottom 32 | height: 100 33 | Repeater { 34 | id: _Repeater_TabBar 35 | model: root.tabBarModel 36 | property variant responder : root.activeButton 37 | delegate: TabBarButton { 38 | property variant dataModel : modelData 39 | width: Math.floor(root.width / root.tabBarModel.length) 40 | icon: modelData.icon 41 | onClicked: { 42 | root.tabClicked(modelData) 43 | root.activeButton = this 44 | root.hideAllPages() 45 | try { 46 | eval(modelData.sourceComponent).show() 47 | } catch (ex) 48 | { 49 | console.trace() 50 | console.error(ex) 51 | } 52 | } 53 | } 54 | Component.onCompleted: if(count > 0) itemAt(0).clicked() 55 | } 56 | } 57 | 58 | Utils.ClickGuard { 59 | visible: !root.enabled 60 | } 61 | 62 | function showView(sourceComponent) 63 | { 64 | for(var i = 0; i < _Repeater_TabBar.count; i++) 65 | { 66 | var obj = _Repeater_TabBar.itemAt(i) 67 | if(obj.dataModel.sourceComponent === sourceComponent) 68 | { 69 | _Repeater_TabBar.itemAt(i).clicked() 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/Theme.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQml 2.2 3 | 4 | Item { 5 | id: root 6 | property string fontFamily : "" 7 | 8 | Instantiator { 9 | asynchronous: false 10 | model: [300, 500, 700, 900, 1000] 11 | delegate: FontLoader { 12 | id: _FontLoader 13 | source: "../assets/MuseoSansRounded-" + modelData + ".otf" 14 | } 15 | onCountChanged: { 16 | if(count === 5) 17 | root.fontFamily = "Museo Sans Rounded" 18 | } 19 | } 20 | 21 | property color colorBaseText : "#252528" 22 | property color colorBaseTextLighter : "#424249" 23 | property color vgColorDarkGreen : "#6c9d2a" 24 | property color vgColorGreen : "#8dc73f" 25 | property color vgColorLightGreen : "#abd03b" 26 | property color vgColorGray : "#606061" 27 | property color vgColorDarkRed : "#9b241f" 28 | property color vgColorLightRed : "#bf3f23" 29 | property color vgColorBeetPurple : "#9D4A79" 30 | 31 | property color lightGrey : "#f7f7f7" 32 | property color lightGreyDarker : "#f3f3f3" 33 | property color lightGreyAccent : "#d1d1d0" 34 | property color lightGreyAccentSecondary : "#eeeeee" 35 | 36 | property int headerHeight : dp(128) 37 | property int statusBarHeight : dp(40) 38 | 39 | property int listLeftRightMargins : dp(40) 40 | 41 | function shadeColor(c, percent) { 42 | var color = c.toString() 43 | var R = parseInt(color.substring(1,3),16); 44 | var G = parseInt(color.substring(3,5),16); 45 | var B = parseInt(color.substring(5,7),16); 46 | 47 | R = parseInt(R * (100 + percent) / 100); 48 | G = parseInt(G * (100 + percent) / 100); 49 | B = parseInt(B * (100 + percent) / 100); 50 | 51 | R = (R<255)?R:255; 52 | G = (G<255)?G:255; 53 | B = (B<255)?B:255; 54 | 55 | var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16)); 56 | var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16)); 57 | var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16)); 58 | 59 | return "#"+RR+GG+BB; 60 | } 61 | 62 | function dp(px) 63 | { 64 | return px 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/utils/VerticalSpacer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Item { 4 | width: parent.width 5 | height: 10; 6 | Fill { color: "black" } 7 | } 8 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/views/AppWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.2 3 | import QtQuick.Dialogs 1.0 4 | import Qt.labs.settings 1.0 5 | 6 | ApplicationWindow { 7 | id: root 8 | 9 | property url activeDatabase 10 | 11 | objectName: "mainWindow" 12 | visible: true 13 | width: 1424 14 | height: 768 15 | title: qsTr("SQLite Editor") 16 | color: "transparent" 17 | flags: Qt.FramelessWindowHint 18 | 19 | Rectangle { 20 | anchors.fill: parent 21 | color: theme.lightgray 22 | radius: theme.windowRadius 23 | } 24 | 25 | Settings { 26 | property alias url: root.activeDatabase 27 | } 28 | 29 | property alias fileDialog : _FileDialog 30 | FileDialog { 31 | id: _FileDialog 32 | title: "Please choose a file" 33 | nameFilters: [ "SQLite3 Databases (*.db *.sqlite3 *.sqlite *.sql3)", "All files (*)" ] 34 | onAccepted: { 35 | activeDatabase = fileUrl 36 | } 37 | } 38 | 39 | menuBar: MenuBar { 40 | Menu { 41 | title: qsTr("File") 42 | MenuItem { 43 | text: qsTr("&Open") 44 | shortcut: StandardKey.Open 45 | onTriggered: { 46 | fileDialog.open() 47 | } 48 | } 49 | MenuItem { 50 | text: qsTr("Exit") 51 | onTriggered: Qt.quit(); 52 | } 53 | } 54 | } 55 | 56 | property alias theme : _Theme 57 | 58 | Theme { 59 | id: _Theme 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/views/Header.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Controls 1.2 3 | import st.app 1.0 as AppStreet 4 | 5 | Rectangle { 6 | anchors.left: parent.left 7 | anchors.right: parent.right 8 | 9 | height: 40 10 | 11 | gradient: theme.headerGradient 12 | radius: theme.windowRadius 13 | 14 | MouseArea { 15 | property int _x 16 | property int _y 17 | 18 | anchors.fill: parent 19 | 20 | onPressed: { 21 | _x = mouse.x 22 | _y = mouse.y 23 | } 24 | onPositionChanged: { 25 | superRoot.x = superRoot.x + mouse.x - _x 26 | superRoot.y = superRoot.y + mouse.y - _y 27 | } 28 | } 29 | 30 | Row { 31 | anchors.verticalCenter: parent.verticalCenter 32 | anchors.left: parent.left 33 | anchors.leftMargin: 10 34 | 35 | height: 14 36 | spacing: 8 37 | 38 | YosemiteButton { 39 | color: theme.yosemite.exitColor 40 | onClicked: { 41 | Qt.quit() 42 | } 43 | } 44 | 45 | YosemiteButton { 46 | color: theme.yosemite.minimizeColor 47 | onClicked: { 48 | superRoot.showMinimized() 49 | } 50 | } 51 | 52 | YosemiteButton { 53 | color: theme.yosemite.expandColor 54 | onClicked: { 55 | superRoot.showFullScreen() 56 | } 57 | } 58 | } 59 | 60 | Label { 61 | anchors.centerIn: parent 62 | 63 | width: 500 64 | 65 | horizontalAlignment: Text.AlignHCenter 66 | color: theme.text 67 | styleColor: theme.white 68 | style: Text.Raised 69 | text: [superRoot.title, superRoot.activeDatabase.toString().replace("file://", "")].filter(Boolean).join(" - ") 70 | } 71 | 72 | Row { 73 | anchors.right: parent.right 74 | anchors.rightMargin: 10 75 | anchors.verticalCenter: parent.verticalCenter 76 | 77 | layoutDirection: Qt.RightToLeft 78 | spacing: 10 79 | 80 | Button { 81 | height: 30 82 | text: "Open Database" 83 | onClicked: { 84 | fileDialog.open() 85 | } 86 | } 87 | 88 | Button { 89 | height: 30 90 | text: "Resize Contents" 91 | onClicked: { 92 | _TableView.visible = false 93 | _TableView.resizeColumnsToContents() 94 | _TableView.visible = true 95 | } 96 | } 97 | 98 | Button { 99 | height: 30 100 | text: "All Table Counts" 101 | onClicked: { 102 | _ListView_Tables.getAllTableRowCounts() 103 | } 104 | } 105 | 106 | Item { 107 | width: 30 108 | height: 30 109 | 110 | visible: _SQLite.status != AppStreet.SQLite.Ready 111 | 112 | Canvas { 113 | id: _Canvas_Spinner 114 | anchors.fill: parent 115 | // renderTarget: Canvas.FramebufferObject 116 | // renderStrategy: Canvas.Threaded 117 | property int centerX : width/2 118 | property int centerY : height/2 119 | property int radius : width / 4 120 | property double degree : 290 121 | 122 | 123 | onPaint: { 124 | var ctx = getContext("2d") 125 | ctx.strokeStyle = theme.text 126 | ctx.beginPath() 127 | ctx.lineWidth = 2 128 | ctx.arc(centerX, centerY, radius, 0, (degree * Math.PI / 180)); 129 | ctx.stroke() 130 | } 131 | 132 | // layer.enabled: true 133 | // layer.smooth: true 134 | 135 | opacity: 0.5 136 | 137 | SequentialAnimation { 138 | loops: Animation.Infinite 139 | running: visible 140 | NumberAnimation { 141 | target: _Canvas_Spinner 142 | property: "rotation" 143 | from: 0; to: 360; 144 | duration: 1200 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | 152 | Rectangle { 153 | anchors.left: parent.left 154 | anchors.right: parent.right 155 | anchors.bottom: parent.bottom 156 | 157 | height: 1 158 | color: theme.headerAccent 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/views/Label.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | 3 | Text { 4 | color: theme.text 5 | } 6 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/views/Theme.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | QtObject { 4 | property int windowRadius : 8 5 | 6 | property color red : "#ef4f44" 7 | property color blue : "#5cacde" 8 | property color lightblue : "#edf2f7" 9 | property color asphalt : "#373f52" 10 | property color white : "#ffffff" 11 | property color lightgray : "#eeeeee" 12 | property color dirtywhite : "#edeef1" 13 | property color gray : "#8d9ca4" 14 | property color text : "#434b4f" 15 | property color black : "#111111" 16 | property color superlightgray : "#F5F5F5" 17 | 18 | property Gradient headerGradient : Gradient { 19 | GradientStop { 20 | position: 0.0 21 | color: "#faf9fa" 22 | } 23 | GradientStop { 24 | position: 1.0 25 | color: "#f3f2f2" 26 | } 27 | } 28 | 29 | property color headerAccent : "#dddddd" 30 | 31 | property QtObject z : QtObject { 32 | property int header : 10 33 | } 34 | 35 | property QtObject yosemite : QtObject { 36 | property color exitColor : "#fc605b" 37 | property color minimizeColor : "#fdbc40" 38 | property color expandColor : "#34c849" 39 | } 40 | 41 | function shadeColor(c, percent) { 42 | var color = c.toString() 43 | var R = parseInt(color.substring(1,3),16); 44 | var G = parseInt(color.substring(3,5),16); 45 | var B = parseInt(color.substring(5,7),16); 46 | 47 | R = parseInt(R * (100 + percent) / 100); 48 | G = parseInt(G * (100 + percent) / 100); 49 | B = parseInt(B * (100 + percent) / 100); 50 | 51 | R = (R<255)?R:255; 52 | G = (G<255)?G:255; 53 | B = (B<255)?B:255; 54 | 55 | var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16)); 56 | var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16)); 57 | var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16)); 58 | 59 | return "#"+RR+GG+BB; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/SQLiteEditor/qml/views/YosemiteButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | id: root 5 | 6 | property color buttonColor 7 | signal clicked 8 | 9 | width: 14 10 | height: width 11 | radius: width / 2 12 | color: !_MouseArea.pressed ? buttonColor : theme.shadeColor(buttonColor, -15) 13 | 14 | MouseArea { 15 | id: _MouseArea 16 | anchors.fill: parent 17 | onClicked: root.clicked() 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/SQLiteEditor/sqlite.cpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2017 Niraj Desai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include "sqlite.h" 24 | 25 | SQLite::SQLite(QObject *parent) : 26 | QObject(parent), 27 | m_dbThread(NULL), 28 | m_databasePath(QUrl("")) 29 | { 30 | // qRegisterMetaType< QList >( "QList" ); 31 | 32 | qDebug() << __PRETTY_FUNCTION__; 33 | 34 | connect(this, SIGNAL(databasePathChanged(QUrl)), 35 | this, SLOT(createThread(QUrl))); 36 | } 37 | 38 | void SQLite::createThread(QUrl databasePath) 39 | { 40 | qDebug() << __PRETTY_FUNCTION__; 41 | if(databasePath.isEmpty()) return; 42 | 43 | if(m_dbThread && m_dbThread->isRunning()) 44 | { 45 | dbThreadStarted(); 46 | } 47 | else 48 | { 49 | m_dbThread = new DbThread(this, databasePath.toString(QUrl::RemoveScheme)); 50 | connect( m_dbThread, SIGNAL( results( const QList& ) ), 51 | this, SLOT( slotResults( const QList& ) ) ); 52 | connect( m_dbThread, SIGNAL(started()), this, SLOT(dbThreadStarted())); 53 | m_dbThread->start(); 54 | } 55 | } 56 | 57 | void SQLite::executeQuery(QString queryStatement) 58 | { 59 | qDebug() << __PRETTY_FUNCTION__; 60 | setStatus(Loading); 61 | m_query = queryStatement; 62 | m_dbThread->execute(queryStatement); 63 | } 64 | 65 | void SQLite::slotResults(const QList &result) 66 | { 67 | qDebug() << __PRETTY_FUNCTION__; 68 | qDebug() << "count="< sqlRecordsIterator(result); 74 | 75 | while(sqlRecordsIterator.hasNext()) 76 | { 77 | QSqlRecord sqlRecord = sqlRecordsIterator.next(); 78 | QVariantMap dataMap; 79 | for(int column = 0; column < sqlRecord.count(); column++) 80 | { 81 | //qDebug() << sqlRecord.fieldName(column); 82 | dataMap.insert(sqlRecord.fieldName(column), sqlRecord.value(column)); 83 | } 84 | val.append(QVariant::fromValue(dataMap)); 85 | } 86 | qDebug() << "count of list=" << val.count(); 87 | resultsReady(val, m_query); 88 | setStatus(Ready); 89 | } 90 | 91 | void SQLite::dbThreadStarted() 92 | { 93 | qDebug() << __PRETTY_FUNCTION__; 94 | databaseOpened(); 95 | if(!m_query.isEmpty()) 96 | { 97 | this->executeQuery(m_query); 98 | } 99 | else 100 | { 101 | connect(this, SIGNAL(queryChanged(QString)), 102 | this, SLOT(executeQuery(QString))); 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /app/SQLiteEditor/sqlite.h: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2017 Niraj Desai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "dbthread.h" 40 | 41 | class SQLite : public QObject 42 | { 43 | Q_OBJECT 44 | Q_ENUMS(Status) 45 | Q_PROPERTY(QUrl databasePath READ databasePath WRITE setDatabasePath NOTIFY databasePathChanged) 46 | Q_PROPERTY(QString query READ getQuery WRITE setQuery NOTIFY queryChanged) 47 | Q_PROPERTY(Status status READ getStatus WRITE setStatus NOTIFY statusChanged) 48 | 49 | public: 50 | explicit SQLite(QObject *parent = 0); 51 | enum Status { 52 | Null, 53 | Ready, 54 | Loading, 55 | Error 56 | }; 57 | QUrl databasePath() const 58 | { 59 | return m_databasePath; 60 | } 61 | 62 | QString getQuery() const 63 | { 64 | return m_query; 65 | } 66 | 67 | Status getStatus() const 68 | { 69 | return m_status; 70 | } 71 | 72 | signals: 73 | 74 | void databaseOpened(); 75 | 76 | void databasePathChanged(QUrl arg); 77 | 78 | void queryChanged(QString arg); 79 | 80 | void statusChanged(Status arg); 81 | 82 | void resultsReady(QVariantList results, QString query); 83 | 84 | private slots: 85 | void createThread(QUrl); 86 | void slotResults( const QList& ); 87 | void dbThreadStarted(); 88 | 89 | public slots: 90 | void executeQuery(QString); 91 | 92 | void setDatabasePath(QUrl arg) 93 | { 94 | qDebug() << __PRETTY_FUNCTION__ << arg; 95 | if (m_databasePath == arg) 96 | return; 97 | 98 | m_databasePath = arg; 99 | emit databasePathChanged(arg); 100 | } 101 | 102 | void setQuery(QString arg) 103 | { 104 | if (m_query == arg) 105 | return; 106 | 107 | m_query = arg; 108 | emit queryChanged(arg); 109 | } 110 | 111 | void setStatus(Status arg) 112 | { 113 | if (m_status == arg) 114 | return; 115 | 116 | m_status = arg; 117 | emit statusChanged(arg); 118 | } 119 | 120 | private: 121 | DbThread *m_dbThread; 122 | 123 | QUrl m_databasePath; 124 | QString m_query; 125 | Status m_status; 126 | }; 127 | -------------------------------------------------------------------------------- /readme-misc/screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ndesai/sqlite-editor-qtqml/c928970862348c8cd291374503e2a3620bcc2f8d/readme-misc/screenshot-01.png --------------------------------------------------------------------------------