mainwindow-full.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. /*
  2. mephi-tasks — a client to NRNU MEPhI Redmine server
  3. Copyright (C) 2015 Dmitry Yu Okunev <dyokunev@ut.mephi.ru> 0x8E30679C
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include <QResizeEvent>
  16. #include <QtAlgorithms>
  17. #include "mainwindow-full.h"
  18. #include "ui_mainwindow-full.h"
  19. #include "helpwindow.h"
  20. #include "htmldelegate.h"
  21. #include "projectmemberswindow.h"
  22. MainWindowFull::MainWindowFull ( QWidget *parent ) :
  23. MainWindowCommon ( parent ),
  24. ui ( new Ui::MainWindowFull )
  25. {
  26. this->ui->setupUi ( this );
  27. connect ( redmine, SIGNAL ( callback_call ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
  28. this, SLOT ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
  29. this->setWindowTitle ( "Система «Задачи» НИЯУ МИФИ" );
  30. this->setCorner ( Qt::TopLeftCorner, Qt::LeftDockWidgetArea );
  31. this->setCorner ( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea );
  32. this->setDockOptions ( AllowTabbedDocks | AllowNestedDocks );
  33. this->ui->navigationDock->installEventFilter ( this );
  34. this->ui->issueDock->installEventFilter ( this );
  35. this->ui->filtersDock->installEventFilter ( this );
  36. this->ui->centralWidget->installEventFilter ( this );
  37. this->ui->navigationDock->setMinimumWidth ( this->navigationDockInitialWidth );
  38. this->ui->filtersDock->setMinimumWidth ( this->filtersDockInitialWidth );
  39. this->ui->issueDock->setMinimumHeight ( this->issueDockInitialHeight );
  40. QStringList projectsColumns;
  41. projectsColumns << "Проект";
  42. this->ui->projects->setHeaderLabels ( projectsColumns );
  43. HTMLDelegate* delegate = new HTMLDelegate();
  44. ui->projects->setItemDelegate ( delegate );
  45. QStringList issuesColumns;
  46. issuesColumns << "Название" << "Исполнитель" << "Срок" << "Статус" << "Обновлено";
  47. this->ui->issuesTree->setHeaderLabels ( issuesColumns );
  48. this->updateRoles();
  49. this->updateIssues();
  50. this->updateProjects();
  51. this->updateMemberships();
  52. this->updateEnumerations();
  53. this->timerUpdateIssues = new QTimer ( this );
  54. connect ( this->timerUpdateIssues, SIGNAL ( timeout() ), this, SLOT ( updateIssues() ) );
  55. this->timerUpdateIssues->start ( 60000 );
  56. this->timerUpdateProjects = new QTimer ( this );
  57. connect ( this->timerUpdateProjects, SIGNAL ( timeout() ), this, SLOT ( updateProjects() ) );
  58. this->timerUpdateProjects->start ( 60000 );
  59. QDate date;
  60. int year_start = 2013;
  61. int year_cur = qMax ( 2015, date.year() );
  62. int year = year_cur;
  63. while ( year >= year_start )
  64. this->ui->issuesFilter_year->addItem ( QString::number ( year-- ) );
  65. this->ui->projects->setSortingEnabled ( true );
  66. this->ui->projects->sortByColumn ( 0, Qt::AscendingOrder );
  67. this->ui->projects->setSelectionMode ( QAbstractItemView::ExtendedSelection );
  68. this->ui->projects->setContextMenuPolicy ( Qt::CustomContextMenu );
  69. this->ui->issuesTree->header()->setSectionResizeMode ( 0, QHeaderView::Stretch );
  70. this->ui->issuesTree->header()->setStretchLastSection ( false );
  71. this->ui->issuesTree->setSortingEnabled ( true );
  72. this->ui->issuesTree->sortByColumn ( 4, Qt::DescendingOrder );
  73. this->ui->issuesTree->setSelectionMode ( QAbstractItemView::ExtendedSelection );
  74. this->ui->issuesTree->setContextMenuPolicy ( Qt::CustomContextMenu );
  75. /*
  76. connect(this->ui->projects, SIGNAL(customContextMenuRequested(const QPoint&)),
  77. this, SLOT(projectsShowContextMenu(const QPoint&)));
  78. connect(this->ui->issuesTree, SIGNAL(customContextMenuRequested(const QPoint&)),
  79. this, SLOT(issuesShowContextMenu(const QPoint&)));
  80. */
  81. this->projectsDisplayRetryTimer.setSingleShot ( true );
  82. connect ( &this->projectsDisplayRetryTimer, SIGNAL ( timeout() ), this, SLOT ( projects_display() ) );
  83. this->ui->issuesFilter_field_assigned_to->addItem ( "", 0 );
  84. this->ui->issuesFilter_field_status ->addItem ( "", 0 );
  85. return;
  86. }
  87. MainWindowFull::~MainWindowFull()
  88. {
  89. delete ui;
  90. }
  91. /**** ui ****/
  92. void unlockDockWidth ( QDockWidget *widget, QResizeEvent *event, int initialWidth )
  93. {
  94. if ( event->oldSize().width() != -1 && event->size().width() != event->oldSize().width() )
  95. if ( event->size().width() - event->oldSize().width() > -initialWidth / 2 )
  96. widget->setMinimumWidth ( 0 );
  97. }
  98. void unlockDockHeight ( QDockWidget *widget, QResizeEvent *event, int initialHeight )
  99. {
  100. if ( event->oldSize().height() != -1 && event->size().height() != event->oldSize().height() )
  101. if ( event->size().height() - event->oldSize().height() > -initialHeight / 2 )
  102. widget->setMinimumHeight ( 0 );
  103. }
  104. void MainWindowFull::on_resize_centralWidget ( QResizeEvent *event )
  105. {
  106. /*
  107. qDebug("centralWidget Resized (New Size) - Width: %d Height: %d (was: %d x %d)",
  108. event->size().width(),
  109. event->size().height(),
  110. event->oldSize().width(),
  111. event->oldSize().height()
  112. );*/
  113. this->ui->issuesLayout->setGeometry ( QRect ( 0, 0, event->size().width(), event->size().height() - 10 ) );
  114. QHeaderView *issuesHeader = this->ui->issuesTree->header();
  115. this->ui->issuesFilter_field_name ->resize ( issuesHeader->sectionSize ( 0 ), 24 );
  116. this->ui->issuesFilter_field_assigned_to->resize ( issuesHeader->sectionSize ( 1 ), 24 );
  117. this->ui->issuesFilter_field_due_date ->resize ( issuesHeader->sectionSize ( 2 ), 24 );
  118. this->ui->issuesFilter_field_status ->resize ( issuesHeader->sectionSize ( 3 ), 24 );
  119. this->ui->issuesFilter_field_updated_on ->resize ( issuesHeader->sectionSize ( 4 ), 24 );
  120. this->ui->issuesFilter_field_name ->move ( issuesHeader->sectionPosition ( 0 ), 0 );
  121. this->ui->issuesFilter_field_assigned_to->move ( issuesHeader->sectionPosition ( 1 ), 0 );
  122. this->ui->issuesFilter_field_due_date ->move ( issuesHeader->sectionPosition ( 2 ), 0 );
  123. this->ui->issuesFilter_field_status ->move ( issuesHeader->sectionPosition ( 3 ), 0 );
  124. this->ui->issuesFilter_field_updated_on ->move ( issuesHeader->sectionPosition ( 4 ), 0 );
  125. return;
  126. }
  127. void MainWindowFull::on_resize_navigationDock ( QResizeEvent *event )
  128. {
  129. unlockDockWidth ( this->ui->navigationDock, event, this->navigationDockInitialWidth );
  130. /*
  131. qDebug("navigationDock Resized (New Size) - Width: %d Height: %d (was: %d x %d)",
  132. event->size().width(),
  133. event->size().height(),
  134. event->oldSize().width(),
  135. event->oldSize().height()
  136. );*/
  137. this->ui->navigationTabs->resize ( event->size().width(), event->size().height() - 20 );
  138. this->ui->projects->resize ( event->size().width(), event->size().height() - 105 );
  139. return;
  140. }
  141. void MainWindowFull::on_resize_issueDock ( QResizeEvent *event )
  142. {
  143. unlockDockHeight ( this->ui->issueDock, event, this->issueDockInitialHeight );
  144. qDebug ( "issueDock Resized (New Size) - Width: %d Height: %d",
  145. event->size().width(),
  146. event->size().height() );
  147. this->ui->issueLayout->setGeometry ( QRect ( 0, 0, event->size().width(), event->size().height() - 10 ) );
  148. this->ui->issueTitleContainer->setGeometry ( QRect ( 0, 0, event->size().width(), 30 ) );
  149. this->ui->issueTitle->setGeometry ( QRect ( 0, 0, event->size().width(), 30 ) );
  150. this->ui->issueLeftColumnWidget ->setGeometry ( QRect ( 0, 40, 320, qMin ( event->size().height() - 70, 270 ) ) );
  151. this->ui->issueLeftColumn ->setGeometry ( QRect ( 0, 0, 320, qMin ( event->size().height() - 70, 270 ) ) );
  152. this->ui->issueCentralColumnWidget->setGeometry ( QRect ( 320, 40, 400, qMin ( event->size().height() - 70, 270 ) ) );
  153. this->ui->issueCentralColumn ->setGeometry ( QRect ( 0, 0, 400, qMin ( event->size().height() - 70, 270 ) ) );
  154. this->ui->issueButtonColumn->setGeometry ( QRect ( 720, 40, 130, qMin ( event->size().height() - 70, 300 ) ) );
  155. return;
  156. }
  157. void MainWindowFull::on_resize_filtersDock ( QResizeEvent *event )
  158. {
  159. unlockDockWidth ( this->ui->filtersDock, event, this->filtersDockInitialWidth );
  160. /*
  161. qDebug("filtersDock Resized (New Size) - Width: %d Height: %d",
  162. event->size().width(),
  163. event->size().height());*/
  164. this->ui->filtersLayout->setGeometry ( QRect ( 0, 0, event->size().width(), event->size().height() - 10 ) );
  165. return;
  166. }
  167. bool MainWindowFull::eventFilter ( QObject *obj, QEvent *event )
  168. {
  169. if ( event->type() == QEvent::Resize ) {
  170. Ui::MainWindowFull *ui = this->ui;
  171. if ( obj == ui->navigationDock )
  172. this->on_resize_navigationDock ( static_cast<QResizeEvent*> ( event ) );
  173. else if ( obj == ui->issueDock )
  174. this->on_resize_issueDock ( static_cast<QResizeEvent*> ( event ) );
  175. else if ( obj == ui->filtersDock )
  176. this->on_resize_filtersDock ( static_cast<QResizeEvent*> ( event ) );
  177. else if ( obj == ui->centralWidget )
  178. this->on_resize_centralWidget ( static_cast<QResizeEvent*> ( event ) );
  179. }
  180. return QWidget::eventFilter ( obj, event );
  181. }
  182. /**** /ui ****/
  183. /**** actions ****/
  184. void MainWindowFull::on_actionHelp_triggered()
  185. {
  186. HelpWindow *win = new HelpWindow ( this );
  187. win->show();
  188. }
  189. void MainWindowFull::on_actionQuit_triggered()
  190. {
  191. application->quit();
  192. }
  193. void MainWindowFull::on_toolActionHelp_triggered()
  194. {
  195. this->on_actionHelp_triggered();
  196. }
  197. /**** /actions ****/
  198. /**** updateProjects ****/
  199. void projectWidgetItemSetText ( QWidget *__this, QTreeWidgetItem *widgetItem, QJsonObject item, RedmineItemTree *tree, int level )
  200. {
  201. ( void ) tree;
  202. ( void ) level;
  203. int item_id = item["id"].toInt();
  204. MainWindowFull *_this = reinterpret_cast<MainWindowFull *> ( __this );
  205. widgetItem->setText ( 0,
  206. item["name"].toString() +
  207. " <span style='color: #808080'>(" +
  208. QString::number ( _this->issues_get_byProjectId ( item_id ).count() ) +
  209. ")</span>" );
  210. return;
  211. }
  212. bool projectsFilter ( QWidget *__this, QJsonObject item )
  213. {
  214. MainWindowFull *_this = reinterpret_cast<MainWindowFull *> ( __this );
  215. ( void ) _this;
  216. ( void ) item;
  217. if ( item["name"].toString().indexOf ( _this->projectsFilter_namePart, Qt::CaseInsensitive ) == -1 )
  218. return false;
  219. return true;
  220. }
  221. void MainWindowFull::projects_display()
  222. {
  223. if ( !this->projectsDisplayMutex.tryLock() ) {
  224. if ( !this->projectsDisplayExceptionMutex.tryLock() )
  225. return;
  226. this->projectsDisplayRetryTimer.start ( 100 );
  227. this->projectsDisplayExceptionMutex.unlock();
  228. return;
  229. }
  230. this->issues_byProjectId.clear();
  231. foreach ( const QJsonObject & issue, this->issues.get() ) {
  232. if ( this->issue_isFiltered ( issue ) )
  233. continue;
  234. QJsonObject project = issue["project"].toObject();
  235. int project_id = project["id"].toInt();
  236. this->issues_byProjectId[project_id].append ( issue );
  237. if ( this->showProjectIssues_recursive ) {
  238. QJsonObject parent;
  239. project = this->projects.get ( project["id"].toInt() );
  240. while ( true ) {
  241. int parent_id;
  242. parent = this->projects.get ( project["parent"].toObject() ["id"].toInt() );
  243. parent_id = parent["id"].toInt();
  244. if ( parent_id == 0 )
  245. break;
  246. this->issues_byProjectId[parent_id].append ( issue );
  247. project = parent;
  248. }
  249. }
  250. }
  251. this->projects.filter ( reinterpret_cast<QWidget *> ( this ), projectsFilter );
  252. this->projects.display ( this->ui->projects, reinterpret_cast<QWidget *> ( this ), projectWidgetItemSetText );
  253. this->projectsDisplayMutex.unlock();
  254. }
  255. /**** /updateProjects ****/
  256. /**** updateIssues ****/
  257. bool MainWindowFull::issue_isFiltered ( QJsonObject issue )
  258. {
  259. if ( !this->showIssues_showClosed ) {
  260. QJsonObject issue_status = issue["status"].toObject();
  261. bool isClosed = redmine->get_issue_status ( issue_status["id"].toInt() ) ["is_closed"].toBool();
  262. if ( isClosed )
  263. return true;
  264. }
  265. if ( this->issuesFilter_queryType != IFQT_ALL ) {
  266. QJsonObject author = issue["author"].toObject();
  267. QJsonObject assignee = issue["assigned_to"].toObject();
  268. QJsonObject me = redmine->me();
  269. int author_id = author["id"].toInt();
  270. int assignee_id = assignee["id"].toInt();
  271. int me_id = me["id"].toInt();
  272. switch ( this->issuesFilter_queryType ) {
  273. case IFQT_TOME:
  274. if ( ! ( assignee_id == me_id ) )
  275. return true;
  276. break;
  277. case IFQT_FROMME:
  278. if ( ! ( assignee_id != me_id && author_id == me_id ) )
  279. return true;
  280. break;
  281. case IFQT_FOLLOWED:
  282. if ( ! ( assignee_id != me_id && author_id != me_id ) )
  283. return true;
  284. break;
  285. case IFQT_ALL:
  286. qFatal ( "MainWindowFull::issue_isFiltered(): this shouldn't happend" );
  287. }
  288. }
  289. if ( this->issuesFilter_field_assignee_id != 0 )
  290. if ( issue["assigned_to"].toObject() ["id"].toInt() != this->issuesFilter_field_assignee_id )
  291. return true;
  292. if ( this->issuesFilter_field_status_id != 0 )
  293. if ( issue["status"].toObject() ["id"].toInt() != this->issuesFilter_field_status_id )
  294. return true;
  295. if ( this->issuesFilter_field_subjectPart.size() )
  296. if ( issue["subject"].toString().indexOf ( this->issuesFilter_field_subjectPart, Qt::CaseInsensitive ) == -1 )
  297. return true;
  298. return false;
  299. }
  300. void issuesWidgetItemSetText ( QWidget *__this, QTreeWidgetItem *widgetItem, QJsonObject issue, RedmineItemTree *tree, int level )
  301. {
  302. ( void ) __this;
  303. ( void ) tree;
  304. ( void ) level;
  305. //int item_id = item["id"].toInt();
  306. //MainWindowFull *_this = reinterpret_cast<MainWindowFull *>(__this);
  307. QJsonObject issue_status = issue["status"].toObject();
  308. // bool isClosed = redmine->get_issue_status(issue_status["id"].toInt())["is_closed"].toBool();
  309. widgetItem->setText ( 0, issue["subject"].toString() );
  310. widgetItem->setText ( 1, issue["assigned_to"].toObject() ["name"].toString() );
  311. QString due_date_str = issue["due_date"].toString();
  312. QDateTime now, date;
  313. now = QDateTime::currentDateTime();
  314. date = QDateTime::fromString ( due_date_str, "yyyy-MM-dd" );
  315. widgetItem->setText ( 2, due_date_str );
  316. widgetItem->setText ( 3, issue_status["name"].toString() );
  317. QDateTime updated_on = redmine->parseDateTime ( issue["updated_on"] );
  318. widgetItem->setText ( 4, updated_on.toString ( "yyyy'-'MM'-'dd HH':'MM" ) );
  319. return;
  320. }
  321. bool issuesFilter ( QWidget *__this, QJsonObject item )
  322. {
  323. MainWindowFull *_this = reinterpret_cast<MainWindowFull *> ( __this );
  324. int project_id;
  325. if ( _this->issue_isFiltered ( item ) )
  326. return false;
  327. if ( _this->selected_projects_id.empty() )
  328. return true;
  329. project_id = item["project"].toObject() ["id"].toInt();
  330. if ( _this->selected_projects_id.contains ( project_id ) )
  331. return true;
  332. if ( !_this->showProjectIssues_recursive )
  333. return false;
  334. foreach ( int selected_project_id, _this->selected_projects_id )
  335. if ( _this->projects.isDescendant ( project_id, selected_project_id ) )
  336. return true;
  337. return false;
  338. }
  339. void MainWindowFull::setIssuesFilterItems ( QComboBox *box, QHash<int, QJsonObject> table_old, QHash<int, QJsonObject> table, QString keyname )
  340. {
  341. QHash<int, int> ids_old;
  342. foreach ( const int &item_id, table_old.keys() )
  343. ids_old.insert ( item_id, item_id );
  344. /*
  345. * Adding new items
  346. */
  347. foreach ( const int &item_id, table.keys() ) {
  348. if ( ids_old.contains ( item_id ) )
  349. ids_old.remove ( item_id );
  350. else
  351. box->addItem ( table[item_id][keyname].toString(), item_id );
  352. }
  353. /*
  354. * Removing old items
  355. */
  356. int idx = box->count();
  357. while ( --idx > 0 ) {
  358. int item_id;
  359. item_id = box->itemData ( idx ).toInt();
  360. if ( ids_old.contains ( item_id ) )
  361. box->removeItem ( idx );
  362. }
  363. /*
  364. * Sorting
  365. */
  366. box->model()->sort ( 0 );
  367. return;
  368. }
  369. void MainWindowFull::issues_display()
  370. {
  371. qDebug ( "MainWindowFull::issues_display()" );
  372. this->issues.filter ( reinterpret_cast<QWidget *> ( this ), issuesFilter );
  373. this->issues.display ( this->ui->issuesTree, reinterpret_cast<QWidget *> ( this ), issuesWidgetItemSetText );
  374. QHash<int, QJsonObject> issuesFiltered_statuses_old = this->issuesFiltered_statuses;
  375. QHash<int, QJsonObject> issuesFiltered_assignees_old = this->issuesFiltered_assignees;
  376. //QHash<int, QJsonObject> issuesFiltered_author_old = this->issuesFiltered_author;
  377. this->issuesFiltered_statuses.clear();
  378. this->issuesFiltered_assignees.clear();
  379. this->issuesFiltered_authors.clear();
  380. foreach ( const QJsonObject & issue, this->issues.filtered.get() ) {
  381. QJsonObject status = issue["status"].toObject();
  382. QJsonObject assignee = issue["assigned_to"].toObject();
  383. QJsonObject author = issue["author"].toObject();
  384. int status_id = status ["id"].toInt();
  385. int assignee_id = assignee["id"].toInt();
  386. int author_id = author ["id"].toInt();
  387. this->issuesFiltered_authors.insert ( author_id, author );
  388. if ( status_id != 0 )
  389. this->issuesFiltered_statuses. insert ( status_id, status );
  390. if ( assignee_id != 0 )
  391. this->issuesFiltered_assignees.insert ( assignee_id, assignee );
  392. }
  393. this->setIssuesFilterItems ( this->ui->issuesFilter_field_assigned_to,
  394. issuesFiltered_assignees_old,
  395. this->issuesFiltered_assignees,
  396. "name" );
  397. this->setIssuesFilterItems ( this->ui->issuesFilter_field_status,
  398. issuesFiltered_statuses_old,
  399. this->issuesFiltered_statuses,
  400. "name" );
  401. this->projects_display();
  402. return;
  403. }
  404. /**** /updateIssues ****/
  405. /**** issue_display ****/
  406. void fill_combobox ( QComboBox *box, QHash<int, QString> values, int current_id )
  407. {
  408. int idx = 0;
  409. foreach ( const int id, values.keys() ) {
  410. box->insertItem ( idx, values[id], id );
  411. if ( id == current_id )
  412. box->setCurrentIndex ( idx );
  413. idx++;
  414. }
  415. box->model()->sort ( 0 );
  416. return;
  417. }
  418. void fill_combobox ( QComboBox *box, QHash<int, QPair<int, QString>> values, int current_id )
  419. {
  420. QList<int> position_list = values.keys();
  421. qSort ( position_list.begin(), position_list.end() );
  422. int idx = 0;
  423. foreach ( const int id, position_list ) {
  424. QPair<int, QString> value = values[id];
  425. box->insertItem ( idx, value.second, value.first );
  426. if ( value.first == current_id )
  427. box->setCurrentIndex ( idx );
  428. idx++;
  429. }
  430. return;
  431. }
  432. void MainWindowFull::issue_display_field ( QWidget *label, QWidget *field, int pos )
  433. {
  434. QPair<QWidget *, QWidget*> row;
  435. if ( pos == -1 )
  436. pos = this->issue_field_pos++ + 100;
  437. row.first = label;
  438. row.second = field;
  439. this->issue_fields[pos] = row;
  440. return;
  441. }
  442. void MainWindowFull::issue_display_dateField ( QString field_name, QString field_value )
  443. {
  444. qDebug ( "issue_display_dateField: %s", field_name.toUtf8().data() );
  445. QLabel *label = new QLabel;
  446. int pos = -1;
  447. if ( field_name == "start_date" ) {
  448. label->setText ( "Начать выполнение: " );
  449. pos = 20;
  450. } else if ( field_name == "due_date" ) {
  451. label->setText ( "Срок выполнения: " );
  452. pos = 21;
  453. } else
  454. label->setText ( field_name + ": " );
  455. QDateEdit *field = new QDateEdit;
  456. QDate date = QDate::fromString ( field_value, "yyyy-MM-dd" );
  457. field->setDisplayFormat ( "yyyy-MM-dd" );
  458. field->setDate ( date );
  459. field->setCalendarPopup ( true );
  460. this->issue_display_field ( label, field, pos );
  461. return;
  462. }
  463. void MainWindowFull::issue_display_dateTimeField ( QString field_name, QString field_value )
  464. {
  465. qDebug ( "issue_display_dateTimeField: %s", field_name.toUtf8().data() );
  466. QDateTime dateTime = redmine->parseDateTime ( field_value );
  467. QLabel *label = new QLabel;
  468. int pos = -1;
  469. if ( field_name == "created_on" ) {
  470. label->setText ( "Создано: " );
  471. pos = 10;
  472. } else if ( field_name == "updated_on" ) {
  473. label->setText ( "Обновлено: " );
  474. pos = 11;
  475. } else
  476. label->setText ( field_name + ": " );
  477. QLabel *field = new QLabel;
  478. field->setText ( dateTime.toString ( "yyyy-MM-dd HH:MM" ) );
  479. this->issue_display_field ( label, field, pos );
  480. return;
  481. }
  482. void MainWindowFull::issue_display_multilineStringField ( QString field_name, QString field_value )
  483. {
  484. ( void ) field_name; ( void ) field_value;
  485. qDebug ( "issue_display_multilineStringField: %s", field_name.toUtf8().data() );
  486. }
  487. void MainWindowFull::issue_display_stringField ( QString field_name, QString field_value )
  488. {
  489. ( void ) field_name; ( void ) field_value;
  490. qDebug ( "issue_display_string: %s", field_name.toUtf8().data() );
  491. }
  492. void MainWindowFull::issue_display_doneRatioField ( QString field_name, QString field_value )
  493. {
  494. ( void ) field_name; ( void ) field_value;
  495. qDebug ( "issue_display_doneRatioField: %s", field_name.toUtf8().data() );
  496. }
  497. void MainWindowFull::issue_display_enumField ( QString field_name, int field_value_id )
  498. {
  499. qDebug ( "issue_display_enumField: %s", field_name.toUtf8().data() );
  500. QList<struct Enumerations::enumeration> enumeration = this->enumerations.get ( Enumerations::EIT_ISSUE ) [field_name];
  501. QLabel *label = new QLabel;
  502. if ( field_name == "priority" )
  503. label->setText ( "Приоритет: " );
  504. else
  505. label->setText ( field_name + ": " );
  506. QComboBox *field = new QComboBox;
  507. QHash<int, QPair<int, QString>> priorities;
  508. foreach ( const struct Enumerations::enumeration & e, enumeration ) {
  509. QPair <int, QString> priority;
  510. priority.first = e.id;
  511. priority.second = e.name;
  512. priorities.insert ( e.position, priority );
  513. }
  514. fill_combobox ( field, priorities, field_value_id );
  515. if ( field_name == "priority" )
  516. this->issue_display_field ( label, field, 3 );
  517. else
  518. this->issue_display_field ( label, field );
  519. return;
  520. }
  521. void MainWindowFull::issue_display_intField ( QString field_name, int field_value )
  522. {
  523. ( void ) field_name; ( void ) field_value;
  524. qDebug ( "issue_display_intField: %s", field_name.toUtf8().data() );
  525. }
  526. void MainWindowFull::issue_display_statusField ( QString field_name, int status_id )
  527. {
  528. ( void ) status_id;
  529. qDebug ( "issue_display_statusField: %s", field_name.toUtf8().data() );
  530. QLabel *label = new QLabel;
  531. label->setText ( "Статус: " );
  532. QHash<int, QPair<int, QString>> statuses;
  533. QJsonArray allowed_statuses = this->issue["status"].toObject() ["allowed"].toArray();
  534. foreach ( const QJsonValue & allowed_status_val, allowed_statuses ) {
  535. QPair <int, QString> status;
  536. QJsonObject allowed_status = allowed_status_val.toObject();
  537. status.first = allowed_status["id"].toInt();
  538. status.second = allowed_status["name"].toString();
  539. statuses.insert ( allowed_status["position"].toInt(), status );
  540. }
  541. QComboBox *field = new QComboBox;
  542. fill_combobox ( field, statuses, status_id );
  543. this->issue_display_field ( label, field, 2 );
  544. return;
  545. }
  546. void MainWindowFull::issue_display_trackerField ( QString field_name, int tracker_id )
  547. {
  548. ( void ) tracker_id;
  549. qDebug ( "issue_display_trackerField: %s", field_name.toUtf8().data() );
  550. }
  551. void MainWindowFull::issue_display_projectField ( QString field_name, int project_id )
  552. {
  553. ( void ) project_id;
  554. qDebug ( "issue_display_projectField: %s", field_name.toUtf8().data() );
  555. }
  556. void MainWindowFull::issue_display_categoryField ( QString field_name, int category_id )
  557. {
  558. ( void ) category_id;
  559. qDebug ( "issue_display_categoryField: %s", field_name.toUtf8().data() );
  560. }
  561. void MainWindowFull::issue_display_assigneeField ( QString field_name, int assignee_user_id )
  562. {
  563. qDebug ( "issue_display_assigneeField: %s", field_name.toUtf8().data() );
  564. int project_id = this->issue["project"].toObject() ["id"].toInt();
  565. QHash<int, QString> assignable;
  566. QList<struct Memberships::membership> memberships = this->memberships.get_byproject ( project_id );
  567. foreach ( const struct Memberships::membership & membership, memberships )
  568. foreach ( const struct Memberships::membership_role & membership_role, membership.roles )
  569. if ( this->roles.get ( membership_role.role_id ).assignable )
  570. assignable.insert ( membership.user_id, membership.user_name );
  571. //int idx;
  572. QLabel *label = new QLabel;
  573. label->setText ( "Исполнитель: " );
  574. QComboBox *field = new QComboBox;
  575. fill_combobox ( field, assignable, assignee_user_id );
  576. this->issue_display_field ( label, field, 1 );
  577. }
  578. void MainWindowFull::issue_display_authorField ( QString field_name, int author_user_id, QString author_user_name )
  579. {
  580. ( void ) field_name;
  581. ( void ) author_user_id;
  582. qDebug ( "issue_display_authorField: %s", field_name.toUtf8().data() );
  583. QLabel *label = new QLabel;
  584. label->setText ( "Автор: " );
  585. QLabel *field = new QLabel;
  586. field->setText ( author_user_name );
  587. this->issue_display_field ( label, field, 0 );
  588. }
  589. void MainWindowFull::issue_clear()
  590. {
  591. QLayoutItem *item;
  592. while ( this->ui->issueLeftColumn->count() ) {
  593. item = this->ui->issueLeftColumn->takeAt ( 0 );
  594. if ( !item->widget() )
  595. qFatal ( "MainWindowFull::issue_clear(): Caught not implemented case" );
  596. delete item->widget();
  597. delete item;
  598. }
  599. this->issue_fields.clear();
  600. this->issue_field_pos = 100;
  601. }
  602. void MainWindowFull::issue_display_postproc()
  603. {
  604. QList<int> ids = this->issue_fields.keys();
  605. qSort ( ids.begin(), ids.end() );
  606. foreach ( const int &id, ids )
  607. this->ui->issueLeftColumn->addRow ( this->issue_fields[id].first, this->issue_fields[id].second );
  608. return;
  609. }
  610. void MainWindowFull::issue_display ( int issue_id )
  611. {
  612. QJsonObject issue;
  613. //QList<QString> std_simplefields << "description" << "start_date" << "due_date" << "created_on" << "updated_on";
  614. //QList<QString> std_arrayfields << "project" << "tracker" << "status" << "priority" << "author" << "assigned_to";
  615. if ( issue_id == 0 ) {
  616. this->ui->issueTitle->setText ( "" );
  617. return;
  618. }
  619. issue = this->issues.get ( issue_id );
  620. this->issue_clear();
  621. this->issue = issue;
  622. this->ui->issueTitle->setText ( "[" + issue["tracker"].toObject() ["name"].toString() + " #" + QString::number ( issue["id"].toInt() ) + "] " + issue["subject"].toString() );
  623. QJsonObject::iterator iterator = issue.begin();
  624. QJsonObject::iterator issue_end = issue.end();
  625. while ( iterator != issue_end ) {
  626. QString key = iterator.key();
  627. QJsonValue value = iterator.value();
  628. iterator++;
  629. if ( key == "custom_fields" )
  630. continue;
  631. if ( value.isString() ) { // Description, String, Date, DateTime or done_ratio
  632. QString value_str = value.toString();
  633. /*
  634. * Checking if it's a Description
  635. */
  636. if ( key == "description" ) {
  637. this->issue_display_multilineStringField ( key, value_str );
  638. continue;
  639. }
  640. /*
  641. * Checking if it's a Date
  642. */
  643. if ( key.endsWith ( "_date" ) ) {
  644. this->issue_display_dateField ( key, value_str );
  645. continue;
  646. }
  647. /*
  648. * Checking if it's a DateTime
  649. */
  650. if ( key.endsWith ( "_on" ) ) {
  651. this->issue_display_dateTimeField ( key, value_str );
  652. continue;
  653. }
  654. /*
  655. * Checking if it's a "done_ratio"
  656. */
  657. if ( key == "done_ratio" ) {
  658. this->issue_display_doneRatioField ( key, value_str );
  659. continue;
  660. }
  661. /*
  662. * Otherwise it's a String
  663. */
  664. this->issue_display_stringField ( key, value_str );
  665. } else if ( value.isObject() ) { // Int, Enum, Assignee, Author, Status, Tracker, Project, Category
  666. int value_int = value.toObject() ["id"].toInt();
  667. QString value_str = value.toObject() ["name"].toString();
  668. /*
  669. * Checking if it's an enumeration
  670. */
  671. if ( this->enumerations.get ( Enumerations::EIT_ISSUE ).contains ( key ) ) {
  672. this->issue_display_enumField ( key, value_int );
  673. continue;
  674. }
  675. /*
  676. * Checking if it's an assignee
  677. */
  678. if ( key == "assigned_to" ) {
  679. this->issue_display_assigneeField ( key, value_int );
  680. continue;
  681. }
  682. /*
  683. * Checking if it's an author
  684. */
  685. if ( key == "author" ) {
  686. this->issue_display_authorField ( key, value_int, value_str );
  687. continue;
  688. }
  689. /*
  690. * Checking if it's a status
  691. */
  692. if ( key == "status" ) {
  693. this->issue_display_statusField ( key, value_int );
  694. continue;
  695. }
  696. /*
  697. * Checking if it's a tracker
  698. */
  699. if ( key == "tracker" ) {
  700. this->issue_display_trackerField ( key, value_int );
  701. continue;
  702. }
  703. /*
  704. * Checking if it's a project
  705. */
  706. if ( key == "project" ) {
  707. this->issue_display_projectField ( key, value_int );
  708. continue;
  709. }
  710. /*
  711. * Checking if it's a project
  712. */
  713. if ( key == "category" ) {
  714. this->issue_display_categoryField ( key, value_int );
  715. continue;
  716. }
  717. /*
  718. * Otherwise it's an integer
  719. */
  720. this->issue_display_intField ( key, value_int );
  721. }
  722. }
  723. this->issue_display_postproc();
  724. return;
  725. }
  726. /**** /issue_display ****/
  727. /**** SIGNALS ****/
  728. void MainWindowFull::on_projects_itemSelectionChanged()
  729. {
  730. this->selected_projects_id.clear();
  731. foreach ( QTreeWidgetItem * selectedProjectItem, this->ui->projects->selectedItems() ) {
  732. QJsonObject project = this->projects.get ( selectedProjectItem );
  733. int project_id = project["id"].toInt();
  734. //qDebug("selected project: %i", project["id"].toInt());
  735. this->selected_projects_id.insert ( project_id, project_id );
  736. }
  737. this->issues_display();
  738. return;
  739. }
  740. void MainWindowFull::on_issuesTree_itemSelectionChanged()
  741. {
  742. this->selected_issues_id.clear();
  743. foreach ( QTreeWidgetItem * selectedIssueItem, this->ui->issuesTree->selectedItems() ) {
  744. QJsonObject issue = this->issues.get ( selectedIssueItem );
  745. int issue_id = issue["id"].toInt();
  746. this->selected_issues_id.insert ( issue_id, issue_id );
  747. }
  748. if ( this->selected_issues_id.count() == 1 )
  749. this->issue_display ( this->selected_issues_id.keys().first() );
  750. else
  751. this->issue_display ( 0 );
  752. }
  753. void MainWindowFull::on_projectsRadio_recursive_off_toggled ( bool checked )
  754. {
  755. if ( this->ui->projectsRadio_recursive_on->isChecked() == checked )
  756. this->ui->projectsRadio_recursive_on->setChecked ( !checked );
  757. }
  758. void MainWindowFull::on_projectsRadio_recursive_on_toggled ( bool checked )
  759. {
  760. if ( this->ui->projectsRadio_recursive_off->isChecked() == checked )
  761. this->ui->projectsRadio_recursive_off->setChecked ( !checked );
  762. if ( this->showProjectIssues_recursive != checked ) {
  763. this->showProjectIssues_recursive = checked;
  764. this->issues_display();
  765. }
  766. }
  767. void MainWindowFull::on_issuesFilter_showClosed_yes_toggled ( bool checked )
  768. {
  769. if ( this->ui->issuesFilter_showClosed_no->isChecked() == checked )
  770. this->ui->issuesFilter_showClosed_no->setChecked ( !checked );
  771. if ( this->showIssues_showClosed != checked ) {
  772. this->showIssues_showClosed = checked;
  773. this->issues_display();
  774. }
  775. }
  776. void MainWindowFull::on_issuesFilter_showClosed_no_toggled ( bool checked )
  777. {
  778. if ( this->ui->issuesFilter_showClosed_yes->isChecked() == checked )
  779. this->ui->issuesFilter_showClosed_yes->setChecked ( !checked );
  780. }
  781. void MainWindowFull::on_issuesFilter_queryType_all_toggled ( bool checked )
  782. {
  783. if (
  784. this->ui->issuesFilter_queryType_all->isChecked() == false &&
  785. this->ui->issuesFilter_queryType_toMe->isChecked() == false &&
  786. this->ui->issuesFilter_queryType_fromMe->isChecked() == false &&
  787. this->ui->issuesFilter_queryType_followed->isChecked() == false
  788. ) {
  789. this->ui->issuesFilter_queryType_all->setChecked ( true );
  790. }
  791. if ( checked == true ) {
  792. this->ui->issuesFilter_queryType_followed->setChecked ( false );
  793. this->ui->issuesFilter_queryType_toMe->setChecked ( false );
  794. this->ui->issuesFilter_queryType_fromMe->setChecked ( false );
  795. }
  796. this->issuesFilter_queryType = IFQT_ALL;
  797. this->issues_display();
  798. return;
  799. }
  800. void MainWindowFull::on_issuesFilter_queryType_followed_toggled ( bool checked )
  801. {
  802. if (
  803. this->ui->issuesFilter_queryType_all->isChecked() == false &&
  804. this->ui->issuesFilter_queryType_toMe->isChecked() == false &&
  805. this->ui->issuesFilter_queryType_fromMe->isChecked() == false &&
  806. this->ui->issuesFilter_queryType_followed->isChecked() == false
  807. ) {
  808. this->ui->issuesFilter_queryType_followed->setChecked ( true );
  809. }
  810. if ( checked == true ) {
  811. this->ui->issuesFilter_queryType_all->setChecked ( false );
  812. this->ui->issuesFilter_queryType_toMe->setChecked ( false );
  813. this->ui->issuesFilter_queryType_fromMe->setChecked ( false );
  814. }
  815. this->issuesFilter_queryType = IFQT_FOLLOWED;
  816. this->issues_display();
  817. return;
  818. }
  819. void MainWindowFull::on_issuesFilter_queryType_fromMe_toggled ( bool checked )
  820. {
  821. if (
  822. this->ui->issuesFilter_queryType_all->isChecked() == false &&
  823. this->ui->issuesFilter_queryType_toMe->isChecked() == false &&
  824. this->ui->issuesFilter_queryType_fromMe->isChecked() == false &&
  825. this->ui->issuesFilter_queryType_followed->isChecked() == false
  826. ) {
  827. this->ui->issuesFilter_queryType_fromMe->setChecked ( true );
  828. }
  829. if ( checked == true ) {
  830. this->ui->issuesFilter_queryType_all->setChecked ( false );
  831. this->ui->issuesFilter_queryType_toMe->setChecked ( false );
  832. this->ui->issuesFilter_queryType_followed->setChecked ( false );
  833. }
  834. this->issuesFilter_queryType = IFQT_FROMME;
  835. this->issues_display();
  836. return;
  837. }
  838. void MainWindowFull::on_issuesFilter_queryType_toMe_toggled ( bool checked )
  839. {
  840. if (
  841. this->ui->issuesFilter_queryType_all->isChecked() == false &&
  842. this->ui->issuesFilter_queryType_toMe->isChecked() == false &&
  843. this->ui->issuesFilter_queryType_fromMe->isChecked() == false &&
  844. this->ui->issuesFilter_queryType_followed->isChecked() == false
  845. ) {
  846. this->ui->issuesFilter_queryType_toMe->setChecked ( true );
  847. }
  848. if ( checked == true ) {
  849. this->ui->issuesFilter_queryType_all->setChecked ( false );
  850. this->ui->issuesFilter_queryType_fromMe->setChecked ( false );
  851. this->ui->issuesFilter_queryType_followed->setChecked ( false );
  852. }
  853. this->issuesFilter_queryType = IFQT_TOME;
  854. this->issues_display();
  855. return;
  856. }
  857. /**** /SIGNALS ****/
  858. void MainWindowFull::on_issuesFilter_year_currentIndexChanged ( int index )
  859. {
  860. this->issuesFilter_yearIdx = index;
  861. this->updateIssues();
  862. }
  863. void MainWindowFull::on_projectFilter_field_name_textChanged ( const QString &arg1 )
  864. {
  865. this->projectsFilter_namePart = arg1;
  866. this->issues_display();
  867. }
  868. void MainWindowFull::projectsShowContextMenu ( const QPoint &pos )
  869. {
  870. QList<int> project_ids = this->selected_projects_id.keys();
  871. QPoint globalPos = this->ui->projects->mapToGlobal ( pos );
  872. QMenu contextMenu;
  873. QAction *membersItem = contextMenu.addAction ( "Участники" );
  874. QAction *selectedItem = contextMenu.exec ( globalPos );
  875. qDebug ( "%p %p", membersItem, selectedItem );
  876. if ( selectedItem == membersItem ) {
  877. ProjectMembersWindow *membersWindow = new ProjectMembersWindow ( this, project_ids );
  878. membersWindow->show();
  879. } else {
  880. }
  881. return;
  882. }
  883. void MainWindowFull::issuesShowContextMenu ( const QPoint &pos )
  884. {
  885. ( void ) pos;
  886. return;
  887. }
  888. void MainWindowFull::on_issuesFilter_field_name_textChanged ( const QString &arg1 )
  889. {
  890. this->issuesFilter_field_subjectPart = arg1;
  891. this->issues_display();
  892. }
  893. void MainWindowFull::on_issuesFilter_field_assigned_to_currentIndexChanged ( int index )
  894. {
  895. int assigned_to_id = this->ui->issuesFilter_field_assigned_to->itemData ( index ).toInt();
  896. if ( this->issuesFilter_field_assignee_id != assigned_to_id ) {
  897. this->issuesFilter_field_assignee_id = assigned_to_id;
  898. this->issues_display();
  899. }
  900. }
  901. void MainWindowFull::on_issuesFilter_field_status_currentIndexChanged ( int index )
  902. {
  903. int status_id = this->ui->issuesFilter_field_status->itemData ( index ).toInt();
  904. if ( this->issuesFilter_field_status_id != status_id ) {
  905. this->issuesFilter_field_status_id = status_id;
  906. this->issues_display();
  907. }
  908. }