diff --git a/MiniZincIDE/MiniZincIDE.pro b/MiniZincIDE/MiniZincIDE.pro index 3099045..26fa6e7 100644 --- a/MiniZincIDE/MiniZincIDE.pro +++ b/MiniZincIDE/MiniZincIDE.pro @@ -4,14 +4,22 @@ # #------------------------------------------------- -QT += core gui webkitwidgets +QT += core gui widgets -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +greaterThan(QT_MAJOR_VERSION, 4): { + greaterThan(QT_MINOR_VERSION, 5): { + QT += webenginewidgets + DEFINES += MINIZINC_IDE_HAVE_WEBENGINE + } + !greaterThan(QT_MINOR_VERSION, 5): { + QT += webkitwidgets + } +} TARGET = MiniZincIDE TEMPLATE = app -VERSION = 2.0.13 +VERSION = 2.0.14 DEFINES += MINIZINC_IDE_VERSION=\\\"$$VERSION\\\" bundled { @@ -86,3 +94,6 @@ FORMS += \ RESOURCES += \ minizincide.qrc + +target.path = /bin +INSTALLS += target diff --git a/MiniZincIDE/aboutdialog.ui b/MiniZincIDE/aboutdialog.ui index 31d51c6..7e34d52 100644 --- a/MiniZincIDE/aboutdialog.ui +++ b/MiniZincIDE/aboutdialog.ui @@ -51,8 +51,8 @@ <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'.Helvetica Neue DeskInterface'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:30px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.minizinc.org"><img src=":/images/mznlogo.png" width="128" height="108" /></a><span style=" font-family:'.Lucida Grande UI';"> </span></p> +</style></head><body style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:30px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.minizinc.org"><img src=":/images/mznicon.png" width="108" height="108" /></a><span style=" font-family:'.Lucida Grande UI';"> </span></p> <p style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.Lucida Grande UI'; font-size:large; font-weight:600;">The MiniZinc IDE</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.Lucida Grande UI';">Version $VERSION</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Lucida Grande UI';"><br /></p> diff --git a/MiniZincIDE/codeeditor.cpp b/MiniZincIDE/codeeditor.cpp index f83794c..78258a4 100755 --- a/MiniZincIDE/codeeditor.cpp +++ b/MiniZincIDE/codeeditor.cpp @@ -129,6 +129,15 @@ void CodeEditor::keyPressEvent(QKeyEvent *e) e->accept(); QTextCursor cursor(textCursor()); cursor.insertText(" "); + } else if (e->key() == Qt::Key_Return) { + e->accept(); + QTextCursor cursor(textCursor()); + QString curLine = cursor.block().text(); + QRegExp leadingWhitespace("^(\\s*)"); + cursor.insertText("\n"); + if (leadingWhitespace.indexIn(curLine) != -1) { + cursor.insertText(leadingWhitespace.cap(1)); + } } else { QPlainTextEdit::keyPressEvent(e); } diff --git a/MiniZincIDE/courserasubmission.cpp b/MiniZincIDE/courserasubmission.cpp index 6e83b65..50b3432 100644 --- a/MiniZincIDE/courserasubmission.cpp +++ b/MiniZincIDE/courserasubmission.cpp @@ -11,6 +11,7 @@ #include <QNetworkReply> #include <QUrlQuery> #include <QCryptographicHash> +#include <QJsonDocument> CourseraSubmission::CourseraSubmission(MainWindow* mw0, CourseraProject& cp) : QDialog(NULL), _cur_phase(S_NONE), project(cp), mw(mw0), @@ -28,7 +29,7 @@ CourseraSubmission::CourseraSubmission(MainWindow* mw0, CourseraProject& cp) : for (int i=0; i<project.models.size(); i++) { const CourseraItem& item = project.models.at(i); QCheckBox* cb = new QCheckBox(item.name); - cb->setChecked(false); + cb->setChecked(true); modelLayout->addWidget(cb); } for (int i=0; i<project.problems.size(); i++) { @@ -41,14 +42,17 @@ CourseraSubmission::CourseraSubmission(MainWindow* mw0, CourseraProject& cp) : modelLayout->addWidget(new QLabel("none")); if (project.problems.empty()) problemLayout->addWidget(new QLabel("none")); - _output_stream.setString(&_submission); + _output_stream.setString(&_output_string); QSettings settings; settings.beginGroup("coursera"); ui->storePassword->setChecked(settings.value("storeLogin",false).toBool()); - ui->login->setText(settings.value("login").toString()); - ui->password->setText(settings.value("password").toString()); + ui->login->setText(settings.value("courseraEmail").toString()); + settings.beginGroup(project.assignmentKey); + ui->password->setText(settings.value("token").toString()); + settings.endGroup(); settings.endGroup(); + } CourseraSubmission::~CourseraSubmission() @@ -58,22 +62,15 @@ CourseraSubmission::~CourseraSubmission() bool storeLogin = ui->storePassword->isChecked(); settings.setValue("storeLogin", storeLogin); if (storeLogin) { - settings.setValue("login",ui->login->text()); - settings.setValue("password",ui->password->text()); - } else { - settings.setValue("login",""); - settings.setValue("password",""); + settings.setValue("courseraEmail",ui->login->text()); + settings.beginGroup(project.assignmentKey); + settings.setValue("token",ui->password->text()); + settings.endGroup(); } settings.endGroup(); delete ui; } -QByteArray CourseraSubmission::challenge_response(QString passwd, QString challenge) -{ - QByteArray hash = QCryptographicHash::hash(QString(challenge+passwd).toLocal8Bit(), QCryptographicHash::Sha1); - return hash.toHex(); -} - void CourseraSubmission::disableUI() { ui->loginGroup->setEnabled(false); @@ -95,15 +92,15 @@ void CourseraSubmission::cancelOperation() switch (_cur_phase) { case S_NONE: return; - case S_WAIT_CHALLENGE: - disconnect(reply, SIGNAL(finished()), this, SLOT(rcv_challenge())); + case S_WAIT_PWD: + disconnect(mw, SIGNAL(finished()), this, SLOT(rcvLoginCheckResponse())); break; case S_WAIT_SOLVE: - disconnect(mw, SIGNAL(finished()), this, SLOT(solver_finished())); + disconnect(mw, SIGNAL(finished()), this, SLOT(solverFinished())); mw->on_actionStop_triggered(); break; case S_WAIT_SUBMIT: - disconnect(reply, SIGNAL(finished()), this, SLOT(rcv_solution_reply())); + disconnect(reply, SIGNAL(finished()), this, SLOT(rcvSubmissionResponse())); break; } ui->textBrowser->insertPlainText("Aborted.\n"); @@ -124,226 +121,202 @@ void CourseraSubmission::reject() QDialog::reject(); } -void CourseraSubmission::on_checkLoginButton_clicked() -{ - _current_model = -2; - get_challenge(); -} +void CourseraSubmission::solveNext() { + int n_problems = project.problems.size(); -void CourseraSubmission::get_challenge() -{ - QString email = ui->login->text(); - if (email.isEmpty()) { - QMessageBox::warning(this, "MiniZinc IDE", - "Enter an email address for Coursera login!"); - _current_model = -2; - return; - } - if (ui->password->text().isEmpty()) { - QMessageBox::warning(this, "MiniZinc IDE", - "Enter a password for Coursera login!"); - _current_model = -2; + bool done = false; + do { + _current_model++; + if (_current_model < n_problems) { + QCheckBox* cb = qobject_cast<QCheckBox*>(ui->problemBox->layout()->itemAt(_current_model)->widget()); + done = cb->isChecked(); + } else { + done = true; + } + } while (!done); + if (_current_model < n_problems) { + + CourseraItem& item = project.problems[_current_model]; + connect(mw, SIGNAL(finished()), this, SLOT(solverFinished())); + ui->textBrowser->insertPlainText("Running "+item.name+"\n"); + _cur_phase = S_WAIT_SOLVE; + _output_string = ""; + mw->addOutput("<div style='color:orange;'>Running Coursera submission "+item.name+"</div><br>\n"); + if (!mw->runWithOutput(item.model, item.data, item.timeout, _output_stream)) { + ui->textBrowser->insertPlainText("Error: could not run "+item.name+"\n"); + ui->textBrowser->insertPlainText("Skipping.\n"); + solveNext(); + } return; + } else { + submitToCoursera(); } - QUrl url("https://class.coursera.org/" + project.course + "/assignment/challenge"); - QUrlQuery q; - q.addQueryItem("email_address", email); - q.addQueryItem("assignment_part_sid", "6vp6Er9J-dev"); - q.addQueryItem("response_encoding","delim"); - url.setQuery(q); - - QNetworkRequest request; - request.setUrl(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - _cur_phase = S_WAIT_CHALLENGE; - reply = IDE::instance()->networkManager->get(request); - connect(reply, SIGNAL(finished()), this, SLOT(rcv_challenge())); - } -void CourseraSubmission::rcv_challenge() +void CourseraSubmission::submitToCoursera() { - disconnect(reply, SIGNAL(finished()), this, SLOT(rcv_challenge())); - - reply->deleteLater(); - QString challenge = reply->readAll(); - QStringList fields = challenge.split("|"); - - if (fields.size() < 7) { - QMessageBox::warning(this, "MiniZinc IDE", - "Error: cannot connect to Coursera.\n"+challenge); - _cur_phase = S_NONE; - enableUI(); - return; - } - - _login = fields[2]; - QString ch = fields[4]; - _state = fields[6]; - - _ch_resp = challenge_response(ui->password->text(),ch); + QUrl url("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1"); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader(QByteArray("Cache-Control"),QByteArray("no-cache")); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - if (_current_model < 0) { - // Check login information - _submission = "0"; - _source = ""; - _sid = project.checkpwdSid; - if (_current_model == -1) - ui->textBrowser->insertPlainText("Checking login\n"); - submit_solution(); - } else { - int n_problems = project.problems.size(); - - CourseraItem& item = - _current_model < n_problems ? - project.problems[_current_model] - : project.models[_current_model-n_problems]; - QStringList allfiles = mw->getProject().files(); - - bool foundFile = false; - for (int i=0; i<allfiles.size(); i++) { - QFileInfo fi(allfiles[i]); - if (fi.fileName()==item.model) { - foundFile = true; - QFile file(fi.absoluteFilePath()); - if (file.open(QFile::ReadOnly | QFile::Text)) { - _source = file.readAll(); - } else { - ui->textBrowser->insertPlainText("Error: could not open "+item.name+"\n"); - ui->textBrowser->insertPlainText("Skipping.\n"); - goto_next(); - return; + // add models + QStringList allfiles = mw->getProject().files(); + for (int i=0; i<project.models.size(); i++) { + const CourseraItem& item = project.models.at(i); + QCheckBox* cb = qobject_cast<QCheckBox*>(ui->modelBox->layout()->itemAt(i)->widget()); + if (cb->isChecked()) { + bool foundFile = false; + for (int i=0; i<allfiles.size(); i++) { + QFileInfo fi(allfiles[i]); + if (fi.fileName()==item.model) { + foundFile = true; + QFile file(fi.absoluteFilePath()); + if (file.open(QFile::ReadOnly | QFile::Text)) { + QJsonObject output; + QTextStream ts(&file); + output["output"] = ts.readAll(); + _parts[item.id] = output; + } else { + ui->textBrowser->insertPlainText("Error: could not open "+item.name+"\n"); + ui->textBrowser->insertPlainText("Skipping.\n"); + solveNext(); + return; + } + break; } - break; } - } - if (!foundFile) { - ui->textBrowser->insertPlainText("Error: could not find "+item.name+"\n"); - ui->textBrowser->insertPlainText("Skipping.\n"); - goto_next(); - return; - } - _sid = item.id; - - if (_current_model < n_problems) { - _submission.clear(); - connect(mw, SIGNAL(finished()), this, SLOT(solver_finished())); - ui->textBrowser->insertPlainText("Running "+item.name+"\n"); - _cur_phase = S_WAIT_SOLVE; - mw->addOutput("<div style='color:orange;'>Running Coursera submission "+item.name+"</div><br>\n"); - if (!mw->runWithOutput(item.model, item.data, item.timeout, _output_stream)) { - ui->textBrowser->insertPlainText("Error: could not run "+item.name+"\n"); + if (!foundFile) { + ui->textBrowser->insertPlainText("Error: could not find "+item.name+"\n"); ui->textBrowser->insertPlainText("Skipping.\n"); - goto_next(); } - return; - } else { - // Submit model source code - _submission = _source; - _source = ""; - ui->textBrowser->insertPlainText("Submitting model "+item.name+"\n"); - submit_solution(); } - } -} -void CourseraSubmission::submit_solution() -{ - QUrl url("https://class.coursera.org/" + project.course + "/assignment/submit"); - QUrlQuery q; - q.addQueryItem("email_address", QUrl::toPercentEncoding(_login)); - q.addQueryItem("assignment_part_sid", QUrl::toPercentEncoding(_sid)); - q.addQueryItem("submission", QUrl::toPercentEncoding(_submission.toUtf8().toBase64())); - q.addQueryItem("submission_aux",QUrl::toPercentEncoding(_source.toUtf8().toBase64())); - q.addQueryItem("challenge_response", QUrl::toPercentEncoding(_ch_resp)); - q.addQueryItem("state",QUrl::toPercentEncoding(_state)); + _submission["assignmentKey"] = project.assignmentKey; + _submission["secret"] = ui->password->text(); + _submission["submitterEmail"] = ui->login->text(); + _submission["parts"] = _parts; + + QJsonDocument doc(_submission); - QNetworkRequest request; - request.setUrl(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); _cur_phase = S_WAIT_SUBMIT; - reply = IDE::instance()->networkManager->post(request,q.toString().toLocal8Bit()); - connect(reply, SIGNAL(finished()), this, SLOT(rcv_solution_reply())); + reply = IDE::instance()->networkManager->post(request,doc.toJson()); + connect(reply, SIGNAL(finished()), this, SLOT(rcvSubmissionResponse())); + ui->textBrowser->insertPlainText("Submitting to Coursera for grading...\n"); } -void CourseraSubmission::rcv_solution_reply() +void CourseraSubmission::rcvSubmissionResponse() { - disconnect(reply, SIGNAL(finished()), this, SLOT(rcv_solution_reply())); + disconnect(reply, SIGNAL(finished()), this, SLOT(rcvSubmissionResponse())); reply->deleteLater(); - QString message = reply->readAll(); - - if (_current_model < 0) { - if (message != "password verified") { - QMessageBox::warning(this, "MiniZinc IDE", - "Login failed! Message: "+message); - _current_model = -2; - _cur_phase = S_NONE; - return; - } - if (_current_model == -2) { - QMessageBox::information(this, "MiniZinc IDE", "Login successful!"); - _cur_phase = S_NONE; - return; + QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + if (doc.object().contains("message")) { + ui->textBrowser->insertPlainText("== "+doc.object()["message"].toString()+"\n"); + } + if (doc.object().contains("details")) { + QJsonObject details = doc.object()["details"].toObject(); + if (details.contains("learnerMessage")) { + ui->textBrowser->insertPlainText("== "+details["learnerMessage"].toString()+"\n"); } } - ui->textBrowser->insertPlainText("== "+message+"\n"); - goto_next(); + + ui->textBrowser->insertPlainText("Done.\n"); + _cur_phase = S_NONE; + ui->runButton->setText("Done."); + ui->runButton->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true); } -void CourseraSubmission::solver_finished() +void CourseraSubmission::solverFinished() { - disconnect(mw, SIGNAL(finished()), this, SLOT(solver_finished())); + disconnect(mw, SIGNAL(finished()), this, SLOT(solverFinished())); - QStringList solutions = _submission.split("----------"); + QStringList solutions = _output_string.split("----------"); if (solutions.size() >= 2) { - _submission = solutions[solutions.size()-2]+"----------"+solutions[solutions.size()-1]; + _output_string = solutions[solutions.size()-2]+"----------"+solutions[solutions.size()-1]; } - if (_submission.size()==0 || _submission[_submission.size()-1] != '\n') - _submission += "\n"; - _submission += "unknown time\nMiniZinc IDE submission"; - ui->textBrowser->insertPlainText("Submitting solution\n"); - submit_solution(); + if (_output_string.size()==0 || _output_string[_output_string.size()-1] != '\n') + _output_string += "\n"; + + QJsonObject output; + output["output"] = _output_string; + _parts[project.problems[_current_model].id] = output; + ui->textBrowser->insertPlainText("Finished\n"); + solveNext(); } -void CourseraSubmission::goto_next() +void CourseraSubmission::on_runButton_clicked() { - int n_models = project.models.size(); - int n_problems = project.problems.size(); - - bool done = false; - do { - _current_model++; - if (_current_model < n_problems) { - QCheckBox* cb = qobject_cast<QCheckBox*>(ui->problemBox->layout()->itemAt(_current_model)->widget()); - done = cb->isChecked(); - } else if (_current_model < n_models+n_problems) { - int idx = _current_model - n_problems; - QCheckBox* cb = qobject_cast<QCheckBox*>(ui->modelBox->layout()->itemAt(idx)->widget()); - done = cb->isChecked(); + if (_cur_phase==S_NONE) { + ui->textBrowser->clear(); + QString email = ui->login->text(); + if (email.isEmpty()) { + QMessageBox::warning(this, "MiniZinc IDE", + "Enter an email address for Coursera login!"); + return; } - if (_current_model >= n_models+n_problems) { - ui->textBrowser->insertPlainText("Done.\n"); - _cur_phase = S_NONE; - ui->runButton->setText("Done."); - ui->runButton->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true); + if (ui->password->text().isEmpty()) { + QMessageBox::warning(this, "MiniZinc IDE", + "Enter an assignment key!"); return; } - } while (!done); - get_challenge(); + + // Send empty request to check password + QUrl url("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1"); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader(QByteArray("Cache-Control"),QByteArray("no-cache")); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QJsonObject checkPwdSubmission; + checkPwdSubmission["assignmentKey"] = project.assignmentKey; + checkPwdSubmission["secret"] = ui->password->text(); + checkPwdSubmission["submitterEmail"] = ui->login->text(); + QJsonObject emptyParts; + checkPwdSubmission["parts"] = emptyParts; + + QJsonDocument doc(checkPwdSubmission); + + _cur_phase = S_WAIT_PWD; + disableUI(); + reply = IDE::instance()->networkManager->post(request,doc.toJson()); + connect(reply, SIGNAL(finished()), this, SLOT(rcvLoginCheckResponse())); + ui->textBrowser->insertPlainText("Checking login and assignment token...\n"); + } else { + cancelOperation(); + } } -void CourseraSubmission::on_runButton_clicked() +void CourseraSubmission::rcvLoginCheckResponse() { - if (_cur_phase==S_NONE) { + disconnect(reply, SIGNAL(finished()), this, SLOT(rcvLoginCheckResponse())); + reply->deleteLater(); + + QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + if (doc.object().contains("message") && doc.object()["message"].toString().endsWith("but found: Set()")) { + ui->textBrowser->insertPlainText("Done.\n"); _current_model = -1; - disableUI(); - goto_next(); + for (int i=0; i<project.problems.size(); i++) { + _parts[project.problems[i].id] = QJsonObject(); + } + for (int i=0; i<project.models.size(); i++) { + _parts[project.models[i].id] = QJsonObject(); + } + solveNext(); } else { - cancelOperation(); + if (doc.object().contains("message")) { + ui->textBrowser->insertPlainText("== "+doc.object()["message"].toString()+"\n"); + } + if (doc.object().contains("details")) { + QJsonObject details = doc.object()["details"].toObject(); + if (details.contains("learnerMessage")) { + ui->textBrowser->insertPlainText(">> "+details["learnerMessage"].toString()+"\n"); + } + } + _cur_phase = S_NONE; + enableUI(); } } @@ -352,9 +325,8 @@ void CourseraSubmission::on_storePassword_toggled(bool checked) if (!checked) { QSettings settings; settings.beginGroup("coursera"); + settings.remove(""); settings.setValue("storeLogin", false); - settings.setValue("login",""); - settings.setValue("password",""); settings.endGroup(); } } diff --git a/MiniZincIDE/courserasubmission.h b/MiniZincIDE/courserasubmission.h index 50ea12a..2e007f7 100644 --- a/MiniZincIDE/courserasubmission.h +++ b/MiniZincIDE/courserasubmission.h @@ -3,6 +3,7 @@ #include <QDialog> #include <QTextStream> +#include <QJsonObject> #include "project.h" class QNetworkReply; @@ -21,45 +22,37 @@ class CourseraSubmission : public QDialog ~CourseraSubmission(); protected: - enum State { S_NONE, S_WAIT_CHALLENGE, S_WAIT_SUBMIT, S_WAIT_SOLVE } _cur_phase; + enum State { S_NONE, S_WAIT_PWD, S_WAIT_SUBMIT, S_WAIT_SOLVE } _cur_phase; int _current_model; - QString _submission; - QString _source; QTextStream _output_stream; + QString _output_string; - QString _login; - QString _sid; - QString _ch_resp; - QString _state; + QJsonObject _submission; + QJsonObject _parts; CourseraProject& project; MainWindow* mw; QNetworkReply* reply; - QByteArray challenge_response(QString passwd, QString challenge); - void disableUI(void); void enableUI(void); void cancelOperation(void); + void solveNext(void); public slots: void reject(); private slots: - void on_checkLoginButton_clicked(); - void get_challenge(); - void rcv_challenge(); - void submit_solution(); - void rcv_solution_reply(); - void solver_finished(); - void goto_next(); + void submitToCoursera(); + void rcvSubmissionResponse(); + void solverFinished(); void on_runButton_clicked(); + void rcvLoginCheckResponse(); void on_storePassword_toggled(bool checked); - private: Ui::CourseraSubmission *ui; }; diff --git a/MiniZincIDE/courserasubmission.ui b/MiniZincIDE/courserasubmission.ui index 01c6290..a99df29 100644 --- a/MiniZincIDE/courserasubmission.ui +++ b/MiniZincIDE/courserasubmission.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>521</width> - <height>585</height> + <height>652</height> </rect> </property> <property name="sizePolicy"> @@ -48,7 +48,7 @@ <item> <widget class="QLabel" name="label_3"> <property name="text"> - <string>Submission login:</string> + <string>Coursera login email:</string> </property> </widget> </item> @@ -65,7 +65,7 @@ <item> <widget class="QLabel" name="label_4"> <property name="text"> - <string>Submission password:</string> + <string>Assignment token:</string> </property> </widget> </item> @@ -87,7 +87,7 @@ </sizepolicy> </property> <property name="text"> - <string>Note that this is your assignment submission password, not your Coursera login and password.</string> + <string>Note that this is your assignment submission token, not your Coursera password.</string> </property> <property name="textFormat"> <enum>Qt::RichText</enum> @@ -105,7 +105,7 @@ <item> <widget class="QCheckBox" name="storePassword"> <property name="text"> - <string>Remember password</string> + <string>Remember login details</string> </property> </widget> </item> @@ -122,13 +122,6 @@ </property> </spacer> </item> - <item> - <widget class="QPushButton" name="checkLoginButton"> - <property name="text"> - <string>Check login</string> - </property> - </widget> - </item> </layout> </item> </layout> diff --git a/MiniZincIDE/htmlpage.cpp b/MiniZincIDE/htmlpage.cpp index c77d019..5ae26ef 100755 --- a/MiniZincIDE/htmlpage.cpp +++ b/MiniZincIDE/htmlpage.cpp @@ -1,6 +1,42 @@ #include "htmlpage.h" #include "mainwindow.h" #include "QDebug" + +#ifdef MINIZINC_IDE_HAVE_WEBENGINE + +HTMLPage::HTMLPage(MainWindow* mw, QWidget *parent) : + QWebEnginePage(parent), _mw(mw), _webChannel(new QWebChannel(this)), _mznide(new MiniZincIDEJS(this)), + loadFinished(false) +{ + connect(this, SIGNAL(loadFinished(bool)), this, SLOT(pageLoadFinished(bool))); + setWebChannel(_webChannel); + _webChannel->registerObject("mznide", _mznide); +} + +void +HTMLPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel, const QString &message, int lineNumber, const QString &sourceID) +{ + _mw->addOutput("<div style='color:red;'>JavaScript message: source " +sourceID + ", line no. " + QString().number(lineNumber) + ": " + message + "</div><br>\n"); +} + +void HTMLPage::runJs(QString js) +{ + runJavaScript(js); +} + +MiniZincIDEJS::MiniZincIDEJS(HTMLPage *p) + : QObject(p), _htmlPage(p) +{ + +} + +void MiniZincIDEJS::selectSolution(int n) +{ + _htmlPage->selectSolution(n); +} + +#else + #include <QWebFrame> HTMLPage::HTMLPage(MainWindow* mw, QWidget *parent) : @@ -23,9 +59,17 @@ HTMLPage::jsCleared() mainFrame()->addToJavaScriptWindowObject("mznide", this); } +void HTMLPage::runJs(QString js) +{ + mainFrame()->evaluateJavaScript(js); +} + +#endif + void HTMLPage::selectSolution(int n) { + qDebug() << "select " << n; _mw->selectJSONSolution(this,n); } @@ -34,8 +78,26 @@ HTMLPage::pageLoadFinished(bool ok) { if (ok) { loadFinished = true; + +#ifdef MINIZINC_IDE_HAVE_WEBENGINE + // Load qwebchannel javascript + QFile qwebchanneljs(":/qtwebchannel/qwebchannel.js"); + if (!qwebchanneljs.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "can't open qrc:///qtwebchannel/qwebchannel.js"; + return; + } + QTextStream qwebchanneljs_in(&qwebchanneljs); + QString qwebchanneljs_text = qwebchanneljs_in.readAll(); + runJs(qwebchanneljs_text); + + QString setup_object("new QWebChannel(qt.webChannelTransport, function (channel) {" + "window.mznide = channel.objects.mznide;" + "});" + ); + runJs(setup_object); +#endif for (int i=0; i<json.size(); i++) { - mainFrame()->evaluateJavaScript(json[i]); + runJs(json[i]); } json.clear(); } @@ -49,7 +111,7 @@ HTMLPage::addSolution(const QString &json0) j.replace("\"","\\\""); j.replace("\n"," "); if (loadFinished) { - mainFrame()->evaluateJavaScript("addSolution('"+j+"')"); + runJs("addSolution('"+j+"')"); } else { json.push_back("addSolution('"+j+"')"); } @@ -60,7 +122,7 @@ HTMLPage::finish(qint64 runtime) { QString jscall = "if (typeof finish == 'function') { finish("+QString().number(runtime)+"); }"; if (loadFinished) { - mainFrame()->evaluateJavaScript(jscall); + runJs(jscall); } else { json.push_back(jscall); } @@ -70,6 +132,6 @@ void HTMLPage::showSolution(int n) { if (loadFinished) { - mainFrame()->evaluateJavaScript("gotoSolution('"+QString().number(n)+"')"); + runJs("gotoSolution('"+QString().number(n)+"')"); } } diff --git a/MiniZincIDE/htmlpage.h b/MiniZincIDE/htmlpage.h index d09e398..cedeaf0 100644 --- a/MiniZincIDE/htmlpage.h +++ b/MiniZincIDE/htmlpage.h @@ -1,10 +1,52 @@ #ifndef HTMLPAGE_H #define HTMLPAGE_H -#include <QWebPage> - class MainWindow; +#ifdef MINIZINC_IDE_HAVE_WEBENGINE + +#include <QWebEnginePage> +#include <QWebChannel> + +class HTMLPage; + +class MiniZincIDEJS : public QObject { + Q_OBJECT +protected: + HTMLPage* _htmlPage; +public: + MiniZincIDEJS(HTMLPage* p); +public slots: + void selectSolution(int n); +}; + +class HTMLPage : public QWebEnginePage +{ + Q_OBJECT +protected: + MainWindow* _mw; + QWebChannel* _webChannel; + MiniZincIDEJS* _mznide; + QStringList json; + bool loadFinished; + void runJs(QString js); +public: + explicit HTMLPage(MainWindow* mw, QWidget *parent = 0); + virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString &message, int lineNumber, const QString &sourceID); + void addSolution(const QString& json); + void showSolution(int n); + void finish(qint64 runtime); +public slots: + void selectSolution(int n); + +private slots: + void pageLoadFinished(bool ok); +}; + +#else + +#include <QWebPage> + class HTMLPage : public QWebPage { Q_OBJECT @@ -12,6 +54,7 @@ class HTMLPage : public QWebPage MainWindow* _mw; QStringList json; bool loadFinished; + void runJs(QString js); public: explicit HTMLPage(MainWindow* mw, QWidget *parent = 0); virtual void javaScriptConsoleMessage(const QString &message, int lineNumber, const QString &sourceID); @@ -26,4 +69,6 @@ private slots: void jsCleared(void); }; +#endif + #endif // HTMLPAGE_H diff --git a/MiniZincIDE/htmlwindow.cpp b/MiniZincIDE/htmlwindow.cpp index 9bfb021..9cd4a74 100755 --- a/MiniZincIDE/htmlwindow.cpp +++ b/MiniZincIDE/htmlwindow.cpp @@ -2,7 +2,6 @@ #include "ui_htmlwindow.h" #include "htmlpage.h" -#include <QWebView> #include <QMdiSubWindow> #include <QDebug> #include <QDockWidget> @@ -15,11 +14,11 @@ HTMLWindow::HTMLWindow(const QVector<VisWindowSpec>& specs, MainWindow* mw, QWid ui->setupUi(this); for (int i=0; i<specs.size(); i++) { - QWebView* wv = new QWebView; + MznIdeWebView* wv = new MznIdeWebView; HTMLPage* p = new HTMLPage(mw,wv); pages.append(p); wv->setPage(p); - loadQueue.append(QPair<QWebView*,QString>(wv,specs[i].url)); + loadQueue.append(QPair<MznIdeWebView*,QString>(wv,specs[i].url)); QDockWidget* dw = new QDockWidget(this); dw->setFeatures(QDockWidget::DockWidgetMovable); dw->setWidget(wv); @@ -28,7 +27,7 @@ HTMLWindow::HTMLWindow(const QVector<VisWindowSpec>& specs, MainWindow* mw, QWid if (specs.size() > 0) { connect(loadQueue[0].first, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); - QWebView* wv0 = loadQueue[0].first; + MznIdeWebView* wv0 = loadQueue[0].first; QString url0 = loadQueue[0].second; wv0->load(QUrl::fromUserInput(url0)); } @@ -65,7 +64,7 @@ void HTMLWindow::loadFinished(bool) loadQueue.pop_front(); if (loadQueue.size() > 0) { connect(loadQueue[0].first, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool))); - QWebView* wv0 = loadQueue[0].first; + MznIdeWebView* wv0 = loadQueue[0].first; QString url0 = loadQueue[0].second; wv0->load(QUrl::fromUserInput(url0)); } diff --git a/MiniZincIDE/htmlwindow.h b/MiniZincIDE/htmlwindow.h index f12141a..3ce5e8e 100644 --- a/MiniZincIDE/htmlwindow.h +++ b/MiniZincIDE/htmlwindow.h @@ -2,7 +2,12 @@ #define HTMLWINDOW_H #include <QMainWindow> + +#ifdef MINIZINC_IDE_HAVE_WEBENGINE +#include <QWebEngineView> +#else #include <QWebView> +#endif #include "htmlpage.h" namespace Ui { @@ -34,7 +39,12 @@ class HTMLWindow : public QMainWindow private: Ui::HTMLWindow *ui; QVector<HTMLPage*> pages; - QVector<QPair<QWebView*,QString> > loadQueue; +#ifdef MINIZINC_IDE_HAVE_WEBENGINE + typedef QWebEngineView MznIdeWebView; +#else + typedef QWebView MznIdeWebView; +#endif + QVector<QPair<MznIdeWebView*,QString> > loadQueue; protected: void closeEvent(QCloseEvent *); private slots: diff --git a/MiniZincIDE/images/mznicon.png b/MiniZincIDE/images/mznicon.png index db8d871..024f853 100644 Binary files a/MiniZincIDE/images/mznicon.png and b/MiniZincIDE/images/mznicon.png differ diff --git a/MiniZincIDE/images/mznlogo.png b/MiniZincIDE/images/mznlogo.png deleted file mode 100644 index f73bc62..0000000 Binary files a/MiniZincIDE/images/mznlogo.png and /dev/null differ diff --git a/MiniZincIDE/mainwindow.cpp b/MiniZincIDE/mainwindow.cpp index e2164e6..1da147c 100755 --- a/MiniZincIDE/mainwindow.cpp +++ b/MiniZincIDE/mainwindow.cpp @@ -837,6 +837,7 @@ void MainWindow::init(const QString& projectFile) projectSort->setSortRole(Qt::UserRole); ui->projectView->setModel(projectSort); ui->projectView->sortByColumn(0, Qt::AscendingOrder); + ui->projectView->setEditTriggers(QAbstractItemView::EditKeyPressed); ui->projectExplorerDockWidget->hide(); connect(ui->projectView, SIGNAL(activated(QModelIndex)), this, SLOT(activateFileInProject(QModelIndex))); @@ -914,7 +915,8 @@ void MainWindow::addFileToProject(bool dznOnly) QStringList fileNames; if (dznOnly) { QString fileName = QFileDialog::getOpenFileName(this, tr("Select a data file to open"), getLastPath(), "MiniZinc data files (*.dzn)"); - fileNames.append(fileName); + if (!fileName.isNull()) + fileNames.append(fileName); } else { fileNames = QFileDialog::getOpenFileNames(this, tr("Select one or more files to open"), getLastPath(), "MiniZinc Files (*.mzn *.dzn)"); } @@ -978,12 +980,12 @@ void MainWindow::onActionProjectRemove_triggered() } } project.removeFile(projectSelectedFile); + setupDznMenu(); } void MainWindow::onActionProjectRename_triggered() { - project.setEditable(projectSelectedIndex); - ui->projectView->edit(projectSelectedIndex); + ui->projectView->edit(ui->projectView->currentIndex()); } void MainWindow::onActionProjectRunWith_triggered() @@ -1442,7 +1444,10 @@ void MainWindow::setupDznMenu() ui->conf_data_file->addItem(dataFiles[i]); } ui->conf_data_file->addItem("Add data file to project..."); - ui->conf_data_file->setCurrentText(curText); + if (curText != "Add data file to project...") + ui->conf_data_file->setCurrentText(curText); + else + ui->conf_data_file->setCurrentIndex(0); } void MainWindow::addOutput(const QString& s, bool html) @@ -1865,7 +1870,7 @@ void MainWindow::pipeOutput() } void MainWindow::procFinished(int, bool showTime) { - if (outputProcess) + if (process && outputProcess) pipeOutput(); readOutput(); updateUiProcessRunning(false); @@ -1896,7 +1901,7 @@ void MainWindow::procError(QProcess::ProcessError e) { if (e==QProcess::FailedToStart) { QMessageBox::critical(this, "MiniZinc IDE", "Failed to start '"+processName+"'. Check your path settings."); } else { - QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc interpreter."); + QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc interpreter `"+processName+"': error code "+QString().number(e)); } procFinished(0); } @@ -1905,7 +1910,7 @@ void MainWindow::outputProcError(QProcess::ProcessError e) { if (e==QProcess::FailedToStart) { QMessageBox::critical(this, "MiniZinc IDE", "Failed to start 'solns2out'. Check your path settings."); } else { - QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc interpreter."); + QMessageBox::critical(this, "MiniZinc IDE", "Unknown error while executing the MiniZinc solution processor."); } procFinished(0); } @@ -2009,8 +2014,10 @@ void MainWindow::on_actionStop_triggered() { ui->actionStop->setEnabled(false); if (process) { + pipeOutput(); disconnect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(procError(QProcess::ProcessError))); + disconnect(process, SIGNAL(finished(int)), this, SLOT(procFinished(int))); processWasStopped = true; #ifdef Q_OS_WIN diff --git a/MiniZincIDE/mainwindow.h b/MiniZincIDE/mainwindow.h index 24eb0b0..9e106ea 100644 --- a/MiniZincIDE/mainwindow.h +++ b/MiniZincIDE/mainwindow.h @@ -18,7 +18,11 @@ #include <QProcess> #include <QTimer> #include <QLabel> +#ifdef MINIZINC_IDE_HAVE_WEBENGINE +#include <QWebEngineView> +#else #include <QWebView> +#endif #include <QSet> #include <QTemporaryDir> #include <QElapsedTimer> diff --git a/MiniZincIDE/minizincide.qrc b/MiniZincIDE/minizincide.qrc index 4066b09..027dda0 100644 --- a/MiniZincIDE/minizincide.qrc +++ b/MiniZincIDE/minizincide.qrc @@ -1,6 +1,5 @@ <RCC> <qresource prefix="/"> - <file>images/mznlogo.png</file> <file>images/about.html</file> <file>images/mznicon.png</file> <file>cheat_sheet.mzn</file> diff --git a/MiniZincIDE/mznide.icns b/MiniZincIDE/mznide.icns index eaeb628..5011b1a 100644 Binary files a/MiniZincIDE/mznide.icns and b/MiniZincIDE/mznide.icns differ diff --git a/MiniZincIDE/mznide.ico b/MiniZincIDE/mznide.ico old mode 100755 new mode 100644 index 4bd2c25..319924c Binary files a/MiniZincIDE/mznide.ico and b/MiniZincIDE/mznide.ico differ diff --git a/MiniZincIDE/project.cpp b/MiniZincIDE/project.cpp index cab301a..3cd45a5 100644 --- a/MiniZincIDE/project.cpp +++ b/MiniZincIDE/project.cpp @@ -140,12 +140,7 @@ void Project::addFile(QTreeView* treeView, QSortFilterProxyModel* sort, const QS delete cp; goto coursera_done; } - cp->course = in.readLine(); - if (in.status() != QTextStream::Ok) { - delete cp; - goto coursera_done; - } - cp->checkpwdSid= in.readLine(); + cp->assignmentKey = in.readLine(); if (in.status() != QTextStream::Ok) { delete cp; goto coursera_done; @@ -253,20 +248,16 @@ QString Project::fileAtIndex(const QModelIndex &index) Qt::ItemFlags Project::flags(const QModelIndex& index) const { - if (index==editable) { - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; - } else { - QStandardItem* item = itemFromIndex(index); - if (!item->hasChildren() && (item==mzn || item==dzn || item==other) ) - return Qt::ItemIsSelectable; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - } + QStandardItem* item = itemFromIndex(index); + if (!item->hasChildren() && (item==mzn || item==dzn || item==other) ) + return Qt::ItemIsSelectable; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; } QStringList Project::dataFiles(void) const { QStringList ret; - for (QMap<QString,QModelIndex>::const_iterator it = _files.begin(); it != _files.end(); ++it) { + for (QMap<QString,QPersistentModelIndex>::const_iterator it = _files.begin(); it != _files.end(); ++it) { if (it.key().endsWith(".dzn")) ret << it.key(); } @@ -298,11 +289,6 @@ void Project::removeFile(const QString &fileName) } } -void Project::setEditable(const QModelIndex &index) -{ - editable = index; -} - void Project::setModified(bool flag, bool files) { if (!projectRoot.isEmpty()) { @@ -337,7 +323,6 @@ void Project::setModified(bool flag, bool files) bool Project::setData(const QModelIndex& index, const QVariant& value, int role) { - editable = QModelIndex(); QString oldName = itemFromIndex(index)->text(); if (oldName==value.toString()) return false; @@ -442,7 +427,8 @@ void Project::currentDataFileIndex(int i, bool init) _currentDatafileIndex = i; ui->conf_data_file->setCurrentIndex(i); } else { - checkModified(); + if (i < ui->conf_data_file->count()-1) + checkModified(); } } diff --git a/MiniZincIDE/project.h b/MiniZincIDE/project.h index c11da46..12b5af7 100644 --- a/MiniZincIDE/project.h +++ b/MiniZincIDE/project.h @@ -39,8 +39,7 @@ class CourseraItem { class CourseraProject { public: QString name; - QString checkpwdSid; - QString course; + QString assignmentKey; QList<CourseraItem> problems; QList<CourseraItem> models; }; @@ -59,7 +58,6 @@ class Project : public QStandardItemModel QString fileAtIndex(const QModelIndex& index); virtual Qt::ItemFlags flags(const QModelIndex& index) const; QStringList dataFiles(void) const; - void setEditable(const QModelIndex& index); virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole ); bool isProjectFile(const QModelIndex& index) { return projectFile->index()==index; } bool isModified() const { return _isModified; } @@ -117,12 +115,11 @@ public slots: bool _isModified; bool _filesModified; QString projectRoot; - QMap<QString, QModelIndex> _files; + QMap<QString, QPersistentModelIndex> _files; QStandardItem* projectFile; QStandardItem* mzn; QStandardItem* dzn; QStandardItem* other; - QModelIndex editable; int _currentDatafileIndex; bool _haveExtraArgs;