123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- /*
- mephi-tasks — a client to NRNU MEPhI Redmine server
- Copyright (C) 2015 Dmitry Yu Okunev <dyokunev@ut.mephi.ru> 0x8E30679C
- 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 3 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.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "mainwindow-rector.h"
- #include "helpwindow.h"
- #include "ui_mainwindow-rector.h"
- #include "common.h"
- #include <QStringList>
- #include <QDesktopServices>
- #include <QDateTime>
- #include <QMenu>
- #include <QList>
- #include <QScrollBar>
- void MainWindowRector::issuesSetup()
- {
- QTableWidget *issues = ui->issues;
- // Columns:
- QStringList columns;
- QSize itemSize;
- columns << "Название" << "Исполнитель" << "Срок" << "Статус" << "Обновлено";
- this->sortColumnAscByIdx[0] = SORT_NAME_ASC;
- this->sortColumnAscByIdx[1] = SORT_ASSIGNEE_ASC;
- this->sortColumnAscByIdx[2] = SORT_DUE_TO_ASC;
- this->sortColumnAscByIdx[3] = SORT_STATUS_POS_ASC;
- this->sortColumnAscByIdx[4] = SORT_UPDATED_ON_ASC;
- this->sortColumnDescByIdx[0] = SORT_NAME_DESC;
- this->sortColumnDescByIdx[1] = SORT_ASSIGNEE_DESC;
- this->sortColumnDescByIdx[2] = SORT_DUE_TO_DESC;
- this->sortColumnDescByIdx[3] = SORT_STATUS_POS_DESC;
- this->sortColumnDescByIdx[4] = SORT_UPDATED_ON_DESC;
- issues->setColumnCount ( columns.size() );
- issues->horizontalHeader()->setSectionResizeMode ( 0, QHeaderView::Stretch );
- issues->horizontalHeader()->setSectionResizeMode ( 1, QHeaderView::Interactive );
- issues->horizontalHeader()->resizeSection ( 1, 150 );
- issues->horizontalHeader()->setSectionResizeMode ( 2, QHeaderView::Interactive );
- issues->horizontalHeader()->resizeSection ( 2, 100 );
- issues->horizontalHeader()->setSectionResizeMode ( 3, QHeaderView::Interactive );
- issues->horizontalHeader()->resizeSection ( 3, 100 );
- issues->horizontalHeader()->setSectionResizeMode ( 4, QHeaderView::Interactive );
- issues->horizontalHeader()->resizeSection ( 4, 130 );
- issues->setHorizontalHeaderLabels ( columns );
- issues->horizontalHeader()->setSortIndicator ( this->sortLogicalIndex, this->sortOrder );
- //issues->verticalHeader()->setDefaultSectionSize(18);
- issues->verticalHeader()->setSectionResizeMode ( QHeaderView::ResizeToContents );
- // Signals:
- connect ( issues, SIGNAL ( cellDoubleClicked ( int, int ) ),
- this, SLOT ( issues_doubleClick ( int, int ) ) );
- connect ( issues->horizontalHeader(), SIGNAL ( sectionClicked ( int ) ),
- this, SLOT ( sortColumnSwitch ( int ) ) );
- issues->horizontalHeader()->setSortIndicatorShown ( true );
- return;
- }
- void MainWindowRector::sortColumnSwitch ( int columnIdx )
- {
- enum ESortColumn newSortColumn;
- this->sortLogicalIndex = columnIdx;
- this->sortOrder = Qt::SortOrder::AscendingOrder;
- newSortColumn = this->sortColumnAscByIdx[columnIdx];
- if ( this->sortColumn[0] == newSortColumn ) {
- this->sortOrder = Qt::SortOrder::DescendingOrder;
- newSortColumn = this->sortColumnDescByIdx[columnIdx];
- }
- this->sortColumn[0] = newSortColumn;
- this->issues_display();
- }
- void MainWindowRector::issues_doubleClick ( int row, int column )
- {
- ( void ) column;
- QString url = QString ( SERVER_URL "/issues/%1" ).arg ( this->issue_row2issue[row]["id"].toInt() );
- QDesktopServices::openUrl ( url );
- return;
- }
- MainWindowRector::MainWindowRector ( QWidget *parent ) :
- MainWindowCommon ( parent ),
- ui ( new Ui::MainWindowRector )
- {
- ui->setupUi ( this );
- /*qDebug("MainWindowRector::MainWindowRector(): %p", this);*/
- this->setWindowTitle ( "Система «Задачи» НИЯУ МИФИ: Поручения ректора" );
- //Qt::WindowFlags flags = this->windowFlags();
- //this->setWindowFlags(flags | Qt::WindowStaysOnTopHint);
- connect ( redmine, SIGNAL ( callback_call ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
- this, SLOT ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
- this->issuesSetup();
- this->updateTasks();
- this->createIconComboBox();
- this->createTrayActions();
- this->createTrayIcon();
- this->status ( GOOD );
- this->setIcon ( GOOD );
- this->trayIcon->show();
- this->timerUpdateTasks = new QTimer ( this );
- connect ( this->timerUpdateTasks, SIGNAL ( timeout() ), this, SLOT ( updateTasks() ) );
- this->timerUpdateTasks->start ( 10000 );
- return;
- }
- struct append_assignee_arg {
- int pos;
- };
- void MainWindowRector::append_assignee ( QNetworkReply *reply, QJsonDocument *coassignee_doc, void *_arg )
- {
- ( void ) reply;
- struct append_assignee_arg *arg = ( struct append_assignee_arg * ) _arg;
- QJsonObject coassignee = coassignee_doc->object() ["user"].toObject();
- QTableWidget *issues = this->ui->issues;
- int pos = arg->pos;
- /*qDebug("pos: %i; answer is: %s", pos, coassignee_doc->toJson().data());*/
- QString firstname = coassignee["firstname"].toString();
- QString lastname = coassignee["lastname"].toString();
- QString initials = firstname.left ( 1 ) + " " + firstname.left ( firstname.indexOf ( " " ) + 2 ).right ( 1 ); // TODO: Move this to class Redmine
- QString fullname = lastname + " " + initials;
- QTableWidgetItem *item = issues->item ( pos, 1 );
- item->setText ( item->text() + "\n " + fullname );
- delete arg;
- return;
- }
- void MainWindowRector::issues_clear()
- {
- /*qDebug("MainWindowRector::issues_clear(): %p", this);*/
- this->issues_list.clear();
- return;
- }
- void MainWindowRector::issue_display_oneissue ( int pos )
- {
- QTableWidget *issues = this->ui->issues;
- QTableWidgetItem *item;
- QJsonObject issue = this->issue_row2issue[pos];
- QJsonObject issue_status = issue["status"].toObject();
- bool isClosed = redmine->get_issue_status ( issue_status["id"].toInt() ) ["is_closed"].toBool();
- QColor closedBgColor = QColor ( 192, 255, 192 );
- //qDebug("Issue: #%i:\t%s", issue["id"].toInt(), issue["subject"].toString().toUtf8().data());
- // New row:
- issues->insertRow ( pos );
- // Issue name:
- item = new QTableWidgetItem ( issue["subject"].toString() );
- item->setFlags ( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
- if ( isClosed ) item->setBackgroundColor ( closedBgColor );
- issues->setItem ( pos, 0, item );
- // Assignee:
- // Assignee:
- QJsonObject assignee_obj = issue["assigned_to"].toObject();
- QString assignee_str = assignee_obj["name"].toString();
- int assignee_id = assignee_obj["id" ].toInt();
- // Setting the assignee value:
- item = new QTableWidgetItem ( assignee_str );
- item->setFlags ( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
- if ( isClosed ) item->setBackgroundColor ( closedBgColor );
- issues->setItem ( pos, 1, item );
- item = issues->item ( pos, 1 );
- // Co-assignees (asynchronous):
- QJsonArray customFields = issue["custom_fields"].toArray();
- foreach ( const QJsonValue & customField, customFields ) {
- if ( customField.toObject() ["name"].toString() == "Соисполнители" ) {
- QJsonArray coassignees_id_obj = customField.toObject() ["value"].toArray();
- foreach ( const QJsonValue & coassignee_id_obj, coassignees_id_obj ) {
- // Don't try to use .toInt() directly, the answer will always be "0":
- int coassignee_id = coassignee_id_obj.toString().toInt();
- if ( coassignee_id != assignee_id ) {
- struct append_assignee_arg *append_assignee_arg_p;
- append_assignee_arg_p = new struct append_assignee_arg;
- // TODO: fix a memleak if redmine->get_user doesn't success
- append_assignee_arg_p->pos = pos;
- redmine->get_user ( coassignee_id,
- ( Redmine::callback_t ) &MainWindowRector::append_assignee,
- ( void * ) append_assignee_arg_p );
- }
- }
- break;
- }
- }
- // Due date:
- QString due_date_str = issue["due_date"].toString();
- QDateTime now, date;
- now = QDateTime::currentDateTime();
- date = QDateTime::fromString ( due_date_str, "yyyy-MM-dd" );
- item = new QTableWidgetItem ( due_date_str );
- item->setFlags ( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
- if ( isClosed ) item->setBackgroundColor ( closedBgColor );
- if ( ( due_date_str != "" ) && ( now.toTime_t() - ( 3600 * 24 - 1 ) > date.toTime_t() ) && ( !isClosed ) ) {
- item->setBackgroundColor ( QColor ( 255, 192, 192 ) );
- this->statusWorsenTo ( BAD );
- }
- issues->setItem ( pos, 2, item );
- // Status:
- item = new QTableWidgetItem ( issue_status["name"].toString() );
- if ( isClosed ) item->setBackgroundColor ( closedBgColor );
- issues->setItem ( pos, 3, item );
- // Updated on:
- QDateTime updated_on = redmine->parseDateTime ( issue["updated_on"] );
- item = new QTableWidgetItem ( updated_on.toString ( "yyyy'-'MM'-'dd HH':'MM" ) );
- if ( isClosed ) item->setBackgroundColor ( closedBgColor );
- issues->setItem ( pos, 4, item );
- return;
- }
- void MainWindowRector::issue_add ( QJsonObject issue )
- {
- this->issues_list.append ( issue );
- return;
- }
- QList<QJsonObject> MainWindowRector::issues_get()
- {
- return this->issues_list;
- }
- void MainWindowRector::issues_display()
- {
- QTableWidget *uiIssues = this->ui->issues;
- QList<QTableWidgetSelectionRange> selected_list = uiIssues->selectedRanges();
- QList<QJsonObject> issues_list = this->issues_get();
- // Clearing the table
- {
- int row_count = uiIssues->rowCount();
- while ( row_count-- > 0 )
- uiIssues->removeRow ( row_count );
- this->issue_row2issue.clear();
- }
- // Filling the table
- int issues_count = 0;
- if ( this->status() == MainWindowRector::BAD )
- this->status ( MainWindowRector::GOOD );
- int scrollValue = uiIssues->verticalScrollBar()->value();
- qSort ( issues_list.begin(), issues_list.end(), this->sortFunctMap[SORT_STATUS_ISCLOSED_ASC] );
- if ( this->sortFunctMap[this->sortColumn[0]] != NULL ) {
- QList<QJsonObject>::iterator iterator,
- issues_list_end_nonclosed = issues_list.end(),
- issues_list_start_closed = issues_list.end();
- for ( iterator = issues_list.begin(); iterator != issues_list.end(); iterator++ ) {
- bool isClosed = redmine->get_issue_status ( ( *iterator ) ["status"] ) ["is_closed"].toBool();
- if ( isClosed ) {
- issues_list_end_nonclosed = iterator; // TODO: find out: why here no "-1" in the expression
- issues_list_start_closed = iterator;
- break;
- }
- }
- if ( issues_list_end_nonclosed != issues_list.begin() )
- qSort ( issues_list.begin(), issues_list_end_nonclosed, this->sortFunctMap[this->sortColumn[0]] );
- if ( issues_list_end_nonclosed != issues_list.end() )
- qSort ( issues_list_start_closed, issues_list.end(), this->sortFunctMap[this->sortColumn[0]] );
- }
- foreach ( const QJsonObject & issue, issues_list ) {
- this->issue_row2issue.insert ( issues_count, issue );
- this->issue_display_oneissue ( issues_count );
- issues_count++;
- }
- uiIssues->verticalScrollBar()->setValue ( scrollValue );
- foreach ( QTableWidgetSelectionRange range, selected_list )
- uiIssues->setRangeSelected (
- QTableWidgetSelectionRange (
- range.topRow(), range.leftColumn(),
- range.bottomRow(), range.rightColumn()
- ),
- true
- );
- this->setIcon ( this->status() );
- return;
- }
- void MainWindowRector::get_issues_callback ( QNetworkReply *reply, QJsonDocument *json, void *arg )
- {
- ( void ) reply;
- ( void ) arg;
- /*qDebug("MainWindowRector::get_issues_callback(): %p %p", this, arg);*/
- //MainWindowRector *win = static_cast<MainWindowRector *>(_win);
- QJsonObject answer = json->object();
- QJsonArray issues = answer["issues"].toArray();
- this->issues_clear();
- foreach ( const QJsonValue & issue_val, issues )
- this->issue_add ( issue_val.toObject() );
- this->issues_display();
- return;
- }
- /* TODO: this function (and related ones) should be replaced with
- * MainWindowCommon::updateIssues() [and related]
- */
- int MainWindowRector::updateTasks()
- {
- redmine->get_issues ( ( Redmine::callback_t ) &MainWindowRector::get_issues_callback, this );
- return 0;
- }
- MainWindowRector::~MainWindowRector()
- {
- //saveSettings();
- delete this->ui;
- delete this->timerUpdateTasks;
- }
- void MainWindowRector::on_actionExit_triggered()
- {
- qApp->quit();
- }
- void MainWindowRector::on_actionHelp_triggered()
- {
- HelpWindow *win = new HelpWindow();
- win->show();
- return;
- }
- void MainWindowRector::showOnTop()
- {
- #ifdef Q_OS_WIN32
- // raise() doesn't work :(
- Qt::WindowFlags flags_old = this->windowFlags();
- Qt::WindowFlags flags_ontop = flags_old | Qt::WindowStaysOnTopHint;
- this->setWindowFlags ( flags_ontop );
- this->show();
- this->setWindowFlags ( flags_old );
- this->show();
- #else
- this->show();
- this->raise();
- #endif
- return;
- }
- void MainWindowRector::toggleShowHide()
- {
- if ( this->isVisible() )
- this->hide();
- else {
- this->showOnTop();
- }
- return;
- }
- void MainWindowRector::createTrayActions()
- {
- showHideAction = new QAction ( tr ( "Показать/Спрятать" ), this );
- connect ( showHideAction, SIGNAL ( triggered() ), this, SLOT ( toggleShowHide() ) );
- quitAction = new QAction ( tr ( "Завершить" ), this );
- connect ( quitAction, SIGNAL ( triggered() ), qApp, SLOT ( quit() ) );
- }
- void MainWindowRector::iconActivated ( QSystemTrayIcon::ActivationReason reason )
- {
- switch ( reason ) {
- case QSystemTrayIcon::Trigger:
- case QSystemTrayIcon::DoubleClick:
- this->toggleShowHide();
- break;
- case QSystemTrayIcon::MiddleClick:
- break;
- default:
- break;
- }
- }
- void MainWindowRector::createTrayIcon()
- {
- trayIconMenu = new QMenu ( this );
- trayIconMenu->addAction ( showHideAction );
- trayIconMenu->addAction ( quitAction );
- trayIcon = new QSystemTrayIcon ( this );
- trayIcon->setContextMenu ( trayIconMenu );
- connect ( trayIcon, SIGNAL ( activated ( QSystemTrayIcon::ActivationReason ) ),
- this, SLOT ( iconActivated ( QSystemTrayIcon::ActivationReason ) ) );
- }
- void MainWindowRector::setIcon ( EIcon index )
- {
- //qDebug("icon: %i", index);
- QIcon icon = this->iconComboBox.itemIcon ( index );
- this->trayIcon->setIcon ( icon );
- this->setWindowIcon ( icon );
- this->trayIcon->setToolTip ( this->iconComboBox.itemText ( index ) );
- }
- void MainWindowRector::createIconComboBox()
- {
- this->iconComboBox.addItem ( QIcon ( ":/images/good.png" ), tr ( "Просроченных задач нет" ) );
- this->iconComboBox.addItem ( QIcon ( ":/images/bad.png" ), tr ( "Есть просроченные задачи" ) );
- return;
- }
- void MainWindowRector::resizeEvent ( QResizeEvent *event )
- {
- ui->issues->resize ( this->width(), this->height() - 50 );
- ui->labelTasksNRNUMEPhI->move ( ( this->width() - ui->labelTasksNRNUMEPhI->width() ) / 2, this->height() - 20 );
- QWidget::resizeEvent ( event );
- return;
- }
- #include <QThread>
- void MainWindowRector::on_issues_itemSelectionChanged()
- {
- QTableWidget *issues = this->ui->issues;
- int columns_count = issues->columnCount();
- int rows_count = issues->rowCount();
- QList<QTableWidgetSelectionRange> selected_list = issues->selectedRanges();
- foreach ( QTableWidgetSelectionRange range, selected_list ) {
- if ( range.leftColumn() != 0 || range.rightColumn() != columns_count - 1 )
- issues->setRangeSelected (
- QTableWidgetSelectionRange (
- range.topRow(), 0,
- range.bottomRow(), columns_count - 1
- ),
- true
- );
- else
- /* Workaround: Drop selection if everything is selected
- * it's required to do not select everything on sort switching
- */
- if ( range.leftColumn() == 0 && range.rightColumn() == columns_count - 1 &&
- range.topRow() == 0 && range.bottomRow() == rows_count - 1 ) {
- issues->setRangeSelected (
- QTableWidgetSelectionRange ( 0, 0, rows_count - 1, columns_count - 1 ),
- false
- );
- break;
- }
- }
- }
|