QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
qgsjsoneditwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsjsoneditwidget.cpp
3 --------------------------------------
4 Date : 3.5.2021
5 Copyright : (C) 2021 Damiano Lombardi
6 Email : damiano at opengis dot ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsjsoneditwidget.h"
17
18#include <QAction>
19#include <QClipboard>
20#include <QDesktopServices>
21#include <QJsonArray>
22#include <QLabel>
23#include <QPushButton>
24#include <QToolTip>
25#include <QUrl>
26
27#include "moc_qgsjsoneditwidget.cpp"
28
30 : QWidget( parent )
31 , mCopyValueAction( new QAction( tr( "Copy Value" ), this ) )
32 , mCopyKeyAction( new QAction( tr( "Copy Key" ), this ) )
33{
34 setupUi( this );
35
37
38 mCodeEditorJson->setReadOnly( true );
39 mCodeEditorJson->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
40 mCodeEditorJson->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
41 mCodeEditorJson->indicatorDefine( QsciScintilla::PlainIndicator, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
42 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORCURRENT, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
43 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 400 );
44
45 mTreeWidget->setContextMenuPolicy( Qt::ActionsContextMenu );
46 mTreeWidget->addAction( mCopyValueAction );
47 mTreeWidget->addAction( mCopyKeyAction );
48
49 connect( mTextToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::textToolButtonClicked );
50 connect( mTreeToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::treeToolButtonClicked );
51
52 connect( mCopyValueAction, &QAction::triggered, this, &QgsJsonEditWidget::copyValueActionTriggered );
53 connect( mCopyKeyAction, &QAction::triggered, this, &QgsJsonEditWidget::copyKeyActionTriggered );
54
55 connect( mCodeEditorJson, &QgsCodeEditorJson::textChanged, this, &QgsJsonEditWidget::codeEditorJsonTextChanged );
56 // Signal indicatorClicked is used because indicatorReleased has a bug in Scintilla and the keyboard modifier state
57 // parameter is not correct. Merge request was submittet to fix it: https://sourceforge.net/p/scintilla/code/merge-requests/26/
58 connect( mCodeEditorJson, &QsciScintilla::indicatorClicked, this, &QgsJsonEditWidget::codeEditorJsonIndicatorClicked );
59 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLSTART, this, &QgsJsonEditWidget::codeEditorJsonDwellStart );
60 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLEND, this, &QgsJsonEditWidget::codeEditorJsonDwellEnd );
61}
62
64{
65 return mCodeEditorJson;
66}
67
69{
70 mJsonText = jsonText;
71 mClickableLinkList.clear();
72
73 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
74
75 mCodeEditorJson->blockSignals( true );
76 if ( jsonDocument.isNull() )
77 {
78 mCodeEditorJson->setText( mJsonText );
79 }
80 else
81 {
82 switch ( mFormatJsonMode )
83 {
85 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Indented ) );
86 break;
88 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Compact ) );
89 break;
91 mCodeEditorJson->setText( mJsonText );
92 break;
93 }
94 }
95 mCodeEditorJson->blockSignals( false );
96
97 refreshTreeView( jsonDocument );
98}
99
101{
102 return mJsonText;
103}
104
106{
107 switch ( view )
108 {
109 case View::Text:
110 {
111 mStackedWidget->setCurrentWidget( mStackedWidgetPageText );
112 mTextToolButton->setChecked( true );
113 mTreeToolButton->setChecked( false );
114 }
115 break;
116 case View::Tree:
117 {
118 mStackedWidget->setCurrentWidget( mStackedWidgetPageTree );
119 mTreeToolButton->setChecked( true );
120 mTextToolButton->setChecked( false );
121 }
122 break;
123 }
124}
125
127{
128 mFormatJsonMode = formatJson;
129}
130
132{
133 mControlsWidget->setVisible( visible );
134}
135
136void QgsJsonEditWidget::textToolButtonClicked( bool checked )
137{
138 if ( checked )
140 else
142}
143
144void QgsJsonEditWidget::treeToolButtonClicked( bool checked )
145{
146 if ( checked )
148 else
150}
151
152void QgsJsonEditWidget::copyValueActionTriggered()
153{
154 if ( !mTreeWidget->currentItem() )
155 return;
156
157 const QJsonValue jsonValue = QJsonValue::fromVariant( mTreeWidget->currentItem()->data( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole ) );
158
159 switch ( jsonValue.type() )
160 {
161 case QJsonValue::Null:
162 case QJsonValue::Bool:
163 case QJsonValue::Double:
164 case QJsonValue::Undefined:
165 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Value ) ) );
166 break;
167 case QJsonValue::String:
168 QApplication::clipboard()->setText( jsonValue.toString() );
169 break;
170 case QJsonValue::Array:
171 {
172 const QJsonDocument jsonDocument( jsonValue.toArray() );
173 QApplication::clipboard()->setText( jsonDocument.toJson() );
174 }
175 break;
176 case QJsonValue::Object:
177 {
178 const QJsonDocument jsonDocument( jsonValue.toObject() );
179 QApplication::clipboard()->setText( jsonDocument.toJson() );
180 }
181 break;
182 }
183}
184
185void QgsJsonEditWidget::copyKeyActionTriggered()
186{
187 if ( !mTreeWidget->currentItem() )
188 return;
189
190 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Key ) ) );
191}
192
193void QgsJsonEditWidget::codeEditorJsonTextChanged()
194{
195 mJsonText = mCodeEditorJson->text();
196 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
197 refreshTreeView( jsonDocument );
198}
199
200void QgsJsonEditWidget::codeEditorJsonIndicatorClicked( int line, int index, Qt::KeyboardModifiers state )
201{
202 if ( !state.testFlag( Qt::ControlModifier ) )
203 return;
204
205 const int position = mCodeEditorJson->positionFromLineIndex( line, index );
206 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT, SCINTILLA_UNDERLINE_INDICATOR_INDEX, position );
207 if ( clickableLinkListIndex <= 0 )
208 return;
209
210 QDesktopServices::openUrl( mClickableLinkList.at( clickableLinkListIndex - 1 ) );
211}
212
213void QgsJsonEditWidget::codeEditorJsonDwellStart( int position, int x, int y )
214{
215 Q_UNUSED( x )
216 Q_UNUSED( y )
217
218 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT, SCINTILLA_UNDERLINE_INDICATOR_INDEX, position );
219 if ( clickableLinkListIndex <= 0 )
220 return;
221
222 QToolTip::showText( QCursor::pos(), tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
223}
224
225void QgsJsonEditWidget::codeEditorJsonDwellEnd( int position, int x, int y )
226{
227 Q_UNUSED( position )
228 Q_UNUSED( x )
229 Q_UNUSED( y )
230 QToolTip::hideText();
231}
232
233void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
234{
235 mTreeWidget->clear();
236
237 if ( jsonDocument.isNull() )
238 {
240 mTextToolButton->setDisabled( true );
241 mTreeToolButton->setDisabled( true );
242 mTreeToolButton->setToolTip( tr( "Invalid JSON, tree view not available" ) );
243 return;
244 }
245 else
246 {
247 mTextToolButton->setEnabled( true );
248 mTreeToolButton->setEnabled( true );
249 mTreeToolButton->setToolTip( tr( "Tree view" ) );
250 }
251
252 if ( jsonDocument.isObject() )
253 {
254 const QStringList keys = jsonDocument.object().keys();
255 for ( const QString &key : keys )
256 {
257 const QJsonValue jsonValue = jsonDocument.object().value( key );
258 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << key );
259 treeWidgetItem->setFont( 0, monospaceFont() );
260 refreshTreeViewItem( treeWidgetItem, jsonValue );
261 mTreeWidget->addTopLevelItem( treeWidgetItem );
262 mTreeWidget->expandItem( treeWidgetItem );
263 }
264 }
265 else if ( jsonDocument.isArray() )
266 {
267 const QJsonArray array = jsonDocument.array();
268 const auto arraySize = array.size();
269 // Limit the number of rows we display, otherwise for pathological cases
270 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
271 // a unbounded number of elements will just stall the GUI forever.
272 constexpr decltype( arraySize ) MAX_ELTS = 200;
273 // If there are too many elements, disable URL highighting as it
274 // performs very poorly.
275 if ( arraySize > MAX_ELTS )
276 mEnableUrlHighlighting = false;
277 for ( auto index = decltype( arraySize ) { 0 }; index < arraySize; index++ )
278 {
279 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << QString::number( index ) );
280 treeWidgetItem->setFont( 0, monospaceFont() );
281 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
282 {
283 refreshTreeViewItem( treeWidgetItem, array.at( index ) );
284 mTreeWidget->addTopLevelItem( treeWidgetItem );
285 mTreeWidget->expandItem( treeWidgetItem );
286 }
287 else if ( index == MAX_ELTS / 2 )
288 {
289 index = arraySize - MAX_ELTS / 2;
290 refreshTreeViewItem( treeWidgetItem, tr( "... truncated ..." ) );
291 mTreeWidget->addTopLevelItem( treeWidgetItem );
292 mTreeWidget->expandItem( treeWidgetItem );
293 }
294 }
295 }
296
297 mTreeWidget->resizeColumnToContents( static_cast<int>( TreeWidgetColumn::Key ) );
298}
299
300void QgsJsonEditWidget::refreshTreeViewItem( QTreeWidgetItem *treeWidgetItem, const QJsonValue &jsonValue )
301{
302 treeWidgetItem->setData( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole, jsonValue.toVariant() );
303
304 switch ( jsonValue.type() )
305 {
306 case QJsonValue::Null:
307 refreshTreeViewItemValue( treeWidgetItem, QStringLiteral( "null" ), QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::Keyword ) );
308 break;
309 case QJsonValue::Bool:
310 refreshTreeViewItemValue( treeWidgetItem, jsonValue.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ), QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::Keyword ) );
311 break;
312 case QJsonValue::Double:
313 refreshTreeViewItemValue( treeWidgetItem, QString::number( jsonValue.toDouble() ), QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::Number ) );
314 break;
315 case QJsonValue::String:
316 {
317 const QString jsonValueString = jsonValue.toString();
318 if ( !mEnableUrlHighlighting || QUrl( jsonValueString ).scheme().isEmpty() )
319 {
320 refreshTreeViewItemValue( treeWidgetItem, jsonValueString, QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ) );
321 }
322 else
323 {
324 QLabel *label = new QLabel( QString( "<a href='%1'>%1</a>" ).arg( jsonValueString ) );
325 label->setOpenExternalLinks( true );
326 label->setFont( monospaceFont() );
327 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
328
329 mClickableLinkList.append( jsonValueString );
330 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast<int>( mClickableLinkList.size() ) );
331 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE, mCodeEditorJson->text().indexOf( jsonValueString ), jsonValueString.size() );
332 }
333 }
334 break;
335 case QJsonValue::Array:
336 {
337 const QJsonArray jsonArray = jsonValue.toArray();
338 const auto arraySize = jsonArray.size();
339 // Limit the number of rows we display, otherwise for pathological cases
340 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
341 // a unbounded number of elements will just stall the GUI forever.
342 constexpr decltype( arraySize ) MAX_ELTS = 200;
343 // If there are too many elements, disable URL highighting as it
344 // performs very poorly.
345 if ( arraySize > MAX_ELTS )
346 mEnableUrlHighlighting = false;
347 for ( auto index = decltype( arraySize ) { 0 }; index < arraySize; index++ )
348 {
349 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
350 treeWidgetItemChild->setFont( 0, monospaceFont() );
351 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
352 {
353 refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
354 treeWidgetItem->addChild( treeWidgetItemChild );
355 treeWidgetItem->setExpanded( true );
356 }
357 else if ( index == MAX_ELTS / 2 )
358 {
359 index = arraySize - MAX_ELTS / 2;
360 refreshTreeViewItem( treeWidgetItemChild, tr( "... truncated ..." ) );
361 treeWidgetItem->addChild( treeWidgetItemChild );
362 treeWidgetItem->setExpanded( true );
363 }
364 }
365 }
366 break;
367 case QJsonValue::Object:
368 {
369 const QJsonObject jsonObject = jsonValue.toObject();
370 const QStringList keys = jsonObject.keys();
371 for ( const QString &key : keys )
372 {
373 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
374 treeWidgetItemChild->setFont( 0, monospaceFont() );
375 refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
376 treeWidgetItem->addChild( treeWidgetItemChild );
377 treeWidgetItem->setExpanded( true );
378 }
379 }
380 break;
381 case QJsonValue::Undefined:
382 refreshTreeViewItemValue( treeWidgetItem, QStringLiteral( "Undefined value" ), QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::DoubleQuote ) );
383 break;
384 }
385}
386
387void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
388{
389 QLabel *label = new QLabel( jsonValueString );
390 label->setFont( monospaceFont() );
391
392 if ( textColor.isValid() )
393 label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
394 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
395}
396
397QFont QgsJsonEditWidget::monospaceFont() const
398{
400 // use standard widget font size, not code editor font size
401 f.setPointSize( font().pointSize() );
402 return f;
403}
A JSON editor based on QScintilla2.
static QFont getMonospaceFont()
Returns the monospaced font to use for code editors.
static QColor color(QgsCodeEditorColorScheme::ColorRole role)
Returns the color to use in the editor for the specified role.
void setJsonText(const QString &jsonText)
Set the JSON text in the widget to jsonText.
QString jsonText() const
Returns the JSON text.
View
View mode, text or tree.
@ Tree
JSON data displayed as tree. Tree view is disabled for invalid JSON data.
@ Text
JSON data displayed as text.
QgsCodeEditorJson * jsonEditor()
Returns a reference to the JSON code editor used in the widget.
QgsJsonEditWidget(QWidget *parent=nullptr)
Constructor for QgsJsonEditWidget.
void setFormatJsonMode(FormatJson formatJson)
Set the formatJson mode.
void setControlsVisible(bool visible)
Set the visibility of controls to visible.
void setView(View view) const
Set the view mode.
FormatJson
Format mode in the text view.
@ Compact
JSON data formatted as a compact one line string.
@ Disabled
JSON data is not formatted.
@ Indented
JSON data formatted with regular indentation.