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