QGIS API Documentation 3.39.0-Master (47f7b3a4989)
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
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->setContextMenuPolicy( Qt::ActionsContextMenu );
44 mTreeWidget->addAction( mCopyValueAction );
45 mTreeWidget->addAction( mCopyKeyAction );
46
47 connect( mTextToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::textToolButtonClicked );
48 connect( mTreeToolButton, &QToolButton::clicked, this, &QgsJsonEditWidget::treeToolButtonClicked );
49
50 connect( mCopyValueAction, &QAction::triggered, this, &QgsJsonEditWidget::copyValueActionTriggered );
51 connect( mCopyKeyAction, &QAction::triggered, this, &QgsJsonEditWidget::copyKeyActionTriggered );
52
53 connect( mCodeEditorJson, &QgsCodeEditorJson::textChanged, this, &QgsJsonEditWidget::codeEditorJsonTextChanged );
54 // Signal indicatorClicked is used because indicatorReleased has a bug in Scintilla and the keyboard modifier state
55 // parameter is not correct. Merge request was submittet to fix it: https://sourceforge.net/p/scintilla/code/merge-requests/26/
56 connect( mCodeEditorJson, &QsciScintilla::indicatorClicked, this, &QgsJsonEditWidget::codeEditorJsonIndicatorClicked );
57 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLSTART, this, &QgsJsonEditWidget::codeEditorJsonDwellStart );
58 connect( mCodeEditorJson, &QsciScintillaBase::SCN_DWELLEND, this, &QgsJsonEditWidget::codeEditorJsonDwellEnd );
59}
60
62{
63 return mCodeEditorJson;
64}
65
66void QgsJsonEditWidget::setJsonText( const QString &jsonText )
67{
68 mJsonText = jsonText;
69 mClickableLinkList.clear();
70
71 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
72
73 mCodeEditorJson->blockSignals( true );
74 if ( jsonDocument.isNull() )
75 {
76 mCodeEditorJson->setText( mJsonText );
77 }
78 else
79 {
80 switch ( mFormatJsonMode )
81 {
83 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Indented ) );
84 break;
86 mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Compact ) );
87 break;
89 mCodeEditorJson->setText( mJsonText );
90 break;
91 }
92 }
93 mCodeEditorJson->blockSignals( false );
94
95 refreshTreeView( jsonDocument );
96}
97
99{
100 return mJsonText;
101}
102
104{
105 switch ( view )
106 {
107 case View::Text:
108 {
109 mStackedWidget->setCurrentWidget( mStackedWidgetPageText );
110 mTextToolButton->setChecked( true );
111 mTreeToolButton->setChecked( false );
112 }
113 break;
114 case View::Tree:
115 {
116 mStackedWidget->setCurrentWidget( mStackedWidgetPageTree );
117 mTreeToolButton->setChecked( true );
118 mTextToolButton->setChecked( false );
119 }
120 break;
121 }
122}
123
125{
126 mFormatJsonMode = formatJson;
127}
128
130{
131 mControlsWidget->setVisible( visible );
132}
133
134void QgsJsonEditWidget::textToolButtonClicked( bool checked )
135{
136 if ( checked )
138 else
140}
141
142void QgsJsonEditWidget::treeToolButtonClicked( bool checked )
143{
144 if ( checked )
146 else
148}
149
150void QgsJsonEditWidget::copyValueActionTriggered()
151{
152 if ( !mTreeWidget->currentItem() )
153 return;
154
155 const QJsonValue jsonValue = QJsonValue::fromVariant( mTreeWidget->currentItem()->data( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole ) );
156
157 switch ( jsonValue.type() )
158 {
159 case QJsonValue::Null:
160 case QJsonValue::Bool:
161 case QJsonValue::Double:
162 case QJsonValue::Undefined:
163 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Value ) ) );
164 break;
165 case QJsonValue::String:
166 QApplication::clipboard()->setText( jsonValue.toString() );
167 break;
168 case QJsonValue::Array:
169 {
170 const QJsonDocument jsonDocument( jsonValue.toArray() );
171 QApplication::clipboard()->setText( jsonDocument.toJson() );
172 }
173 break;
174 case QJsonValue::Object:
175 {
176 const QJsonDocument jsonDocument( jsonValue.toObject() );
177 QApplication::clipboard()->setText( jsonDocument.toJson() );
178 }
179 break;
180 }
181}
182
183void QgsJsonEditWidget::copyKeyActionTriggered()
184{
185 if ( !mTreeWidget->currentItem() )
186 return;
187
188 QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Key ) ) );
189}
190
191void QgsJsonEditWidget::codeEditorJsonTextChanged()
192{
193 mJsonText = mCodeEditorJson->text();
194 const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
195 refreshTreeView( jsonDocument );
196}
197
198void QgsJsonEditWidget::codeEditorJsonIndicatorClicked( int line, int index, Qt::KeyboardModifiers state )
199{
200 if ( !state.testFlag( Qt::ControlModifier ) )
201 return;
202
203 const int position = mCodeEditorJson->positionFromLineIndex( line, index );
204 const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
205 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
206 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,
219 SCINTILLA_UNDERLINE_INDICATOR_INDEX,
220 position );
221 if ( clickableLinkListIndex <= 0 )
222 return;
223
224 QToolTip::showText( QCursor::pos(),
225 tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
226}
227
228void QgsJsonEditWidget::codeEditorJsonDwellEnd( int position, int x, int y )
229{
230 Q_UNUSED( position )
231 Q_UNUSED( x )
232 Q_UNUSED( y )
233 QToolTip::hideText();
234}
235
236void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
237{
238 mTreeWidget->clear();
239
240 if ( jsonDocument.isNull() )
241 {
243 mTextToolButton->setDisabled( true );
244 mTreeToolButton->setDisabled( true );
245 mTreeToolButton->setToolTip( tr( "Invalid JSON, tree view not available" ) );
246 return;
247 }
248 else
249 {
250 mTextToolButton->setEnabled( true );
251 mTreeToolButton->setEnabled( true );
252 mTreeToolButton->setToolTip( tr( "Tree view" ) );
253 }
254
255 if ( jsonDocument.isObject() )
256 {
257 const QStringList keys = jsonDocument.object().keys();
258 for ( const QString &key : keys )
259 {
260 const QJsonValue jsonValue = jsonDocument.object().value( key );
261 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << key );
262 treeWidgetItem->setFont( 0, monospaceFont() );
263 refreshTreeViewItem( treeWidgetItem, jsonValue );
264 mTreeWidget->addTopLevelItem( treeWidgetItem );
265 mTreeWidget->expandItem( treeWidgetItem );
266 }
267 }
268 else if ( jsonDocument.isArray() )
269 {
270 const QJsonArray array = jsonDocument.array();
271 const auto arraySize = array.size();
272 // Limit the number of rows we display, otherwise for pathological cases
273 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
274 // a unbounded number of elements will just stall the GUI forever.
275 constexpr decltype( arraySize ) MAX_ELTS = 200;
276 // If there are too many elements, disable URL highighting as it
277 // performs very poorly.
278 if ( arraySize > MAX_ELTS )
279 mEnableUrlHighlighting = false;
280 for ( auto index = decltype( arraySize ) {0}; index < arraySize; index++ )
281 {
282 QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << QString::number( index ) );
283 treeWidgetItem->setFont( 0, monospaceFont() );
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" ),
317 QgsCodeEditor::color( QgsCodeEditorColorScheme::ColorRole::Keyword ) );
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 label->setFont( monospaceFont() );
338 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
339
340 mClickableLinkList.append( jsonValueString );
341 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast< int >( mClickableLinkList.size() ) );
342 mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE,
343 mCodeEditorJson->text().indexOf( jsonValueString ),
344 jsonValueString.size() );
345 }
346 }
347 break;
348 case QJsonValue::Array:
349 {
350 const QJsonArray jsonArray = jsonValue.toArray();
351 const auto arraySize = jsonArray.size();
352 // Limit the number of rows we display, otherwise for pathological cases
353 // like https://github.com/qgis/QGIS/pull/55847#issuecomment-1902077683
354 // a unbounded number of elements will just stall the GUI forever.
355 constexpr decltype( arraySize ) MAX_ELTS = 200;
356 // If there are too many elements, disable URL highighting as it
357 // performs very poorly.
358 if ( arraySize > MAX_ELTS )
359 mEnableUrlHighlighting = false;
360 for ( auto index = decltype( arraySize ) {0}; index < arraySize; index++ )
361 {
362 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
363 treeWidgetItemChild->setFont( 0, monospaceFont() );
364 if ( arraySize <= MAX_ELTS || ( index < MAX_ELTS / 2 || index + MAX_ELTS / 2 > arraySize ) )
365 {
366 refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
367 treeWidgetItem->addChild( treeWidgetItemChild );
368 treeWidgetItem->setExpanded( true );
369 }
370 else if ( index == MAX_ELTS / 2 )
371 {
372 index = arraySize - MAX_ELTS / 2;
373 refreshTreeViewItem( treeWidgetItemChild, tr( "... truncated ..." ) );
374 treeWidgetItem->addChild( treeWidgetItemChild );
375 treeWidgetItem->setExpanded( true );
376 }
377 }
378 }
379 break;
380 case QJsonValue::Object:
381 {
382 const QJsonObject jsonObject = jsonValue.toObject();
383 const QStringList keys = jsonObject.keys();
384 for ( const QString &key : keys )
385 {
386 QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
387 treeWidgetItemChild->setFont( 0, monospaceFont() );
388 refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
389 treeWidgetItem->addChild( treeWidgetItemChild );
390 treeWidgetItem->setExpanded( true );
391 }
392 }
393 break;
394 case QJsonValue::Undefined:
395 refreshTreeViewItemValue( treeWidgetItem,
396 QStringLiteral( "Undefined value" ),
398 break;
399 }
400}
401
402void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
403{
404 QLabel *label = new QLabel( jsonValueString );
405 label->setFont( monospaceFont() );
406
407 if ( textColor.isValid() )
408 label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
409 mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
410}
411
412QFont QgsJsonEditWidget::monospaceFont() const
413{
415 // use standard widget font size, not code editor font size
416 f.setPointSize( font().pointSize() );
417 return f;
418}
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.