QGIS API Documentation  3.23.0-Master (c716e02dd3)
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 
63 void QgsJsonEditWidget::setJsonText( const QString &jsonText )
64 {
65  mJsonText = jsonText;
66  mClickableLinkList.clear();
67 
68  const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
69 
70  mCodeEditorJson->blockSignals( true );
71  if ( jsonDocument.isNull() )
72  {
73  mCodeEditorJson->setText( mJsonText );
74  }
75  else
76  {
77  switch ( mFormatJsonMode )
78  {
80  mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Indented ) );
81  break;
83  mCodeEditorJson->setText( jsonDocument.toJson( QJsonDocument::Compact ) );
84  break;
86  mCodeEditorJson->setText( mJsonText );
87  break;
88  }
89  }
90  mCodeEditorJson->blockSignals( false );
91 
92  refreshTreeView( jsonDocument );
93 }
94 
96 {
97  return mJsonText;
98 }
99 
101 {
102  switch ( view )
103  {
104  case View::Text:
105  {
106  mStackedWidget->setCurrentWidget( mStackedWidgetPageText );
107  mTextToolButton->setChecked( true );
108  mTreeToolButton->setChecked( false );
109  }
110  break;
111  case View::Tree:
112  {
113  mStackedWidget->setCurrentWidget( mStackedWidgetPageTree );
114  mTreeToolButton->setChecked( true );
115  mTextToolButton->setChecked( false );
116  }
117  break;
118  }
119 }
120 
122 {
123  mFormatJsonMode = formatJson;
124 }
125 
127 {
128  mControlsWidget->setVisible( visible );
129 }
130 
131 void QgsJsonEditWidget::textToolButtonClicked( bool checked )
132 {
133  if ( checked )
134  setView( View::Text );
135  else
136  setView( View::Tree );
137 }
138 
139 void QgsJsonEditWidget::treeToolButtonClicked( bool checked )
140 {
141  if ( checked )
142  setView( View::Tree );
143  else
144  setView( View::Text );
145 }
146 
147 void QgsJsonEditWidget::copyValueActionTriggered()
148 {
149  if ( !mTreeWidget->currentItem() )
150  return;
151 
152  const QJsonValue jsonValue = QJsonValue::fromVariant( mTreeWidget->currentItem()->data( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole ) );
153 
154  switch ( jsonValue.type() )
155  {
156  case QJsonValue::Null:
157  case QJsonValue::Bool:
158  case QJsonValue::Double:
159  case QJsonValue::Undefined:
160  QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Value ) ) );
161  break;
162  case QJsonValue::String:
163  QApplication::clipboard()->setText( jsonValue.toString() );
164  break;
165  case QJsonValue::Array:
166  {
167  const QJsonDocument jsonDocument( jsonValue.toArray() );
168  QApplication::clipboard()->setText( jsonDocument.toJson() );
169  }
170  break;
171  case QJsonValue::Object:
172  {
173  const QJsonDocument jsonDocument( jsonValue.toObject() );
174  QApplication::clipboard()->setText( jsonDocument.toJson() );
175  }
176  break;
177  }
178 }
179 
180 void QgsJsonEditWidget::copyKeyActionTriggered()
181 {
182  if ( !mTreeWidget->currentItem() )
183  return;
184 
185  QApplication::clipboard()->setText( mTreeWidget->currentItem()->text( static_cast<int>( TreeWidgetColumn::Key ) ) );
186 }
187 
188 void QgsJsonEditWidget::codeEditorJsonTextChanged()
189 {
190  mJsonText = mCodeEditorJson->text();
191  const QJsonDocument jsonDocument = QJsonDocument::fromJson( mJsonText.toUtf8() );
192  refreshTreeView( jsonDocument );
193 }
194 
195 void QgsJsonEditWidget::codeEditorJsonIndicatorClicked( int line, int index, Qt::KeyboardModifiers state )
196 {
197  if ( !state.testFlag( Qt::ControlModifier ) )
198  return;
199 
200  const int position = mCodeEditorJson->positionFromLineIndex( line, index );
201  const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
202  SCINTILLA_UNDERLINE_INDICATOR_INDEX,
203  position );
204  if ( clickableLinkListIndex <= 0 )
205  return;
206 
207  QDesktopServices::openUrl( mClickableLinkList.at( clickableLinkListIndex - 1 ) );
208 }
209 
210 void QgsJsonEditWidget::codeEditorJsonDwellStart( int position, int x, int y )
211 {
212  Q_UNUSED( x )
213  Q_UNUSED( y )
214 
215  const int clickableLinkListIndex = mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORVALUEAT,
216  SCINTILLA_UNDERLINE_INDICATOR_INDEX,
217  position );
218  if ( clickableLinkListIndex <= 0 )
219  return;
220 
221  QToolTip::showText( QCursor::pos(),
222  tr( "%1\nCTRL + click to follow link" ).arg( mClickableLinkList.at( clickableLinkListIndex - 1 ) ) );
223 }
224 
225 void 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 
233 void QgsJsonEditWidget::refreshTreeView( const QJsonDocument &jsonDocument )
234 {
235  mTreeWidget->clear();
236 
237  if ( jsonDocument.isNull() )
238  {
239  setView( View::Text );
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  refreshTreeViewItem( treeWidgetItem, jsonValue );
260  mTreeWidget->addTopLevelItem( treeWidgetItem );
261  mTreeWidget->expandItem( treeWidgetItem );
262  }
263  }
264  else if ( jsonDocument.isArray() )
265  {
266  for ( int index = 0; index < jsonDocument.array().size(); index++ )
267  {
268  QTreeWidgetItem *treeWidgetItem = new QTreeWidgetItem( mTreeWidget, QStringList() << QString::number( index ) );
269  refreshTreeViewItem( treeWidgetItem, jsonDocument.array().at( index ) );
270  mTreeWidget->addTopLevelItem( treeWidgetItem );
271  mTreeWidget->expandItem( treeWidgetItem );
272  }
273  }
274 
275  mTreeWidget->resizeColumnToContents( static_cast<int>( TreeWidgetColumn::Key ) );
276 }
277 
278 void QgsJsonEditWidget::refreshTreeViewItem( QTreeWidgetItem *treeWidgetItem, const QJsonValue &jsonValue )
279 {
280  treeWidgetItem->setData( static_cast<int>( TreeWidgetColumn::Value ), Qt::UserRole, jsonValue.toVariant() );
281 
282  switch ( jsonValue.type() )
283  {
284  case QJsonValue::Null:
285  refreshTreeViewItemValue( treeWidgetItem,
286  QStringLiteral( "null" ),
288  break;
289  case QJsonValue::Bool:
290  refreshTreeViewItemValue( treeWidgetItem,
291  jsonValue.toBool() ? QStringLiteral( "true" ) : QStringLiteral( "false" ),
293  break;
294  case QJsonValue::Double:
295  refreshTreeViewItemValue( treeWidgetItem,
296  QString::number( jsonValue.toDouble() ),
298  break;
299  case QJsonValue::String:
300  {
301  const QString jsonValueString = jsonValue.toString();
302  if ( QUrl( jsonValueString ).scheme().isEmpty() )
303  {
304  refreshTreeViewItemValue( treeWidgetItem,
305  jsonValueString,
307  }
308  else
309  {
310  QLabel *label = new QLabel( QString( "<a href='%1'>%1</a>" ).arg( jsonValueString ) );
311  label->setOpenExternalLinks( true );
312  mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
313 
314  mClickableLinkList.append( jsonValueString );
315  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_SETINDICATORVALUE, static_cast< int >( mClickableLinkList.size() ) );
316  mCodeEditorJson->SendScintilla( QsciScintillaBase::SCI_INDICATORFILLRANGE,
317  mCodeEditorJson->text().indexOf( jsonValueString ),
318  jsonValueString.size() );
319  }
320  }
321  break;
322  case QJsonValue::Array:
323  {
324  const QJsonArray jsonArray = jsonValue.toArray();
325  for ( int index = 0; index < jsonArray.size(); index++ )
326  {
327  QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << QString::number( index ) );
328  refreshTreeViewItem( treeWidgetItemChild, jsonArray.at( index ) );
329  treeWidgetItem->addChild( treeWidgetItemChild );
330  treeWidgetItem->setExpanded( true );
331  }
332  }
333  break;
334  case QJsonValue::Object:
335  {
336  const QJsonObject jsonObject = jsonValue.toObject();
337  const QStringList keys = jsonObject.keys();
338  for ( const QString &key : keys )
339  {
340  QTreeWidgetItem *treeWidgetItemChild = new QTreeWidgetItem( treeWidgetItem, QStringList() << key );
341  refreshTreeViewItem( treeWidgetItemChild, jsonObject.value( key ) );
342  treeWidgetItem->addChild( treeWidgetItemChild );
343  treeWidgetItem->setExpanded( true );
344  }
345  }
346  break;
347  case QJsonValue::Undefined:
348  refreshTreeViewItemValue( treeWidgetItem,
349  QStringLiteral( "Undefined value" ),
351  break;
352  }
353 }
354 
355 void QgsJsonEditWidget::refreshTreeViewItemValue( QTreeWidgetItem *treeWidgetItem, const QString &jsonValueString, const QColor &textColor )
356 {
357  QLabel *label = new QLabel( jsonValueString );
358  if ( textColor.isValid() )
359  label->setStyleSheet( QStringLiteral( "color: %1;" ).arg( textColor.name() ) );
360  mTreeWidget->setItemWidget( treeWidgetItem, static_cast<int>( TreeWidgetColumn::Value ), label );
361 }
@ DoubleQuote
Double quote color.
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.
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.