QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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#include "moc_qgsjsoneditwidget.cpp"
18
19#include <QAction>
20#include <QClipboard>
21#include <QDesktopServices>
22#include <QJsonArray>
23#include <QLabel>
24#include <QPushButton>
25#include <QToolTip>
26#include <QUrl>
27
29 : QWidget( parent )
30 , mCopyValueAction( new QAction( tr( "Copy Value" ), this ) )
31 , mCopyKeyAction( new QAction( tr( "Copy Key" ), this ) )
32{
33 setupUi( this );
34
36
37 mCodeEditorJson->setReadOnly( true );
38 mCodeEditorJson->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
39 mCodeEditorJson->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
40 mCodeEditorJson->indicatorDefine( QsciScintilla::PlainIndicator, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
41 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORCURRENT, SCINTILLA_UNDERLINE_INDICATOR_INDEX );
42 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETMOUSEDWELLTIME, 400 );
43
44 mTreeWidget->setContextMenuPolicy( Qt::ActionsContextMenu );
45 mTreeWidget->addAction( mCopyValueAction );
46 mTreeWidget->addAction( mCopyKeyAction );
47
48 connect( mTextToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::textToolButtonClicked );
49 connect( mTreeToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::treeToolButtonClicked );
50
51 connect( mCopyValueAction, &QAction::triggered, this, &QgsJsonEditWidget::copyValueActionTriggered );
52 connect( mCopyKeyAction, &QAction::triggered, this, &QgsJsonEditWidget::copyKeyActionTriggered );
53
54 connect( mCodeEditorJson, &QgsCodeEditorJson::textChanged, this, &QgsJsonEditWidget::codeEditorJsonTextChanged );
55 // Signal indicatorClicked is used because indicatorReleased has a bug in Scintilla and the keyboard modifier state
56 // parameter is not correct. Merge request was submittet to fix it: https://sourceforge.net/p/scintilla/code/merge-requests/26/
57 connect( mCodeEditorJson, &QsciScintilla::indicatorClicked, this, &QgsJsonEditWidget::codeEditorJsonIndicatorClicked );
58 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLSTART, this, &QgsJsonEditWidget::codeEditorJsonDwellStart );
59 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLEND, this, &QgsJsonEditWidget::codeEditorJsonDwellEnd );
60}
61
63{
64 return mCodeEditorJson;
65}
66
67void QgsJsonEditWidget::setJsonText( const QString &jsonText )
68{
69 mJsonText = jsonText;
70 mClickableLinkList.clear();
71
72 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
73
74 mCodeEditorJson->blockSignals( true );
75 if ( jsonDocument.isNull() )
76 {
77 mCodeEditorJson->setText( mJsonText );
78 }
79 else
80 {
81 switch ( mFormatJsonMode )
82 {
84 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Indented ) );
85 break;
87 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Compact ) );
88 break;
90 mCodeEditorJson->setText( mJsonText );
91 break;
92 }
93 }
94 mCodeEditorJson->blockSignals( false );
95
96 refreshTreeView( jsonDocument );
97}
98
100{
101 return mJsonText;
102}
103
105{
106 switch ( view )
107 {
108 case View::Text:
109 {
110 mStackedWidget->setCurrentWidget( mStackedWidgetPageText );
111 mTextToolButton->setChecked( true );
112 mTreeToolButton->setChecked( false );
113 }
114 break;
115 case View::Tree:
116 {
117 mStackedWidget->setCurrentWidget( mStackedWidgetPageTree );
118 mTreeToolButton->setChecked( true );
119 mTextToolButton->setChecked( false );
120 }
121 break;
122 }
123}
124
126{
127 mFormatJsonMode = formatJson;
128}
129
131{
132 mControlsWidget->setVisible( visible );
133}
134
135void QgsJsonEditWidget::textToolButtonClicked( bool checked )
136{
137 if ( checked )
139 else
141}
142
143void QgsJsonEditWidget::treeToolButtonClicked( bool checked )
144{
145 if ( checked )
147 else
149}
150
151void QgsJsonEditWidget::copyValueActionTriggered()
152{
153 if ( !mTreeWidget->currentItem() )
154 return;
155
156 const QJsonValue jsonValue = QJsonValue::fromVariant( mTreeWidget->currentItem()->data( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole ) );
157
158 switch ( jsonValue.type() )
159 {
160 case QJsonValue::Null:
161 case QJsonValue::Bool:
162 case QJsonValue::Double:
163 case QJsonValue::Undefined:
164 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Value ) ) );
165 break;
166 case QJsonValue::String:
167 QApplication::clipboard()->setText( jsonValue.toString() );
168 break;
169 case QJsonValue::Array:
170 {
171 const QJsonDocument jsonDocument( jsonValue.toArray() );
172 QApplication::clipboard()->setText( jsonDocument.toJson() );
173 }
174 break;
175 case QJsonValue::Object:
176 {
177 const QJsonDocument jsonDocument( jsonValue.toObject() );
178 QApplication::clipboard()->setText( jsonDocument.toJson() );
179 }
180 break;
181 }
182}
183
184void QgsJsonEditWidget::copyKeyActionTriggered()
185{
186 if ( !mTreeWidget->currentItem() )
187 return;
188
189 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Key ) ) );
190}
191
192void QgsJsonEditWidget::codeEditorJsonTextChanged()
193{
194 mJsonText = mCodeEditorJson->text();
195 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
196 refreshTreeView( jsonDocument );
197}
198
199void QgsJsonEditWidget::codeEditorJsonIndicatorClicked( int line, int index, Qt::KeyboardModifiers state )
200{
201 if ( !state.testFlag( Qt::ControlModifier ) )
202 return;
203
204 const int position = mCodeEditorJson->positionFromLineIndex( line, index );
205 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
206 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
207 position );
208 if ( clickableLinkListIndex <= 0 )
209 return;
210
211 QDesktopServices::openUrl( mClickableLinkList.at( clickableLinkListIndex - 1 ) );
212}
213
214void QgsJsonEditWidget::codeEditorJsonDwellStart( int position, int x, int y )
215{
216 Q_UNUSED( x )
217 Q_UNUSED( y )
218
219 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
220 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
221 position );
222 if ( clickableLinkListIndex <= 0 )
223 return;
224
225 QToolTip::showText( QCursor::pos(),
226 tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
227}
228
229void QgsJsonEditWidget::codeEditorJsonDwellEnd( int position, int x, int y )
230{
231 Q_UNUSED( position )
232 Q_UNUSED( x )
233 Q_UNUSED( y )
234 QToolTip::hideText();
235}
236
237void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
238{
239 mTreeWidget->clear();
240
241 if ( jsonDocument.isNull() )
242 {
244 mTextToolButton->setDisabled( true );
245 mTreeToolButton->setDisabled( true );
246 mTreeToolButton->setToolTip( tr( "Invalid JSON, tree view not available" ) );
247 return;
248 }
249 else
250 {
251 mTextToolButton->setEnabled( true );
252 mTreeToolButton->setEnabled( true );
253 mTreeToolButton->setToolTip( tr( "Tree view" ) );
254 }
255
256 if ( jsonDocument.isObject() )
257 {
258 const QStringList keys = jsonDocument.object().keys();
259 for ( const QString &key : keys )
260 {
261 const QJsonValue jsonValue = jsonDocument.object().value( key );
262 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << key );
263 treeWidgetItem->setFont( 0, monospaceFont() );
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 treeWidgetItem->setFont( 0, monospaceFont() );
285 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
286 {
287 refreshTreeViewItem( treeWidgetItem, array.at( index ) );
288 mTreeWidget->addTopLevelItem( treeWidgetItem );
289 mTreeWidget->expandItem( treeWidgetItem );
290 }
291 else if ( index == MAX_ELTS / 2 )
292 {
293 index = arraySize - MAX_ELTS / 2;
294 refreshTreeViewItem( treeWidgetItem, tr( "... truncated ..." ) );
295 mTreeWidget->addTopLevelItem( treeWidgetItem );
296 mTreeWidget->expandItem( treeWidgetItem );
297 }
298 }
299 }
300
301 mTreeWidget->resizeColumnToContents( static_cast<int>( TreeWidgetColumn::Key ) );
302}
303
304void QgsJsonEditWidget::refreshTreeViewItem( QTreeWidgetItem *treeWidgetItem, const QJsonValue &jsonValue )
305{
306 treeWidgetItem->setData( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole, jsonValue.toVariant() );
307
308 switch ( jsonValue.type() )
309 {
310 case QJsonValue::Null:
311 refreshTreeViewItemValue( treeWidgetItem,
312 QStringLiteral( "null" ),
314 break;
315 case QJsonValue::Bool:
316 refreshTreeViewItemValue( treeWidgetItem,
317 jsonValue.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ),
318 QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::Keyword ) );
319 break;
320 case QJsonValue::Double:
321 refreshTreeViewItemValue( treeWidgetItem,
322 QString::number( jsonValue.toDouble() ),
324 break;
325 case QJsonValue::String:
326 {
327 const QString jsonValueString = jsonValue.toString();
328 if ( !mEnableUrlHighlighting || QUrl( jsonValueString ).scheme().isEmpty() )
329 {
330 refreshTreeViewItemValue( treeWidgetItem,
331 jsonValueString,
333 }
334 else
335 {
336 QLabel *label = new QLabel( QString( "<a href='%1'>%1</a>" ).arg( jsonValueString ) );
337 label->setOpenExternalLinks( true );
338 label->setFont( monospaceFont() );
339 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
340
341 mClickableLinkList.append( jsonValueString );
342 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast< int >( mClickableLinkList.size() ) );
343 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE,
344 mCodeEditorJson->text().indexOf( jsonValueString ),
345 jsonValueString.size() );
346 }
347 }
348 break;
349 case QJsonValue::Array:
350 {
351 const QJsonArray jsonArray = jsonValue.toArray();
352 const auto arraySize = jsonArray.size();
353 // Limit the number of rows we display, otherwise for pathological cases
354 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
355 // a unbounded number of elements will just stall the GUI forever.
356 constexpr decltype( arraySize ) MAX_ELTS = 200;
357 // If there are too many elements, disable URL highighting as it
358 // performs very poorly.
359 if ( arraySize > MAX_ELTS )
360 mEnableUrlHighlighting = false;
361 for ( auto index = decltype( arraySize ) {0}; index < arraySize; index++ )
362 {
363 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
364 treeWidgetItemChild->setFont( 0, monospaceFont() );
365 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
366 {
367 refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
368 treeWidgetItem->addChild( treeWidgetItemChild );
369 treeWidgetItem->setExpanded( true );
370 }
371 else if ( index == MAX_ELTS / 2 )
372 {
373 index = arraySize - MAX_ELTS / 2;
374 refreshTreeViewItem( treeWidgetItemChild, tr( "... truncated ..." ) );
375 treeWidgetItem->addChild( treeWidgetItemChild );
376 treeWidgetItem->setExpanded( true );
377 }
378 }
379 }
380 break;
381 case QJsonValue::Object:
382 {
383 const QJsonObject jsonObject = jsonValue.toObject();
384 const QStringList keys = jsonObject.keys();
385 for ( const QString &key : keys )
386 {
387 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
388 treeWidgetItemChild->setFont( 0, monospaceFont() );
389 refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
390 treeWidgetItem->addChild( treeWidgetItemChild );
391 treeWidgetItem->setExpanded( true );
392 }
393 }
394 break;
395 case QJsonValue::Undefined:
396 refreshTreeViewItemValue( treeWidgetItem,
397 QStringLiteral( "Undefined value" ),
399 break;
400 }
401}
402
403void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
404{
405 QLabel *label = new QLabel( jsonValueString );
406 label->setFont( monospaceFont() );
407
408 if ( textColor.isValid() )
409 label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
410 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
411}
412
413QFont QgsJsonEditWidget::monospaceFont() const
414{
416 // use standard widget font size, not code editor font size
417 f.setPointSize( font().pointSize() );
418 return f;
419}
Defines a color scheme for use in QgsCodeEditor widgets.
@ DoubleQuote
Double quote color.
A JSON editor based on QScintilla2.
A text 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.