source: src-qt4/pc-softwaremanager/mainUI.cpp @ 6763bdf

9.2-releasereleng/10.0releng/10.0.1releng/10.0.2releng/10.0.3releng/10.1
Last change on this file since 6763bdf was 6763bdf, checked in by Ken Moore <ken@…>, 13 months ago

Have the AppCafe? also check/update the installed PBI icons whenever it just updates a single PBI. This should catch those rare instances where the AppCafe? is aware of an installed PBI before the icon is added to the PBI database and update the icon on the TreeWidgetItem?.

  • Property mode set to 100644
File size: 41.0 KB
Line 
1/***************************************************************************
2 *   Copyright (C) 2011 - iXsystems                                       *
3 *   kris@pcbsd.org  *
4 *   tim@pcbsd.org   *
5 *   ken@pcbsd.org   *
6 *                                                                         *
7 *   Permission is hereby granted, free of charge, to any person obtaining *
8 *   a copy of this software and associated documentation files (the       *
9 *   "Software"), to deal in the Software without restriction, including   *
10 *   without limitation the rights to use, copy, modify, merge, publish,   *
11 *   distribute, sublicense, and/or sell copies of the Software, and to    *
12 *   permit persons to whom the Software is furnished to do so, subject to *
13 *   the following conditions:                                             *
14 *                                                                         *
15 *   The above copyright notice and this permission notice shall be        *
16 *   included in all copies or substantial portions of the Software.       *
17 *                                                                         *
18 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
19 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
20 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
21 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
22 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
23 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
24 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
25 ***************************************************************************/
26#include "mainUI.h"
27#include "ui_mainUI.h"
28
29MainUI::MainUI(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainUI){
30  //initialization function
31  ui->setupUi(this); //load the Qt-Designer file
32  defaultIcon = ":/application.png";
33  statusLabel = new QLabel();
34  ui->statusbar->addWidget(statusLabel);
35}
36
37void MainUI::setWardenMode(QString dir, QString ip){
38  wardenDir = dir; wardenIP = ip;
39}
40
41void MainUI::ProgramInit()
42{ 
43   qDebug("Application starting...");
44   //Initial Checks
45     bool wardenMode = !wardenDir.isEmpty();
46     bool ok = Extras::checkUser(wardenMode);
47     if(!ok && wardenMode){  //Not root
48       QMessageBox::information( this, tr("Error!"), tr("The AppCafe must be run as root when in Warden compatibility mode") );
49       close();     
50     }else if( !ok && !wardenMode){
51       QMessageBox::information( this, tr("Error!"), tr("The AppCafe must be started with user permissions!")+"\n"+tr("The user must also be a part of the \"operator\" group") );
52       close();
53     }
54   
55   //Initialize the backend worker class
56   //qDebug() << "Initialize Backend";
57   PBI = new PBIBackend();
58     if(wardenMode){ PBI->setWardenMode(wardenDir, wardenIP); }
59     
60   //Initialize the Installed tab
61   //qDebug() << "Initialize Installed Tab";
62   initializeInstalledTab();
63   //Initialize the PBI Browser
64   //qDebug() << "Initialize Browser Tab";
65   initializeBrowserTab();
66   
67   //Now startup the backend
68   //qDebug() << "Startup Backend";
69     connect(PBI,SIGNAL(LocalPBIChanges()),this,SLOT(slotRefreshInstallTab()) );
70     connect(PBI,SIGNAL(PBIStatusChange(QString)),this,SLOT(slotPBIStatusUpdate(QString)) );
71     connect(PBI,SIGNAL(RepositoryInfoReady()),this,SLOT(slotEnableBrowser()) );
72     connect(PBI,SIGNAL(NoRepoAvailable()),this,SLOT(slotDisableBrowser()) );
73     connect(PBI,SIGNAL(SearchComplete(QStringList,QStringList)),this,SLOT(slotShowSearchResults(QStringList, QStringList)) );
74     connect(PBI,SIGNAL(SimilarFound(QStringList)),this,SLOT(slotShowSimilarApps(QStringList)) );
75     connect(PBI,SIGNAL(Error(QString,QString,QStringList)),this,SLOT(slotDisplayError(QString,QString,QStringList)) );
76     PBI->start();
77
78   //Make sure we start on the installed tab
79   ui->tabWidget->setCurrentWidget(ui->tab_installed);
80}
81
82void MainUI::slotSingleInstance(){
83  this->raise();
84  this->showNormal();
85  this->activateWindow();
86}
87
88void MainUI::closeEvent(QCloseEvent *event){
89  bool safe = PBI->safeToQuit();
90  if(!safe){
91    //Verify that they want to continue
92    QMessageBox::StandardButton button = QMessageBox::warning(this, tr("AppCafe Processes Running"), tr("The AppCafe currently has actions pending. Do you want to cancel all running processes and quit anyway?"), QMessageBox::Yes | QMessageBox::Cancel,QMessageBox::Cancel);
93    if(button == QMessageBox::Yes){ //close down
94      PBI->cancelActions( PBI->installedList() ); //close down safely
95    }else{
96      event->ignore();
97      return;
98    }
99  }
100  this->close();
101}
102// ========================
103// ===== MENU OPTIONS =====
104// ========================
105void MainUI::on_actionImport_PBI_List_triggered(){
106  QString file = QFileDialog::getOpenFileName( this, tr("Import PBI File List"), QDir::homePath(), tr("PBI List (*.pbilist)"));
107  if(file.isEmpty()){ return; } //action cancelled
108  bool ok = PBI->importPbiListFromFile(file);
109  if(!ok){ qDebug() << QMessageBox::warning(this,tr("Import Error"),tr("There was an error importing the PBI list")+"\n"+tr("Please make sure that the file has not been corrupted and try again")); }
110}
111
112void MainUI::on_actionExport_PBI_List_triggered(){
113  QString file = QFileDialog::getSaveFileName( this, tr("Export PBI File List"), QDir::homePath()+"/exportfile.pbilist", tr("PBI List (*.pbilist)"));
114  if(file.isEmpty()){ return; } //action cancelled
115  bool ok = PBI->exportPbiListToFile(file);
116  if(!ok){ qDebug() << QMessageBox::warning(this,tr("Export Error"),tr("There was an error exporting the PBI list")+"\n"+tr("Please make sure that you have the proper directory permissions and try again")); }
117}
118
119void MainUI::on_actionQuit_triggered(){
120  this->close();
121}
122
123void MainUI::on_actionAppCafe_Settings_triggered(){
124  PBI->openConfigurationDialog();
125}
126
127// =========================
128// ===== INSTALLED TAB =====
129// =========================
130void MainUI::initializeInstalledTab(){
131  //Setup the action menu for installed applications
132  actionMenu = new QMenu();
133    actionMenu->addAction( QIcon(":icons/view-refresh.png"), tr("Update"), this, SLOT(slotActionUpdate()) );
134    actionMenu->addSeparator();
135    QMenu *dmenu = actionMenu->addMenu( QIcon(":icons/xdg_desktop.png"), tr("Desktop Icons"));
136      dmenu->addAction( QIcon(":icons/add.png"),tr("Add"),this,SLOT(slotActionAddDesktop()) );
137      dmenu->addAction( QIcon(":icons/remove.png"),tr("Remove"),this,SLOT(slotActionRemoveDesktop()) );
138    QMenu *mmenu = actionMenu->addMenu( QIcon(":icons/xdg_menu.png"), tr("Menu Icons"));
139      mmenu->addAction( QIcon(":icons/add.png"),tr("Add"),this,SLOT(slotActionAddMenu()) );
140      mmenu->addAction( QIcon(":icons/remove.png"),tr("Remove"),this,SLOT(slotActionRemoveMenu()) ); 
141      mmenu->addAction( QIcon(":icons/add-root.png"),tr("Add (All Users)"),this,SLOT(slotActionAddMenuAll()) );
142    QMenu *pmenu = actionMenu->addMenu( QIcon(":icons/xdg_paths.png"), tr("Path Links"));
143      pmenu->addAction( QIcon(":icons/add.png"),tr("Add"),this,SLOT(slotActionAddPath()) );
144      pmenu->addAction( QIcon(":icons/remove.png"),tr("Remove"),this,SLOT(slotActionRemovePath()) ); 
145      pmenu->addAction( QIcon(":icons/add-root.png"),tr("Add (All Users)"),this,SLOT(slotActionAddPathAll()) );
146    QMenu *fmenu = actionMenu->addMenu( QIcon(":icons/xdg_mime.png"), tr("File Associations"));
147      fmenu->addAction( QIcon(":icons/add.png"),tr("Add"),this,SLOT(slotActionAddMime()) );
148      fmenu->addAction( QIcon(":icons/remove.png"),tr("Remove"),this,SLOT(slotActionRemoveMime()) ); 
149      fmenu->addAction( QIcon(":icons/add-root.png"),tr("Add (All Users)"),this,SLOT(slotActionAddMimeAll()) );
150    actionMenu->addSeparator();
151    actionMenu->addAction( QIcon(":icons/remove.png"), tr("Uninstall"), this, SLOT(slotActionRemove()) );
152    actionMenu->addSeparator();
153    actionMenu->addAction( QIcon(":icons/dialog-cancel.png"), tr("Cancel Actions"), this, SLOT(slotActionCancel()) );
154  //Setup the binary menu for installed applications
155  appBinMenu = new QMenu();
156  ui->tool_install_startApp->setMenu(appBinMenu);
157    connect(appBinMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotStartApp(QAction*)) );
158  //Now setup the action button
159  ui->tool_install_performaction->setMenu(actionMenu);
160  ui->tool_install_performaction->setPopupMode(QToolButton::InstantPopup);
161  //Now setup any defaults for the installed tab
162  ui->tool_install_gotobrowserpage->setEnabled(FALSE); //disable it until the browser is ready
163  ui->tree_install_apps->setIconSize(QSize(22,22));
164  slotRefreshInstallTab();
165}
166
167void MainUI::formatInstalledItemDisplay(QTreeWidgetItem *item){
168  //simplification function for filling the tree widget item with the appropriate information about the PBI
169  QString ID = item->whatsThis(0);
170  if(item->text(0).isEmpty()){  //new entry - get everything
171    QStringList vals; vals <<"name"<<"version"<<"status"; //same as column order
172    if(!vals.isEmpty()){
173      vals = PBI->PBIInfo(ID, vals);
174      for(int i=0; i<vals.length(); i++){ item->setText(i,vals[i]); }
175      QString icon = PBI->PBIInfo(ID, QStringList("icon")).join("");
176        //Load a default icon if none found
177      if(icon.isEmpty()){ icon = defaultIcon; }
178      item->setIcon(0,QIcon(icon) );
179      item->setCheckState(0,Qt::Unchecked);
180    }
181  }else{ // Just update the necesary info
182    QStringList vals; vals << "version" << "status";
183    vals = PBI->PBIInfo(ID,vals);
184    if(!vals.isEmpty()){ item->setText(1,vals[0]); item->setText(2,vals[1]); }
185  }
186}
187
188QStringList MainUI::getCheckedItems(){
189  //Return the pbiID's of all the checked items
190  QStringList output;
191  for(int i=0; i<ui->tree_install_apps->topLevelItemCount(); i++){
192    if(ui->tree_install_apps->topLevelItem(i)->checkState(0) == Qt::Checked){
193      output << ui->tree_install_apps->topLevelItem(i)->whatsThis(0);
194    }
195  }
196  //If no items checked, use the item that is selected instead
197  if(output.isEmpty()){
198    //Make sure that an item is selected
199    if(ui->tree_install_apps->topLevelItemCount() >0){
200      output << ui->tree_install_apps->currentItem()->whatsThis(0); 
201    }
202  }
203  return output;       
204}
205
206// === SLOTS ===
207void MainUI::slotRefreshInstallTab(){
208  //Update the list of installed PBI's w/o clearing the list (loses selections)
209   //Get the list we need (in order)
210  QStringList installList = PBI->installedList();
211  installList.sort(); //sort in ascending order
212   //Get the list we have now
213  QStringList cList;
214  for(int i=0; i<ui->tree_install_apps->topLevelItemCount(); i++){
215    cList << ui->tree_install_apps->topLevelItem(i)->whatsThis(0);
216  }
217  //Quick finish if no items installed
218  if(installList.isEmpty()){
219    ui->tree_install_apps->clear();
220  }else{
221    //Now make adjustments as necessary
222    for(int i=0; i<installList.length(); i++){
223      //Detemine what action should be done with this item location
224      int todo = 0; //0=insert new item, 1=update current item, 2=remove current item
225      if(i < cList.length()){
226        if(installList[i] == cList[i]){ todo=1; }
227        else if( installList.contains(cList[i]) && !cList.contains(installList[i]) ){ todo=0; } //new item to be inserted here
228        else if( !installList.contains(cList[i]) ){ todo=2; } //current item in this location should be removed
229      }
230      //Now perform the action on this location
231      if(todo==0){ 
232        //insert new item
233        QTreeWidgetItem *item = new QTreeWidgetItem; //create the item
234        item->setWhatsThis(0,installList[i]);
235        //Now format the display
236        formatInstalledItemDisplay(item);
237        if(item->text(0).isEmpty()){
238          //Do not put empty items into the display
239          delete item;
240        }else{
241          //Now insert this item onto the list
242          ui->tree_install_apps->insertTopLevelItem(i,item);
243          cList.insert(i,installList[i]); //reflect this inclusion into the current list
244        }
245      }else if(todo==1){
246        //Update current item
247        formatInstalledItemDisplay( ui->tree_install_apps->topLevelItem(i) );
248      }else{
249        //Remove current item
250        ui->tree_install_apps->takeTopLevelItem(i);
251        cList.removeAt(i); //reflect the change to the current list
252        i--; //Re-check the item that should be in this location
253      }
254    }
255    //Now makesure that there are no extra items at the end
256    int il = installList.length();
257    while(il < cList.length()){
258      ui->tree_install_apps->takeTopLevelItem(il);
259      cList.removeAt(il); //reflect the change to the current list
260    }
261  } //end of empty list check
262 
263  //Make sure that there is an item selected
264  if(ui->tree_install_apps->topLevelItemCount() > 0 ){
265    if( ui->tree_install_apps->selectedItems().isEmpty() ){
266      ui->tree_install_apps->setCurrentItem( ui->tree_install_apps->topLevelItem(0) );
267    }
268    //Now re-size the columns to the minimum required width
269    for(int i=0; i<3; i++){
270      ui->tree_install_apps->resizeColumnToContents(i);
271    } 
272  }
273  slotUpdateSelectedPBI();; //Update the info boxes
274  slotDisplayStats();
275}
276
277void MainUI::slotPBIStatusUpdate(QString pbiID){
278  //This will do a full update of a particlar PBI entry
279  //    and just update/check the icons for all the other PBI's
280  for(int i=0; i<ui->tree_install_apps->topLevelItemCount(); i++){
281    QString itemID = ui->tree_install_apps->topLevelItem(i)->whatsThis(0);
282    if(itemID == pbiID){
283      QString stat = PBI->PBIInfo(pbiID,QStringList()<<"status").join("");
284      ui->tree_install_apps->topLevelItem(i)->setText(2,stat);
285      // See if we need to update the download progress bar
286      QString appID;
287      if(ui->tree_install_apps->topLevelItemCount() > 0){
288         appID = ui->tree_install_apps->currentItem()->whatsThis(0);
289      }
290      if ( appID == pbiID ) {
291        slotUpdateSelectedPBI(true);; //only update status
292      }
293    }else{
294      //Just check/update the icon if necessary
295      if(ui->tree_install_apps->topLevelItem(i)->icon(0).isNull()){
296         ui->tree_install_apps->topLevelItem(i)->setIcon(0, QIcon( PBI->PBIInfo(itemID, QStringList() << "icon").join("") ));
297      }
298    }
299  }
300}
301
302void MainUI::on_group_install_showinfo_toggled(bool show){
303  if(ui->tree_install_apps->topLevelItemCount() > 0){
304    ui->group_install_info->setVisible(show);
305  }else{
306    ui->group_install_info->setVisible(FALSE);   
307  }
308}
309
310void MainUI::on_tool_install_gotobrowserpage_clicked(){
311  //When you want to open up the browser page for an application
312  QString appID = Extras::nameToID( ui->tree_install_apps->currentItem()->text(0) );
313  slotGoToApp(appID);
314}
315
316void MainUI::on_tool_install_toggleall_clicked(){
317  //Determine if the current item is checked or unchecked
318  bool checkall = (ui->tree_install_apps->currentItem()->checkState(0) == Qt::Unchecked);
319  for(int i=0; i<ui->tree_install_apps->topLevelItemCount(); i++){
320    if(checkall){ ui->tree_install_apps->topLevelItem(i)->setCheckState(0,Qt::Checked); }
321    else{ui->tree_install_apps->topLevelItem(i)->setCheckState(0,Qt::Unchecked); }
322  }
323}
324
325void MainUI::on_tree_install_apps_itemSelectionChanged(){
326  //When an installed PBI is clicked on
327  slotUpdateSelectedPBI();
328}
329
330void MainUI::on_check_install_autoupdate_clicked(){
331  //Get the current item
332  QString appID;
333  if(ui->tree_install_apps->topLevelItemCount() > 0){
334    appID = ui->tree_install_apps->currentItem()->whatsThis(0);
335  }
336  if(appID.isEmpty()){return;}
337  //Now determine the current state of the checkbox
338  bool enabled = ui->check_install_autoupdate->isChecked();
339  //Now have the backend make the change
340  PBI->enableAutoUpdate(appID, enabled);
341}
342
343void MainUI::on_tool_install_update_clicked(){
344  //Get the current item
345  QString appID;
346  if(ui->tree_install_apps->topLevelItemCount() > 0){
347    appID = ui->tree_install_apps->currentItem()->whatsThis(0);
348  }
349  if(appID.isEmpty()){return;}
350  PBI->upgradePBI(QStringList() << appID);
351}
352
353void MainUI::on_tool_install_remove_clicked(){
354  //Get the current item
355  QString appID;
356  if(ui->tree_install_apps->topLevelItemCount() > 0){
357    appID = ui->tree_install_apps->currentItem()->whatsThis(0);
358  }
359  if(appID.isEmpty()){return;}
360  //Verify removal
361  if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify PBI Removal"), tr("Are you sure you wish to remove this application?")+"\n\n"+appID,QMessageBox::Yes | QMessageBox::Cancel,QMessageBox::Cancel) ){
362    PBI->removePBI(QStringList() << appID);
363  }
364}
365
366void MainUI::on_tool_install_cancel_clicked(){
367  //Get the current item
368  QString appID;
369  if(ui->tree_install_apps->topLevelItemCount() > 0){
370    appID = ui->tree_install_apps->currentItem()->whatsThis(0);
371  }
372  if(appID.isEmpty()){return;}
373  PBI->cancelActions(QStringList() << appID);
374 
375}
376
377// === SELECTED PBI ACTIONS ===
378void MainUI::slotActionAddDesktop(){
379  QStringList items = getCheckedItems();
380  PBI->addDesktopIcons(items,FALSE); //only for current user
381}
382
383void MainUI::slotActionRemoveDesktop(){
384  QStringList items = getCheckedItems();
385  PBI->rmDesktopIcons(items,FALSE);  //Only for current user
386}
387
388void MainUI::slotActionAddPath(){
389  QStringList items = getCheckedItems();
390  PBI->addPaths(items,FALSE);  //Only for current user 
391}
392
393void MainUI::slotActionRemovePath(){
394  QStringList items = getCheckedItems();
395  PBI->rmPaths(items,FALSE);  //Only for current user
396}
397
398void MainUI::slotActionAddPathAll(){
399  QStringList items = getCheckedItems();
400  PBI->addPaths(items,TRUE);  //For all users (root permissions required)
401}
402
403void MainUI::slotActionAddMenu(){
404  QStringList items = getCheckedItems();
405  PBI->addMenuIcons(items,FALSE);  //Only for current user
406}
407
408void MainUI::slotActionRemoveMenu(){
409  QStringList items = getCheckedItems();
410  PBI->rmMenuIcons(items,FALSE);  //Only for current user       
411}
412
413void MainUI::slotActionAddMenuAll(){
414  QStringList items = getCheckedItems();
415  PBI->addMenuIcons(items,TRUE);  //For all users (root permissions required)   
416}
417
418void MainUI::slotActionAddMime(){
419  QStringList items = getCheckedItems();
420  PBI->addMimeTypes(items,FALSE);  //Only for current user     
421}
422
423void MainUI::slotActionRemoveMime(){
424  QStringList items = getCheckedItems();
425  PBI->rmMimeTypes(items,FALSE);  //Only for current user       
426}
427
428void MainUI::slotActionAddMimeAll(){
429    QStringList items = getCheckedItems();
430  PBI->addMimeTypes(items,TRUE);  //For all users (root permissions required)   
431}
432
433void MainUI::slotActionUpdate(){
434  QStringList checkedID = getCheckedItems();
435  if(!checkedID.isEmpty()){
436    PBI->upgradePBI(checkedID); 
437  }
438}
439
440void MainUI::slotActionRemove(){
441  QStringList checkedID = getCheckedItems();
442  if(!checkedID.isEmpty()){
443    //Verify that the user really wants to remove these apps
444    if( QMessageBox::Yes == QMessageBox::question(this,tr("Verify PBI Removal"), tr("Are you sure you wish to remove these applications?")+"\n\n"+checkedID.join("\n"),QMessageBox::Yes | QMessageBox::Cancel,QMessageBox::Cancel) ){
445      PBI->removePBI(checkedID);
446    }
447  }
448}
449
450void MainUI::slotActionCancel(){
451  QStringList checkedID = getCheckedItems();
452  if(!checkedID.isEmpty()){
453    PBI->cancelActions(checkedID); 
454  }
455}
456
457void MainUI::slotStartApp(QAction* act){
458  qDebug() << "Starting external application:" << act->text();
459  //Get the command from the action
460  QString desktopfile = act->whatsThis();
461  QString cmd = "xdg-open "+desktopfile;
462  //Startup the command externally
463  QProcess::execute(cmd);
464}
465
466void MainUI::slotUpdateSelectedPBI(bool statusonly){
467  QString appID;
468  if(ui->tree_install_apps->topLevelItemCount() > 0){
469    appID = ui->tree_install_apps->currentItem()->whatsThis(0);
470  }
471  if(appID.isEmpty()){
472    ui->group_install_info->setVisible(FALSE);
473    return;
474  }else{
475    ui->group_install_info->setVisible( ui->group_install_showinfo->isChecked() );       
476  }
477  if(!statusonly){
478    //Get the PBI info for that item
479    QStringList vals; 
480    vals << "name" << "icon" << "author" << "website" << "version" << "license";
481    QStringList bools;
482    bools << "autoupdate" << "hasdesktopicons" << "hasmenuicons" << "requiresroot";
483    vals = PBI->PBIInfo(appID,vals);
484    bools = PBI->PBIInfo(appID,bools);
485    //Make sure the info lists are not empty
486    if(vals.isEmpty() || bools.isEmpty()){
487      ui->group_install_info->setVisible(FALSE);
488      return; 
489    }
490    //Load a default icon if none found
491    if(vals[1].isEmpty()){ vals[1] = defaultIcon; }
492    //Now set the info on the UI
493    bool desktopSC = (bools[1] == "true"); // XDG desktop entries available
494    bool menuSC= (bools[2] == "true");   // XDG menu entries available
495    bool autoupdate = (bools[0] == "true"); //auto-update enabled
496    bool rootonly = (bools[3] == "true"); //requires root
497    //Create the shortcuts string
498    QString shortcuts;
499      if(desktopSC && menuSC){ shortcuts = tr("Desktop/Menu"); }
500      else if(desktopSC){ shortcuts = tr("Desktop"); }
501      else if(menuSC){ shortcuts = tr("Menu"); }
502      else{ shortcuts = tr("None"); }
503    //Now display that info on the UI
504    ui->label_install_app->setText(vals[0]);
505    ui->label_install_icon->setPixmap( QPixmap(vals[1]) );
506    if(vals[3].isEmpty()){ 
507      ui->label_install_author->setText(vals[2]); 
508      ui->label_install_author->setToolTip("");
509    }else{ 
510      ui->label_install_author->setText("<a href="+vals[3]+">"+vals[2]+"</a>"); 
511      ui->label_install_author->setToolTip(vals[3]); //show website URL as tooltip
512    }
513    ui->label_install_license->setText(vals[5]);
514    ui->label_install_version->setText(vals[4]);
515    ui->label_install_shortcuts->setText(shortcuts);
516    ui->check_install_autoupdate->setChecked(autoupdate);
517 
518    //Adjust the quick action buttons as necessary
519    if( PBI->isWorking(appID) ){
520      //Actions pending/working only show cancel button
521      ui->tool_install_cancel->setVisible(TRUE);
522      ui->tool_install_remove->setVisible(FALSE);
523      ui->tool_install_update->setVisible(FALSE);
524      ui->tool_install_startApp->setVisible(FALSE);
525    }else{
526      //Nothing pending
527      ui->tool_install_cancel->setVisible(FALSE);
528      if( !PBI->isInstalled(appID).isEmpty() ){ 
529        //Remove Button
530        ui->tool_install_remove->setVisible(TRUE);
531        if(rootonly){ ui->tool_install_remove->setIcon(QIcon(":icons/remove-root.png")); }
532        else{ ui->tool_install_remove->setIcon(QIcon(":icons/remove.png")); }
533        //Update
534        if(PBI->upgradeAvailable(appID).isEmpty()){ ui->tool_install_update->setVisible(FALSE); }
535        else{
536          ui->tool_install_update->setVisible(TRUE); 
537          if(rootonly){ ui->tool_install_update->setIcon(QIcon(":icons/app_upgrade_small-root.png")); }
538          else{ ui->tool_install_update->setIcon(QIcon(":icons/app_upgrade_small.png")); }
539        }
540        //Start Application binaries
541        QStringList bins = PBI->pbiBinList(appID);
542        appBinMenu->clear();
543        for(int i=0; i<bins.length(); i++){
544          QAction *act = new QAction(this);
545            act->setText(bins[i].section("::::",0,0)); //set name
546            act->setWhatsThis(bins[i].section("::::",1,10)); //set command string
547          appBinMenu->addAction(act);
548        }
549        if(appBinMenu->isEmpty()){ ui->tool_install_startApp->setVisible(FALSE); }
550        else{ ui->tool_install_startApp->setVisible(TRUE); }
551      }else{ 
552        ui->tool_install_remove->setVisible(FALSE); 
553        ui->tool_install_update->setVisible(FALSE); 
554        ui->tool_install_startApp->setVisible(FALSE);
555      }   
556    }
557  }
558  //Update the current status indicators
559  QString stat = PBI->currentAppStatus(appID,true); //get the raw status
560  if(stat.isEmpty() || stat == "DLSTART"  || stat == "DLDONE"){
561    //Not currently running - hide the display indicators
562    ui->group_install_appStat->setVisible(FALSE);
563  }else if(stat.startsWith("DLSTAT::")){
564    //Currently downloading - show download status indicators
565    QString percent = stat.section("::",1,1);
566    QString total = stat.section("::",2,2);
567    QString speed = stat.section("::",3,3);
568    ui->group_install_appStat->setVisible(TRUE);
569      ui->progress_install_DL->setVisible(TRUE);
570    if(total == "??"){ ui->label_install_status->setText( tr("Downloading file:") ); }
571    else{ ui->label_install_status->setText( QString(tr("Downloading %1 file:")).arg(total) ); }
572    if(percent == "??"){
573      ui->progress_install_DL->setMinimum(0); ui->progress_install_DL->setMaximum(0);
574    }else{
575      ui->progress_install_DL->setMinimum(0); ui->progress_install_DL->setMaximum(100);
576      ui->progress_install_DL->setValue( int(percent.toFloat()) );
577    }
578    if(speed == "??"){ ui->label_install_DL->setVisible(FALSE); }
579    else{
580      ui->label_install_DL->setVisible(TRUE);
581      ui->label_install_DL->setText(speed);
582    }
583  }else{
584    //Currently installing/removing/updating - show last message from process
585    ui->label_install_status->setText(stat);
586    ui->group_install_appStat->setVisible(TRUE);
587      ui->progress_install_DL->setVisible(FALSE);
588      ui->label_install_DL->setVisible(FALSE);
589  }
590}
591
592// ==========================
593// ====== BROWSER TAB =======
594// ==========================
595void MainUI::initializeBrowserTab(){
596  ui->tab_browse->setEnabled(FALSE);
597  //Always make sure that the browser starts off on the "home" page
598  ui->stacked_browser->setCurrentWidget(ui->page_home);
599  //With invisible shortcut buttons
600  ui->tool_browse_cat->setVisible(FALSE);
601  ui->tool_browse_app->setVisible(FALSE);
602  //Clear any items left over from the designer form
603  clearScrollArea(ui->scroll_br_home_newapps);
604  clearScrollArea(ui->scroll_br_home_cats);
605  //Search functionality
606  searchTimer = new QTimer();
607    searchTimer->setSingleShot(TRUE);
608    searchTimer->setInterval(300); // 0.3 sec wait before a search
609    connect(searchTimer,SIGNAL(timeout()),this,SLOT(slotGoToSearch()) );
610  connect(ui->tool_browse_search,SIGNAL(clicked()),this,SLOT(slotGoToSearch()) );
611  connect(ui->line_browse_searchbar,SIGNAL(returnPressed()),this,SLOT(slotGoToSearch()) );
612}
613
614// === SLOTS ===
615void MainUI::slotDisableBrowser(bool shownotification){
616  if(shownotification){ qDebug() << "No Repo Available: De-activating the Browser"; }
617  ui->tabWidget->setCurrentWidget(ui->tab_installed);
618  ui->tool_install_gotobrowserpage->setEnabled(FALSE);
619  ui->tab_browse->setEnabled(FALSE);
620  slotDisplayStats();
621}
622
623void MainUI::slotEnableBrowser(){
624  qDebug() << "Repo Ready: - generating browser home page";
625  //Now create the browser home page
626  slotUpdateBrowserHome();
627  //And allow the user to go there
628  ui->tool_install_gotobrowserpage->setEnabled(TRUE);
629  ui->tab_browse->setEnabled(TRUE);
630  slotDisplayStats();
631}
632
633void MainUI::slotUpdateBrowserHome(){
634  //Load the Categories
635  QStringList cats = PBI->browserCategories();
636  clearScrollArea(ui->scroll_br_home_cats);
637  QVBoxLayout *catlayout = new QVBoxLayout;
638  QStringList info; info << "name" << "description" << "icon";
639  cats.sort(); //sort them alphabetically
640  for(int i=0; i<cats.length(); i++){
641    QStringList data = PBI->CatInfo(cats[i],info);
642    if(!data.isEmpty()){
643      LargeItemWidget *item = new LargeItemWidget(cats[i],data[0],data[1],data[2]);
644      connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToCategory(QString)) );
645      catlayout->addWidget(item);
646    }
647  }
648  catlayout->addStretch(); //add a spacer to the end
649  ui->scroll_br_home_cats->widget()->setLayout(catlayout);
650  //Load the newest applications
651  clearScrollArea(ui->scroll_br_home_newapps);
652  QHBoxLayout *newapplayout = new QHBoxLayout;
653  QStringList newapps = PBI->getRecentApps();
654  for(int i=0; i<newapps.length(); i++){
655    QStringList appdata = PBI->AppInfo(newapps[i],QStringList() << "name" << "icon" << "latestversion");
656    if(!appdata.isEmpty()){
657      SmallItemWidget *item = new SmallItemWidget(newapps[i],appdata[0],appdata[1],appdata[2]);
658      connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToApp(QString)) );
659      newapplayout->addWidget(item);
660    }
661  }
662  newapplayout->addStretch(); //add a spacer to the end
663  newapplayout->setContentsMargins(0,0,0,0);
664  newapplayout->setSpacing(0);
665  ui->scroll_br_home_newapps->widget()->setLayout(newapplayout);
666  //Make sure that the newapps scrollarea is the proper fit vertically (no vertical scrolling)
667  ui->scroll_br_home_newapps->setMinimumHeight(ui->scroll_br_home_newapps->widget()->minimumSizeHint().height()+ui->scroll_br_home_newapps->horizontalScrollBar()->height());
668 
669  //Make sure the new apps area is invisible if no items available
670  if(newapps.isEmpty()){ ui->group_br_home_newapps->setVisible(FALSE); }
671  else{ ui->group_br_home_newapps->setVisible(TRUE); }
672  //make sure the home page is visible in the browser (slotGoToHome without changing tabs)
673  ui->stacked_browser->setCurrentWidget(ui->page_home); 
674  //Make sure the shortcut buttons are disabled
675  ui->tool_browse_cat->setVisible(FALSE);
676  ui->tool_browse_app->setVisible(FALSE); 
677 
678}
679
680void MainUI::slotGoToHome(){
681  ui->tabWidget->setCurrentWidget(ui->tab_browse);
682  ui->stacked_browser->setCurrentWidget(ui->page_home); 
683  //Make sure the shortcut buttons are disabled
684  ui->tool_browse_cat->setVisible(FALSE);
685  ui->tool_browse_app->setVisible(FALSE);
686}
687
688void MainUI::slotGoToCategory(QString cat){
689  qDebug() << "Show Category:" << cat;
690  //Get the apps in this category
691  QStringList apps = PBI->browserApps(cat);
692  if(apps.isEmpty()){
693    qDebug() << "Invalid category:" << cat;
694    return;
695  }
696  clearScrollArea(ui->scroll_br_cat_apps);
697  apps.sort();
698  QVBoxLayout *applayout = new QVBoxLayout;
699  QStringList info; info << "name" << "description" << "icon";
700  for(int i=0; i<apps.length(); i++){
701    QStringList data = PBI->AppInfo(apps[i],info);
702    if(!data.isEmpty()){
703      //Trim the description if necessary
704      if(data[1].size()>100){ data[1] = data[1].left(100).append("..."); }
705      LargeItemWidget *item = new LargeItemWidget(apps[i],data[0],data[1],data[2]);
706      connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToApp(QString)) );
707      applayout->addWidget(item); 
708    }
709  }
710  applayout->addStretch();
711  ui->scroll_br_cat_apps->widget()->setLayout(applayout);
712  //Now enable/disable the shortcut buttons
713  ui->tool_browse_app->setVisible(FALSE);
714  ui->tool_browse_cat->setVisible(TRUE);
715    QStringList catinfo = PBI->CatInfo(cat,QStringList() << "name" << "icon");
716    ui->tool_browse_cat->setText(catinfo[0]);
717    if(catinfo[1].isEmpty()){ catinfo[1] = defaultIcon; }
718    ui->tool_browse_cat->setIcon(QIcon(catinfo[1]));
719  ui->tabWidget->setCurrentWidget(ui->tab_browse);
720  ui->stacked_browser->setCurrentWidget(ui->page_cat);
721  //Now save that this category is currently displayed
722  cCat = cat;
723}
724
725void MainUI::slotGoToApp(QString appID){
726  qDebug() << "Show App:" << appID;
727  //Get the general application info
728  QStringList data; 
729  data << "name" << "icon" << "author" << "website" << "license" << "type" << "description" \
730       << "category" << "requiresroot" << "latestversion" << "latestarch" <<"latestsize" \
731       << "backupversion" << "backuparch" << "backupsize";
732  data = PBI->AppInfo(appID,data);
733  if(data.isEmpty()){
734    qDebug() << "Invalid App:" << appID;
735    return;
736  }
737  //Start the search for similar apps
738  PBI->searchSimilar = appID;
739  ui->group_bapp_similar->setVisible(FALSE);
740  QTimer::singleShot(0,PBI,SLOT(startSimilarSearch()));
741  //Now Check icon
742  if(data[1].isEmpty()){ data[1] = defaultIcon; }
743  //Now fill the UI with the data
744  ui->label_bapp_name->setText(data[0]);
745  ui->label_bapp_icon->setPixmap(QPixmap(data[1]));
746  if(data[3].isEmpty()){ ui->label_bapp_authorweb->setText(data[2]); }
747  else{ ui->label_bapp_authorweb->setText("<a href="+data[3]+">"+data[2]+"</a>"); }
748  ui->label_bapp_authorweb->setToolTip(data[3]);
749  ui->label_bapp_license->setText(data[4]);
750  ui->label_bapp_type->setText(data[5]);
751  ui->text_bapp_description->setText(data[6]);
752  //Now determine the appropriate version info
753  QString pbiID = PBI->isInstalled(appID); //get pbiID
754  QString cVer;
755  if(!pbiID.isEmpty()){ cVer = PBI->PBIInfo(pbiID,QStringList("version")).join(""); }
756  bool useLatest=FALSE;
757  bool nobackup = data[12].isEmpty();
758  if(cVer.isEmpty()){ useLatest=TRUE; } //not currently installed
759  else if(cVer != data[9]){ useLatest=TRUE;} //not the latest version
760  //Now put the proper version info on the UI
761  if(useLatest || nobackup){
762    ui->label_bapp_version->setText(data[9]);
763    ui->label_bapp_arch->setText(data[10]);
764    if(data[11].isEmpty()){ ui->label_bapp_size->setText(tr("Unknown")); }
765    else{ ui->label_bapp_size->setText( Extras::sizeKToDisplay(data[11]) ); }
766  }else{
767    ui->label_bapp_version->setText(data[12]);
768    ui->label_bapp_arch->setText(data[13]);
769    if(data[14].isEmpty()){ ui->label_bapp_size->setText(tr("Unknown")); }
770    else{ ui->label_bapp_size->setText( Extras::sizeKToDisplay(data[14]) ); }
771  }
772  //Now update the download button appropriately
773  QString ico;
774  QString working = PBI->currentAppStatus(appID);
775  if(!working.isEmpty()){ //app currently pending or actually doing something
776    ui->tool_bapp_download->setText(working);
777    ui->tool_bapp_download->setIcon(QIcon(":icons/working.png"));
778    ui->tool_bapp_download->setEnabled(FALSE);
779  }else if(useLatest && cVer.isEmpty()){ //new installation
780    ui->tool_bapp_download->setText(tr("Install Now!"));
781    ico = ":icons/app_download.png";
782    ui->tool_bapp_download->setEnabled(TRUE);
783  }else if(useLatest){ //Update available
784    ui->tool_bapp_download->setText(tr("Update"));
785    ico = ":icons/app_upgrade.png";
786    ui->tool_bapp_download->setEnabled(TRUE);
787  }else if(!nobackup){  //Downgrade available
788    ui->tool_bapp_download->setText(tr("Downgrade"));
789    ico = ":icons/app_downgrade.png";
790    ui->tool_bapp_download->setEnabled(TRUE);
791  }else{ //already installed (no downgrade available)
792    ui->tool_bapp_download->setText(tr("Installed"));
793    ui->tool_bapp_download->setIcon(QIcon(":icons/dialog-ok.png"));
794    ui->tool_bapp_download->setEnabled(FALSE);
795  }
796  //Now set the icon appropriately if it requires root permissions
797  if(!ico.isEmpty()){
798    if(data[8]=="true"){ //requires root permissions to install
799      ico.replace(".png","-root.png");
800    }
801    ui->tool_bapp_download->setIcon(QIcon(ico));
802  }
803  ui->tool_bapp_download->setWhatsThis(appID); //set for slot
804  //Now enable/disable the shortcut buttons
805  ui->tool_browse_app->setVisible(TRUE);
806    ui->tool_browse_app->setText(data[0]);
807    ui->tool_browse_app->setIcon(QIcon(data[1]));
808  QStringList catinfo = PBI->CatInfo(Extras::nameToID(data[7]),QStringList() << "name" << "icon");
809  if(!catinfo.isEmpty()){
810    ui->tool_browse_cat->setVisible(TRUE);
811    ui->tool_browse_cat->setText(catinfo[0]);
812    if(catinfo[1].isEmpty()){ catinfo[1] = defaultIcon; }
813    ui->tool_browse_cat->setIcon(QIcon(catinfo[1]));
814  }
815  ui->tabWidget->setCurrentWidget(ui->tab_browse);
816  ui->stacked_browser->setCurrentWidget(ui->page_app);
817       
818}
819
820void MainUI::slotGoToSearch(){
821  searchTimer->stop(); //just in case "return" was pressed to start the search
822  QString search = ui->line_browse_searchbar->text();
823  if(search.isEmpty()){ return; }
824  PBI->searchTerm = search;
825  QTimer::singleShot(1,PBI,SLOT(startAppSearch()));
826  ui->label_bsearch_info->setText( tr("Searching the application database. Please Wait....") );
827    ui->label_bsearch_info->setVisible(TRUE);
828    ui->group_bsearch_best->setVisible(FALSE);
829    ui->group_bsearch_other->setVisible(FALSE);
830 
831}
832       
833void MainUI::slotShowSimilarApps(QStringList apps){
834  qDebug() << " - Similar applications:" << apps;
835  if(apps.isEmpty()){ ui->group_bapp_similar->setVisible(FALSE); }
836  else{
837    clearScrollArea(ui->scroll_bapp_similar);
838    QHBoxLayout *layout = new QHBoxLayout;
839    for(int i=0; i<apps.length(); i++){
840      QStringList appdata = PBI->AppInfo(apps[i],QStringList() << "name" << "icon");
841      if(!appdata.isEmpty()){
842        SmallItemWidget *item = new SmallItemWidget(apps[i],appdata[0],appdata[1],"");
843        connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToApp(QString)) );
844        layout->addWidget(item);
845      }
846    }
847    layout->addStretch(); //add a spacer to the end
848    layout->setContentsMargins(1,1,1,1);
849    ui->scroll_bapp_similar->widget()->setLayout(layout);
850    //Make sure that the similar scrollarea is the proper fit vertically (no vertical scrolling)
851    ui->scroll_bapp_similar->setMinimumHeight(ui->scroll_bapp_similar->widget()->minimumSizeHint().height()+ui->scroll_bapp_similar->horizontalScrollBar()->height());
852    //Now make the group visible as appropriate
853    ui->group_bapp_similar->setVisible(TRUE);
854    if(ui->group_bapp_similar->isChecked()){ ui->scroll_bapp_similar->setVisible(TRUE); }
855    else{ ui->scroll_bapp_similar->setVisible(FALSE); }
856    //Now make sure the app page has the proper layout dimensions
857    ui->page_app->updateGeometry();
858  }
859}
860
861void MainUI::slotShowSearchResults(QStringList best, QStringList rest){
862  //Now display the search results
863  if(best.isEmpty()){
864    ui->label_bsearch_info->setText( QString(tr("No Search Results Found for the term: %1")).arg(ui->line_browse_searchbar->text()) );
865    ui->label_bsearch_info->setVisible(TRUE);
866    ui->group_bsearch_best->setVisible(FALSE);
867    ui->group_bsearch_other->setVisible(FALSE);
868  }else{
869    ui->label_bsearch_info->setVisible(FALSE);
870    //Now fill the "best" section
871    clearScrollArea(ui->scroll_bsearch_best);
872    QVBoxLayout *layout = new QVBoxLayout;
873    QStringList info; info << "name" << "description" << "icon";
874    for(int i=0; i<best.length(); i++){
875      QStringList data = PBI->AppInfo(best[i],info);
876      if(!data.isEmpty()){
877        //Trim the description if necessary
878        if(data[1].size()>100){ data[1] = data[1].left(100).append("..."); }
879        LargeItemWidget *item = new LargeItemWidget(best[i],data[0],data[1],data[2]);
880        connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToApp(QString)) );
881        layout->addWidget(item); 
882      }
883    }
884    layout->addStretch();
885    ui->scroll_bsearch_best->widget()->setLayout(layout);
886    ui->group_bsearch_best->setVisible(TRUE);
887   
888    //Now fill the other results
889    if(rest.isEmpty()){ ui->group_bsearch_other->setVisible(FALSE); }
890    else{
891      ui->group_bsearch_other->setVisible(TRUE);
892      clearScrollArea(ui->scroll_bsearch_other);
893      QVBoxLayout *layout2 = new QVBoxLayout;
894      for(int i=0; i<rest.length(); i++){
895        QStringList data = PBI->AppInfo(rest[i],info);
896        if(!data.isEmpty()){
897          //Trim the description if necessary
898          if(data[1].size()>100){ data[1] = data[1].left(100).append("..."); }
899          LargeItemWidget *item = new LargeItemWidget(rest[i],data[0],data[1],data[2]);
900          connect(item,SIGNAL(appClicked(QString)),this,SLOT(slotGoToApp(QString)) );
901          layout2->addWidget(item); 
902        }
903      }
904      layout2->addStretch();
905      ui->scroll_bsearch_other->widget()->setLayout(layout2);
906    }
907  }
908       
909  //need to make sure the search bar still has keyboard focus (just in case)
910  ui->tabWidget->setCurrentWidget(ui->tab_browse);
911  ui->stacked_browser->setCurrentWidget(ui->page_search);
912 
913}
914
915void MainUI::on_tabWidget_currentChanged(){
916  if(ui->tabWidget->currentWidget() == ui->tab_browse){
917    //Refresh the app page if that is the one currently showing
918    if(ui->stacked_browser->currentWidget() == ui->page_app){ on_tool_browse_app_clicked(); }     
919  }
920}
921
922void MainUI::on_tool_browse_home_clicked(){
923  slotGoToHome();
924}
925
926void MainUI::on_tool_browse_cat_clicked(){
927  QString cat = Extras::nameToID(ui->tool_browse_cat->text());
928  if(cCat == cat){ //already loaded - just show it (prevents resetting scroll positions)
929    ui->stacked_browser->setCurrentWidget(ui->page_cat);
930  }else{ //load and show the category
931    slotGoToCategory(cat);
932  }
933}
934
935void MainUI::on_tool_browse_app_clicked(){
936  QString appID = Extras::nameToID(ui->tool_browse_app->text());
937  slotGoToApp(appID);
938}
939
940void MainUI::on_line_browse_searchbar_textChanged(){
941  //Connect this to a singleshot timer, so the search functionality is only
942  //  run once after a short wait rather than every time a new character is typed
943 
944  //Live search only after 3 characters have been typed
945  if(ui->line_browse_searchbar->text().length() > 2){
946    searchTimer->start();
947  }else{
948    searchTimer->stop();         
949  }
950}
951
952void MainUI::on_tool_bapp_download_clicked(){
953  QString appID = ui->tool_bapp_download->whatsThis();
954  //Verify the app installation
955  if( QMessageBox::Yes != QMessageBox::question(this,tr("Verify Installation"), tr("Are you sure you want to install this application?")+"\n\n"+appID,QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)){
956    return;
957  }
958  PBI->installApp(QStringList() << appID);
959  ui->tool_bapp_download->setEnabled(FALSE); //make sure it cannot be clicked more than once before page refresh
960  //Now show the Installed tab
961  ui->tabWidget->setCurrentWidget(ui->tab_installed);
962}
963
964void MainUI::on_group_br_home_newapps_toggled(bool show){
965  ui->scroll_br_home_newapps->setVisible(show);
966}
967
968void MainUI::on_group_bapp_similar_toggled(bool show){
969  ui->scroll_bapp_similar->setVisible(show);
970}
971
972// ================================
973// ==========  OTHER ==============
974// ================================
975void MainUI::clearScrollArea(QScrollArea* area){
976  QWidget *wgt = area->takeWidget();
977  delete wgt; //delete the widget and all children
978  area->setWidget( new QWidget() ); //create a new widget in the scroll area
979  area->widget()->setContentsMargins(0,0,0,0);
980}
981
982void MainUI::slotDisplayError(QString title,QString message,QStringList log){
983  QMessageBox *dlg = new QMessageBox(this);
984    dlg->setWindowTitle(title);
985    dlg->setText(message);
986    dlg->setDetailedText(log.join("\n"));
987  dlg->exec();
988}
989
990void MainUI::slotDisplayStats(){
991  int avail = PBI->numAvailable;
992  int installed = PBI->numInstalled;
993  QString text;
994  if(avail == -1){
995    text = QString(tr("Installed: %1")).arg(QString::number(installed)); 
996  }else{
997    text = QString(tr("Installed: %1")+"\t"+tr("Available: %2")).arg(QString::number(installed), QString::number(avail)); 
998  }
999  //Get the number of installed/available applications and display it
1000  statusLabel->setText(text);   
1001}
Note: See TracBrowser for help on using the repository browser.