Skip to content

Commit 927da79

Browse files
committed
macro browser treeWidget
- handle network error without message box - qmap for deterministic order
1 parent 9e80616 commit 927da79

File tree

2 files changed

+142
-126
lines changed

2 files changed

+142
-126
lines changed

src/macrobrowserui.cpp

Lines changed: 130 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
#include <QJsonDocument>
55
#include <QJsonArray>
66

7+
const int MacroBrowserUI::FileRole = Qt::UserRole + 0;
8+
const int MacroBrowserUI::UrlRole = Qt::UserRole + 1;
9+
const int MacroBrowserUI::PathRole = Qt::UserRole + 2;
10+
const int MacroBrowserUI::PopulatedRole = Qt::UserRole + 3;
11+
Q_DECLARE_METATYPE(QTreeWidgetItem *)
712

813
MacroBrowserUI::MacroBrowserUI(QWidget *parent):QDialog (parent)
914
{
10-
tableWidget=new QTableWidget(4,1);
11-
tableWidget->setHorizontalHeaderLabels(QStringList()<<tr("Macro name"));
12-
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
13-
tableWidget->horizontalHeader()->setStretchLastSection(true);
14-
connect(tableWidget,SIGNAL(itemClicked(QTableWidgetItem*)),SLOT(itemClicked(QTableWidgetItem*)));
15+
treeWidget=new QTreeWidget(parent);
16+
treeWidget->setHeaderHidden(true);
1517
auto *lblName=new QLabel(tr("Name"));
1618
lblName->setAlignment(Qt::AlignRight);
1719
auto *lblDescription=new QLabel(tr("Description"));
@@ -24,16 +26,14 @@ MacroBrowserUI::MacroBrowserUI(QWidget *parent):QDialog (parent)
2426
gridLay->setColumnStretch(0,1);
2527
gridLay->setColumnStretch(1,0);
2628
gridLay->setColumnStretch(2,1);
27-
gridLay->addWidget(tableWidget,1,0,5,1);
29+
gridLay->addWidget(treeWidget,1,0,5,1);
2830
gridLay->addWidget(lblName,1,1);
2931
gridLay->addWidget(lblDescription,2,1);
3032
gridLay->addWidget(leName,1,2);
31-
gridLay->addWidget(teDescription,2,2);
33+
gridLay->addWidget(teDescription,2,2,4,1);
3234

3335
buttonBox=new QDialogButtonBox();
3436
buttonBox->setStandardButtons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
35-
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
36-
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
3737
auto *layout=new QVBoxLayout();
3838
layout->addLayout(gridLay);
3939
layout->addWidget(buttonBox);
@@ -42,7 +42,13 @@ MacroBrowserUI::MacroBrowserUI(QWidget *parent):QDialog (parent)
4242
config=dynamic_cast<ConfigManager *>(ConfigManagerInterface::getInstance());
4343
networkManager = new QNetworkAccessManager();
4444

45-
requestMacroList("");
45+
connect(treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),SLOT(slotCurrentItemChanged(QTreeWidgetItem*)));
46+
connect(treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)),SLOT(slotItemClicked(QTreeWidgetItem*)));
47+
connect(treeWidget, SIGNAL(itemActivated(QTreeWidgetItem*,int)),SLOT(slotItemClicked(QTreeWidgetItem*)));
48+
connect(treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)),SLOT(slotItemExpanded(QTreeWidgetItem*)));
49+
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
50+
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
51+
requestMacroList();
4652
}
4753

4854
MacroBrowserUI::~MacroBrowserUI()
@@ -51,20 +57,16 @@ MacroBrowserUI::~MacroBrowserUI()
5157
networkManager->deleteLater();
5258
networkManager=nullptr;
5359
}
54-
foreach(QList<QTableWidgetItem *>lst,itemCache){
55-
foreach(auto *item,lst){
56-
delete item;
57-
}
58-
}
60+
treeWidget->~QTreeWidget();
5961
}
6062

6163
QList<Macro> MacroBrowserUI::getSelectedMacros()
6264
{
6365
QList<Macro> lst;
64-
foreach(QList<QTableWidgetItem *>listOfItems,itemCache){
66+
foreach(QList<QTreeWidgetItem *>listOfItems,itemCache){
6567
foreach(auto *item,listOfItems){
66-
if(item->checkState()==Qt::Checked){
67-
QString url=item->data(Qt::UserRole).toString();
68+
if(item->checkState(0)==Qt::Checked) {
69+
QString url=item->data(0,UrlRole).toString();
6870
QString macroJson=cache.value(url);
6971
if(!macroJson.isEmpty()){
7072
Macro m;
@@ -78,25 +80,34 @@ QList<Macro> MacroBrowserUI::getSelectedMacros()
7880
return lst;
7981
}
8082

81-
const QNetworkRequest::Attribute AttributeDirectURL = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User);
82-
const QNetworkRequest::Attribute AttributeURL = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User+1);
83+
const QNetworkRequest::Attribute mbAttributeItem = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User);
84+
const QNetworkRequest::Attribute mbAttributeIsFile = static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User+1);
8385

84-
void MacroBrowserUI::requestMacroList(const QString &path,const bool &directURL)
86+
/* Note (1): The elements of the top level directory of the repository are added as top level items to the treeWidget. Thus they have no
87+
* parent item (folder). Since folder items carry the path in the tree and the (repository) url as data, it is necessary to distinguish
88+
* this situation where we use "" as path (think of the treeWidget as a folder).
89+
*/
90+
void MacroBrowserUI::requestMacroList(QTreeWidgetItem *currentItem,const bool &isFile)
8591
{
8692
if(!networkManager){
87-
return;
93+
networkManager = new QNetworkAccessManager();
94+
if(!networkManager) return;
8895
}
89-
QString url=config->URLmacroRepository+path;
90-
//QString url="https://api.github.com/repos/sunderme/texstudio-macro/contents/"+path;
91-
if(directURL){
92-
url=path;
96+
QString path=(currentItem ? currentItem->data(0,PathRole).toString() : ""); // s. (1)
97+
QString url;
98+
if(isFile) {
99+
// like https://raw.githubusercontent.com/texstudio-org/texstudio-macro/master/automatedTextmanipulation/autoLabel.txsMacro
100+
url=(currentItem ? currentItem->data(0,UrlRole).toString() : ""); // s. (1)
93101
}else{
94-
currentPath=path;
102+
path=(currentItem ? currentItem->data(0,PathRole).toString() : ""); // s. (1)
103+
// like https://api.github.com/repos/texstudio/texstudio-macro/contents/automatedTextmanipulation
104+
url=config->URLmacroRepository+path;
95105
}
106+
96107
QNetworkRequest request = QNetworkRequest(QUrl(url));
97108
request.setRawHeader("User-Agent", "TeXstudio Macro Browser");
98-
request.setAttribute(AttributeDirectURL,directURL);
99-
request.setAttribute(AttributeURL,url);
109+
request.setAttribute(mbAttributeIsFile,isFile);
110+
request.setAttribute(mbAttributeItem,QVariant::fromValue(currentItem));
100111
QNetworkReply *reply = networkManager->get(request);
101112
connect(reply, SIGNAL(finished()), SLOT(onRequestCompleted()));
102113
#if QT_VERSION_MAJOR<6
@@ -106,71 +117,20 @@ void MacroBrowserUI::requestMacroList(const QString &path,const bool &directURL)
106117
#endif
107118
}
108119

109-
void MacroBrowserUI::itemClicked(QTableWidgetItem *item)
110-
{
111-
QString url=item->data(Qt::UserRole).toString();
112-
if(url.isEmpty()){
113-
// descend into folder
114-
leName->setText("");
115-
teDescription->setPlainText("");
116-
if(item->text()==".."){
117-
int c=currentPath.lastIndexOf('/');
118-
url=currentPath.left(c);
119-
}else{
120-
url=currentPath+"/"+item->text();
121-
}
122-
if(itemCache.contains(url)){
123-
// reuse cached
124-
currentPath=url;
125-
int i=0;
126-
for(int i=0;i<tableWidget->rowCount();i++){
127-
tableWidget->takeItem(i,0);
128-
}
129-
if(!url.isEmpty()){
130-
auto *item=new QTableWidgetItem(QIcon::fromTheme("file"),"..");
131-
tableWidget->setRowCount(i+1);
132-
tableWidget->setItem(i++,0,item);
133-
}
134-
foreach(QTableWidgetItem *item,itemCache.value(url)){
135-
tableWidget->setRowCount(i+1);
136-
tableWidget->setItem(i++,0,item);
137-
}
138-
}else{
139-
requestMacroList(url);
140-
}
141-
}else{
142-
if(cache.contains(url)){
143-
// reuse cached
144-
QByteArray ba = cache.value(url).toUtf8();
145-
QJsonDocument jsonDoc=QJsonDocument::fromJson(ba);
146-
QJsonObject dd=jsonDoc.object();
147-
leName->setText(dd["name"].toString());
148-
QJsonArray array=dd["description"].toArray();
149-
QVariantList vl=array.toVariantList();
150-
QString text;
151-
foreach(auto v,vl){
152-
if(!text.isEmpty()){
153-
text+="\n";
154-
}
155-
text+=v.toString();
156-
}
157-
teDescription->setPlainText(text);
158-
}
159-
else{
160-
requestMacroList(url,true);
161-
}
162-
}
163-
}
164-
165120
void MacroBrowserUI::onRequestError()
166121
{
167122
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
168123
if (!reply) return;
169124

170-
QMessageBox::warning(this, tr("Browse macro repository"),
171-
tr("Repository not found. Network error:%1").arg("\n"+reply->errorString()),
172-
QMessageBox::Ok,
173-
QMessageBox::Ok);
125+
QString text=tr("Repository not found. Network error:%1");
126+
QTreeWidgetItem *currentItem=reply->request().attribute(mbAttributeItem).value<QTreeWidgetItem*>();
127+
if (currentItem) {
128+
currentItem->child(0)->setText(0,text.arg("\n"+reply->errorString()));
129+
}else{
130+
QTreeWidgetItem *twi = new QTreeWidgetItem(QStringList() << text.arg("\n"+reply->errorString()));
131+
treeWidget->addTopLevelItem(twi);
132+
}
133+
174134
networkManager->deleteLater();
175135
networkManager=nullptr;
176136
}
@@ -180,10 +140,23 @@ void MacroBrowserUI::onRequestCompleted()
180140
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
181141
if (!reply || reply->error() != QNetworkReply::NoError) return;
182142

143+
QTreeWidgetItem *currentItem=reply->request().attribute(mbAttributeItem).value<QTreeWidgetItem*>();
144+
bool isFile=reply->request().attribute(mbAttributeIsFile).toBool();
145+
QString path;
146+
QString url;
147+
if(isFile) {
148+
url=(currentItem ? currentItem->data(0,UrlRole).toString() : ""); // s. (1)
149+
}else{
150+
path=(currentItem ? currentItem->data(0,PathRole).toString() : ""); // s. (1)
151+
url=config->URLmacroRepository+path;
152+
}
153+
154+
if(path!="" && !currentItem) return;
155+
183156
QByteArray ba = reply->readAll();
184157

185-
if(reply->request().attribute(AttributeDirectURL).toBool()){
186-
// download requested
158+
if(isFile){
159+
// file download requested
187160
QJsonDocument jsonDoc=QJsonDocument::fromJson(ba);
188161
QJsonObject dd=jsonDoc.object();
189162
leName->setText(dd["name"].toString());
@@ -198,53 +171,89 @@ void MacroBrowserUI::onRequestCompleted()
198171
}
199172
teDescription->setPlainText(text);
200173
// cache complete macro
201-
QString url=reply->request().attribute(AttributeURL).toString();
202174
cache.insert(url,QString(ba));
203175
}else{
204-
// folder overview requested
205-
//tableWidget->clearContents();
206-
for(int i=0;i<tableWidget->rowCount();i++){
207-
tableWidget->takeItem(i,0);
208-
}
176+
// folder contents requested
209177
QJsonDocument jsonDoc=QJsonDocument::fromJson(ba);
210178
QJsonArray elements=jsonDoc.array();
211-
int i=0;
212-
// add .. (up)
213-
if(!currentPath.isEmpty()){
214-
auto *item=new QTableWidgetItem(QIcon::fromTheme("file"),"..");
215-
tableWidget->setRowCount(i+1);
216-
tableWidget->setItem(i++,0,item);
179+
if (currentItem) { // not needed when inserting top level items, s. (1)
180+
currentItem->takeChildren();
181+
currentItem->setData(0,PopulatedRole,true);
217182
}
218-
QList<QTableWidgetItem*> listOfItems;
183+
QList<QTreeWidgetItem*> listOfItems;
219184
foreach(auto element,elements){
220185
QJsonObject dd=element.toObject();
221186
if(dd["type"].toString()=="file"){
222187
QString name=dd["name"].toString();
223188
if(name.endsWith(".txsMacro")){
224-
auto *item=new QTableWidgetItem(QIcon::fromTheme("file"),name);
225-
item->setData(Qt::UserRole,dd["download_url"].toString());
226-
item->setCheckState(Qt::Unchecked);
227-
tableWidget->setRowCount(i+1);
228-
tableWidget->setItem(i++,0,item);
229-
if(i==1){
230-
requestMacroList(item->data(Qt::UserRole).toString(),true);
231-
}
232-
listOfItems<<item;
189+
auto *twi=new QTreeWidgetItem(QStringList()<<name);
190+
twi->setData(0,FileRole,true);
191+
twi->setData(0,UrlRole,dd["download_url"].toString());
192+
twi->setCheckState(0,Qt::Unchecked);
193+
currentItem->addChild(twi);
194+
listOfItems<<twi;
233195
}
234-
}else{
235-
// folder
196+
}else{ // folder
236197
QString name=dd["name"].toString();
237-
auto *item=new QTableWidgetItem(QIcon::fromTheme("folder"),name);
238-
tableWidget->setRowCount(i+1);
239-
tableWidget->setItem(i++,0,item);
198+
auto *item=new QTreeWidgetItem(QStringList()<<name);
199+
QFont ft = item->font(0);
200+
ft.setBold(true);
201+
item->setFont(0,ft);
202+
item->setText(0,name);
203+
QTreeWidgetItem *twi = new QTreeWidgetItem(QStringList() << tr("<loading...>"));
204+
item->addChild(twi);
205+
item->setData(0,FileRole,false);
206+
item->setData(0,UrlRole, url);
207+
item->setData(0,PathRole, path+name);
208+
if(!currentItem) treeWidget->addTopLevelItem(item);
209+
else item->addChild(item);
240210
listOfItems<<item;
241211
}
242-
//tableWidget->setRowCount(i);
243212
}
244-
tableWidget->setCurrentCell(0,0);
245-
itemCache.insert(currentPath,listOfItems);
213+
itemCache.insert(path,listOfItems);
246214
}
247215
}
248216

217+
void MacroBrowserUI::slotCurrentItemChanged(QTreeWidgetItem *item)
218+
{
219+
bool isFile=item->data(0,FileRole).toBool();
220+
if(!isFile){
221+
leName->setText("");
222+
teDescription->setPlainText("");
223+
}else{
224+
QString url=item->data(0,UrlRole).toString();
225+
if(cache.contains(url)){
226+
// reuse cached
227+
QByteArray ba = cache.value(url).toUtf8();
228+
QJsonDocument jsonDoc=QJsonDocument::fromJson(ba);
229+
QJsonObject dd=jsonDoc.object();
230+
leName->setText(dd["name"].toString());
231+
QJsonArray array=dd["description"].toArray();
232+
QVariantList vl=array.toVariantList();
233+
QString text;
234+
foreach(auto v,vl){
235+
if(!text.isEmpty()){
236+
text+="\n";
237+
}
238+
text+=v.toString();
239+
}
240+
teDescription->setPlainText(text);
241+
}
242+
else{
243+
requestMacroList(item,true);
244+
}
245+
}
246+
}
249247

248+
void MacroBrowserUI::slotItemClicked(QTreeWidgetItem *item)
249+
{
250+
bool isFile=item->data(0,FileRole).toBool();
251+
if(!isFile) return;
252+
treeWidget->setCurrentItem(item); // this may trigger slotCurrentItemChanged
253+
}
250254

255+
void MacroBrowserUI::slotItemExpanded(QTreeWidgetItem *item){
256+
bool populated=item->data(0,PopulatedRole).toBool();
257+
if(populated) return;
258+
requestMacroList(item);
259+
}

src/macrobrowserui.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,24 @@ class MacroBrowserUI : public QDialog
2323
private slots:
2424
void onRequestError();
2525
void onRequestCompleted();
26-
void requestMacroList(const QString &path="",const bool &directURL=false);
27-
void itemClicked(QTableWidgetItem *item);
26+
void requestMacroList(QTreeWidgetItem *item=nullptr,const bool &isFile=false);
27+
void slotItemExpanded(QTreeWidgetItem *item);
28+
void slotCurrentItemChanged(QTreeWidgetItem *item);
29+
void slotItemClicked(QTreeWidgetItem *item);
30+
31+
private:
32+
static const int FileRole;
33+
static const int UrlRole;
34+
static const int PathRole;
35+
static const int PopulatedRole;
2836

2937
protected:
30-
QTableWidget *tableWidget;
38+
QTreeWidget *treeWidget;
3139
QDialogButtonBox *buttonBox;
3240
QLineEdit *leName;
3341
QPlainTextEdit *teDescription;
34-
QString currentPath;
3542
QHash<QString,QString> cache;
36-
QHash<QString,QList<QTableWidgetItem *> > itemCache;
43+
QMap<QString,QList<QTreeWidgetItem *> > itemCache;
3744

3845
ConfigManager *config;
3946

0 commit comments

Comments
 (0)