Merge branch 'extlogging'

This commit is contained in:
Klaas Freitag 2012-06-11 10:14:52 +02:00
commit 1af154fc96
19 changed files with 582 additions and 85 deletions

View file

@ -23,7 +23,7 @@ else()
set( CSYNC_CONFIG_DIR "${MINGW_ROOT}/etc/csync" )
endif()
set( BUILD_OWNCLOUD_OSX_BUNDLE @BUILD_OWNCLOUD_OSX_BUNDLE@)
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
if(APPLE AND NOT BUILD_OWNCLOUD_OSX_BUNDLE)
message( FATAL_ERROR "You're trying to build a bundle although you haven't built mirall in bundle mode.\n Add -DBUILD_OWNCLOUD_O")
endif()

View file

@ -1,5 +1,5 @@
set( VERSION_MAJOR 1 )
set( VERSION_MINOR 0 )
set( VERSION_PATCH 2 )
set( VERSION_PATCH 3 )
set( VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH} )

View file

@ -43,7 +43,7 @@ mirall/mirallconfigfile.cpp
mirall/updatedetector.cpp
mirall/occinfo.cpp
mirall/sslerrordialog.cpp
mirall/logbrowser.cpp
)
set(mirall_HEADERS
@ -63,6 +63,7 @@ set(mirall_HEADERS
mirall/csyncthread.h
mirall/updatedetector.h
mirall/sslerrordialog.h
mirall/logbrowser.h
)
if( UNIX AND NOT APPLE)
@ -182,11 +183,11 @@ endif()
find_program(KRAZY2_EXECUTABLE krazy2)
if(KRAZY2_EXECUTABLE)
# s/y k/y ALL k/ for building this target always
add_custom_target( krazy krazy2 --check-sets c++,qt4,foss
${PROJECT_SOURCE_DIR}/src/mirall/*.ui
${PROJECT_SOURCE_DIR}/src/mirall/*.h
${PROJECT_SOURCE_DIR}/src/mirall/*.cpp
# s/y k/y ALL k/ for building this target always
add_custom_target( krazy krazy2 --check-sets c++,qt4,foss
${PROJECT_SOURCE_DIR}/src/mirall/*.ui
${PROJECT_SOURCE_DIR}/src/mirall/*.h
${PROJECT_SOURCE_DIR}/src/mirall/*.cpp
)
endif()

View file

@ -12,6 +12,8 @@
* for more details.
*/
#define LOG_TO_CALLBACK // FIXME: This should be in csync.
#include "mirall/application.h"
#include "mirall/folder.h"
#include "mirall/folderwatcher.h"
@ -34,6 +36,8 @@
#endif
#include "mirall/inotify.h"
#include <csync.h>
#include <QtCore>
#include <QtGui>
#include <QHash>
@ -44,6 +48,20 @@
namespace Mirall {
// application logging handler.
void mirallLogCatcher(QtMsgType type, const char *msg)
{
Q_UNUSED(type)
Logger::instance()->mirallLog( msg );
}
void csyncLogCatcher(const char *msg)
{
Logger::instance()->csyncLog( QString::fromUtf8(msg) );
}
// ----------------------------------------------------------------------------------
Application::Application(int &argc, char **argv) :
QApplication(argc, argv),
_tray(0),
@ -65,11 +83,7 @@ Application::Application(int &argc, char **argv) :
processEvents();
// Internationalization support.
qDebug() << ""; // relaxing debug output in qtCreator
qDebug() << QString( "################## %1 %2 %3 ").arg(_theme->appName())
.arg( QLocale::system().name() )
.arg(_theme->version());
setupLogBrowser();
QTranslator *qtTranslator = new QTranslator;
qtTranslator->load("qt_" + QLocale::system().name(),
@ -117,6 +131,9 @@ Application::Application(int &argc, char **argv) :
connect( _statusDialog, SIGNAL(removeFolderAlias( const QString&)),
SLOT(slotRemoveFolder(const QString&)));
connect( _statusDialog, SIGNAL(openLogBrowser()), this, SLOT(slotOpenLogBrowser()));
#if 0
connect( _statusDialog, SIGNAL(fetchFolderAlias(const QString&)),
SLOT(slotFetchFolder( const QString&)));
@ -259,7 +276,6 @@ void Application::setupActions()
_actionConfigure = new QAction(tr("Configure..."), this);
QObject::connect(_actionConfigure, SIGNAL(triggered(bool)), SLOT(slotConfigure()));
_actionQuit = new QAction(tr("Quit"), this);
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), SLOT(quit()));
}
@ -308,6 +324,21 @@ void Application::setupContextMenu()
_tray->setContextMenu(_contextMenu);
}
void Application::setupLogBrowser()
{
// init the log browser.
_logBrowser = new LogBrowser;
qInstallMsgHandler( mirallLogCatcher );
csync_set_log_callback( csyncLogCatcher );
if( arguments().contains("--logwindow") || arguments().contains("-l")) {
slotOpenLogBrowser();
}
qDebug() << QString( "################## %1 %2 %3 ").arg(_theme->appName())
.arg( QLocale::system().name() )
.arg(_theme->version());
}
/*
* open the folder with the given Alais
*/
@ -446,6 +477,12 @@ void Application::slotOpenStatus()
}
}
void Application::slotOpenLogBrowser()
{
_logBrowser->show();
_logBrowser->raise();
}
/*
* the folder is to be removed. The slot is called from a signal emitted by
* the status dialog, which removes the folder from its list by itself.

View file

@ -21,7 +21,7 @@
#include "mirall/syncresult.h"
#include "mirall/folder.h"
#include "mirall/logbrowser.h"
#include "mirall/folderman.h"
class QAction;
@ -62,6 +62,7 @@ protected:
void setupActions();
void setupSystemTray();
void setupContextMenu();
void setupLogBrowser();
//folders have to be disabled while making config changes
void computeOverallSyncStatus();
@ -75,6 +76,7 @@ protected slots:
void slotNoOwnCloudFound( QNetworkReply* );
void slotCheckAuthentication();
void slotAuthCheck( const QString& ,QNetworkReply* );
void slotOpenLogBrowser();
void slotStartUpdateDetector();
@ -104,6 +106,7 @@ private:
ownCloudInfo *_ocInfo;
UpdateDetector *_updateDetector;
QMap<Folder*, QString> _overallStatusStrings;
LogBrowser *_logBrowser;
};
} // namespace Mirall

View file

@ -137,11 +137,12 @@ void CSyncThread::run()
wStats->seenFiles = 0;
wStats->conflicts = 0;
wStats->error = 0;
const char *statedb = 0;
_mutex.lock();
if( csync_create(&csync,
_source.toLocal8Bit().data(),
_target.toLocal8Bit().data()) < 0 ) {
_source.toLocal8Bit().data(),
_target.toLocal8Bit().data()) < 0 ) {
emit csyncError( tr("CSync create failed.") );
}
// FIXME: Check if we really need this stringcopy!
@ -214,6 +215,17 @@ void CSyncThread::run()
goto cleanup;
}
// After csync_init the statedb file name can be emitted
statedb = csync_get_statedb_file( csync );
if( statedb ) {
QString stateDbFile = QString::fromUtf8(statedb);
free((void*)statedb);
emit csyncStateDbFile( stateDbFile );
} else {
qDebug() << "WRN: Unable to get csync statedb file name";
}
qDebug() << "############################################################### >>";
if( csync_update(csync) < 0 ) {
emit csyncError(tr("CSync Update failed."));
@ -240,7 +252,6 @@ void CSyncThread::run()
emit csyncError(tr("Local filesystem problems. Better disable Syncing and check."));
goto cleanup;
}
qDebug() << " ..... Local walk finished: " << walkTime.elapsed();
// emit the treewalk results. Do not touch the wStats after this.
emit treeWalkResult(wStats);
@ -248,6 +259,7 @@ void CSyncThread::run()
_mutex.lock();
if( _localCheckOnly ) {
_mutex.unlock();
qDebug() << " ..... Local only walk finished: " << walkTime.elapsed();
// we have to go out here as its local check only.
goto cleanup;
} else {
@ -295,6 +307,8 @@ int CSyncThread::getauth(const char *prompt,
void *userdata
)
{
int re = 0;
QString qPrompt = QString::fromLocal8Bit( prompt ).trimmed();
_mutex.lock();
@ -311,9 +325,11 @@ int CSyncThread::getauth(const char *prompt,
strncpy( buf, "yes", 3 );
} else {
qDebug() << "Unknown prompt: <" << prompt << ">";
re = -1;
}
}
_mutex.unlock();
return re;
}
}

View file

@ -68,7 +68,9 @@ public:
signals:
void treeWalkResult(WalkStats*);
void csyncError(const QString&);
void csyncError( const QString& );
void csyncStateDbFile( const QString& );
private:
static int getauth(const char *prompt,

View file

@ -262,5 +262,10 @@ QString Folder::backend() const
return _backend;
}
void Folder::wipe()
{
}
} // namespace Mirall

View file

@ -129,6 +129,11 @@ public:
*/
QString backend() const;
/**
* This is called if the sync folder definition is removed. Do cleanups here.
*/
virtual void wipe();
QIcon icon( int size ) const;
QTimer *_pollTimer;

View file

@ -337,6 +337,7 @@ void FolderMan::removeFolder( const QString& alias )
if( _folderMap.contains( alias )) {
qDebug() << "Removing " << alias;
Folder *f = _folderMap.take( alias );
f->wipe();
f->deleteLater();
} else {
qDebug() << "!! Can not remove " << alias << ", not in folderMap.";

252
src/mirall/logbrowser.cpp Normal file
View file

@ -0,0 +1,252 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "logbrowser.h"
#include "stdio.h"
#include <iostream>
#include <QDialogButtonBox>
#include <QTextDocument>
#include <QLayout>
#include <QPushButton>
#include <QLabel>
#include <QFileDialog>
#include <QDir>
#include <QTextStream>
#include <QMessageBox>
#include <QCoreApplication>
#include <QDebug>
#include "mirall/mirallconfigfile.h"
namespace Mirall {
Logger* Logger::_instance=0;
Logger::Logger( QObject* parent)
: QObject(parent),
_showTime(true),
_doLogging(false)
{
}
Logger *Logger::instance()
{
if( !Logger::_instance ) Logger::_instance = new Logger;
return Logger::_instance;
}
void Logger::destroy()
{
if( Logger::_instance ) {
delete Logger::_instance;
Logger::_instance = 0;
}
}
void Logger::log(Log log)
{
if( ! _doLogging ) return;
QString msg;
if( _showTime ) {
msg = log.timeStamp.toString("MM-dd hh:mm:ss:zzz") + " ";
}
if( log.source == Log::CSync ) {
// msg += "csync - ";
} else {
// msg += "ownCloud - ";
}
msg += log.message;
// _logs.append(log);
// std::cout << qPrintable(log.message) << std::endl;
emit newLog(msg);
}
void Logger::csyncLog( const QString& message )
{
Log log;
log.source = Log::CSync;
log.timeStamp = QDateTime::currentDateTime();
log.message = message;
Logger::instance()->log(log);
}
void Logger::mirallLog( const QString& message )
{
Log log_;
log_.source = Log::Mirall;
log_.timeStamp = QDateTime::currentDateTime();
log_.message = message;
Logger::instance()->log( log_ );
}
void Logger::setEnabled( bool state )
{
_doLogging = state;
}
// ==============================================================================
LogWidget::LogWidget(QWidget *parent)
:QTextEdit(parent)
{
setReadOnly( true );
setLineWrapMode( QTextEdit::NoWrap );
QFont font;
font.setFamily("Courier New");
font.setFixedPitch(true);
document()->setDefaultFont( font );
MirallConfigFile cfg;
int lines = cfg.maxLogLines();
// qDebug() << "# ## Have " << lines << " Loglines!";
document()->setMaximumBlockCount( lines );
}
// ==============================================================================
LogBrowser::LogBrowser(QWidget *parent) :
QDialog(parent),
_logWidget( new LogWidget(parent) )
{
setWindowTitle(tr("Log Output"));
setMinimumWidth(600);
QVBoxLayout *mainLayout = new QVBoxLayout;
// mainLayout->setMargin(0);
mainLayout->addWidget( _logWidget );
QHBoxLayout *toolLayout = new QHBoxLayout;
mainLayout->addLayout( toolLayout );
// Search input field
QLabel *lab = new QLabel(tr("&Search: "));
_findTermEdit = new QLineEdit;
lab->setBuddy( _findTermEdit );
toolLayout->addWidget(lab);
toolLayout->addWidget( _findTermEdit );
// find button
QPushButton *findBtn = new QPushButton;
findBtn->setText( tr("&Find") );
connect( findBtn, SIGNAL(clicked()), this, SLOT(slotFind()));
toolLayout->addWidget( findBtn );
// stretch
toolLayout->addStretch(1);
_statusLabel = new QLabel;
toolLayout->addWidget( _statusLabel );
toolLayout->addStretch(5);
QDialogButtonBox *btnbox = new QDialogButtonBox;
QPushButton *closeBtn = btnbox->addButton( QDialogButtonBox::Close );
connect(closeBtn,SIGNAL(clicked()),this,SLOT(close()));
mainLayout->addWidget( btnbox );
// save Button
_saveBtn = new QPushButton;
_saveBtn->setText( tr("S&ave") );
_saveBtn->setToolTip(tr("Save the log file to a file on disk for debugging."));
btnbox->addButton(_saveBtn, QDialogButtonBox::ActionRole);
connect( _saveBtn, SIGNAL(clicked()),this, SLOT(slotSave()));
setLayout( mainLayout );
setModal(false);
// needs to be a queued connection as logs from other threads come in
connect(Logger::instance(), SIGNAL(newLog(QString)),this,SLOT(slotNewLog(QString)), Qt::QueuedConnection);
}
void LogBrowser::show()
{
QDialog::show();
Logger::instance()->setEnabled(true);
}
void LogBrowser::close()
{
Logger::instance()->setEnabled(false);
QDialog::close();
}
void LogBrowser::slotNewLog( const QString& msg )
{
_logWidget->append( msg );
}
void LogBrowser::slotFind()
{
QString searchText = _findTermEdit->text();
if( searchText.isEmpty() ) return;
search( searchText );
}
void LogBrowser::search( const QString& str )
{
QList<QTextEdit::ExtraSelection> extraSelections;
_logWidget->moveCursor(QTextCursor::Start);
QColor color = QColor(Qt::gray).lighter(130);
_statusLabel->clear();
while(_logWidget->find(str))
{
QTextEdit::ExtraSelection extra;
extra.format.setBackground(color);
extra.cursor = _logWidget->textCursor();
extraSelections.append(extra);
}
QString stat = QString("Search term %1 with %2 search results.").arg(str).arg(extraSelections.count());
_statusLabel->setText(stat);
_logWidget->setExtraSelections(extraSelections);
}
void LogBrowser::slotSave()
{
_saveBtn->setEnabled(false);
QCoreApplication::processEvents();
QString saveFile = QFileDialog::getSaveFileName( this, tr("Save log file"), QDir::homePath() );
if( ! saveFile.isEmpty() ) {
QFile file(saveFile);
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << _logWidget->toPlainText();
file.flush();
file.close();
} else {
QMessageBox::critical(this, tr("Error"), tr("Could not write to log file ")+ saveFile);
}
}
_saveBtn->setEnabled(true);
}
} // namespace

104
src/mirall/logbrowser.h Normal file
View file

@ -0,0 +1,104 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef LOGBROWSER_H
#define LOGBROWSER_H
#include <QTextEdit>
#include <QObject>
#include <QList>
#include <QDateTime>
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
namespace Mirall {
struct Log{
typedef enum{
Mirall,
CSync
} Source;
QDateTime timeStamp;
Source source;
QString message;
};
class Logger : public QObject
{
Q_OBJECT
public:
void log(Log log);
static void csyncLog( const QString& message );
static void mirallLog( const QString& message );
const QList<Log>& logs() const {return _logs;}
static Logger* instance();
static void destroy();
void setEnabled( bool );
signals:
void newLog(const QString&);
protected:
Logger(QObject* parent=0);
QList<Log> _logs;
bool _showTime;
bool _doLogging;
static Logger* _instance;
};
class LogWidget : public QTextEdit
{
Q_OBJECT
public:
explicit LogWidget(QWidget *parent = 0);
signals:
};
class LogBrowser : public QDialog
{
Q_OBJECT
public:
explicit LogBrowser(QWidget *parent = 0);
signals:
public slots:
void show();
void close();
protected slots:
void slotNewLog( const QString &msg );
void slotFind();
void search( const QString& );
void slotSave();
private:
LogWidget *_logWidget;
QLineEdit *_findTermEdit;
QPushButton *_saveBtn;
QLabel *_statusLabel;
};
} // namespace
#endif // LOGBROWSER_H

View file

@ -350,7 +350,13 @@ bool MirallConfigFile::ownCloudSkipUpdateCheck( const QString& connection ) cons
return skipIt;
}
int MirallConfigFile::maxLogLines() const
{
QSettings settings( configFile(), QSettings::IniFormat );
settings.beginGroup("Logging");
int logLines = settings.value( "maxLogLines", 20000 ).toInt();
return logLines;
}
QByteArray MirallConfigFile::basicAuthHeader() const
{

View file

@ -51,6 +51,9 @@ public:
QString ownCloudVersion() const;
void setOwnCloudVersion( const QString& );
// max count of lines in the log window
int maxLogLines() const;
bool ownCloudSkipUpdateCheck( const QString& connection = QString() ) const;
/* Poll intervals in milliseconds */

View file

@ -68,6 +68,7 @@ ownCloudFolder::ownCloudFolder(const QString &alias,
ownCloudFolder::~ownCloudFolder()
{
}
#ifndef USE_INOTIFY
@ -144,6 +145,7 @@ void ownCloudFolder::startSync(const QStringList &pathList)
QObject::connect(_csync, SIGNAL(finished()), SLOT(slotCSyncFinished()));
QObject::connect(_csync, SIGNAL(terminated()), SLOT(slotCSyncTerminated()));
connect(_csync, SIGNAL(csyncError(const QString)), SLOT(slotCSyncError(const QString)));
connect(_csync, SIGNAL(csyncStateDbFile(QString)), SLOT(slotCsyncStateDbFile(QString)));
connect( _csync, SIGNAL(treeWalkResult(WalkStats*)),
this, SLOT(slotThreadTreeWalkResult(WalkStats*)));
@ -200,6 +202,12 @@ void ownCloudFolder::slotCSyncError(const QString& err)
_csyncError = true;
}
void ownCloudFolder::slotCsyncStateDbFile( const QString& file )
{
qDebug() << "Got csync statedb file: " << file;
_csyncStateDbFile = file;
}
void ownCloudFolder::slotCSyncTerminated()
{
// do not ask csync here for reasons.
@ -253,5 +261,31 @@ void ownCloudFolder::slotTerminateSync()
}
}
// This removes the csync File database if the sync folder definition is removed
// permanentely. This is needed to provide a clean startup again in case another
// local folder is synced to the same ownCloud.
// See http://bugs.owncloud.org/thebuggenie/owncloud/issues/oc-788
void ownCloudFolder::wipe()
{
if( !_csyncStateDbFile.isEmpty() ) {
QFile file(_csyncStateDbFile);
if( file.exists() ) {
if( !file.remove()) {
qDebug() << "WRN: Failed to remove existing csync StateDB " << _csyncStateDbFile;
} else {
qDebug() << "wipe: Removed csync StateDB " << _csyncStateDbFile;
}
} else {
qDebug() << "WRN: statedb is empty, can not remove.";
}
// Check if the tmp database file also exists
QString ctmpName = _csyncStateDbFile + ".ctmp";
QFile ctmpFile( ctmpName );
if( ctmpFile.exists() ) {
ctmpFile.remove();
}
}
}
} // ns

View file

@ -38,6 +38,8 @@ public:
virtual bool isBusy() const;
virtual void startSync(const QStringList &pathList);
virtual void wipe();
public slots:
void startSync();
void slotTerminateSync();
@ -48,6 +50,7 @@ private slots:
void slotCSyncFinished();
void slotThreadTreeWalkResult( WalkStats* );
void slotCSyncTerminated();
void slotCsyncStateDbFile(const QString&);
#ifndef USE_INOTIFY
void slotPollTimerRemoteCheck();
@ -62,6 +65,7 @@ private:
QStringList _errors;
bool _csyncError;
ulong _lastSeenFiles;
QString _csyncStateDbFile;
};
}

View file

@ -191,6 +191,7 @@ StatusDialog::StatusDialog( Theme *theme, QWidget *parent) :
connect(_ButtonEnable, SIGNAL(clicked()), this, SLOT(slotEnableFolder()));
connect(_ButtonInfo, SIGNAL(clicked()), this, SLOT(slotInfoFolder()));
connect(_ButtonAdd, SIGNAL(clicked()), this, SLOT(slotAddSync()));
connect(_ButtonLog, SIGNAL(clicked()), SIGNAL(openLogBrowser()));
_ButtonRemove->setEnabled(false);
_ButtonFetch->setEnabled(false);

View file

@ -77,6 +77,7 @@ signals:
void enableFolderAlias( const QString&, const bool );
void infoFolderAlias( const QString& );
void openFolderAlias( const QString& );
void openLogBrowser();
/* start the add a folder wizard. */
void addASync();

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>496</width>
<height>327</height>
<width>544</width>
<height>308</height>
</rect>
</property>
<property name="windowTitle">
@ -26,73 +26,84 @@
</property>
</widget>
</item>
<item row="1" column="0" rowspan="7">
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="_folderList"/>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="_ButtonAdd">
<property name="text">
<string>Add Sync...</string>
</property>
</widget>
<item row="1" column="1" colspan="2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="_ButtonAdd">
<property name="text">
<string>Add Sync...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonRemove">
<property name="text">
<string>Remove...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonFetch">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Fetch...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonPush">
<property name="text">
<string>Push...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonEnable">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonInfo">
<property name="text">
<string>Info...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonLog">
<property name="text">
<string>open Log Window...</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>56</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="_ButtonRemove">
<property name="text">
<string>Remove...</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="_ButtonFetch">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Fetch...</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="_ButtonPush">
<property name="text">
<string>Push...</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="_ButtonEnable">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="_ButtonInfo">
<property name="text">
<string>Info...</string>
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>56</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="5">
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0">
<item>
<widget class="QLabel" name="_ocUrlLabel">
<property name="text">
@ -102,14 +113,14 @@
</item>
</layout>
</item>
<item row="9" column="0" colspan="3">
<item row="3" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="4" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -122,7 +133,7 @@
</property>
</spacer>
</item>
<item row="10" column="1">
<item row="4" column="2">
<widget class="QPushButton" name="_ButtonClose">
<property name="text">
<string>Close</string>
@ -130,6 +141,17 @@
</widget>
</item>
</layout>
<zorder>label</zorder>
<zorder>_ButtonAdd</zorder>
<zorder>_ButtonRemove</zorder>
<zorder>_ButtonFetch</zorder>
<zorder>_ButtonPush</zorder>
<zorder>_ButtonEnable</zorder>
<zorder>_ButtonInfo</zorder>
<zorder>line</zorder>
<zorder>_ButtonClose</zorder>
<zorder>_ButtonLog</zorder>
<zorder>_ocUrlLabel</zorder>
</widget>
<resources/>
<connections/>