//
// ClientKit for Perforce
//
// Easily create custom Perforce clients with a wide range of capabilities using Qt and web technologies
//
// The starting page for a web application can be stored in a web server, a local file,
// a Perforce server, or a QResource. A QResource could be complied into the EXE for maximum security,
// or linked at runtime. The EXE could be constrained to accept QResource files that contain a matching
// secret key.
//
// A function pointer exposed to the JavaScript provides API access to both configuring the ClientKit
// container and running Perforce server commands
//
// This program should make use of no proprietary Perforce knowledge,
// and should be able to be duplicated by a programmer with no special Perforce knowledge.
//
// Thin Client, from http://doc.qt.nokia.com/4.7-snapshot/qtwebkit-bridge.html
// Another use case is using Qt as a native backend to a full web application, referred to
// here as a thin client. In this use-case, the entire UI is driven by HTML, JavaScript and
// CSS, and native Qt-based components are used to allow that application access to native
// features not usually exposed to the web, or to enable helper components that are best
// written with C++.
//
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QtWebKit>
#include <QFrame>
#include <QUrl>
#include <QFile>
#include <QVariant>
#include <QDir>
#include <QCoreApplication>
#include <iostream>
#include "clientapi.h"
#include "enviro.h"
#pragma comment(lib, "winmm.lib") // these 3 for building on windows
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "advapi32.lib")
#include "KCommon.h"
static const QString P4PointerName = "p4";
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
mMainWidget = new QFrame( this );
QVBoxLayout* pageLayout = new QVBoxLayout();
QWebView* webview = new QWebView(this);
mWindowStyle = "normal";
mWindowStyles << "normal" << "transparent" << "invisible";
mWebPage = new QWebPage(this);
// Manage capabilities of JavaScript code here to fit your security model
mWebPage->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);
mWebPage->settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true);
mWebPage->settings()->setAttribute(QWebSettings::JavascriptCanAccessClipboard, true);
mWebPage->settings()->setAttribute(QWebSettings::OfflineStorageDatabaseEnabled, true);
mWebPage->settings()->setAttribute(QWebSettings::OfflineWebApplicationCacheEnabled, true);
mWebPage->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true);
// mWebPage->settings()->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
// mWebPage->settings()->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
mWebPage->settings()->setAttribute(QWebSettings::SpatialNavigationEnabled, true);
createClientKitApi(P4PointerName);
QWebFrame* frame = mWebPage->mainFrame();
mWebPage->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); // add to API
connect(frame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(handleJavaScriptWindowObjectCleared()));
webview->setPage(mWebPage);
// Bootstrap: 1. first we look for a configuration file in .././conf/clientkit.conf from the .exe file location
QString appDirPath = QCoreApplication::applicationDirPath();
QDir confDir = QDir(appDirPath);
confDir.cdUp();
QString confFilename = confDir.filePath("clientkit.conf"); // once clientkit.exe is installed in a \bin dir, change this to look in a sibling dir called 'config'
QString startFilename;
QString startFileContents;
if (QFile::exists(confFilename))
{
QString confFileContents = readFile(confFilename);
QMap<QString, QString> settingsMap = parseConfFile(confFileContents);
if (settingsMap.contains("startFile"))
startFilename = settingsMap["startFile"];
}
else
{
// Bootstrap: 2. Then we look for an index.html in the current directory
QString currentDir = QDir::currentPath();
QString defaultIndex = currentDir + "/" + "index.html"; // fixme: don't do string manipulation
startFilename = defaultIndex;
}
// Here we have the filename we want to try to load
QFile startFile(startFilename);
// Security tip: constrain the type of file that can be loaded here. For example, could disallow loading local files
if (!startFile.exists()) // Bootstrap: 3. use default message
{
startFileContents = "<html><body><h2>ClientKit did not find a clientkit.conf file in the executable directory or an index.html file in the current directory</h2><p><br>Default startup file not found:"+startFilename+"</p><br>Create this file to define your start page<p><p>You can also create a clientkit.conf file and define<p><p>startFile=<My filepath to an html file><p></body></html>";
webview->setHtml(startFileContents);
}
else
webview->setUrl(startFilename);
pageLayout->setContentsMargins(0,0,0,0);
pageLayout->addWidget(webview);
mMainWidget->setLayout(pageLayout);
setCentralWidget( mMainWidget );
}
MainWindow::~MainWindow()
{
}
QMap<QString, QString> MainWindow::parseConfFile(QString fileContents)
{
QMap<QString, QString> configMap;
QStringList lines = fileContents.split(QRegExp("(\\r\\n)|(\\n\\r)|\\r|\\n"), QString::SkipEmptyParts);
for (int i = 0; i < lines.size(); ++i)
{
QString line = lines.at(i).toLocal8Bit().constData();
if (line[0] != '#') // ignore comment lines
{
line.trimmed();
if (!line.isEmpty())
{
int pos = line.indexOf(" ");
if (pos == -1)
continue;
QString key = line.left(pos);
QString value = line.mid(pos+1);
configMap.insert(key, value);
}
}
}
return configMap;
}
bool
MainWindow::createClientKitApi(QString pointerName)
{
ClientKitApi* clientKitApi = new ClientKitApi(this, this, pointerName);
mClientKitApiMap.insert(pointerName, clientKitApi);
mWebPage->mainFrame()->addToJavaScriptWindowObject(pointerName, clientKitApi);
return true;
}
bool
MainWindow::setWindowStyle(QString styleName)
{
Qt::WindowFlags flags;
if (styleName=="transparent")
{
setStyleSheet("background:transparent;");
setAttribute(Qt::WA_TranslucentBackground, false);
flags = Qt::Window;
}
else if (styleName=="normal")
{
setStyleSheet("background:white;");
setAttribute(Qt::WA_TranslucentBackground, false);
flags = Qt::Window;
}
else if (styleName=="invisible")
{
setStyleSheet("background:transparent;");
setAttribute(Qt::WA_TranslucentBackground, true);
flags = Qt::FramelessWindowHint;
}
this->setWindowFlags(flags);
this->show();
mWindowStyle = styleName;
return true;
}
void
MainWindow::handleJavaScriptWindowObjectCleared()
{
QWebFrame* frame = mWebPage->mainFrame();
QMapIterator<QString, ClientKitApi*> mapi(mClientKitApiMap);
while (mapi.hasNext())
{
mapi.next();
frame->addToJavaScriptWindowObject(mapi.key(), mapi.value()); // This the magic that makes the pointer visible to JavaScript
// can use evaluateJavaScript here to add JavaScript to execution context
}
QList<QWebFrame *> childFrames = frame->childFrames();
for(int i = 0; i < childFrames.size(); i++)
{
QMapIterator<QString, ClientKitApi*> mapi(mClientKitApiMap);
while (mapi.hasNext())
{
mapi.next();
frame->addToJavaScriptWindowObject(mapi.key(), mapi.value());
// can use evaluateJavaScript here to add JavaScript to execution context
}
}
}
QString
MainWindow::readFile(QString filename) // currently only reads a local file
{
QString filecontents;
QFile f( filename );
if ( !f.exists() )
{
mMessageLog += "File not found: " + filename;
return QString();
}
f.open( QIODevice::ReadOnly );
QTextStream ts ( &f );
QTextCodec * codec = QTextCodec::codecForName("UTF-8");
ts.setCodec( codec );
filecontents = ts.readAll();
return filecontents;
}