Add a basic GTK update installation dialog and a stub function in UpdateInstaller to restart the main app after the update is complete.

* Add optional GTK mode to the updater build.  This pulls in a large number of extra dependencies,
   but dependencies which should be fairly ubiquitous on Linux systems.  If these dependencies prove
   to be a problem we could look at providing an alternative, more basic UI and loading the appropriate
   UI dynamically.
 * Implement update progress dialog for GTK.  The dialog needs visual polish but is functional.
This commit is contained in:
Robert Knight 2011-08-23 12:29:47 +01:00
parent f1367671b6
commit 85c4c58dc9
7 changed files with 252 additions and 22 deletions

View file

@ -3,6 +3,17 @@ add_subdirectory(tests)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
include(UsePkgConfig)
pkgconfig(gtk+-2.0 GTK2_INCLUDE_DIR GTK2_LINK_DIR GTK2_LINK_FLAGS GTK2_CFLAGS)
option(ENABLE_GTK ON)
if (ENABLE_GTK)
include_directories(${GTK2_INCLUDE_DIR})
add_definitions(${GTK2_CFLAGS})
add_definitions(-DENABLE_GTK)
endif()
add_definitions(-DTIXML_USE_STL) add_definitions(-DTIXML_USE_STL)
set (SOURCES set (SOURCES
@ -15,6 +26,10 @@ set (SOURCES
UpdaterOptions.cpp UpdaterOptions.cpp
) )
if (ENABLE_GTK)
set(SOURCES ${SOURCES} UpdateDialogGtk.cpp)
endif()
set (HEADERS set (HEADERS
Dir.h Dir.h
FileOps.h FileOps.h
@ -25,6 +40,10 @@ set (HEADERS
UpdaterOptions.h UpdaterOptions.h
) )
if (ENABLE_GTK)
set(HEADERS ${HEADERS} UpdateDialogGtk.h)
endif()
add_library(updatershared add_library(updatershared
${SOURCES} ${SOURCES}
${HEADERS} ${HEADERS}
@ -35,8 +54,14 @@ target_link_libraries(updatershared
tinyxml tinyxml
minizip minizip
tinythread tinythread
${GTK2_LINK_FLAGS}
) )
if (ENABLE_GTK)
target_link_libraries(updatershared
${GTK2_LINK_FLAGS})
endif()
if (UNIX) if (UNIX)
target_link_libraries(updatershared pthread) target_link_libraries(updatershared pthread)
endif() endif()
@ -52,7 +77,6 @@ if(APPLE)
) )
endif() endif()
target_link_libraries(updater target_link_libraries(updater
updatershared updatershared
) )

118
src/UpdateDialogGtk.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "UpdateDialogGtk.h"
#include "Log.h"
#include "StringUtils.h"
#include <glib.h>
#include <gtk/gtk.h>
UpdateDialogGtk::UpdateDialogGtk()
: m_restartApp(false)
{
}
bool UpdateDialogGtk::restartApp() const
{
return m_restartApp;
}
void UpdateDialogGtk::init(int argc, char** argv)
{
gtk_init(&argc,&argv);
m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(m_window),"Mendeley Updater");
m_progressLabel = gtk_label_new("Installing Updates");
GtkWidget* windowLayout = gtk_vbox_new(FALSE,3);
GtkWidget* buttonLayout = gtk_hbox_new(FALSE,3);
m_finishButton = gtk_button_new_with_label("Finish");
gtk_widget_set_sensitive(m_finishButton,false);
m_progressBar = gtk_progress_bar_new();
gtk_signal_connect(GTK_OBJECT(m_finishButton),"clicked",
GTK_SIGNAL_FUNC(UpdateDialogGtk::finish),this);
gtk_container_add(GTK_CONTAINER(m_window),windowLayout);
gtk_container_set_border_width(GTK_CONTAINER(m_window),8);
gtk_container_add(GTK_CONTAINER(windowLayout),m_progressLabel);
gtk_container_add(GTK_CONTAINER(windowLayout),m_progressBar);
gtk_container_add(GTK_CONTAINER(windowLayout),buttonLayout);
gtk_box_pack_start(GTK_BOX(buttonLayout),m_finishButton,true,false,0);
gtk_widget_show(m_progressLabel);
gtk_widget_show(windowLayout);
gtk_widget_show(buttonLayout);
gtk_widget_show(m_finishButton);
gtk_widget_show(m_progressBar);
gtk_widget_show(m_window);
}
void UpdateDialogGtk::exec()
{
gtk_main();
}
void UpdateDialogGtk::finish(void* _dialog)
{
UpdateDialogGtk* dialog = static_cast<UpdateDialogGtk*>(_dialog);
dialog->m_restartApp = true;
gtk_main_quit();
}
gboolean UpdateDialogGtk::notify(void* _message)
{
Message* message = static_cast<Message*>(_message);
switch (message->type)
{
case Message::UpdateProgress:
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(message->dialog->m_progressBar),message->progress/100.0);
break;
case Message::UpdateFailed:
gtk_label_set_text(GTK_LABEL(message->dialog->m_progressLabel),
("There was a problem installing the update: " + message->message).c_str());;
gtk_widget_set_sensitive(message->dialog->m_finishButton,true);
break;
case Message::UpdateFinished:
gtk_label_set_text(GTK_LABEL(message->dialog->m_progressLabel),
"Update installed. Click 'Finish' to restart the application.");
gtk_widget_set_sensitive(message->dialog->m_finishButton,true);
break;
}
delete message;
// do not invoke this function again
return false;
}
// callbacks during update installation
void UpdateDialogGtk::updateError(const std::string& errorMessage)
{
Message* message = new Message(this,Message::UpdateFailed);
message->message = errorMessage;
g_idle_add(&UpdateDialogGtk::notify,message);
}
bool UpdateDialogGtk::updateRetryCancel(const std::string& message)
{
// TODO
}
void UpdateDialogGtk::updateProgress(int percentage)
{
Message* message = new Message(this,Message::UpdateProgress);
message->progress = percentage;
g_idle_add(&UpdateDialogGtk::notify,message);
}
void UpdateDialogGtk::updateFinished()
{
Message* message = new Message(this,Message::UpdateFinished);
g_idle_add(&UpdateDialogGtk::notify,message);
}

View file

@ -1,7 +1,55 @@
#pragma once #pragma once
// TODO - UI for update dialog under Gtk #include "UpdateObserver.h"
class UpdateDialogGtk
#include <gtk/gtk.h>
class UpdateDialogGtk : public UpdateObserver
{ {
public:
UpdateDialogGtk();
bool restartApp() const;
void init(int argc, char** argv);
void exec();
// observer callbacks - these may be called
// from a background thread
virtual void updateError(const std::string& errorMessage);
virtual bool updateRetryCancel(const std::string& message);
virtual void updateProgress(int percentage);
virtual void updateFinished();
private:
struct Message
{
enum Type
{
UpdateFailed,
UpdateProgress,
UpdateFinished
};
Message(UpdateDialogGtk* _dialog, Type _type)
: dialog(_dialog)
, type(_type)
{
}
UpdateDialogGtk* dialog;
Type type;
std::string message;
int progress;
};
static void finish(void* dialog);
static gboolean notify(void* message);
GtkWidget* m_window;
GtkWidget* m_progressLabel;
GtkWidget* m_finishButton;
GtkWidget* m_progressBar;
bool m_restartApp;
}; };

View file

@ -302,3 +302,13 @@ bool UpdateInstaller::checkAccess()
} }
} }
void UpdateInstaller::setObserver(UpdateObserver* observer)
{
m_observer = observer;
}
void UpdateInstaller::restartMainApp()
{
LOG(Warn,"Restarting main app not implemented");
}

View file

@ -30,6 +30,8 @@ class UpdateInstaller
void run() throw (); void run() throw ();
void restartMainApp();
private: private:
void cleanup(); void cleanup();
void revert(); void revert();

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string>
class UpdateObserver class UpdateObserver
{ {
public: public:

View file

@ -6,17 +6,13 @@
#include "tinythread.h" #include "tinythread.h"
#if defined(PLATFORM_WINDOWS) #if defined(PLATFORM_LINUX) and defined(ENABLE_GTK)
#include "UpdateDialogWin32.h"
#elif defined(PLATFORM_MAC)
#include "UpdateDialogCocoa.h"
#elif defined(PLATFORM_LINUX)
#include "UpdateDialogGtk.h" #include "UpdateDialogGtk.h"
#endif #endif
#include <iostream> #include <iostream>
void setupUi(UpdateInstaller* installer); void runWithUi(int argc, char** argv, UpdateInstaller* installer);
void runUpdaterThread(void* arg) void runUpdaterThread(void* arg)
{ {
@ -44,11 +40,6 @@ int main(int argc, char** argv)
script.parse(options.script); script.parse(options.script);
} }
if (options.mode == UpdateInstaller::Main)
{
setupUi(&installer);
}
LOG(Info,"started updater. install-dir: " + options.installDir LOG(Info,"started updater. install-dir: " + options.installDir
+ ", package-dir: " + options.packageDir + ", package-dir: " + options.packageDir
+ ", wait-pid: " + intToStr(options.waitPid) + ", wait-pid: " + intToStr(options.waitPid)
@ -61,20 +52,55 @@ int main(int argc, char** argv)
installer.setScript(&script); installer.setScript(&script);
installer.setWaitPid(options.waitPid); installer.setWaitPid(options.waitPid);
tthread::thread updaterThread(runUpdaterThread,&installer); if (options.mode == UpdateInstaller::Main)
updaterThread.join(); {
runWithUi(argc,argv,&installer);
}
else
{
installer.run();
}
return 0; return 0;
} }
void setupUi(UpdateInstaller* installer) #ifdef PLATFORM_LINUX
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{ {
#if defined(PLATFORM_WINDOWS) #ifdef ENABLE_GTK
UpdateDialogWin32 dialog; LOG(Info,"setting up GTK UI");
#elif defined(PLATFORM_MAC)
UpdateDialogCocoa dialog;
#elif defined(PLATFORM_LINUX)
UpdateDialogGtk dialog; UpdateDialogGtk dialog;
installer->setObserver(&dialog);
dialog.init(argc,argv);
tthread::thread updaterThread(runUpdaterThread,installer);
dialog.exec();
updaterThread.join();
if (dialog.restartApp())
{
LOG(Info,"Restarting app after install");
installer->restartMainApp();
}
#else
// no UI available - do a silent install
installer->run();
installer->restartMainApp();
#endif #endif
} }
#endif
#ifdef PLATFORM_MAC
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{
// TODO - Cocoa UI
installer->run();
}
#endif
#ifdef PLATFORM_WINDOWS
void runWithUi(int argc, char** argv, UpdateInstaller* installer)
{
// TODO - Windows UI
installer->run();
}
#endif