QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
28 : QWidget( parent )
29 , mCopyValueAction( new QAction( tr( "Copy value" ), this ) )
30 , mCopyKeyAction( new QAction( tr( "Copy key" ), this ) )
31{
32 setupUi( this );
33
35
36 mCodeEditorJson->setReadOnly( true );
37 mCodeEditorJson->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
38 mCodeEditorJson->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
39 mCodeEditorJson->indicatorDefine( QsciScintilla::PlainIndicator, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
40 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORCURRENT, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
41 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 400 );
42
43 mTreeWidget->setStyleSheet( QStringLiteral( "font-family: %1;" ).arg( QgsCodeEditor::getMonospaceFont().family() ) );
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
68void QgsJsonEditWidget::setJsonText( const QString &jsonText )
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,
207 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
208 position );
209 if ( clickableLinkListIndex <= 0 )
210 return;
211
212 QDesktopServices::openUrl( mClickableLinkList.at( clickableLinkListIndex - 1 ) );
213}
214
215void QgsJsonEditWidget::codeEditorJsonDwellStart( int position, int x, int y )
216{
217 Q_UNUSED( x )
218 Q_UNUSED( y )
219
220 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
221 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
222 position );
223 if ( clickableLinkListIndex <= 0 )
224 return;
225
226 QToolTip::showText( QCursor::pos(),
227 tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
228}
229
230void QgsJsonEditWidget::codeEditorJsonDwellEnd( int position, int x, int y )
231{
232 Q_UNUSED( position )
233 Q_UNUSED( x )
234 Q_UNUSED( y )
235 QToolTip::hideText();
236}
237
238void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
239{
240 mTreeWidget->clear();
241
242 if ( jsonDocument.isNull() )
243 {
245 mTextToolButton->setDisabled( true );
246 mTreeToolButton->setDisabled( true );
247 mTreeToolButton->setToolTip( tr( "Invalid JSON, tree view not available" ) );
248 return;
249 }
250 else
251 {
252 mTextToolButton->setEnabled( true );
253 mTreeToolButton->setEnabled( true );
254 mTreeToolButton->setToolTip( tr( "Tree view" ) );
255 }
256
257 if ( jsonDocument.isObject() )
258 {
259 const QStringList keys = jsonDocument.object().keys();
260 for ( const QString &key : keys )
261 {
262 const QJsonValue jsonValue = jsonDocument.object().value( key );
263 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << key );
264 refreshTreeViewItem( treeWidgetItem, jsonValue );
265 mTreeWidget->addTopLevelItem( treeWidgetItem );
266 mTreeWidget->expandItem( treeWidgetItem );
267 }
268 }
269 else if ( jsonDocument.isArray() )
270 {
271 const QJsonArray array = jsonDocument.array();
272 const auto arraySize = array.size();
273 // Limit the number of rows we display, otherwise for pathological cases
274 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
275 // a unbounded number of elements will just stall the GUI forever.
276 constexpr decltype( arraySize ) MAX_ELTS = 200;
277 // If there are too many elements, disable URL highighting as it
278 // performs very poorly.
279 if ( arraySize > MAX_ELTS )
280 mEnableUrlHighlighting = false;
281 for ( auto index = decltype( arraySize ) {0}; index < arraySize; index++ )
282 {
283 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << QString::number( index ) );
284 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
285 {
286 refreshTreeViewItem( treeWidgetItem, array.at( index ) );
287 mTreeWidget->addTopLevelItem( treeWidgetItem );
288 mTreeWidget->expandItem( treeWidgetItem );
289 }
290 else if ( index == MAX_ELTS / 2 )
291 {
292 index = arraySize - MAX_ELTS / 2;
293 refreshTreeViewItem( treeWidgetItem, tr( "... truncated ..." ) );
294 mTreeWidget->addTopLevelItem( treeWidgetItem );
295 mTreeWidget->expandItem( treeWidgetItem );
296 }
297 }
298 }
299
300 mTreeWidget->resizeColumnToContents( static_cast<int>( TreeWidgetColumn::Key ) );
301}
302
303void QgsJsonEditWidget::refreshTreeViewItem( QTreeWidgetItem *treeWidgetItem, const QJsonValue &jsonValue )
304{
305 treeWidgetItem->setData( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole, jsonValue.toVariant() );
306
307 switch ( jsonValue.type() )
308 {
309 case QJsonValue::Null:
310 refreshTreeViewItemValue( treeWidgetItem,
311 QStringLiteral( "null" ),
313 break;
314 case QJsonValue::Bool:
315 refreshTreeViewItemValue( treeWidgetItem,
316 jsonValue.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ),
318 break;
319 case QJsonValue::Double:
320 refreshTreeViewItemValue( treeWidgetItem,
321 QString::number( jsonValue.toDouble() ),
323 break;
324 case QJsonValue::String:
325 {
326 const QString jsonValueString = jsonValue.toString();
327 if ( !mEnableUrlHighlighting || QUrl( jsonValueString ).scheme().isEmpty() )
328 {
329 refreshTreeViewItemValue( treeWidgetItem,
330 jsonValueString,
332 }
333 else
334 {
335 QLabel *label = new QLabel( QString( "<a href='%1'>%1</a>" ).arg( jsonValueString ) );
336 label->setOpenExternalLinks( true );
337 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
338
339 mClickableLinkList.append( jsonValueString );
340 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast< int >( mClickableLinkList.size() ) );
341 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE,
342 mCodeEditorJson->text().indexOf( jsonValueString ),
343 jsonValueString.size() );
344 }
345 }
346 break;
347 case QJsonValue::Array:
348 {
349 const QJsonArray jsonArray = jsonValue.toArray();
350 const auto arraySize = jsonArray.size();
351 // Limit the number of rows we display, otherwise for pathological cases
352 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
353 // a unbounded number of elements will just stall the GUI forever.
354 constexpr decltype( arraySize ) MAX_ELTS = 200;
355 // If there are too many elements, disable URL highighting as it
356 // performs very poorly.
357 if ( arraySize > MAX_ELTS )
358 mEnableUrlHighlighting = false;
359 for ( auto index = decltype( arraySize ) {0}; index < arraySize; index++ )
360 {
361 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
362 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
363 {
364 refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
365 treeWidgetItem->addChild( treeWidgetItemChild );
366 treeWidgetItem->setExpanded( true );
367 }
368 else if ( index == MAX_ELTS / 2 )
369 {
370 index = arraySize - MAX_ELTS / 2;
371 refreshTreeViewItem( treeWidgetItemChild, tr( "... truncated ..." ) );
372 treeWidgetItem->addChild( treeWidgetItemChild );
373 treeWidgetItem->setExpanded( true );
374 }
375 }
376 }
377 break;
378 case QJsonValue::Object:
379 {
380 const QJsonObject jsonObject = jsonValue.toObject();
381 const QStringList keys = jsonObject.keys();
382 for ( const QString &key : keys )
383 {
384 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
385 refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
386 treeWidgetItem->addChild( treeWidgetItemChild );
387 treeWidgetItem->setExpanded( true );
388 }
389 }
390 break;
391 case QJsonValue::Undefined:
392 refreshTreeViewItemValue( treeWidgetItem,
393 QStringLiteral( "Undefined value" ),
395 break;
396 }
397}
398
399void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
400{
401 QLabel *label = new QLabel( jsonValueString );
402 if ( textColor.isValid() )
403 label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
404 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
405}
@ DoubleQuote
Double quote color.
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.