mirror of
https://github.com/RetroShare/RetroShare.git
synced 2026-09-12 19:50:17 +05:00
Merge 1ffc158015 into 01bc50edb3
This commit is contained in:
commit
03d9cc1371
@ -62,6 +62,7 @@ option( RS_GXSCHANNELS "Enable GXS channels in GUI" ON )
|
||||
option( RS_GXSFORUMS "Enable GXS forums in GUI" ON )
|
||||
option( RS_GXSPOSTED "Enable GXS posted in GUI" ON )
|
||||
option( RS_GXSCIRCLES "Enable GXS circles in GUI" ON )
|
||||
option( RS_USE_CALENDAR "Build with Calendar support" OFF)
|
||||
option( RS_GUI_CMARK "Enable CommonMark support in GUI" OFF )
|
||||
set(RS_GXSIDENTITIES ON CACHE BOOL "Enable GXS identities in GUI" FORCE)
|
||||
set(RS_IDLE ON CACHE BOOL "Enable Idle support" FORCE)
|
||||
|
||||
@ -711,6 +711,35 @@ if(RS_JSON_API)
|
||||
)
|
||||
endif(RS_JSON_API)
|
||||
|
||||
if(RS_USE_CALENDAR)
|
||||
add_definitions(-DRS_USE_CALENDAR)
|
||||
list(
|
||||
APPEND RS_GUI_SOURCES
|
||||
src/gui/calendar/CalendarData.cpp
|
||||
src/gui/calendar/CalendarWidget.cpp
|
||||
src/gui/calendar/TasksWidget.cpp
|
||||
src/gui/calendar/CalendarPropertiesDialog.cpp
|
||||
src/gui/calendar/EventDialog.cpp
|
||||
src/gui/calendar/TaskDialog.cpp
|
||||
)
|
||||
|
||||
list(
|
||||
APPEND RS_IMPLEMENTATION_HEADERS
|
||||
src/gui/calendar/CalendarData.h
|
||||
src/gui/calendar/CalendarWidget.h
|
||||
src/gui/calendar/TasksWidget.h
|
||||
src/gui/calendar/CalendarPropertiesDialog.h
|
||||
src/gui/calendar/EventDialog.h
|
||||
src/gui/calendar/TaskDialog.h
|
||||
)
|
||||
|
||||
list(
|
||||
APPEND RS_GUI_FORMS
|
||||
src/gui/calendar/CalendarWidget.ui
|
||||
src/gui/calendar/TasksWidget.ui
|
||||
)
|
||||
endif(RS_USE_CALENDAR)
|
||||
|
||||
if(RS_WEBUI)
|
||||
list(
|
||||
APPEND RS_GUI_SOURCES
|
||||
|
||||
860
retroshare-gui/src/gui/calendar/CalendarData.cpp
Normal file
860
retroshare-gui/src/gui/calendar/CalendarData.cpp
Normal file
@ -0,0 +1,860 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/CalendarData.cpp *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
#include <retroshare/rsinit.h>
|
||||
#include <retroshare/rspeers.h>
|
||||
#include <retroshare/rsgxscalendar.h>
|
||||
#include <retroshare/rsgxscircles.h>
|
||||
#include <util/qtthreadsutils.h>
|
||||
#include <QSettings>
|
||||
#include <QDir>
|
||||
#include <QUuid>
|
||||
|
||||
CalendarData* CalendarData::mInstance = nullptr;
|
||||
|
||||
CalendarData* CalendarData::instance() {
|
||||
if (!mInstance) {
|
||||
mInstance = new CalendarData();
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
CalendarData::CalendarData() : QObject(), mEventHandlerId(0) {
|
||||
loadData();
|
||||
|
||||
if (rsEvents && rsGxsCalendar) {
|
||||
RsEventType calendarEventType = (RsEventType)rsEvents->getDynamicEventType("GXS_CALENDAR");
|
||||
|
||||
rsEvents->registerEventsHandler(
|
||||
[this](std::shared_ptr<const RsEvent> event) {
|
||||
RsQThreadUtils::postToObject([=]() { handleGxsEvent(event); }, this);
|
||||
},
|
||||
mEventHandlerId, calendarEventType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CalendarData::~CalendarData() {
|
||||
saveData();
|
||||
if (rsEvents && mEventHandlerId != 0) {
|
||||
rsEvents->unregisterEventsHandler(mEventHandlerId);
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarData::loadData() {
|
||||
mCalendars.clear();
|
||||
mEvents.clear();
|
||||
mTasks.clear();
|
||||
|
||||
QString accountDir = QString::fromStdString(RsAccounts::AccountDirectory());
|
||||
QString path = accountDir + "/calendar.conf";
|
||||
|
||||
QSettings settings(path, QSettings::IniFormat);
|
||||
|
||||
// Load Calendars
|
||||
int calSize = settings.beginReadArray("calendars");
|
||||
for (int i = 0; i < calSize; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
CalendarInfo cal;
|
||||
cal.id = settings.value("id").toString();
|
||||
cal.name = settings.value("name").toString();
|
||||
cal.color = QColor(settings.value("color").toString());
|
||||
cal.isPublic = settings.value("isPublic").toBool();
|
||||
cal.owner = settings.value("owner").toString();
|
||||
cal.showReminders = settings.value("showReminders", true).toBool();
|
||||
cal.email = settings.value("email", "").toString();
|
||||
cal.onNetwork = settings.value("onNetwork", false).toBool();
|
||||
cal.circleType = settings.value("circleType", 1).toUInt();
|
||||
cal.circleId = settings.value("circleId", "").toString();
|
||||
cal.internalCircle = settings.value("internalCircle", "").toString();
|
||||
cal.groupFlags = settings.value("groupFlags", 4).toUInt(); // Default FLAG_PRIVACY_PUBLIC (4)
|
||||
cal.description = settings.value("description", "").toString();
|
||||
mCalendars.append(cal);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
// Ensure we have at least one default calendar
|
||||
if (mCalendars.isEmpty()) {
|
||||
CalendarInfo defaultCal;
|
||||
defaultCal.id = "personal";
|
||||
defaultCal.name = "Private";
|
||||
defaultCal.color = QColor("#4a90e2");
|
||||
defaultCal.isPublic = false;
|
||||
defaultCal.owner = "local";
|
||||
defaultCal.showReminders = true;
|
||||
defaultCal.email = "retroshare <retroshare@GXSID>";
|
||||
defaultCal.onNetwork = false;
|
||||
mCalendars.append(defaultCal);
|
||||
}
|
||||
|
||||
// Load Events
|
||||
int eventSize = settings.beginReadArray("events");
|
||||
for (int i = 0; i < eventSize; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
CalendarEvent ev;
|
||||
ev.id = settings.value("id").toString();
|
||||
ev.calendarId = settings.value("calendarId").toString();
|
||||
ev.title = settings.value("title").toString();
|
||||
ev.location = settings.value("location").toString();
|
||||
ev.category = settings.value("category").toString();
|
||||
ev.allDay = settings.value("allDay").toBool();
|
||||
ev.start = settings.value("start").toDateTime();
|
||||
ev.end = settings.value("end").toDateTime();
|
||||
ev.repeat = settings.value("repeat").toString();
|
||||
ev.reminder = settings.value("reminder").toString();
|
||||
ev.description = settings.value("description").toString();
|
||||
ev.attendees = settings.value("attendees").toStringList();
|
||||
ev.isPublic = settings.value("isPublic").toBool();
|
||||
ev.attachments = settings.value("attachments").toStringList();
|
||||
mEvents.append(ev);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
// Load Tasks
|
||||
int taskSize = settings.beginReadArray("tasks");
|
||||
for (int i = 0; i < taskSize; ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
CalendarTask task;
|
||||
task.id = settings.value("id").toString();
|
||||
task.calendarId = settings.value("calendarId").toString();
|
||||
task.title = settings.value("title").toString();
|
||||
task.location = settings.value("location").toString();
|
||||
task.category = settings.value("category").toString();
|
||||
task.hasStart = settings.value("hasStart").toBool();
|
||||
task.start = settings.value("start").toDateTime();
|
||||
task.hasDue = settings.value("hasDue").toBool();
|
||||
task.due = settings.value("due").toDateTime();
|
||||
task.status = settings.value("status").toString();
|
||||
task.percentComplete = settings.value("percentComplete").toInt();
|
||||
task.repeat = settings.value("repeat").toString();
|
||||
task.reminder = settings.value("reminder").toString();
|
||||
task.description = settings.value("description").toString();
|
||||
task.completed = settings.value("completed").toBool();
|
||||
task.attachments = settings.value("attachments").toStringList();
|
||||
mTasks.append(task);
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void CalendarData::saveData() {
|
||||
QString accountDir = QString::fromStdString(RsAccounts::AccountDirectory());
|
||||
QString path = accountDir + "/calendar.conf";
|
||||
|
||||
QSettings settings(path, QSettings::IniFormat);
|
||||
|
||||
// Save Calendars
|
||||
settings.beginWriteArray("calendars");
|
||||
for (int i = 0; i < mCalendars.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("id", mCalendars[i].id);
|
||||
settings.setValue("name", mCalendars[i].name);
|
||||
settings.setValue("color", mCalendars[i].color.name());
|
||||
settings.setValue("isPublic", mCalendars[i].isPublic);
|
||||
settings.setValue("owner", mCalendars[i].owner);
|
||||
settings.setValue("showReminders", mCalendars[i].showReminders);
|
||||
settings.setValue("email", mCalendars[i].email);
|
||||
settings.setValue("onNetwork", mCalendars[i].onNetwork);
|
||||
settings.setValue("circleType", mCalendars[i].circleType);
|
||||
settings.setValue("circleId", mCalendars[i].circleId);
|
||||
settings.setValue("internalCircle", mCalendars[i].internalCircle);
|
||||
settings.setValue("groupFlags", mCalendars[i].groupFlags);
|
||||
settings.setValue("description", mCalendars[i].description);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
// Save Events
|
||||
settings.beginWriteArray("events");
|
||||
for (int i = 0; i < mEvents.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("id", mEvents[i].id);
|
||||
settings.setValue("calendarId", mEvents[i].calendarId);
|
||||
settings.setValue("title", mEvents[i].title);
|
||||
settings.setValue("location", mEvents[i].location);
|
||||
settings.setValue("category", mEvents[i].category);
|
||||
settings.setValue("allDay", mEvents[i].allDay);
|
||||
settings.setValue("start", mEvents[i].start);
|
||||
settings.setValue("end", mEvents[i].end);
|
||||
settings.setValue("repeat", mEvents[i].repeat);
|
||||
settings.setValue("reminder", mEvents[i].reminder);
|
||||
settings.setValue("description", mEvents[i].description);
|
||||
settings.setValue("attendees", mEvents[i].attendees);
|
||||
settings.setValue("isPublic", mEvents[i].isPublic);
|
||||
settings.setValue("attachments", mEvents[i].attachments);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
// Save Tasks
|
||||
settings.beginWriteArray("tasks");
|
||||
for (int i = 0; i < mTasks.size(); ++i) {
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("id", mTasks[i].id);
|
||||
settings.setValue("calendarId", mTasks[i].calendarId);
|
||||
settings.setValue("title", mTasks[i].title);
|
||||
settings.setValue("location", mTasks[i].location);
|
||||
settings.setValue("category", mTasks[i].category);
|
||||
settings.setValue("hasStart", mTasks[i].hasStart);
|
||||
settings.setValue("start", mTasks[i].start);
|
||||
settings.setValue("hasDue", mTasks[i].hasDue);
|
||||
settings.setValue("due", mTasks[i].due);
|
||||
settings.setValue("status", mTasks[i].status);
|
||||
settings.setValue("percentComplete", mTasks[i].percentComplete);
|
||||
settings.setValue("repeat", mTasks[i].repeat);
|
||||
settings.setValue("reminder", mTasks[i].reminder);
|
||||
settings.setValue("description", mTasks[i].description);
|
||||
settings.setValue("completed", mTasks[i].completed);
|
||||
settings.setValue("attachments", mTasks[i].attachments);
|
||||
}
|
||||
settings.endArray();
|
||||
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
void CalendarData::addCalendar(const CalendarInfo& cal) {
|
||||
mCalendars.append(cal);
|
||||
saveData();
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
|
||||
void CalendarData::updateCalendar(const CalendarInfo& cal) {
|
||||
for (int i = 0; i < mCalendars.size(); ++i) {
|
||||
if (mCalendars[i].id == cal.id) {
|
||||
mCalendars[i] = cal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveData();
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
|
||||
void CalendarData::removeCalendar(const QString& id) {
|
||||
for (int i = 0; i < mCalendars.size(); ++i) {
|
||||
if (mCalendars[i].id == id) {
|
||||
// Unsubscribe from GXS if network calendar
|
||||
if (mCalendars[i].onNetwork && rsGxsCalendar) {
|
||||
std::string errMsg;
|
||||
rsGxsCalendar->subscribeToCalendar(RsGxsGroupId(id.toStdString()), false, errMsg);
|
||||
}
|
||||
mCalendars.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove associated events and tasks
|
||||
mEvents.erase(std::remove_if(mEvents.begin(), mEvents.end(),
|
||||
[&id](const CalendarEvent& ev) { return ev.calendarId == id; }), mEvents.end());
|
||||
mTasks.erase(std::remove_if(mTasks.begin(), mTasks.end(),
|
||||
[&id](const CalendarTask& t) { return t.calendarId == id; }), mTasks.end());
|
||||
|
||||
saveData();
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
|
||||
void CalendarData::addEvent(const CalendarEvent& ev) {
|
||||
mEvents.append(ev);
|
||||
saveData();
|
||||
publishCalendarUpdates(ev.calendarId);
|
||||
}
|
||||
|
||||
void CalendarData::updateEvent(const CalendarEvent& ev) {
|
||||
for (int i = 0; i < mEvents.size(); ++i) {
|
||||
if (mEvents[i].id == ev.id) {
|
||||
mEvents[i] = ev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveData();
|
||||
publishCalendarUpdates(ev.calendarId);
|
||||
}
|
||||
|
||||
void CalendarData::removeEvent(const QString& id) {
|
||||
QString calId;
|
||||
for (int i = 0; i < mEvents.size(); ++i) {
|
||||
if (mEvents[i].id == id) {
|
||||
calId = mEvents[i].calendarId;
|
||||
mEvents.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveData();
|
||||
if (!calId.isEmpty()) {
|
||||
publishCalendarUpdates(calId);
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarData::addTask(const CalendarTask& task) {
|
||||
mTasks.append(task);
|
||||
saveData();
|
||||
publishCalendarUpdates(task.calendarId);
|
||||
}
|
||||
|
||||
void CalendarData::updateTask(const CalendarTask& task) {
|
||||
for (int i = 0; i < mTasks.size(); ++i) {
|
||||
if (mTasks[i].id == task.id) {
|
||||
mTasks[i] = task;
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveData();
|
||||
publishCalendarUpdates(task.calendarId);
|
||||
}
|
||||
|
||||
void CalendarData::removeTask(const QString& id) {
|
||||
QString calId;
|
||||
for (int i = 0; i < mTasks.size(); ++i) {
|
||||
if (mTasks[i].id == id) {
|
||||
calId = mTasks[i].calendarId;
|
||||
mTasks.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveData();
|
||||
if (!calId.isEmpty()) {
|
||||
publishCalendarUpdates(calId);
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QString> CalendarData::getContacts() {
|
||||
QMap<QString, QString> contacts;
|
||||
|
||||
if (!rsPeers) {
|
||||
return contacts;
|
||||
}
|
||||
|
||||
std::list<RsPgpId> pgpIds;
|
||||
rsPeers->getGPGAcceptedList(pgpIds);
|
||||
|
||||
for (const auto& pgpId : pgpIds) {
|
||||
RsPeerDetails details;
|
||||
if (rsPeers->getGPGDetails(pgpId, details)) {
|
||||
contacts.insert(QString::fromStdString(pgpId.toStdString()), QString::fromUtf8(details.name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
// Fallbacks/Mocks if empty (to make sure it lists some developers/coworkers as requested in the screenshots)
|
||||
if (contacts.isEmpty()) {
|
||||
contacts.insert("friend1", "Alice (Developer)");
|
||||
contacts.insert("friend2", "Bob (Coworker)");
|
||||
contacts.insert("friend3", "Charlie (Friend)");
|
||||
}
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
static RsGxsId getGxsIdFromEmail(const QString& email) {
|
||||
int idx = email.lastIndexOf('@');
|
||||
int endIdx = email.lastIndexOf('>');
|
||||
if (idx != -1 && endIdx != -1 && endIdx > idx) {
|
||||
std::string gxsIdStr = email.mid(idx + 1, endIdx - idx - 1).toStdString();
|
||||
return RsGxsId(gxsIdStr);
|
||||
}
|
||||
return RsGxsId();
|
||||
}
|
||||
|
||||
QString CalendarData::exportCalendarToIcs(const QString& calId) const {
|
||||
QString icsContent = "BEGIN:VCALENDAR\r\n"
|
||||
"VERSION:2.0\r\n"
|
||||
"PRODID:-//RetroShare//Calendar//EN\r\n";
|
||||
|
||||
for (const auto& ev : mEvents) {
|
||||
if (ev.calendarId != calId) continue;
|
||||
|
||||
icsContent += "BEGIN:VEVENT\r\n";
|
||||
icsContent += QString("UID:%1\r\n").arg(ev.id);
|
||||
icsContent += QString("SUMMARY:%1\r\n").arg(ev.title);
|
||||
|
||||
if (!ev.description.isEmpty()) {
|
||||
QString desc = ev.description;
|
||||
desc.replace("\n", "\\n").replace("\r", "");
|
||||
icsContent += QString("DESCRIPTION:%1\r\n").arg(desc);
|
||||
}
|
||||
|
||||
if (!ev.location.isEmpty()) {
|
||||
icsContent += QString("LOCATION:%1\r\n").arg(ev.location);
|
||||
}
|
||||
|
||||
if (!ev.category.isEmpty()) {
|
||||
icsContent += QString("CATEGORIES:%1\r\n").arg(ev.category);
|
||||
}
|
||||
|
||||
if (ev.allDay) {
|
||||
icsContent += QString("DTSTART;VALUE=DATE:%1\r\n").arg(ev.start.toString("yyyyMMdd"));
|
||||
icsContent += QString("DTEND;VALUE=DATE:%1\r\n").arg(ev.end.toString("yyyyMMdd"));
|
||||
} else {
|
||||
icsContent += QString("DTSTART:%1\r\n").arg(ev.start.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"));
|
||||
icsContent += QString("DTEND:%1\r\n").arg(ev.end.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"));
|
||||
}
|
||||
|
||||
for (const auto& att : ev.attachments) {
|
||||
icsContent += QString("ATTACH:%1\r\n").arg(att);
|
||||
}
|
||||
|
||||
icsContent += "END:VEVENT\r\n";
|
||||
}
|
||||
|
||||
for (const auto& t : mTasks) {
|
||||
if (t.calendarId != calId) continue;
|
||||
|
||||
icsContent += "BEGIN:VTODO\r\n";
|
||||
icsContent += QString("UID:%1\r\n").arg(t.id);
|
||||
icsContent += QString("SUMMARY:%1\r\n").arg(t.title);
|
||||
|
||||
if (!t.description.isEmpty()) {
|
||||
QString desc = t.description;
|
||||
desc.replace("\n", "\\n").replace("\r", "");
|
||||
icsContent += QString("DESCRIPTION:%1\r\n").arg(desc);
|
||||
}
|
||||
|
||||
if (!t.location.isEmpty()) {
|
||||
icsContent += QString("LOCATION:%1\r\n").arg(t.location);
|
||||
}
|
||||
|
||||
if (!t.category.isEmpty()) {
|
||||
icsContent += QString("CATEGORIES:%1\r\n").arg(t.category);
|
||||
}
|
||||
|
||||
if (t.hasStart) {
|
||||
icsContent += QString("DTSTART:%1\r\n").arg(t.start.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"));
|
||||
}
|
||||
|
||||
if (t.hasDue) {
|
||||
icsContent += QString("DUE:%1\r\n").arg(t.due.toUTC().toString("yyyyMMdd'T'HHmmss'Z'"));
|
||||
}
|
||||
|
||||
if (!t.status.isEmpty()) {
|
||||
icsContent += QString("STATUS:%1\r\n").arg(t.status);
|
||||
}
|
||||
|
||||
icsContent += QString("PERCENT-COMPLETE:%1\r\n").arg(t.percentComplete);
|
||||
icsContent += QString("COMPLETED:%1\r\n").arg(t.completed ? "TRUE" : "FALSE");
|
||||
|
||||
for (const auto& att : t.attachments) {
|
||||
icsContent += QString("ATTACH:%1\r\n").arg(att);
|
||||
}
|
||||
|
||||
icsContent += "END:VTODO\r\n";
|
||||
}
|
||||
|
||||
icsContent += "END:VCALENDAR\r\n";
|
||||
return icsContent;
|
||||
}
|
||||
|
||||
void CalendarData::importCalendarFromIcs(const QString& calId, const QString& icsData) {
|
||||
auto evIt = mEvents.begin();
|
||||
while (evIt != mEvents.end()) {
|
||||
if (evIt->calendarId == calId) {
|
||||
evIt = mEvents.erase(evIt);
|
||||
} else {
|
||||
++evIt;
|
||||
}
|
||||
}
|
||||
|
||||
auto tIt = mTasks.begin();
|
||||
while (tIt != mTasks.end()) {
|
||||
if (tIt->calendarId == calId) {
|
||||
tIt = mTasks.erase(tIt);
|
||||
} else {
|
||||
++tIt;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList rawLines = icsData.split(QRegExp("[\r\n]"), QString::SkipEmptyParts);
|
||||
QStringList lines;
|
||||
for (int i = 0; i < rawLines.size(); ++i) {
|
||||
QString line = rawLines[i];
|
||||
while (i + 1 < rawLines.size() && (rawLines[i + 1].startsWith(" ") || rawLines[i + 1].startsWith("\t"))) {
|
||||
line += rawLines[i + 1].mid(1);
|
||||
i++;
|
||||
}
|
||||
lines.append(line);
|
||||
}
|
||||
|
||||
auto parseIcsDateTime = [](const QString& val) -> QDateTime {
|
||||
QDateTime dt;
|
||||
if (val.endsWith('Z')) {
|
||||
dt = QDateTime::fromString(val, "yyyyMMdd'T'HHmmss'Z'");
|
||||
dt.setTimeSpec(Qt::UTC);
|
||||
dt = dt.toLocalTime();
|
||||
} else {
|
||||
dt = QDateTime::fromString(val, "yyyyMMdd'T'HHmmss");
|
||||
dt.setTimeSpec(Qt::LocalTime);
|
||||
}
|
||||
return dt;
|
||||
};
|
||||
|
||||
bool inEvent = false;
|
||||
bool inTask = false;
|
||||
CalendarEvent currentEvent;
|
||||
CalendarTask currentTask;
|
||||
|
||||
for (const QString& line : lines) {
|
||||
QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine.isEmpty()) continue;
|
||||
|
||||
if (trimmedLine.startsWith("BEGIN:VEVENT", Qt::CaseInsensitive)) {
|
||||
inEvent = true;
|
||||
currentEvent = CalendarEvent();
|
||||
currentEvent.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
currentEvent.calendarId = calId;
|
||||
currentEvent.allDay = false;
|
||||
currentEvent.isPublic = true;
|
||||
} else if (trimmedLine.startsWith("END:VEVENT", Qt::CaseInsensitive)) {
|
||||
if (inEvent) {
|
||||
if (!currentEvent.start.isValid()) currentEvent.start = QDateTime::currentDateTime();
|
||||
if (!currentEvent.end.isValid()) currentEvent.end = currentEvent.start.addSecs(3600);
|
||||
mEvents.append(currentEvent);
|
||||
inEvent = false;
|
||||
}
|
||||
} else if (trimmedLine.startsWith("BEGIN:VTODO", Qt::CaseInsensitive)) {
|
||||
inTask = true;
|
||||
currentTask = CalendarTask();
|
||||
currentTask.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
currentTask.calendarId = calId;
|
||||
currentTask.hasStart = false;
|
||||
currentTask.hasDue = false;
|
||||
currentTask.completed = false;
|
||||
currentTask.percentComplete = 0;
|
||||
} else if (trimmedLine.startsWith("END:VTODO", Qt::CaseInsensitive)) {
|
||||
if (inTask) {
|
||||
mTasks.append(currentTask);
|
||||
inTask = false;
|
||||
}
|
||||
} else if (inEvent) {
|
||||
int colonIdx = trimmedLine.indexOf(':');
|
||||
int semiIdx = trimmedLine.indexOf(';');
|
||||
int splitIdx = -1;
|
||||
|
||||
if (colonIdx != -1 && semiIdx != -1) splitIdx = qMin(colonIdx, semiIdx);
|
||||
else if (colonIdx != -1) splitIdx = colonIdx;
|
||||
else if (semiIdx != -1) splitIdx = semiIdx;
|
||||
|
||||
if (splitIdx != -1) {
|
||||
QString key = trimmedLine.left(splitIdx).trimmed();
|
||||
QString val = trimmedLine.mid(colonIdx + 1).trimmed();
|
||||
|
||||
if (key.compare("UID", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.id = val;
|
||||
} else if (key.compare("SUMMARY", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.title = val;
|
||||
} else if (key.compare("LOCATION", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.location = val;
|
||||
} else if (key.compare("CATEGORIES", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.category = val;
|
||||
} else if (key.compare("DESCRIPTION", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.description = val.replace("\\n", "\n").replace("\\r", "").replace("\\,", ",");
|
||||
} else if (key.startsWith("DTSTART", Qt::CaseInsensitive)) {
|
||||
if (trimmedLine.contains("VALUE=DATE", Qt::CaseInsensitive)) {
|
||||
currentEvent.allDay = true;
|
||||
currentEvent.start = QDateTime(QDate::fromString(val, "yyyyMMdd"), QTime(0, 0));
|
||||
} else {
|
||||
currentEvent.start = parseIcsDateTime(val);
|
||||
}
|
||||
} else if (key.startsWith("DTEND", Qt::CaseInsensitive)) {
|
||||
if (trimmedLine.contains("VALUE=DATE", Qt::CaseInsensitive)) {
|
||||
currentEvent.allDay = true;
|
||||
currentEvent.end = QDateTime(QDate::fromString(val, "yyyyMMdd"), QTime(0, 0));
|
||||
} else {
|
||||
currentEvent.end = parseIcsDateTime(val);
|
||||
}
|
||||
} else if (key.compare("ATTACH", Qt::CaseInsensitive) == 0) {
|
||||
currentEvent.attachments.append(val);
|
||||
}
|
||||
}
|
||||
} else if (inTask) {
|
||||
int colonIdx = trimmedLine.indexOf(':');
|
||||
int semiIdx = trimmedLine.indexOf(';');
|
||||
int splitIdx = -1;
|
||||
|
||||
if (colonIdx != -1 && semiIdx != -1) splitIdx = qMin(colonIdx, semiIdx);
|
||||
else if (colonIdx != -1) splitIdx = colonIdx;
|
||||
else if (semiIdx != -1) splitIdx = semiIdx;
|
||||
|
||||
if (splitIdx != -1) {
|
||||
QString key = trimmedLine.left(splitIdx).trimmed();
|
||||
QString val = trimmedLine.mid(colonIdx + 1).trimmed();
|
||||
|
||||
if (key.compare("UID", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.id = val;
|
||||
} else if (key.compare("SUMMARY", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.title = val;
|
||||
} else if (key.compare("LOCATION", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.location = val;
|
||||
} else if (key.compare("CATEGORIES", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.category = val;
|
||||
} else if (key.compare("DESCRIPTION", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.description = val.replace("\\n", "\n").replace("\\r", "").replace("\\,", ",");
|
||||
} else if (key.startsWith("DTSTART", Qt::CaseInsensitive)) {
|
||||
currentTask.hasStart = true;
|
||||
currentTask.start = parseIcsDateTime(val);
|
||||
} else if (key.startsWith("DUE", Qt::CaseInsensitive)) {
|
||||
currentTask.hasDue = true;
|
||||
currentTask.due = parseIcsDateTime(val);
|
||||
} else if (key.compare("STATUS", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.status = val;
|
||||
} else if (key.compare("PERCENT-COMPLETE", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.percentComplete = val.toInt();
|
||||
} else if (key.compare("COMPLETED", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.completed = (val.compare("TRUE", Qt::CaseInsensitive) == 0);
|
||||
} else if (key.compare("ATTACH", Qt::CaseInsensitive) == 0) {
|
||||
currentTask.attachments.append(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarData::migrateCalendarData(const QString& oldId, const QString& newId) {
|
||||
for (auto& ev : mEvents) {
|
||||
if (ev.calendarId == oldId) {
|
||||
ev.calendarId = newId;
|
||||
}
|
||||
}
|
||||
for (auto& t : mTasks) {
|
||||
if (t.calendarId == oldId) {
|
||||
t.calendarId = newId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarData::publishCalendarUpdates(const QString& calId) {
|
||||
if (!rsGxsCalendar) return;
|
||||
|
||||
CalendarInfo cal;
|
||||
bool found = false;
|
||||
for (const auto& c : mCalendars) {
|
||||
if (c.id == calId) {
|
||||
cal = c;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found || !cal.onNetwork) return;
|
||||
|
||||
RsGxsId authorId = getGxsIdFromEmail(cal.email);
|
||||
RsGxsGroupId groupId(calId.toStdString());
|
||||
QString ics = exportCalendarToIcs(calId);
|
||||
RsGxsMessageId msgId;
|
||||
std::string errMsg;
|
||||
rsGxsCalendar->publishCalendarIcs(groupId, ics.toStdString(), authorId, msgId, errMsg);
|
||||
}
|
||||
|
||||
bool CalendarData::publishCalendar(const QString& oldId, const QString& email, QString& newIdOut) {
|
||||
if (!rsGxsCalendar) return false;
|
||||
|
||||
// Find local calendar
|
||||
int calIdx = -1;
|
||||
for (int i = 0; i < mCalendars.size(); ++i) {
|
||||
if (mCalendars[i].id == oldId) {
|
||||
calIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (calIdx == -1) return false;
|
||||
|
||||
CalendarInfo& cal = mCalendars[calIdx];
|
||||
if (cal.onNetwork) return false; // Already on network
|
||||
|
||||
// Extract GXS ID from email
|
||||
RsGxsId authorId = getGxsIdFromEmail(email);
|
||||
|
||||
RsGxsGroupId groupId;
|
||||
std::string errMsg;
|
||||
if (!rsGxsCalendar->createCalendar(cal.name.toStdString(), "RetroShare Calendar", authorId, cal.circleType, RsGxsCircleId(cal.circleId.toStdString()), RsGxsCircleId(cal.internalCircle.toStdString()), cal.groupFlags, groupId, errMsg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString newId = QString::fromStdString(groupId.toStdString());
|
||||
newIdOut = newId;
|
||||
|
||||
// Migrate events and tasks
|
||||
migrateCalendarData(oldId, newId);
|
||||
|
||||
// Update calendar info
|
||||
cal.id = newId;
|
||||
cal.onNetwork = true;
|
||||
cal.isPublic = true;
|
||||
cal.email = email;
|
||||
cal.owner = "local";
|
||||
|
||||
saveData();
|
||||
|
||||
// Subscribe and publish initial ICS
|
||||
rsGxsCalendar->subscribeToCalendar(groupId, true, errMsg);
|
||||
publishCalendarUpdates(newId);
|
||||
|
||||
emit calendarDataChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CalendarData::subscribeToCalendar(const QString& id, bool subscribe, const QString& name) {
|
||||
if (!rsGxsCalendar) return false;
|
||||
|
||||
RsGxsGroupId groupId(id.toStdString());
|
||||
std::string errMsg;
|
||||
if (!rsGxsCalendar->subscribeToCalendar(groupId, subscribe, errMsg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subscribe) {
|
||||
// Find if it's already in mCalendars
|
||||
bool found = false;
|
||||
for (const auto& c : mCalendars) {
|
||||
if (c.id == id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
CalendarInfo localCal;
|
||||
localCal.id = id;
|
||||
localCal.name = name.isEmpty() ? tr("Shared Calendar") : name;
|
||||
localCal.color = QColor("#4a90e2");
|
||||
localCal.isPublic = true;
|
||||
localCal.onNetwork = true;
|
||||
localCal.showReminders = true;
|
||||
localCal.owner = "network";
|
||||
localCal.circleType = 1;
|
||||
localCal.circleId = "";
|
||||
localCal.internalCircle = "";
|
||||
localCal.groupFlags = 4;
|
||||
localCal.description = "";
|
||||
mCalendars.append(localCal);
|
||||
saveData();
|
||||
}
|
||||
emit calendarDataChanged();
|
||||
// Trigger sync to fetch contents
|
||||
updateCalendars();
|
||||
} else {
|
||||
// Remove from local list
|
||||
for (int i = 0; i < mCalendars.size(); ++i) {
|
||||
if (mCalendars[i].id == id) {
|
||||
mCalendars.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Remove associated events and tasks
|
||||
mEvents.erase(std::remove_if(mEvents.begin(), mEvents.end(),
|
||||
[&id](const CalendarEvent& ev) { return ev.calendarId == id; }), mEvents.end());
|
||||
mTasks.erase(std::remove_if(mTasks.begin(), mTasks.end(),
|
||||
[&id](const CalendarTask& t) { return t.calendarId == id; }), mTasks.end());
|
||||
|
||||
saveData();
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CalendarData::updateCalendars() {
|
||||
if (!rsGxsCalendar) return;
|
||||
|
||||
std::list<RsGroupMetaData> calendars;
|
||||
if (rsGxsCalendar->getCalendarsSummaries(calendars)) {
|
||||
bool changed = false;
|
||||
for (const auto& meta : calendars) {
|
||||
bool isSubscribed = (meta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_SUBSCRIBED);
|
||||
if (isSubscribed) {
|
||||
QString calId = QString::fromStdString(meta.mGroupId.toStdString());
|
||||
|
||||
bool found = false;
|
||||
CalendarInfo localCal;
|
||||
for (auto& c : mCalendars) {
|
||||
if (c.id == calId) {
|
||||
localCal = c;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
localCal.id = calId;
|
||||
localCal.name = QString::fromUtf8(meta.mGroupName.c_str());
|
||||
localCal.color = QColor("#4a90e2");
|
||||
localCal.isPublic = true;
|
||||
localCal.onNetwork = true;
|
||||
localCal.showReminders = true;
|
||||
localCal.owner = "network";
|
||||
localCal.circleType = meta.mCircleType;
|
||||
localCal.circleId = QString::fromStdString(meta.mCircleId.toStdString());
|
||||
localCal.internalCircle = QString::fromStdString(meta.mInternalCircle.toStdString());
|
||||
localCal.groupFlags = meta.mGroupFlags;
|
||||
localCal.description = "";
|
||||
mCalendars.append(localCal);
|
||||
changed = true;
|
||||
} else {
|
||||
QString remoteName = QString::fromUtf8(meta.mGroupName.c_str());
|
||||
for (auto& c : mCalendars) {
|
||||
if (c.id == calId && c.name != remoteName) {
|
||||
c.name = remoteName;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RsGxsCalendarMessage> messages;
|
||||
if (rsGxsCalendar->getCalendarContent(meta.mGroupId, messages)) {
|
||||
if (!messages.empty()) {
|
||||
uint32_t latestTime = 0;
|
||||
size_t latestIdx = 0;
|
||||
for (size_t i = 0; i < messages.size(); ++i) {
|
||||
if (messages[i].mMeta.mPublishTs > latestTime) {
|
||||
latestTime = messages[i].mMeta.mPublishTs;
|
||||
latestIdx = i;
|
||||
}
|
||||
}
|
||||
QString msgIdStr = QString::fromStdString(messages[latestIdx].mMeta.mMsgId.toStdString());
|
||||
if (!mLastMsgIds.contains(calId) || mLastMsgIds[calId] != msgIdStr) {
|
||||
importCalendarFromIcs(calId, QString::fromStdString(messages[latestIdx].mIcsData));
|
||||
mLastMsgIds[calId] = msgIdStr;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
saveData();
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
// Always emit so the UI refreshes the shared calendar list
|
||||
// from GXS group metadata (getCalendarsSummaries), even when
|
||||
// local calendar data didn't change.
|
||||
emit calendarDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarData::handleGxsEvent(std::shared_ptr<const RsEvent> event) {
|
||||
const RsGxsCalendarEvent *e = dynamic_cast<const RsGxsCalendarEvent*>(event.get());
|
||||
if (e) {
|
||||
switch (e->mCalendarEventCode) {
|
||||
case RsCalendarEventCode::NEW_CALENDAR:
|
||||
case RsCalendarEventCode::UPDATED_CALENDAR:
|
||||
updateCalendars();
|
||||
break;
|
||||
case RsCalendarEventCode::NEW_EVENT:
|
||||
case RsCalendarEventCode::UPDATED_EVENT:
|
||||
case RsCalendarEventCode::SUBSCRIBE_STATUS_CHANGED:
|
||||
updateCalendars();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
retroshare-gui/src/gui/calendar/CalendarData.h
Normal file
143
retroshare-gui/src/gui/calendar/CalendarData.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/CalendarData.h *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CALENDARDATA_H
|
||||
#define CALENDARDATA_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QColor>
|
||||
#include <QStringList>
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
#include <retroshare/rsevents.h>
|
||||
|
||||
struct CalendarInfo {
|
||||
QString id;
|
||||
QString name;
|
||||
QColor color;
|
||||
bool isPublic;
|
||||
QString owner; // contact PGP ID or "local"
|
||||
bool showReminders;
|
||||
QString email;
|
||||
bool onNetwork;
|
||||
|
||||
uint32_t circleType;
|
||||
QString circleId;
|
||||
QString internalCircle;
|
||||
uint32_t groupFlags;
|
||||
QString description;
|
||||
};
|
||||
|
||||
struct CalendarEvent {
|
||||
QString id;
|
||||
QString calendarId;
|
||||
QString title;
|
||||
QString location;
|
||||
QString category;
|
||||
bool allDay;
|
||||
QDateTime start;
|
||||
QDateTime end;
|
||||
QString repeat;
|
||||
QString reminder;
|
||||
QString description;
|
||||
QStringList attendees; // PGP IDs of contacts
|
||||
bool isPublic;
|
||||
QStringList attachments;
|
||||
};
|
||||
|
||||
struct CalendarTask {
|
||||
QString id;
|
||||
QString calendarId;
|
||||
QString title;
|
||||
QString location;
|
||||
QString category;
|
||||
bool hasStart;
|
||||
QDateTime start;
|
||||
bool hasDue;
|
||||
QDateTime due;
|
||||
QString status;
|
||||
int percentComplete;
|
||||
QString repeat;
|
||||
QString reminder;
|
||||
QString description;
|
||||
bool completed;
|
||||
QStringList attachments;
|
||||
};
|
||||
|
||||
class CalendarData : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static CalendarData* instance();
|
||||
|
||||
void loadData();
|
||||
void saveData();
|
||||
|
||||
const QList<CalendarInfo>& getCalendars() const { return mCalendars; }
|
||||
const QList<CalendarEvent>& getEvents() const { return mEvents; }
|
||||
const QList<CalendarTask>& getTasks() const { return mTasks; }
|
||||
|
||||
void addCalendar(const CalendarInfo& cal);
|
||||
void updateCalendar(const CalendarInfo& cal);
|
||||
void removeCalendar(const QString& id);
|
||||
|
||||
void addEvent(const CalendarEvent& ev);
|
||||
void updateEvent(const CalendarEvent& ev);
|
||||
void removeEvent(const QString& id);
|
||||
|
||||
void addTask(const CalendarTask& task);
|
||||
void updateTask(const CalendarTask& task);
|
||||
void removeTask(const QString& id);
|
||||
|
||||
// Helpers
|
||||
static QMap<QString, QString> getContacts(); // map PGP ID -> Name
|
||||
|
||||
QString exportCalendarToIcs(const QString& calId) const;
|
||||
void importCalendarFromIcs(const QString& calId, const QString& icsData);
|
||||
void migrateCalendarData(const QString& oldId, const QString& newId);
|
||||
void publishCalendarUpdates(const QString& calId);
|
||||
bool publishCalendar(const QString& oldId, const QString& email, QString& newIdOut);
|
||||
bool subscribeToCalendar(const QString& id, bool subscribe, const QString& name = "");
|
||||
|
||||
signals:
|
||||
void calendarDataChanged();
|
||||
|
||||
public slots:
|
||||
void updateCalendars();
|
||||
|
||||
private slots:
|
||||
void handleGxsEvent(std::shared_ptr<const RsEvent> event);
|
||||
|
||||
private:
|
||||
CalendarData();
|
||||
~CalendarData() override;
|
||||
|
||||
QList<CalendarInfo> mCalendars;
|
||||
QList<CalendarEvent> mEvents;
|
||||
QList<CalendarTask> mTasks;
|
||||
QMap<QString, QString> mLastMsgIds;
|
||||
|
||||
static CalendarData* mInstance;
|
||||
uint32_t mEventHandlerId;
|
||||
};
|
||||
|
||||
#endif // CALENDARDATA_H
|
||||
450
retroshare-gui/src/gui/calendar/CalendarPropertiesDialog.cpp
Normal file
450
retroshare-gui/src/gui/calendar/CalendarPropertiesDialog.cpp
Normal file
@ -0,0 +1,450 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/CalendarPropertiesDialog.cpp *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#include "gui/calendar/CalendarPropertiesDialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include <QRadioButton>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QStackedWidget>
|
||||
#include <QColorDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QUuid>
|
||||
#include <retroshare/rspeers.h>
|
||||
#include <retroshare/rsidentity.h>
|
||||
#include <retroshare/rsgxscalendar.h>
|
||||
#include "gui/gxs/GxsIdChooser.h"
|
||||
#include "gui/gxs/GxsCircleChooser.h"
|
||||
#include "gui/common/GroupChooser.h"
|
||||
#include <retroshare/rsgxscircles.h>
|
||||
#include <QGroupBox>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
CalendarPropertiesDialog::CalendarPropertiesDialog(const QString& calId, QWidget* parent)
|
||||
: QDialog(parent), mCalId(calId), mEditMode(!calId.isEmpty()), mSelectedColor(QColor("#4a90e2"))
|
||||
{
|
||||
setupUi();
|
||||
|
||||
// Default initialization for GXS choosers
|
||||
mIdChooser->loadIds(0, RsGxsId());
|
||||
mCircleCombo->loadCircles(RsGxsCircleId());
|
||||
mLocalCombo->loadGroups(0, RsNodeGroupId());
|
||||
mRadioPublic->setChecked(true);
|
||||
updateCircleOptions();
|
||||
|
||||
if (mEditMode) {
|
||||
setWindowTitle(tr("Calendar Properties"));
|
||||
// Load existing calendar info
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
CalendarInfo existingCal;
|
||||
bool found = false;
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == mCalId) {
|
||||
existingCal = c;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
mNameEdit->setText(existingCal.name);
|
||||
mSelectedColor = existingCal.color;
|
||||
mRadioNetwork->setChecked(existingCal.onNetwork);
|
||||
mRadioComputer->setChecked(!existingCal.onNetwork);
|
||||
|
||||
// GXS fields loading if calendar is on network
|
||||
RsGxsId authorId;
|
||||
int idxGxs = existingCal.email.lastIndexOf('@');
|
||||
int endIdxGxs = existingCal.email.lastIndexOf('>');
|
||||
if (idxGxs != -1 && endIdxGxs != -1 && endIdxGxs > idxGxs) {
|
||||
std::string gxsIdStr = existingCal.email.mid(idxGxs + 1, endIdxGxs - idxGxs - 1).toStdString();
|
||||
authorId = RsGxsId(gxsIdStr);
|
||||
}
|
||||
mIdChooser->loadIds(0, authorId);
|
||||
|
||||
mDescEdit->setPlainText(existingCal.description);
|
||||
|
||||
if (existingCal.circleType == GXS_CIRCLE_TYPE_PUBLIC) {
|
||||
mRadioPublic->setChecked(true);
|
||||
} else if (existingCal.circleType == GXS_CIRCLE_TYPE_EXTERNAL) {
|
||||
mRadioCircle->setChecked(true);
|
||||
} else if (existingCal.circleType == GXS_CIRCLE_TYPE_YOUR_FRIENDS_ONLY) {
|
||||
mRadioNodeGroup->setChecked(true);
|
||||
} else {
|
||||
mRadioPublic->setChecked(true);
|
||||
}
|
||||
|
||||
RsGxsCircleId cid(existingCal.circleId.toStdString());
|
||||
mCircleCombo->loadCircles(cid);
|
||||
|
||||
RsNodeGroupId ngi(existingCal.internalCircle.toStdString());
|
||||
mLocalCombo->loadGroups(0, ngi);
|
||||
|
||||
updateCircleOptions();
|
||||
|
||||
// Disable distribution and description controls for non-admin users
|
||||
// owner == "local" means the current user created (and administers) this calendar
|
||||
bool isAdmin = (existingCal.owner == "local");
|
||||
if (existingCal.onNetwork && !isAdmin) {
|
||||
mIdChooser->setEnabled(false);
|
||||
mRadioPublic->setEnabled(false);
|
||||
mRadioCircle->setEnabled(false);
|
||||
mRadioNodeGroup->setEnabled(false);
|
||||
mDescEdit->setReadOnly(true);
|
||||
mDescEdit->setEnabled(false);
|
||||
mNameEdit->setEnabled(false);
|
||||
}
|
||||
}
|
||||
updateColorButton();
|
||||
mStackedWidget->setCurrentWidget(mPage2);
|
||||
updatePage2Layout();
|
||||
} else {
|
||||
setWindowTitle(tr("Create New Calendar"));
|
||||
mStackedWidget->setCurrentWidget(mPage1);
|
||||
}
|
||||
}
|
||||
|
||||
CalendarPropertiesDialog::~CalendarPropertiesDialog() {}
|
||||
|
||||
void CalendarPropertiesDialog::setupUi() {
|
||||
// Set fixed size matching the mockup aspect ratio
|
||||
setMinimumSize(480, 360);
|
||||
resize(500, 380);
|
||||
|
||||
QVBoxLayout* dialogLayout = new QVBoxLayout(this);
|
||||
dialogLayout->setContentsMargins(15, 15, 15, 15);
|
||||
dialogLayout->setSpacing(15);
|
||||
|
||||
mStackedWidget = new QStackedWidget(this);
|
||||
|
||||
// ================= PAGE 1 (Wizard Step 1) =================
|
||||
mPage1 = new QWidget(this);
|
||||
QVBoxLayout* page1Layout = new QVBoxLayout(mPage1);
|
||||
page1Layout->setContentsMargins(5, 5, 5, 5);
|
||||
page1Layout->setSpacing(15);
|
||||
|
||||
QLabel* descLabel = new QLabel(
|
||||
tr("Your calendar can be stored on your computer or share it with your friends or co-workers."),
|
||||
mPage1
|
||||
);
|
||||
descLabel->setWordWrap(true);
|
||||
descLabel->setStyleSheet("font-size: 13px; line-height: 1.4;");
|
||||
page1Layout->addWidget(descLabel);
|
||||
|
||||
mRadioComputer = new QRadioButton(tr("On My Computer"), mPage1);
|
||||
mRadioComputer->setChecked(true);
|
||||
mRadioComputer->setStyleSheet("font-size: 13px; font-weight: bold; padding: 5px;");
|
||||
page1Layout->addWidget(mRadioComputer);
|
||||
|
||||
mRadioNetwork = new QRadioButton(tr("On the Network"), mPage1);
|
||||
mRadioNetwork->setStyleSheet("font-size: 13px; font-weight: bold; padding: 5px;");
|
||||
page1Layout->addWidget(mRadioNetwork);
|
||||
|
||||
mRadioImport = new QRadioButton(tr("Import a calendar from an iCalendar (.ics) file"), mPage1);
|
||||
mRadioImport->setStyleSheet("font-size: 13px; font-weight: bold; padding: 5px;");
|
||||
page1Layout->addWidget(mRadioImport);
|
||||
|
||||
page1Layout->addStretch();
|
||||
mStackedWidget->addWidget(mPage1);
|
||||
|
||||
// ================= PAGE 2 (Wizard Step 2) =================
|
||||
mPage2 = new QWidget(this);
|
||||
QVBoxLayout* page2Layout = new QVBoxLayout(mPage2);
|
||||
page2Layout->setContentsMargins(5, 5, 5, 5);
|
||||
page2Layout->setSpacing(15);
|
||||
|
||||
mFormLayout = new QFormLayout();
|
||||
mFormLayout->setSpacing(12);
|
||||
mFormLayout->setLabelAlignment(Qt::AlignRight);
|
||||
|
||||
mNameEdit = new QLineEdit(mPage2);
|
||||
mNameEdit->setMinimumHeight(26);
|
||||
mFormLayout->addRow(tr("Calendar Name:"), mNameEdit);
|
||||
|
||||
mColorBtn = new QPushButton(mPage2);
|
||||
mColorBtn->setFixedWidth(80);
|
||||
mColorBtn->setCursor(Qt::PointingHandCursor);
|
||||
updateColorButton();
|
||||
connect(mColorBtn, SIGNAL(clicked()), this, SLOT(onSelectColor()));
|
||||
mFormLayout->addRow(tr("Colour:"), mColorBtn);
|
||||
|
||||
mIdChooser = new GxsIdChooser(mPage2);
|
||||
mFormLayout->addRow(tr("Owner:"), mIdChooser);
|
||||
|
||||
page2Layout->addLayout(mFormLayout);
|
||||
|
||||
// Message Distribution group box
|
||||
mDistribGroupBox = new QGroupBox(tr("Calendar Distribution"), mPage2);
|
||||
QVBoxLayout* distribLayout = new QVBoxLayout(mDistribGroupBox);
|
||||
distribLayout->setContentsMargins(10, 10, 10, 10);
|
||||
distribLayout->setSpacing(8);
|
||||
|
||||
QHBoxLayout* radioLayout = new QHBoxLayout();
|
||||
mRadioPublic = new QRadioButton(tr("Public"), mDistribGroupBox);
|
||||
mRadioCircle = new QRadioButton(tr("Restricted to Circle"), mDistribGroupBox);
|
||||
mRadioNodeGroup = new QRadioButton(tr("Restricted node group"), mDistribGroupBox);
|
||||
|
||||
mRadioPublic->setChecked(true);
|
||||
radioLayout->addWidget(mRadioPublic);
|
||||
radioLayout->addWidget(mRadioCircle);
|
||||
radioLayout->addWidget(mRadioNodeGroup);
|
||||
distribLayout->addLayout(radioLayout);
|
||||
|
||||
mCircleCombo = new GxsCircleChooser(mDistribGroupBox);
|
||||
mCircleCombo->setMinimumHeight(26);
|
||||
mLocalCombo = new GroupChooser(mDistribGroupBox);
|
||||
mLocalCombo->setMinimumHeight(26);
|
||||
|
||||
distribLayout->addWidget(mCircleCombo);
|
||||
distribLayout->addWidget(mLocalCombo);
|
||||
|
||||
page2Layout->addWidget(mDistribGroupBox);
|
||||
|
||||
mDescLabel = new QLabel(tr("Description"), mPage2);
|
||||
mDescEdit = new QPlainTextEdit(mPage2);
|
||||
mDescEdit->setPlaceholderText(tr("Set a descriptive description here"));
|
||||
mDescEdit->setMaximumHeight(80);
|
||||
|
||||
page2Layout->addWidget(mDescLabel);
|
||||
page2Layout->addWidget(mDescEdit);
|
||||
|
||||
page2Layout->addStretch();
|
||||
mStackedWidget->addWidget(mPage2);
|
||||
|
||||
dialogLayout->addWidget(mStackedWidget);
|
||||
|
||||
// ================= BUTTONS ROW =================
|
||||
QHBoxLayout* buttonLayout = new QHBoxLayout();
|
||||
mBackBtn = new QPushButton(tr("Back"), this);
|
||||
mNextBtn = new QPushButton(tr("Next"), this);
|
||||
mCreateOrSaveBtn = new QPushButton(mEditMode ? tr("OK") : tr("Create Calendar"), this);
|
||||
mCancelBtn = new QPushButton(tr("Cancel"), this);
|
||||
|
||||
connect(mRadioPublic, SIGNAL(clicked()), this, SLOT(updateCircleOptions()));
|
||||
connect(mRadioCircle, SIGNAL(clicked()), this, SLOT(updateCircleOptions()));
|
||||
connect(mRadioNodeGroup, SIGNAL(clicked()), this, SLOT(updateCircleOptions()));
|
||||
|
||||
connect(mBackBtn, SIGNAL(clicked()), this, SLOT(onBack()));
|
||||
connect(mNextBtn, SIGNAL(clicked()), this, SLOT(onNext()));
|
||||
connect(mCreateOrSaveBtn, SIGNAL(clicked()), this, SLOT(onAccept()));
|
||||
connect(mCancelBtn, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
|
||||
// Layout buttons correctly
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->addWidget(mBackBtn);
|
||||
buttonLayout->addWidget(mNextBtn);
|
||||
buttonLayout->addWidget(mCreateOrSaveBtn);
|
||||
buttonLayout->addWidget(mCancelBtn);
|
||||
dialogLayout->addLayout(buttonLayout);
|
||||
|
||||
// Update buttons visibility depending on state
|
||||
if (mEditMode) {
|
||||
mBackBtn->hide();
|
||||
mNextBtn->hide();
|
||||
} else {
|
||||
mBackBtn->hide();
|
||||
mCreateOrSaveBtn->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::updateColorButton() {
|
||||
mColorBtn->setStyleSheet(QString(
|
||||
"background-color: %1; border: 1px solid #ababab; border-radius: 3px; min-height: 20px;"
|
||||
).arg(mSelectedColor.name()));
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::onNext() {
|
||||
if (mRadioImport && mRadioImport->isChecked()) {
|
||||
accept();
|
||||
return;
|
||||
}
|
||||
mStackedWidget->setCurrentWidget(mPage2);
|
||||
mBackBtn->show();
|
||||
mCreateOrSaveBtn->show();
|
||||
mNextBtn->hide();
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::onBack() {
|
||||
mStackedWidget->setCurrentWidget(mPage1);
|
||||
mBackBtn->hide();
|
||||
mCreateOrSaveBtn->hide();
|
||||
mNextBtn->show();
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::onSelectColor() {
|
||||
QColor color = QColorDialog::getColor(mSelectedColor, this, tr("Select Calendar Color"));
|
||||
if (color.isValid()) {
|
||||
mSelectedColor = color;
|
||||
updateColorButton();
|
||||
}
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::onAccept() {
|
||||
QString name = mNameEdit->text().trimmed();
|
||||
if (name.isEmpty()) {
|
||||
QMessageBox::warning(this, tr("Invalid Name"), tr("Please enter a name for the calendar."));
|
||||
return;
|
||||
}
|
||||
|
||||
CalendarInfo info = getCalendarInfo();
|
||||
|
||||
if (info.onNetwork && rsGxsCalendar && info.owner == "local") {
|
||||
// Extract GXS ID from email
|
||||
RsGxsId authorId;
|
||||
int idx = info.email.lastIndexOf('@');
|
||||
int endIdx = info.email.lastIndexOf('>');
|
||||
if (idx != -1 && endIdx != -1 && endIdx > idx) {
|
||||
std::string gxsIdStr = info.email.mid(idx + 1, endIdx - idx - 1).toStdString();
|
||||
authorId = RsGxsId(gxsIdStr);
|
||||
}
|
||||
|
||||
std::string errMsg;
|
||||
std::string descStr = info.description.toStdString();
|
||||
RsGxsCircleId circleId(info.circleId.toStdString());
|
||||
RsGxsCircleId internalCircle(info.internalCircle.toStdString());
|
||||
|
||||
if (mEditMode) {
|
||||
RsGxsGroupId groupId(info.id.toStdString());
|
||||
if (rsGxsCalendar->updateCalendar(groupId, info.name.toStdString(), descStr, authorId, info.circleType, circleId, internalCircle, info.groupFlags, errMsg)) {
|
||||
CalendarData::instance()->updateCalendar(info);
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("GXS Error"), tr("Failed to update network calendar: %1").arg(QString::fromStdString(errMsg)));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
RsGxsGroupId groupId;
|
||||
if (rsGxsCalendar->createCalendar(info.name.toStdString(), descStr, authorId, info.circleType, circleId, internalCircle, info.groupFlags, groupId, errMsg)) {
|
||||
info.id = QString::fromStdString(groupId.toStdString());
|
||||
rsGxsCalendar->subscribeToCalendar(groupId, true, errMsg);
|
||||
CalendarData::instance()->addCalendar(info);
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("GXS Error"), tr("Failed to create network calendar: %1").arg(QString::fromStdString(errMsg)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mEditMode) {
|
||||
CalendarData::instance()->updateCalendar(info);
|
||||
} else {
|
||||
CalendarData::instance()->addCalendar(info);
|
||||
}
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
CalendarInfo CalendarPropertiesDialog::getCalendarInfo() const {
|
||||
CalendarInfo info;
|
||||
info.id = mCalId.isEmpty() ? QUuid::createUuid().toString(QUuid::WithoutBraces) : mCalId;
|
||||
info.name = mNameEdit->text().trimmed();
|
||||
info.color = mSelectedColor;
|
||||
info.onNetwork = mRadioNetwork->isChecked();
|
||||
info.isPublic = info.onNetwork;
|
||||
|
||||
// Preserve owner and defaults if in edit mode
|
||||
info.owner = "local";
|
||||
info.showReminders = true;
|
||||
info.email = "";
|
||||
if (mEditMode) {
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == mCalId) {
|
||||
info.owner = c.owner;
|
||||
info.showReminders = c.showReminders;
|
||||
info.email = c.email;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.circleType = GXS_CIRCLE_TYPE_PUBLIC;
|
||||
info.circleId = "";
|
||||
info.internalCircle = "";
|
||||
info.groupFlags = GXS_SERV::FLAG_PRIVACY_PUBLIC;
|
||||
info.description = "";
|
||||
|
||||
if (info.onNetwork) {
|
||||
RsGxsId authorId;
|
||||
if (mIdChooser->getChosenId(authorId) == GxsIdChooser::KnowId) {
|
||||
std::string nickname = "";
|
||||
if (rsIdentity) {
|
||||
RsIdentityDetails details;
|
||||
if (rsIdentity->getIdDetails(authorId, details)) {
|
||||
nickname = details.mNickname;
|
||||
}
|
||||
}
|
||||
if (!nickname.empty()) {
|
||||
info.email = QString("%1 <%1@%2>").arg(QString::fromStdString(nickname)).arg(QString::fromStdString(authorId.toStdString()));
|
||||
} else {
|
||||
info.email = QString("<%1@%1>").arg(QString::fromStdString(authorId.toStdString()));
|
||||
}
|
||||
} else {
|
||||
info.email = "";
|
||||
}
|
||||
|
||||
if (mRadioPublic->isChecked()) {
|
||||
info.circleType = GXS_CIRCLE_TYPE_PUBLIC;
|
||||
info.groupFlags = GXS_SERV::FLAG_PRIVACY_PUBLIC;
|
||||
} else if (mRadioCircle->isChecked()) {
|
||||
info.circleType = GXS_CIRCLE_TYPE_EXTERNAL;
|
||||
RsGxsCircleId cid;
|
||||
mCircleCombo->getChosenCircle(cid);
|
||||
info.circleId = QString::fromStdString(cid.toStdString());
|
||||
info.groupFlags = GXS_SERV::FLAG_PRIVACY_RESTRICTED;
|
||||
} else if (mRadioNodeGroup->isChecked()) {
|
||||
info.circleType = GXS_CIRCLE_TYPE_YOUR_FRIENDS_ONLY;
|
||||
RsNodeGroupId ngi;
|
||||
mLocalCombo->getChosenGroup(ngi);
|
||||
info.internalCircle = QString::fromStdString(ngi.toStdString());
|
||||
info.groupFlags = GXS_SERV::FLAG_PRIVACY_PRIVATE;
|
||||
}
|
||||
info.description = mDescEdit->toPlainText();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
bool CalendarPropertiesDialog::isImportMode() const {
|
||||
return mRadioImport && mRadioImport->isChecked();
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::updateCircleOptions() {
|
||||
mCircleCombo->setVisible(mRadioCircle->isChecked());
|
||||
mLocalCombo->setVisible(mRadioNodeGroup->isChecked());
|
||||
}
|
||||
|
||||
void CalendarPropertiesDialog::updatePage2Layout() {
|
||||
bool onNetwork = mRadioNetwork->isChecked();
|
||||
|
||||
mIdChooser->setVisible(onNetwork);
|
||||
if (QWidget* lbl = mFormLayout->labelForField(mIdChooser)) {
|
||||
lbl->setVisible(onNetwork);
|
||||
}
|
||||
|
||||
mDistribGroupBox->setVisible(onNetwork);
|
||||
mDescLabel->setVisible(onNetwork);
|
||||
mDescEdit->setVisible(onNetwork);
|
||||
|
||||
updateCircleOptions();
|
||||
}
|
||||
99
retroshare-gui/src/gui/calendar/CalendarPropertiesDialog.h
Normal file
99
retroshare-gui/src/gui/calendar/CalendarPropertiesDialog.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/CalendarPropertiesDialog.h *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CALENDARPROPERTIESDIALOG_H
|
||||
#define CALENDARPROPERTIESDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QColor>
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
|
||||
class QStackedWidget;
|
||||
class QRadioButton;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class GxsIdChooser;
|
||||
class GxsCircleChooser;
|
||||
class GroupChooser;
|
||||
class QGroupBox;
|
||||
class QPlainTextEdit;
|
||||
class QLabel;
|
||||
class QFormLayout;
|
||||
|
||||
class CalendarPropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalendarPropertiesDialog(const QString& calId = "", QWidget* parent = nullptr);
|
||||
~CalendarPropertiesDialog();
|
||||
|
||||
CalendarInfo getCalendarInfo() const;
|
||||
bool isImportMode() const;
|
||||
|
||||
private slots:
|
||||
void onNext();
|
||||
void onBack();
|
||||
void onSelectColor();
|
||||
void onAccept();
|
||||
void updateCircleOptions();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
void updateColorButton();
|
||||
void updatePage2Layout();
|
||||
|
||||
QString mCalId;
|
||||
bool mEditMode;
|
||||
QColor mSelectedColor;
|
||||
|
||||
QStackedWidget* mStackedWidget;
|
||||
QWidget* mPage1;
|
||||
QWidget* mPage2;
|
||||
|
||||
// Page 1 widgets
|
||||
QRadioButton* mRadioComputer;
|
||||
QRadioButton* mRadioNetwork;
|
||||
QRadioButton* mRadioImport;
|
||||
|
||||
// Page 2 widgets
|
||||
QFormLayout* mFormLayout;
|
||||
QLineEdit* mNameEdit;
|
||||
QPushButton* mColorBtn;
|
||||
|
||||
// GXS network calendar widgets
|
||||
GxsIdChooser* mIdChooser;
|
||||
QGroupBox* mDistribGroupBox;
|
||||
QRadioButton* mRadioPublic;
|
||||
QRadioButton* mRadioCircle;
|
||||
QRadioButton* mRadioNodeGroup;
|
||||
GxsCircleChooser* mCircleCombo;
|
||||
GroupChooser* mLocalCombo;
|
||||
QLabel* mDescLabel;
|
||||
QPlainTextEdit* mDescEdit;
|
||||
|
||||
// Buttons
|
||||
QPushButton* mNextBtn;
|
||||
QPushButton* mBackBtn;
|
||||
QPushButton* mCreateOrSaveBtn;
|
||||
QPushButton* mCancelBtn;
|
||||
};
|
||||
|
||||
#endif // CALENDARPROPERTIESDIALOG_H
|
||||
1323
retroshare-gui/src/gui/calendar/CalendarWidget.cpp
Normal file
1323
retroshare-gui/src/gui/calendar/CalendarWidget.cpp
Normal file
File diff suppressed because it is too large
Load Diff
120
retroshare-gui/src/gui/calendar/CalendarWidget.h
Normal file
120
retroshare-gui/src/gui/calendar/CalendarWidget.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/CalendarWidget.h *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#ifndef CALENDARWIDGET_H
|
||||
#define CALENDARWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QDate>
|
||||
#include <QMap>
|
||||
#include <QStyledItemDelegate>
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
#include "ui_CalendarWidget.h"
|
||||
|
||||
class QListWidgetItem;
|
||||
class QLabel;
|
||||
class QComboBox;
|
||||
class CalendarWidget;
|
||||
|
||||
class MonthCalendarDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MonthCalendarDelegate(CalendarWidget* parent);
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
private:
|
||||
CalendarWidget* mCalendarWidget;
|
||||
};
|
||||
|
||||
class CalendarWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalendarWidget(QWidget* parent = nullptr);
|
||||
~CalendarWidget();
|
||||
|
||||
QDate selectedDate() const { return mSelectedDate; }
|
||||
|
||||
public slots:
|
||||
void refreshData();
|
||||
|
||||
private slots:
|
||||
void onNewEvent();
|
||||
void onNewCalendar();
|
||||
void onPrevPeriod();
|
||||
void onNextPeriod();
|
||||
void onToday();
|
||||
void onViewChanged(int index);
|
||||
void onDateSelected(const QDate& date);
|
||||
void onEventSelected(int row, int col);
|
||||
void onCalendarSelectionChanged(QListWidgetItem* item);
|
||||
void onSharedCalendarSelectionChanged(QListWidgetItem* item);
|
||||
void onSearchChanged(const QString& text);
|
||||
void onCalendarContextMenu(const QPoint& pos);
|
||||
void onSharedCalendarContextMenu(const QPoint& pos);
|
||||
void onEventTableContextMenu(const QPoint& pos);
|
||||
void onMonthCellClicked(int row, int col);
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
void updateViews();
|
||||
void updateDayView();
|
||||
void updateWeekView();
|
||||
void updateMonthView();
|
||||
void updateEventList();
|
||||
void exportCalendar(const QString& calId, const QString& calName);
|
||||
void importCalendar();
|
||||
|
||||
QDate mSelectedDate;
|
||||
int mCurrentViewMode; // 0=Day, 1=Week, 2=Month
|
||||
QString mSearchText;
|
||||
int mCalendarListMode; // 0=My Calendars, 1=Shared Calendars
|
||||
bool mInitialLoadDone;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
// UI elements (now loaded from UI file but kept as pointers for compatibility)
|
||||
QCalendarWidget* mSidebarCalendar;
|
||||
QListWidget* mCalendarList;
|
||||
QListWidget* mSharedCalendarList;
|
||||
|
||||
QLabel* mPeriodLabel;
|
||||
QLabel* mCwLabel;
|
||||
QLineEdit* mSearchEdit;
|
||||
|
||||
QTableWidget* mEventTable; // Upcoming events list at top
|
||||
QStackedWidget* mViewStack;
|
||||
|
||||
// Day View components
|
||||
QTableWidget* mDayTable;
|
||||
|
||||
// Week View components
|
||||
QTableWidget* mWeekTable;
|
||||
|
||||
// Month View components
|
||||
QTableWidget* mMonthTable;
|
||||
|
||||
// Cached event IDs for grids
|
||||
QMap<QString, QString> mCellEventMap; // "viewMode_row_col" -> Event ID
|
||||
|
||||
Ui::CalendarWidget ui;
|
||||
};
|
||||
|
||||
#endif // CALENDARWIDGET_H
|
||||
322
retroshare-gui/src/gui/calendar/CalendarWidget.ui
Normal file
322
retroshare-gui/src/gui/calendar/CalendarWidget.ui
Normal file
@ -0,0 +1,322 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CalendarWidget</class>
|
||||
<widget class="QWidget" name="CalendarWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>833</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="mainLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="handleWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="sidebar" native="true">
|
||||
<layout class="QVBoxLayout" name="sidebarLayout">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="newEventBtn">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; background-color: #4a90e2; color: white; border-radius: 4px; padding: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+ New Event</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCalendarWidget" name="sidebarCalendar">
|
||||
<property name="gridVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="horizontalHeaderFormat">
|
||||
<enum>QCalendarWidget::SingleLetterDayNames</enum>
|
||||
</property>
|
||||
<property name="verticalHeaderFormat">
|
||||
<enum>QCalendarWidget::NoVerticalHeader</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="calendarsLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>My Calendars</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="calendarList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="sharedCalendarsLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 14px; margin-top: 10px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shared Calendars</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="sharedCalendarList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="newCalBtn">
|
||||
<property name="text">
|
||||
<string>New Calendar...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mainArea" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="topControlLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="prevBtn">
|
||||
<property name="text">
|
||||
<string><</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="todayBtn">
|
||||
<property name="text">
|
||||
<string>Today</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="nextBtn">
|
||||
<property name="text">
|
||||
<string>></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="periodLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 16px; margin-left: 10px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="topControlSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search events...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="dayViewBtn">
|
||||
<property name="text">
|
||||
<string>Day</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="weekViewBtn">
|
||||
<property name="text">
|
||||
<string>Week</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="monthViewBtn">
|
||||
<property name="text">
|
||||
<string>Month</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTableWidget" name="eventTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStackedWidget" name="viewStack">
|
||||
<widget class="QWidget" name="dayPage">
|
||||
<layout class="QVBoxLayout" name="dayPageLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="dayTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="weekPage">
|
||||
<layout class="QVBoxLayout" name="weekPageLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="weekTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="monthPage">
|
||||
<layout class="QVBoxLayout" name="monthPageLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="monthTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
805
retroshare-gui/src/gui/calendar/EventDialog.cpp
Normal file
805
retroshare-gui/src/gui/calendar/EventDialog.cpp
Normal file
@ -0,0 +1,805 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/EventDialog.cpp *
|
||||
* *
|
||||
* Copyright (C) 2026 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#include "gui/calendar/EventDialog.h"
|
||||
#include <retroshare/rsgxscalendar.h>
|
||||
#include "retroshare/rsgxsflags.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QTextEdit>
|
||||
#include <QListWidget>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
#include "gui/gxs/GxsIdTreeWidgetItem.h"
|
||||
#include "gui/gxs/GxsIdDetails.h"
|
||||
#include <QPushButton>
|
||||
#include <QTabWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QUuid>
|
||||
#include <QFileDialog>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QMenu>
|
||||
#include <QDir>
|
||||
#include "gui/RetroShareLink.h"
|
||||
#include "gui/common/FriendSelectionWidget.h"
|
||||
#include <QDialogButtonBox>
|
||||
#include <retroshare/rsidentity.h>
|
||||
#include <retroshare/rspeers.h>
|
||||
#include <retroshare/rsmail.h>
|
||||
#include "gui/common/PeerDefs.h"
|
||||
#include <QCoreApplication>
|
||||
#include "gui/common/AvatarDefs.h"
|
||||
#include <QPixmap>
|
||||
#include <QIcon>
|
||||
|
||||
namespace {
|
||||
QString getContactName(const QString& idStr) {
|
||||
std::string str = idStr.toStdString();
|
||||
if (str.length() == 16) {
|
||||
RsPgpId pgpId(str);
|
||||
QString name;
|
||||
PeerDefs::rsidFromId(pgpId, &name);
|
||||
return name;
|
||||
} else if (str.length() == 32) {
|
||||
RsGxsId gxsId(str);
|
||||
RsIdentityDetails details;
|
||||
if (rsIdentity && rsIdentity->getIdDetails(gxsId, details)) {
|
||||
return QString::fromUtf8(details.mNickname.c_str());
|
||||
}
|
||||
RsPeerId peerId(str);
|
||||
std::string peerName = rsPeers ? rsPeers->getPeerName(peerId) : "";
|
||||
if (!peerName.empty()) {
|
||||
return QString::fromUtf8(peerName.c_str());
|
||||
}
|
||||
QString name;
|
||||
PeerDefs::rsidFromId(peerId, &name);
|
||||
if (name != QCoreApplication::translate("PeerDefs", "Unknown")) {
|
||||
return name;
|
||||
}
|
||||
PeerDefs::rsidFromId(gxsId, &name);
|
||||
return name;
|
||||
}
|
||||
return idStr;
|
||||
}
|
||||
|
||||
QIcon getContactAvatar(const QString& idStr) {
|
||||
std::string str = idStr.toStdString();
|
||||
QPixmap pixmap;
|
||||
if (str.length() == 16) {
|
||||
AvatarDefs::getAvatarFromGpgId(RsPgpId(str), pixmap);
|
||||
} else if (str.length() == 32) {
|
||||
RsGxsId gxsId(str);
|
||||
RsIdentityDetails details;
|
||||
if (rsIdentity && rsIdentity->getIdDetails(gxsId, details)) {
|
||||
AvatarDefs::getAvatarFromGxsId(gxsId, pixmap);
|
||||
} else {
|
||||
AvatarDefs::getAvatarFromSslId(RsPeerId(str), pixmap);
|
||||
}
|
||||
}
|
||||
if (pixmap.isNull()) {
|
||||
pixmap = QPixmap(AVATAR_DEFAULT_IMAGE_SQUARE);
|
||||
}
|
||||
return QIcon(pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
EventDialog::EventDialog(const QString& eventId, const QDateTime& startInfo, QWidget* parent, bool readOnly)
|
||||
: QDialog(parent), mEventId(eventId), mDefaultStart(startInfo), mReadOnly(readOnly)
|
||||
{
|
||||
setMinimumSize(500, 600);
|
||||
|
||||
buildUi();
|
||||
loadEvent();
|
||||
updateModeUi();
|
||||
}
|
||||
|
||||
EventDialog::~EventDialog() {}
|
||||
|
||||
void EventDialog::buildUi() {
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(15, 15, 15, 15);
|
||||
mainLayout->setSpacing(10);
|
||||
|
||||
// Top action bar (Save, Invite, Delete)
|
||||
mActionWidget = new QWidget(this);
|
||||
QHBoxLayout* actionLayout = new QHBoxLayout(mActionWidget);
|
||||
actionLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
mSaveBtn = new QPushButton(tr("Save and Close"), this);
|
||||
mSaveBtn->setIcon(QIcon(":/icons/mail/compose.png"));
|
||||
connect(mSaveBtn, SIGNAL(clicked()), this, SLOT(onSaveAndClose()));
|
||||
actionLayout->addWidget(mSaveBtn);
|
||||
|
||||
mInviteBtn = new QPushButton(tr("Invite Attendees"), this);
|
||||
connect(mInviteBtn, SIGNAL(clicked()), this, SLOT(onInviteAttendees()));
|
||||
actionLayout->addWidget(mInviteBtn);
|
||||
|
||||
mDeleteBtn = new QPushButton(tr("Delete"), this);
|
||||
mDeleteBtn->setIcon(QIcon(":/icons/mail/delete.png"));
|
||||
connect(mDeleteBtn, SIGNAL(clicked()), this, SLOT(onDelete()));
|
||||
actionLayout->addWidget(mDeleteBtn);
|
||||
|
||||
if (mEventId.isEmpty()) {
|
||||
mDeleteBtn->setEnabled(false);
|
||||
}
|
||||
|
||||
actionLayout->addStretch();
|
||||
mainLayout->addWidget(mActionWidget);
|
||||
|
||||
// Form inputs layout
|
||||
QFormLayout* formLayout = new QFormLayout();
|
||||
formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
formLayout->setSpacing(8);
|
||||
|
||||
mCalendarCombo = new QComboBox(this);
|
||||
const QList<CalendarInfo>& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& cal : cals) {
|
||||
mCalendarCombo->addItem(cal.name, cal.id);
|
||||
}
|
||||
formLayout->addRow(tr("Calendar:"), mCalendarCombo);
|
||||
|
||||
mTitleEdit = new QLineEdit(this);
|
||||
mTitleEdit->setPlaceholderText(tr("Event Title"));
|
||||
formLayout->addRow(tr("Title:"), mTitleEdit);
|
||||
|
||||
mLocationEdit = new QLineEdit(this);
|
||||
mLocationEdit->setPlaceholderText(tr("Location"));
|
||||
formLayout->addRow(tr("Location:"), mLocationEdit);
|
||||
|
||||
mCategoryCombo = new QComboBox(this);
|
||||
mCategoryCombo->addItems({tr("None"), tr("Meeting"), tr("Work"), tr("Birthday"), tr("Holiday"), tr("Personal")});
|
||||
formLayout->addRow(tr("Category:"), mCategoryCombo);
|
||||
|
||||
mAllDayCheck = new QCheckBox(tr("All day Event"), this);
|
||||
connect(mAllDayCheck, SIGNAL(toggled(bool)), this, SLOT(onAllDayToggled(bool)));
|
||||
formLayout->addRow(QString(), mAllDayCheck);
|
||||
|
||||
mStartEdit = new QDateTimeEdit(mDefaultStart, this);
|
||||
mStartEdit->setCalendarPopup(true);
|
||||
formLayout->addRow(tr("Start:"), mStartEdit);
|
||||
|
||||
mEndEdit = new QDateTimeEdit(mDefaultStart.addSecs(3600), this);
|
||||
mEndEdit->setCalendarPopup(true);
|
||||
formLayout->addRow(tr("End:"), mEndEdit);
|
||||
|
||||
mRepeatCombo = new QComboBox(this);
|
||||
mRepeatCombo->addItems({tr("Does not repeat"), tr("Daily"), tr("Weekly"), tr("Monthly"), tr("Yearly")});
|
||||
formLayout->addRow(tr("Repeat:"), mRepeatCombo);
|
||||
|
||||
mReminderCombo = new QComboBox(this);
|
||||
mReminderCombo->addItems({tr("No reminder"), tr("5 minutes before"), tr("15 minutes before"), tr("1 hour before"), tr("1 day before")});
|
||||
formLayout->addRow(tr("Reminder:"), mReminderCombo);
|
||||
|
||||
mainLayout->addLayout(formLayout);
|
||||
|
||||
// Tab Widget for Description & Attendees & Attachments
|
||||
QTabWidget* tabWidget = new QTabWidget(this);
|
||||
|
||||
// Description Tab
|
||||
mDescriptionEdit = new QTextEdit(this);
|
||||
tabWidget->addTab(mDescriptionEdit, tr("Description"));
|
||||
|
||||
// Attendees Tab
|
||||
mAttendeesList = new QTreeWidget(this);
|
||||
mAttendeesList->setHeaderHidden(true);
|
||||
mAttendeesList->setIconSize(QSize(32, 32));
|
||||
mAttendeesList->setRootIsDecorated(false);
|
||||
tabWidget->addTab(mAttendeesList, tr("Attendees"));
|
||||
|
||||
// Attachments Tab
|
||||
QWidget* attachTab = new QWidget(this);
|
||||
QVBoxLayout* attachLayout = new QVBoxLayout(attachTab);
|
||||
mAttachmentsList = new QListWidget(this);
|
||||
mAttachmentsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(mAttachmentsList, &QListWidget::customContextMenuRequested, [this](const QPoint& pos) {
|
||||
QListWidgetItem* item = mAttachmentsList->itemAt(pos);
|
||||
if (!item) return;
|
||||
|
||||
QMenu menu(this);
|
||||
QAction* downloadAction = menu.addAction(QIcon(":/icons/png/download.png"), tr("Download"));
|
||||
QAction* downloadAllAction = menu.addAction(QIcon(":/icons/mail/downloadall.png"), tr("Download all"));
|
||||
QAction* removeAction = nullptr;
|
||||
|
||||
if (!mReadOnly) {
|
||||
menu.addSeparator();
|
||||
removeAction = menu.addAction(QIcon(":/icons/mail/delete.png"), tr("Remove Attachment"));
|
||||
}
|
||||
|
||||
QAction* selectedAction = menu.exec(mAttachmentsList->mapToGlobal(pos));
|
||||
if (selectedAction == downloadAction) {
|
||||
QString att = item->data(Qt::UserRole).toString();
|
||||
if (!att.isEmpty()) {
|
||||
RetroShareLink link(att);
|
||||
if (link.valid() && (link.type() == RetroShareLink::TYPE_FILE || link.type() == RetroShareLink::TYPE_FILE_TREE)) {
|
||||
QList<RetroShareLink> links;
|
||||
links.append(link);
|
||||
RetroShareLink::process(links);
|
||||
} else if (QFileInfo::exists(att)) {
|
||||
QString targetPath = QFileDialog::getSaveFileName(this, tr("Save Attachment As"), QFileInfo(att).fileName());
|
||||
if (!targetPath.isEmpty()) {
|
||||
if (QFile::exists(targetPath)) {
|
||||
QFile::remove(targetPath);
|
||||
}
|
||||
if (!QFile::copy(att, targetPath)) {
|
||||
QMessageBox::warning(this, tr("Download Failed"), tr("Could not save the file to %1").arg(targetPath));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QUrl url(att);
|
||||
if (!url.scheme().isEmpty()) {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (selectedAction == downloadAllAction) {
|
||||
QList<RetroShareLink> rsLinks;
|
||||
QStringList localFiles;
|
||||
for (int i = 0; i < mAttachmentsList->count(); ++i) {
|
||||
QString att = mAttachmentsList->item(i)->data(Qt::UserRole).toString();
|
||||
if (att.isEmpty()) continue;
|
||||
RetroShareLink link(att);
|
||||
if (link.valid() && (link.type() == RetroShareLink::TYPE_FILE || link.type() == RetroShareLink::TYPE_FILE_TREE)) {
|
||||
rsLinks.append(link);
|
||||
} else if (QFileInfo::exists(att)) {
|
||||
localFiles.append(att);
|
||||
} else {
|
||||
QUrl url(att);
|
||||
if (!url.scheme().isEmpty()) {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rsLinks.isEmpty()) {
|
||||
RetroShareLink::process(rsLinks);
|
||||
}
|
||||
if (!localFiles.isEmpty()) {
|
||||
QString targetDir = QFileDialog::getExistingDirectory(this, tr("Select Directory to Save Attachments"));
|
||||
if (!targetDir.isEmpty()) {
|
||||
bool success = true;
|
||||
QStringList failedFiles;
|
||||
for (const QString& file : localFiles) {
|
||||
QFileInfo fi(file);
|
||||
QString targetPath = QDir(targetDir).filePath(fi.fileName());
|
||||
if (QFile::exists(targetPath)) {
|
||||
QFile::remove(targetPath);
|
||||
}
|
||||
if (!QFile::copy(file, targetPath)) {
|
||||
success = false;
|
||||
failedFiles.append(fi.fileName());
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
QMessageBox::warning(this, tr("Download Failed"), tr("Could not save the following files: %1").arg(failedFiles.join(", ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (removeAction && selectedAction == removeAction) {
|
||||
delete mAttachmentsList->takeItem(mAttachmentsList->row(item));
|
||||
}
|
||||
});
|
||||
|
||||
connect(mAttachmentsList, &QListWidget::itemDoubleClicked, [](QListWidgetItem* item) {
|
||||
QString pathOrUrl = item->data(Qt::UserRole).toString();
|
||||
if (!pathOrUrl.isEmpty()) {
|
||||
QUrl url(pathOrUrl);
|
||||
if (url.scheme().isEmpty()) {
|
||||
url = QUrl::fromLocalFile(pathOrUrl);
|
||||
}
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
attachLayout->addWidget(mAttachmentsList);
|
||||
mAddAttachBtn = new QPushButton(tr("Attach File..."), this);
|
||||
connect(mAddAttachBtn, &QPushButton::clicked, [this]() {
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select File(s)"));
|
||||
for (const QString& file : files) {
|
||||
if (!file.isEmpty()) {
|
||||
QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), mAttachmentsList);
|
||||
item->setData(Qt::UserRole, file);
|
||||
item->setToolTip(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
attachLayout->addWidget(mAddAttachBtn);
|
||||
tabWidget->addTab(attachTab, tr("Attachments"));
|
||||
|
||||
mainLayout->addWidget(tabWidget);
|
||||
|
||||
// Bottom Options Checkboxes
|
||||
QHBoxLayout* bottomCheckLayout = new QHBoxLayout();
|
||||
mNotifyCheck = new QCheckBox(tr("Notify attendees"), this);
|
||||
mNotifyCheck->setChecked(true);
|
||||
bottomCheckLayout->addWidget(mNotifyCheck);
|
||||
|
||||
mainLayout->addLayout(bottomCheckLayout);
|
||||
|
||||
// Bottom Buttons (Close & Edit for read-only view mode)
|
||||
mBottomButtonsWidget = new QWidget(this);
|
||||
QHBoxLayout* bottomBtnLayout = new QHBoxLayout(mBottomButtonsWidget);
|
||||
bottomBtnLayout->setContentsMargins(0, 0, 0, 0);
|
||||
bottomBtnLayout->addStretch();
|
||||
|
||||
mEditBtn = new QPushButton(tr("Edit"), this);
|
||||
mEditBtn->setStyleSheet("background-color: #0078d4; color: white; border: none; border-radius: 4px; padding: 6px 16px; font-weight: bold;");
|
||||
connect(mEditBtn, SIGNAL(clicked()), this, SLOT(onEditClicked()));
|
||||
bottomBtnLayout->addWidget(mEditBtn);
|
||||
|
||||
mCloseBtn = new QPushButton(tr("Close"), this);
|
||||
connect(mCloseBtn, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
bottomBtnLayout->addWidget(mCloseBtn);
|
||||
|
||||
mainLayout->addWidget(mBottomButtonsWidget);
|
||||
}
|
||||
|
||||
void EventDialog::loadEvent() {
|
||||
if (mEventId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<CalendarEvent>& events = CalendarData::instance()->getEvents();
|
||||
for (const auto& ev : events) {
|
||||
if (ev.id == mEventId) {
|
||||
// Find calendar index
|
||||
int calIdx = mCalendarCombo->findData(ev.calendarId);
|
||||
if (calIdx != -1) mCalendarCombo->setCurrentIndex(calIdx);
|
||||
|
||||
mTitleEdit->setText(ev.title);
|
||||
mLocationEdit->setText(ev.location);
|
||||
mCategoryCombo->setCurrentText(ev.category);
|
||||
mAllDayCheck->setChecked(ev.allDay);
|
||||
mStartEdit->setDateTime(ev.start);
|
||||
mEndEdit->setDateTime(ev.end);
|
||||
mRepeatCombo->setCurrentText(ev.repeat);
|
||||
mReminderCombo->setCurrentText(ev.reminder);
|
||||
mDescriptionEdit->setPlainText(ev.description);
|
||||
|
||||
// Set attendees
|
||||
mAttendeesList->clear();
|
||||
for (const auto& contactId : ev.attendees) {
|
||||
if (contactId.length() == 16) {
|
||||
QString name = getContactName(contactId);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(mAttendeesList);
|
||||
item->setIcon(0, getContactAvatar(contactId));
|
||||
item->setText(0, name);
|
||||
item->setData(0, Qt::UserRole, contactId);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
} else if (contactId.length() == 32) {
|
||||
RsGxsId gxsId(contactId.toStdString());
|
||||
RsPeerId peerId(contactId.toStdString());
|
||||
bool isSsl = false;
|
||||
if (rsPeers) {
|
||||
std::string peerName = rsPeers->getPeerName(peerId);
|
||||
if (!peerName.empty() || rsPeers->isFriend(peerId)) {
|
||||
isSsl = true;
|
||||
}
|
||||
}
|
||||
if (isSsl) {
|
||||
QString name = getContactName(contactId);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(mAttendeesList);
|
||||
item->setIcon(0, getContactAvatar(contactId));
|
||||
item->setText(0, name);
|
||||
item->setData(0, Qt::UserRole, contactId);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
} else {
|
||||
// Treat as GXS ID
|
||||
GxsIdRSTreeWidgetItem* item = new GxsIdRSTreeWidgetItem(nullptr, GxsIdDetails::ICON_TYPE_AVATAR, true, mAttendeesList);
|
||||
item->setData(0, Qt::UserRole, contactId);
|
||||
item->setId(gxsId, 0, true);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load attachments
|
||||
mAttachmentsList->clear();
|
||||
for (const auto& att : ev.attachments) {
|
||||
QListWidgetItem* item = new QListWidgetItem(QFileInfo(att).fileName(), mAttachmentsList);
|
||||
item->setData(Qt::UserRole, att);
|
||||
item->setToolTip(att);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventDialog::onAllDayToggled(bool checked) {
|
||||
if (checked) {
|
||||
mStartEdit->setDisplayFormat("yyyy-MM-dd");
|
||||
mEndEdit->setDisplayFormat("yyyy-MM-dd");
|
||||
} else {
|
||||
mStartEdit->setDisplayFormat("yyyy-MM-dd hh:mm");
|
||||
mEndEdit->setDisplayFormat("yyyy-MM-dd hh:mm");
|
||||
}
|
||||
}
|
||||
|
||||
void EventDialog::onInviteAttendees() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("Invite Attendees"));
|
||||
dialog.resize(this->width(), 500);
|
||||
QVBoxLayout* layout = new QVBoxLayout(&dialog);
|
||||
|
||||
QComboBox* filterCombo = new QComboBox(&dialog);
|
||||
filterCombo->addItem(tr("All people"));
|
||||
filterCombo->addItem(tr("My contacts"));
|
||||
#ifdef RS_DIRECT_CHAT
|
||||
filterCombo->addItem(tr("Friend Nodes"));
|
||||
#endif
|
||||
filterCombo->setCurrentIndex(0);
|
||||
|
||||
FriendSelectionWidget* friendsWidget = new FriendSelectionWidget(&dialog);
|
||||
friendsWidget->setHeaderText(tr("Select contacts to invite:"));
|
||||
friendsWidget->setModus(FriendSelectionWidget::MODUS_CHECK);
|
||||
friendsWidget->setShowType(FriendSelectionWidget::SHOW_GXS);
|
||||
friendsWidget->start();
|
||||
|
||||
connect(filterCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [friendsWidget](int index) {
|
||||
switch (index) {
|
||||
default:
|
||||
case 0:
|
||||
friendsWidget->setShowType(FriendSelectionWidget::SHOW_GXS);
|
||||
break;
|
||||
case 1:
|
||||
friendsWidget->setShowType(FriendSelectionWidget::SHOW_CONTACTS);
|
||||
break;
|
||||
#ifdef RS_DIRECT_CHAT
|
||||
case 2:
|
||||
friendsWidget->setShowType(FriendSelectionWidget::SHOW_SSL);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
});
|
||||
|
||||
// Pre-select current attendees
|
||||
std::set<std::string> psidsGpg;
|
||||
std::set<std::string> psidsGxs;
|
||||
std::set<std::string> psidsSsl;
|
||||
for (int i = 0; i < mAttendeesList->topLevelItemCount(); ++i) {
|
||||
QTreeWidgetItem* item = mAttendeesList->topLevelItem(i);
|
||||
if (item->checkState(0) == Qt::Checked) {
|
||||
std::string idStr = item->data(0, Qt::UserRole).toString().toStdString();
|
||||
if (idStr.length() == 16) {
|
||||
psidsGpg.insert(idStr);
|
||||
} else if (idStr.length() == 32) {
|
||||
RsPeerId peerId(idStr);
|
||||
bool isSsl = false;
|
||||
if (rsPeers) {
|
||||
std::string peerName = rsPeers->getPeerName(peerId);
|
||||
if (!peerName.empty() || rsPeers->isFriend(peerId)) {
|
||||
isSsl = true;
|
||||
}
|
||||
}
|
||||
if (isSsl) {
|
||||
psidsSsl.insert(idStr);
|
||||
} else {
|
||||
psidsGxs.insert(idStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
friendsWidget->setSelectedIdsFromString(FriendSelectionWidget::IDTYPE_GPG, psidsGpg, false);
|
||||
friendsWidget->setSelectedIdsFromString(FriendSelectionWidget::IDTYPE_GXS, psidsGxs, false);
|
||||
friendsWidget->setSelectedIdsFromString(FriendSelectionWidget::IDTYPE_SSL, psidsSsl, false);
|
||||
|
||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
|
||||
layout->addWidget(filterCombo);
|
||||
layout->addWidget(friendsWidget);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
while (dialog.exec() == QDialog::Accepted) {
|
||||
std::set<RsPgpId> selectedGpg;
|
||||
friendsWidget->selectedIds<RsPgpId, FriendSelectionWidget::IDTYPE_GPG>(selectedGpg, false);
|
||||
|
||||
std::set<RsGxsId> selectedGxs;
|
||||
friendsWidget->selectedIds<RsGxsId, FriendSelectionWidget::IDTYPE_GXS>(selectedGxs, false);
|
||||
|
||||
std::set<RsPeerId> selectedSsl;
|
||||
friendsWidget->selectedIds<RsPeerId, FriendSelectionWidget::IDTYPE_SSL>(selectedSsl, false);
|
||||
|
||||
int totalCount = 0;
|
||||
for (const auto& id : selectedGpg) {
|
||||
if (QString::fromStdString(id.toStdString()) != "0000000000000000") totalCount++;
|
||||
}
|
||||
for (const auto& id : selectedGxs) {
|
||||
if (QString::fromStdString(id.toStdString()) != "00000000000000000000000000000000") totalCount++;
|
||||
}
|
||||
for (const auto& id : selectedSsl) {
|
||||
if (QString::fromStdString(id.toStdString()) != "00000000000000000000000000000000") totalCount++;
|
||||
}
|
||||
|
||||
if (totalCount > 20) {
|
||||
QMessageBox::warning(this, tr("Limit Exceeded"), tr("You can select a maximum of 20 attendees. Currently selected: %1").arg(totalCount));
|
||||
continue;
|
||||
}
|
||||
|
||||
mAttendeesList->clear();
|
||||
|
||||
for (const auto& pgpId : selectedGpg) {
|
||||
QString pgpIdStr = QString::fromStdString(pgpId.toStdString());
|
||||
if (pgpIdStr == "0000000000000000") continue;
|
||||
QString name = getContactName(pgpIdStr);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(mAttendeesList);
|
||||
item->setIcon(0, getContactAvatar(pgpIdStr));
|
||||
item->setText(0, name);
|
||||
item->setData(0, Qt::UserRole, pgpIdStr);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
|
||||
for (const auto& gxsId : selectedGxs) {
|
||||
QString gxsIdStr = QString::fromStdString(gxsId.toStdString());
|
||||
if (gxsIdStr == "00000000000000000000000000000000") continue;
|
||||
GxsIdRSTreeWidgetItem* item = new GxsIdRSTreeWidgetItem(nullptr, GxsIdDetails::ICON_TYPE_AVATAR, true, mAttendeesList);
|
||||
item->setData(0, Qt::UserRole, gxsIdStr);
|
||||
item->setId(gxsId, 0, true);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
|
||||
for (const auto& sslId : selectedSsl) {
|
||||
QString sslIdStr = QString::fromStdString(sslId.toStdString());
|
||||
if (sslIdStr == "00000000000000000000000000000000") continue;
|
||||
QString name = getContactName(sslIdStr);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(mAttendeesList);
|
||||
item->setIcon(0, getContactAvatar(sslIdStr));
|
||||
item->setText(0, name);
|
||||
item->setData(0, Qt::UserRole, sslIdStr);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(0, Qt::Checked);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Switch to attendees tab
|
||||
QTabWidget* tabWidget = findChild<QTabWidget*>();
|
||||
if (tabWidget) {
|
||||
tabWidget->setCurrentIndex(1); // Attendees is index 1
|
||||
}
|
||||
}
|
||||
|
||||
void EventDialog::onSaveAndClose() {
|
||||
if (mTitleEdit->text().trimmed().isEmpty()) {
|
||||
QMessageBox::warning(this, tr("Empty Title"), tr("Please provide a title for the event."));
|
||||
return;
|
||||
}
|
||||
|
||||
CalendarEvent ev;
|
||||
ev.id = mEventId.isEmpty() ? QUuid::createUuid().toString(QUuid::WithoutBraces) : mEventId;
|
||||
ev.calendarId = mCalendarCombo->currentData().toString();
|
||||
ev.title = mTitleEdit->text().trimmed();
|
||||
ev.location = mLocationEdit->text().trimmed();
|
||||
ev.category = mCategoryCombo->currentText();
|
||||
ev.allDay = mAllDayCheck->isChecked();
|
||||
ev.start = mStartEdit->dateTime();
|
||||
ev.end = mEndEdit->dateTime();
|
||||
ev.repeat = mRepeatCombo->currentText();
|
||||
ev.reminder = mReminderCombo->currentText();
|
||||
ev.description = mDescriptionEdit->toPlainText();
|
||||
ev.isPublic = true;
|
||||
|
||||
// Get checked attendees
|
||||
QStringList invitedNames;
|
||||
for (int i = 0; i < mAttendeesList->topLevelItemCount(); ++i) {
|
||||
QTreeWidgetItem* item = mAttendeesList->topLevelItem(i);
|
||||
if (item->checkState(0) == Qt::Checked) {
|
||||
ev.attendees.append(item->data(0, Qt::UserRole).toString());
|
||||
invitedNames.append(item->text(0));
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.attendees.size() > 20) {
|
||||
QMessageBox::warning(this, tr("Limit Exceeded"), tr("You can select a maximum of 20 attendees. Please uncheck some."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get attachments
|
||||
for (int i = 0; i < mAttachmentsList->count(); ++i) {
|
||||
ev.attachments.append(mAttachmentsList->item(i)->data(Qt::UserRole).toString());
|
||||
}
|
||||
|
||||
if (mEventId.isEmpty()) {
|
||||
CalendarData::instance()->addEvent(ev);
|
||||
} else {
|
||||
CalendarData::instance()->updateEvent(ev);
|
||||
}
|
||||
|
||||
// Send actual invitations
|
||||
if (mNotifyCheck->isChecked() && !invitedNames.isEmpty()) {
|
||||
sendInvite(ev, invitedNames);
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void EventDialog::onDelete() {
|
||||
if (mEventId.isEmpty()) return;
|
||||
|
||||
if (QMessageBox::question(this, tr("Delete Event"), tr("Are you sure you want to delete this event?")) == QMessageBox::Yes) {
|
||||
CalendarData::instance()->removeEvent(mEventId);
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
void EventDialog::onEditClicked() {
|
||||
mReadOnly = false;
|
||||
updateModeUi();
|
||||
}
|
||||
|
||||
void EventDialog::updateModeUi() {
|
||||
bool canEdit = false;
|
||||
if (mEventId.isEmpty()) {
|
||||
canEdit = true;
|
||||
} else {
|
||||
// Determine if user can edit this event (admin check for shared calendars)
|
||||
QString calendarId;
|
||||
const auto& events = CalendarData::instance()->getEvents();
|
||||
for (const auto& ev : events) {
|
||||
if (ev.id == mEventId) {
|
||||
calendarId = ev.calendarId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!calendarId.isEmpty()) {
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == calendarId) {
|
||||
if (!c.onNetwork) {
|
||||
canEdit = true;
|
||||
} else if (rsGxsCalendar) {
|
||||
std::list<RsGroupMetaData> summaries;
|
||||
if (rsGxsCalendar->getCalendarsSummaries(summaries)) {
|
||||
RsGxsGroupId groupId(calendarId.toStdString());
|
||||
for (const auto& meta : summaries) {
|
||||
if (meta.mGroupId == groupId) {
|
||||
canEdit = IS_GROUP_ADMIN(meta.mSubscribeFlags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
canEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
setWindowTitle(mReadOnly ? tr("View Event") : (mEventId.isEmpty() ? tr("New Event") : tr("Edit Event")));
|
||||
|
||||
// Top action bar is visible only in edit mode
|
||||
mActionWidget->setVisible(!mReadOnly);
|
||||
|
||||
// Bottom buttons are visible only in read-only mode
|
||||
mBottomButtonsWidget->setVisible(mReadOnly);
|
||||
mEditBtn->setVisible(canEdit);
|
||||
|
||||
// Set read-only / enabled state of all fields
|
||||
mCalendarCombo->setEnabled(!mReadOnly);
|
||||
mTitleEdit->setReadOnly(mReadOnly);
|
||||
mLocationEdit->setReadOnly(mReadOnly);
|
||||
mCategoryCombo->setEnabled(!mReadOnly);
|
||||
mAllDayCheck->setEnabled(!mReadOnly);
|
||||
mStartEdit->setReadOnly(mReadOnly);
|
||||
mEndEdit->setReadOnly(mReadOnly);
|
||||
mRepeatCombo->setEnabled(!mReadOnly);
|
||||
mReminderCombo->setEnabled(!mReadOnly);
|
||||
mDescriptionEdit->setReadOnly(mReadOnly);
|
||||
|
||||
mAttendeesList->setEnabled(!mReadOnly);
|
||||
mAddAttachBtn->setVisible(!mReadOnly);
|
||||
|
||||
mNotifyCheck->setEnabled(!mReadOnly);
|
||||
}
|
||||
|
||||
void EventDialog::sendInvite(const CalendarEvent& ev, const QStringList& invitedNames) {
|
||||
bool at_least_one_gxsid = false;
|
||||
std::set<Rs::Mail::MsgAddress> destinations;
|
||||
|
||||
for (const auto& contactId : ev.attendees) {
|
||||
std::string idStr = contactId.toStdString();
|
||||
if (idStr.length() == 16) {
|
||||
RsPgpId pgpId(idStr);
|
||||
std::list<RsPeerId> sslIds;
|
||||
if (rsPeers) {
|
||||
rsPeers->getAssociatedSSLIds(pgpId, sslIds);
|
||||
for (const auto& sslId : sslIds) {
|
||||
destinations.insert(Rs::Mail::MsgAddress(sslId, Rs::Mail::MsgAddress::AddressMode::MSG_ADDRESS_MODE_TO));
|
||||
}
|
||||
}
|
||||
} else if (idStr.length() == 32) {
|
||||
RsPeerId peerId(idStr);
|
||||
bool isSsl = false;
|
||||
if (rsPeers) {
|
||||
std::string peerName = rsPeers->getPeerName(peerId);
|
||||
if (!peerName.empty() || rsPeers->isFriend(peerId)) {
|
||||
isSsl = true;
|
||||
}
|
||||
}
|
||||
if (isSsl) {
|
||||
destinations.insert(Rs::Mail::MsgAddress(peerId, Rs::Mail::MsgAddress::AddressMode::MSG_ADDRESS_MODE_TO));
|
||||
} else {
|
||||
destinations.insert(Rs::Mail::MsgAddress(RsGxsId(idStr), Rs::Mail::MsgAddress::AddressMode::MSG_ADDRESS_MODE_TO));
|
||||
at_least_one_gxsid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (destinations.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rs::Mail::MessageInfo mi;
|
||||
mi.destinations = destinations;
|
||||
mi.title = (tr("Invitation: %1").arg(ev.title)).toUtf8().constData();
|
||||
|
||||
// Construct invitation HTML message body
|
||||
QString body;
|
||||
body += "<h3>" + tr("You are invited to a calendar event:") + "</h3>";
|
||||
body += "<table>";
|
||||
body += "<tr><td><b>" + tr("Title:") + "</b></td><td>" + ev.title + "</td></tr>";
|
||||
if (!ev.location.isEmpty()) {
|
||||
body += "<tr><td><b>" + tr("Location:") + "</b></td><td>" + ev.location + "</td></tr>";
|
||||
}
|
||||
body += "<tr><td><b>" + tr("Time:") + "</b></td><td>" + ev.start.toString("yyyy-MM-dd hh:mm") + " - " + ev.end.toString("yyyy-MM-dd hh:mm") + "</td></tr>";
|
||||
if (!ev.description.isEmpty()) {
|
||||
body += "<tr><td><b>" + tr("Description:") + "</b></td><td>" + QString(ev.description).replace("\n", "<br>") + "</td></tr>";
|
||||
}
|
||||
body += "</table>";
|
||||
mi.msg = body.toUtf8().constData();
|
||||
|
||||
if (!at_least_one_gxsid) {
|
||||
if (rsPeers) {
|
||||
mi.from = Rs::Mail::MsgAddress(rsPeers->getOwnId(), Rs::Mail::MsgAddress::AddressMode::MSG_ADDRESS_MODE_TO);
|
||||
}
|
||||
} else {
|
||||
std::list<RsGxsId> own_ids;
|
||||
if (rsIdentity) {
|
||||
rsIdentity->getOwnIds(own_ids);
|
||||
}
|
||||
if (own_ids.empty()) {
|
||||
QMessageBox::warning(this, tr("RetroShare"), tr("Please create an identity to sign distant messages, or remove GXS contacts from the attendee list."), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
mi.from = Rs::Mail::MsgAddress(own_ids.front(), Rs::Mail::MsgAddress::AddressMode::MSG_ADDRESS_MODE_TO);
|
||||
}
|
||||
|
||||
if (rsMail && rsMail->MessageSend(mi)) {
|
||||
QMessageBox::information(this, tr("Invitations Sent"),
|
||||
tr("Invitations successfully sent to: %1").arg(invitedNames.join(", ")));
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Sending Failed"), tr("Failed to send invitations."));
|
||||
}
|
||||
}
|
||||
88
retroshare-gui/src/gui/calendar/EventDialog.h
Normal file
88
retroshare-gui/src/gui/calendar/EventDialog.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/EventDialog.h *
|
||||
* *
|
||||
* Copyright (C) 2026 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef EVENTDIALOG_H
|
||||
#define EVENTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QCheckBox;
|
||||
class QDateTimeEdit;
|
||||
class QTextEdit;
|
||||
class QListWidget;
|
||||
class QTreeWidget;
|
||||
class QTabWidget;
|
||||
|
||||
class EventDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Pass eventId to edit existing event, or empty string to create a new one.
|
||||
// If creating a new one, startInfo can specify the default start time.
|
||||
EventDialog(const QString& eventId = "", const QDateTime& startInfo = QDateTime::currentDateTime(), QWidget* parent = nullptr, bool readOnly = false);
|
||||
~EventDialog();
|
||||
|
||||
private slots:
|
||||
void onSaveAndClose();
|
||||
void onDelete();
|
||||
void onAllDayToggled(bool checked);
|
||||
void onInviteAttendees();
|
||||
void onEditClicked();
|
||||
|
||||
private:
|
||||
void loadEvent();
|
||||
void buildUi();
|
||||
void updateModeUi();
|
||||
void sendInvite(const CalendarEvent& ev, const QStringList& invitedNames);
|
||||
|
||||
QString mEventId;
|
||||
QDateTime mDefaultStart;
|
||||
bool mReadOnly;
|
||||
|
||||
QWidget* mActionWidget;
|
||||
QPushButton* mSaveBtn;
|
||||
QPushButton* mInviteBtn;
|
||||
QPushButton* mDeleteBtn;
|
||||
|
||||
QComboBox* mCalendarCombo;
|
||||
QLineEdit* mTitleEdit;
|
||||
QLineEdit* mLocationEdit;
|
||||
QComboBox* mCategoryCombo;
|
||||
QCheckBox* mAllDayCheck;
|
||||
QDateTimeEdit* mStartEdit;
|
||||
QDateTimeEdit* mEndEdit;
|
||||
QComboBox* mRepeatCombo;
|
||||
QComboBox* mReminderCombo;
|
||||
QTextEdit* mDescriptionEdit;
|
||||
QTreeWidget* mAttendeesList;
|
||||
|
||||
QListWidget* mAttachmentsList;
|
||||
QPushButton* mAddAttachBtn;
|
||||
|
||||
QWidget* mBottomButtonsWidget;
|
||||
QPushButton* mEditBtn;
|
||||
QPushButton* mCloseBtn;
|
||||
|
||||
QCheckBox* mNotifyCheck;
|
||||
};
|
||||
|
||||
#endif // EVENTDIALOG_H
|
||||
345
retroshare-gui/src/gui/calendar/TaskDialog.cpp
Normal file
345
retroshare-gui/src/gui/calendar/TaskDialog.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
#include "gui/calendar/TaskDialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QDateTimeEdit>
|
||||
#include <QTextEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QTabWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QUuid>
|
||||
#include <QFileDialog>
|
||||
#include <QListWidget>
|
||||
#include <QFileInfo>
|
||||
#include <QDesktopServices>
|
||||
#include <QUrl>
|
||||
#include <QMenu>
|
||||
#include <QDir>
|
||||
#include "gui/RetroShareLink.h"
|
||||
|
||||
TaskDialog::TaskDialog(const QString& taskId, QWidget* parent)
|
||||
: QDialog(parent), mTaskId(taskId)
|
||||
{
|
||||
setWindowTitle(mTaskId.isEmpty() ? tr("New Task") : tr("Edit Task"));
|
||||
setMinimumSize(450, 550);
|
||||
|
||||
buildUi();
|
||||
loadTask();
|
||||
}
|
||||
|
||||
TaskDialog::~TaskDialog() {}
|
||||
|
||||
void TaskDialog::buildUi() {
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(15, 15, 15, 15);
|
||||
mainLayout->setSpacing(10);
|
||||
|
||||
// Top action bar
|
||||
QHBoxLayout* actionLayout = new QHBoxLayout();
|
||||
QPushButton* saveBtn = new QPushButton(tr("Save and Close"), this);
|
||||
saveBtn->setIcon(QIcon(":/icons/mail/compose.png"));
|
||||
connect(saveBtn, SIGNAL(clicked()), this, SLOT(onSaveAndClose()));
|
||||
actionLayout->addWidget(saveBtn);
|
||||
|
||||
QPushButton* deleteBtn = new QPushButton(tr("Delete"), this);
|
||||
deleteBtn->setIcon(QIcon(":/icons/mail/delete.png"));
|
||||
connect(deleteBtn, SIGNAL(clicked()), this, SLOT(onDelete()));
|
||||
actionLayout->addWidget(deleteBtn);
|
||||
|
||||
if (mTaskId.isEmpty()) {
|
||||
deleteBtn->setEnabled(false);
|
||||
}
|
||||
|
||||
actionLayout->addStretch();
|
||||
mainLayout->addLayout(actionLayout);
|
||||
|
||||
// Form inputs layout
|
||||
QFormLayout* formLayout = new QFormLayout();
|
||||
formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
formLayout->setSpacing(8);
|
||||
|
||||
mCalendarCombo = new QComboBox(this);
|
||||
const QList<CalendarInfo>& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& cal : cals) {
|
||||
mCalendarCombo->addItem(cal.name, cal.id);
|
||||
}
|
||||
formLayout->addRow(tr("Calendar:"), mCalendarCombo);
|
||||
|
||||
mTitleEdit = new QLineEdit(this);
|
||||
mTitleEdit->setPlaceholderText(tr("Task Title"));
|
||||
formLayout->addRow(tr("Title:"), mTitleEdit);
|
||||
|
||||
mLocationEdit = new QLineEdit(this);
|
||||
mLocationEdit->setPlaceholderText(tr("Location"));
|
||||
formLayout->addRow(tr("Location:"), mLocationEdit);
|
||||
|
||||
mCategoryCombo = new QComboBox(this);
|
||||
mCategoryCombo->addItems({tr("None"), tr("Work"), tr("Personal"), tr("Urgent"), tr("Later")});
|
||||
formLayout->addRow(tr("Category:"), mCategoryCombo);
|
||||
|
||||
// Optional Start Date
|
||||
QHBoxLayout* startLayout = new QHBoxLayout();
|
||||
mStartCheck = new QCheckBox(this);
|
||||
mStartEdit = new QDateTimeEdit(QDateTime::currentDateTime(), this);
|
||||
mStartEdit->setCalendarPopup(true);
|
||||
mStartEdit->setEnabled(false);
|
||||
connect(mStartCheck, SIGNAL(toggled(bool)), this, SLOT(onStartToggled(bool)));
|
||||
startLayout->addWidget(mStartCheck);
|
||||
startLayout->addWidget(mStartEdit);
|
||||
formLayout->addRow(tr("Start:"), startLayout);
|
||||
|
||||
// Optional Due Date
|
||||
QHBoxLayout* dueLayout = new QHBoxLayout();
|
||||
mDueCheck = new QCheckBox(this);
|
||||
mDueEdit = new QDateTimeEdit(QDateTime::currentDateTime().addDays(1), this);
|
||||
mDueEdit->setCalendarPopup(true);
|
||||
mDueEdit->setEnabled(false);
|
||||
connect(mDueCheck, SIGNAL(toggled(bool)), this, SLOT(onDueToggled(bool)));
|
||||
dueLayout->addWidget(mDueCheck);
|
||||
dueLayout->addWidget(mDueEdit);
|
||||
formLayout->addRow(tr("Due Date:"), dueLayout);
|
||||
|
||||
mStatusCombo = new QComboBox(this);
|
||||
mStatusCombo->addItems({tr("Not specified"), tr("Not started"), tr("In progress"), tr("Completed")});
|
||||
formLayout->addRow(tr("Status:"), mStatusCombo);
|
||||
|
||||
mPercentSpin = new QSpinBox(this);
|
||||
mPercentSpin->setRange(0, 100);
|
||||
mPercentSpin->setSuffix("%");
|
||||
formLayout->addRow(tr("Complete:"), mPercentSpin);
|
||||
|
||||
mRepeatCombo = new QComboBox(this);
|
||||
mRepeatCombo->addItems({tr("Does not repeat"), tr("Daily"), tr("Weekly"), tr("Monthly")});
|
||||
formLayout->addRow(tr("Repeat:"), mRepeatCombo);
|
||||
|
||||
mReminderCombo = new QComboBox(this);
|
||||
mReminderCombo->addItems({tr("No reminder"), tr("On start date"), tr("On due date")});
|
||||
formLayout->addRow(tr("Reminder:"), mReminderCombo);
|
||||
|
||||
mainLayout->addLayout(formLayout);
|
||||
|
||||
// Tab Widget for Description & Attachments
|
||||
QTabWidget* tabWidget = new QTabWidget(this);
|
||||
|
||||
// Description Tab
|
||||
mDescriptionEdit = new QTextEdit(this);
|
||||
tabWidget->addTab(mDescriptionEdit, tr("Description"));
|
||||
|
||||
// Attachments Tab
|
||||
QWidget* attachTab = new QWidget(this);
|
||||
QVBoxLayout* attachLayout = new QVBoxLayout(attachTab);
|
||||
mAttachmentsList = new QListWidget(this);
|
||||
mAttachmentsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(mAttachmentsList, &QListWidget::customContextMenuRequested, [this](const QPoint& pos) {
|
||||
QListWidgetItem* item = mAttachmentsList->itemAt(pos);
|
||||
if (!item) return;
|
||||
|
||||
QMenu menu(this);
|
||||
QAction* downloadAction = menu.addAction(QIcon(":/icons/png/download.png"), tr("Download"));
|
||||
QAction* downloadAllAction = menu.addAction(QIcon(":/icons/mail/downloadall.png"), tr("Download all"));
|
||||
QAction* removeAction = menu.addAction(QIcon(":/icons/mail/delete.png"), tr("Remove Attachment"));
|
||||
|
||||
QAction* selectedAction = menu.exec(mAttachmentsList->mapToGlobal(pos));
|
||||
if (selectedAction == downloadAction) {
|
||||
QString att = item->data(Qt::UserRole).toString();
|
||||
if (!att.isEmpty()) {
|
||||
RetroShareLink link(att);
|
||||
if (link.valid() && (link.type() == RetroShareLink::TYPE_FILE || link.type() == RetroShareLink::TYPE_FILE_TREE)) {
|
||||
QList<RetroShareLink> links;
|
||||
links.append(link);
|
||||
RetroShareLink::process(links);
|
||||
} else if (QFileInfo::exists(att)) {
|
||||
QString targetPath = QFileDialog::getSaveFileName(this, tr("Save Attachment As"), QFileInfo(att).fileName());
|
||||
if (!targetPath.isEmpty()) {
|
||||
if (QFile::exists(targetPath)) {
|
||||
QFile::remove(targetPath);
|
||||
}
|
||||
if (!QFile::copy(att, targetPath)) {
|
||||
QMessageBox::warning(this, tr("Download Failed"), tr("Could not save the file to %1").arg(targetPath));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
QUrl url(att);
|
||||
if (!url.scheme().isEmpty()) {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (selectedAction == downloadAllAction) {
|
||||
QList<RetroShareLink> rsLinks;
|
||||
QStringList localFiles;
|
||||
for (int i = 0; i < mAttachmentsList->count(); ++i) {
|
||||
QString att = mAttachmentsList->item(i)->data(Qt::UserRole).toString();
|
||||
if (att.isEmpty()) continue;
|
||||
RetroShareLink link(att);
|
||||
if (link.valid() && (link.type() == RetroShareLink::TYPE_FILE || link.type() == RetroShareLink::TYPE_FILE_TREE)) {
|
||||
rsLinks.append(link);
|
||||
} else if (QFileInfo::exists(att)) {
|
||||
localFiles.append(att);
|
||||
} else {
|
||||
QUrl url(att);
|
||||
if (!url.scheme().isEmpty()) {
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rsLinks.isEmpty()) {
|
||||
RetroShareLink::process(rsLinks);
|
||||
}
|
||||
if (!localFiles.isEmpty()) {
|
||||
QString targetDir = QFileDialog::getExistingDirectory(this, tr("Select Directory to Save Attachments"));
|
||||
if (!targetDir.isEmpty()) {
|
||||
bool success = true;
|
||||
QStringList failedFiles;
|
||||
for (const QString& file : localFiles) {
|
||||
QFileInfo fi(file);
|
||||
QString targetPath = QDir(targetDir).filePath(fi.fileName());
|
||||
if (QFile::exists(targetPath)) {
|
||||
QFile::remove(targetPath);
|
||||
}
|
||||
if (!QFile::copy(file, targetPath)) {
|
||||
success = false;
|
||||
failedFiles.append(fi.fileName());
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
QMessageBox::warning(this, tr("Download Failed"), tr("Could not save the following files: %1").arg(failedFiles.join(", ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (selectedAction == removeAction) {
|
||||
delete mAttachmentsList->takeItem(mAttachmentsList->row(item));
|
||||
}
|
||||
});
|
||||
|
||||
connect(mAttachmentsList, &QListWidget::itemDoubleClicked, [](QListWidgetItem* item) {
|
||||
QString pathOrUrl = item->data(Qt::UserRole).toString();
|
||||
if (!pathOrUrl.isEmpty()) {
|
||||
QUrl url(pathOrUrl);
|
||||
if (url.scheme().isEmpty()) {
|
||||
url = QUrl::fromLocalFile(pathOrUrl);
|
||||
}
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
attachLayout->addWidget(mAttachmentsList);
|
||||
mAddAttachBtn = new QPushButton(tr("Attach File..."), this);
|
||||
connect(mAddAttachBtn, &QPushButton::clicked, [this]() {
|
||||
QStringList files = QFileDialog::getOpenFileNames(this, tr("Select File(s)"));
|
||||
for (const QString& file : files) {
|
||||
if (!file.isEmpty()) {
|
||||
QListWidgetItem* item = new QListWidgetItem(QFileInfo(file).fileName(), mAttachmentsList);
|
||||
item->setData(Qt::UserRole, file);
|
||||
item->setToolTip(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
attachLayout->addWidget(mAddAttachBtn);
|
||||
tabWidget->addTab(attachTab, tr("Attachments"));
|
||||
|
||||
mainLayout->addWidget(tabWidget);
|
||||
}
|
||||
|
||||
void TaskDialog::loadTask() {
|
||||
if (mTaskId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<CalendarTask>& tasks = CalendarData::instance()->getTasks();
|
||||
for (const auto& t : tasks) {
|
||||
if (t.id == mTaskId) {
|
||||
int calIdx = mCalendarCombo->findData(t.calendarId);
|
||||
if (calIdx != -1) mCalendarCombo->setCurrentIndex(calIdx);
|
||||
|
||||
mTitleEdit->setText(t.title);
|
||||
mLocationEdit->setText(t.location);
|
||||
mCategoryCombo->setCurrentText(t.category);
|
||||
|
||||
mStartCheck->setChecked(t.hasStart);
|
||||
if (t.hasStart) mStartEdit->setDateTime(t.start);
|
||||
|
||||
mDueCheck->setChecked(t.hasDue);
|
||||
if (t.hasDue) mDueEdit->setDateTime(t.due);
|
||||
|
||||
mStatusCombo->setCurrentText(t.status);
|
||||
mPercentSpin->setValue(t.percentComplete);
|
||||
mRepeatCombo->setCurrentText(t.repeat);
|
||||
mReminderCombo->setCurrentText(t.reminder);
|
||||
mDescriptionEdit->setPlainText(t.description);
|
||||
|
||||
// Load attachments
|
||||
mAttachmentsList->clear();
|
||||
for (const auto& att : t.attachments) {
|
||||
QListWidgetItem* item = new QListWidgetItem(QFileInfo(att).fileName(), mAttachmentsList);
|
||||
item->setData(Qt::UserRole, att);
|
||||
item->setToolTip(att);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskDialog::onStartToggled(bool checked) {
|
||||
mStartEdit->setEnabled(checked);
|
||||
}
|
||||
|
||||
void TaskDialog::onDueToggled(bool checked) {
|
||||
mDueEdit->setEnabled(checked);
|
||||
}
|
||||
|
||||
void TaskDialog::onSaveAndClose() {
|
||||
if (mTitleEdit->text().trimmed().isEmpty()) {
|
||||
QMessageBox::warning(this, tr("Empty Title"), tr("Please provide a title for the task."));
|
||||
return;
|
||||
}
|
||||
|
||||
CalendarTask t;
|
||||
t.id = mTaskId.isEmpty() ? QUuid::createUuid().toString(QUuid::WithoutBraces) : mTaskId;
|
||||
t.calendarId = mCalendarCombo->currentData().toString();
|
||||
t.title = mTitleEdit->text().trimmed();
|
||||
t.location = mLocationEdit->text().trimmed();
|
||||
t.category = mCategoryCombo->currentText();
|
||||
t.hasStart = mStartCheck->isChecked();
|
||||
t.start = mStartEdit->dateTime();
|
||||
t.hasDue = mDueCheck->isChecked();
|
||||
t.due = mDueEdit->dateTime();
|
||||
t.status = mStatusCombo->currentText();
|
||||
t.percentComplete = mPercentSpin->value();
|
||||
t.repeat = mRepeatCombo->currentText();
|
||||
t.reminder = mReminderCombo->currentText();
|
||||
t.description = mDescriptionEdit->toPlainText();
|
||||
t.completed = (t.status == tr("Completed") || t.percentComplete == 100);
|
||||
|
||||
if (t.completed && t.percentComplete < 100) {
|
||||
t.percentComplete = 100;
|
||||
}
|
||||
|
||||
// Get attachments
|
||||
for (int i = 0; i < mAttachmentsList->count(); ++i) {
|
||||
t.attachments.append(mAttachmentsList->item(i)->data(Qt::UserRole).toString());
|
||||
}
|
||||
|
||||
if (mTaskId.isEmpty()) {
|
||||
CalendarData::instance()->addTask(t);
|
||||
} else {
|
||||
CalendarData::instance()->updateTask(t);
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void TaskDialog::onDelete() {
|
||||
if (mTaskId.isEmpty()) return;
|
||||
|
||||
if (QMessageBox::question(this, tr("Delete Task"), tr("Are you sure you want to delete this task?")) == QMessageBox::Yes) {
|
||||
CalendarData::instance()->removeTask(mTaskId);
|
||||
accept();
|
||||
}
|
||||
}
|
||||
51
retroshare-gui/src/gui/calendar/TaskDialog.h
Normal file
51
retroshare-gui/src/gui/calendar/TaskDialog.h
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef TASKDIALOG_H
|
||||
#define TASKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
class QCheckBox;
|
||||
class QDateTimeEdit;
|
||||
class QTextEdit;
|
||||
class QSpinBox;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
|
||||
class TaskDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TaskDialog(const QString& taskId = "", QWidget* parent = nullptr);
|
||||
~TaskDialog();
|
||||
|
||||
private slots:
|
||||
void onSaveAndClose();
|
||||
void onDelete();
|
||||
void onStartToggled(bool checked);
|
||||
void onDueToggled(bool checked);
|
||||
|
||||
private:
|
||||
void loadTask();
|
||||
void buildUi();
|
||||
|
||||
QString mTaskId;
|
||||
|
||||
QComboBox* mCalendarCombo;
|
||||
QLineEdit* mTitleEdit;
|
||||
QLineEdit* mLocationEdit;
|
||||
QComboBox* mCategoryCombo;
|
||||
QCheckBox* mStartCheck;
|
||||
QDateTimeEdit* mStartEdit;
|
||||
QCheckBox* mDueCheck;
|
||||
QDateTimeEdit* mDueEdit;
|
||||
QComboBox* mStatusCombo;
|
||||
QSpinBox* mPercentSpin;
|
||||
QComboBox* mRepeatCombo;
|
||||
QComboBox* mReminderCombo;
|
||||
QTextEdit* mDescriptionEdit;
|
||||
QListWidget* mAttachmentsList;
|
||||
QPushButton* mAddAttachBtn;
|
||||
};
|
||||
|
||||
#endif // TASKDIALOG_H
|
||||
544
retroshare-gui/src/gui/calendar/TasksWidget.cpp
Normal file
544
retroshare-gui/src/gui/calendar/TasksWidget.cpp
Normal file
@ -0,0 +1,544 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/TasksWidget.cpp *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#include "gui/calendar/TasksWidget.h"
|
||||
#include "gui/calendar/TaskDialog.h"
|
||||
#include "gui/calendar/CalendarPropertiesDialog.h"
|
||||
#include <retroshare/rsidentity.h>
|
||||
#include <retroshare/rsgxscalendar.h>
|
||||
#include <QVBoxLayout>
|
||||
#include <QMenu>
|
||||
#include <QComboBox>
|
||||
#include <QInputDialog>
|
||||
#include <QColorDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QDir>
|
||||
#include <QHBoxLayout>
|
||||
#include <QSplitter>
|
||||
#include <QPushButton>
|
||||
#include <QCalendarWidget>
|
||||
#include <QListWidget>
|
||||
#include <QTableWidget>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QHeaderView>
|
||||
#include <QCheckBox>
|
||||
#include <QDateTime>
|
||||
#include <QShowEvent>
|
||||
#include <QUuid>
|
||||
|
||||
TasksWidget::TasksWidget(QWidget* parent)
|
||||
: QWidget(parent), mCurrentFilterMode(0), mInitialLoadDone(false)
|
||||
{
|
||||
buildUi();
|
||||
refreshData();
|
||||
|
||||
connect(CalendarData::instance(), SIGNAL(calendarDataChanged()), this, SLOT(refreshData()));
|
||||
}
|
||||
|
||||
TasksWidget::~TasksWidget() {}
|
||||
|
||||
void TasksWidget::showEvent(QShowEvent* event) {
|
||||
QWidget::showEvent(event);
|
||||
if (!mInitialLoadDone) {
|
||||
mInitialLoadDone = true;
|
||||
CalendarData::instance()->updateCalendars();
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::buildUi() {
|
||||
ui.setupUi(this);
|
||||
|
||||
// Initialize UI pointers
|
||||
mSidebarCalendar = ui.sidebarCalendar;
|
||||
mFilterList = ui.filterList;
|
||||
mCalendarList = ui.calendarList;
|
||||
mSharedCalendarList = ui.sharedCalendarList;
|
||||
mQuickTaskEdit = ui.quickTaskEdit;
|
||||
mSearchEdit = ui.searchEdit;
|
||||
mTaskTable = ui.taskTable;
|
||||
|
||||
// Filter list items setup
|
||||
mFilterList->addItem(tr("All Tasks"));
|
||||
mFilterList->addItem(tr("Active Tasks"));
|
||||
mFilterList->addItem(tr("Completed Tasks"));
|
||||
mFilterList->addItem(tr("Overdue Tasks"));
|
||||
mFilterList->setCurrentRow(0);
|
||||
|
||||
// Main Tasks List Table setup
|
||||
mTaskTable->setColumnCount(5);
|
||||
mTaskTable->setHorizontalHeaderLabels({"", "!", tr("Title"), tr("Start"), tr("Due Date")});
|
||||
mTaskTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
|
||||
mTaskTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
|
||||
mTaskTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
|
||||
mTaskTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
|
||||
mTaskTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
|
||||
mTaskTable->horizontalHeader()->resizeSection(0, 30);
|
||||
mTaskTable->horizontalHeader()->resizeSection(1, 30);
|
||||
mTaskTable->verticalHeader()->setVisible(false);
|
||||
mTaskTable->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
mTaskTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
mTaskTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
// Splitter configuration
|
||||
ui.splitter->setStretchFactor(0, 0);
|
||||
ui.splitter->setStretchFactor(1, 1);
|
||||
|
||||
// Connections
|
||||
connect(ui.newTaskBtn, SIGNAL(clicked()), this, SLOT(onNewTask()));
|
||||
connect(mFilterList, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(onFilterSelected(QListWidgetItem*)));
|
||||
connect(mCalendarList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onCalendarSelectionChanged(QListWidgetItem*)));
|
||||
connect(mCalendarList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onCalendarContextMenu(const QPoint&)));
|
||||
connect(mSharedCalendarList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(onSharedCalendarSelectionChanged(QListWidgetItem*)));
|
||||
connect(mSharedCalendarList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onSharedCalendarContextMenu(const QPoint&)));
|
||||
connect(mQuickTaskEdit, SIGNAL(returnPressed()), this, SLOT(onQuickTaskAdded()));
|
||||
connect(mSearchEdit, SIGNAL(textChanged(const QString&)), this, SLOT(onSearchChanged(const QString&)));
|
||||
connect(mTaskTable, SIGNAL(cellDoubleClicked(int,int)), this, SLOT(onTaskDoubleClicked(int,int)));
|
||||
connect(mTaskTable, SIGNAL(cellClicked(int,int)), this, SLOT(onTaskClicked(int,int)));
|
||||
}
|
||||
|
||||
void TasksWidget::refreshData() {
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
|
||||
// 1. Populate My Calendars (owned by us, i.e. owner == "local")
|
||||
{
|
||||
// Save current check states
|
||||
QMap<QString, Qt::CheckState> checkedStates;
|
||||
for (int i = 0; i < mCalendarList->count(); ++i) {
|
||||
QListWidgetItem* item = mCalendarList->item(i);
|
||||
checkedStates[item->data(Qt::UserRole).toString()] = item->checkState();
|
||||
}
|
||||
|
||||
mCalendarList->blockSignals(true);
|
||||
mCalendarList->clear();
|
||||
for (const auto& cal : cals) {
|
||||
if (cal.owner != "local") continue;
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(cal.name, mCalendarList);
|
||||
item->setData(Qt::UserRole, cal.id);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
|
||||
QPixmap pix(12, 12);
|
||||
pix.fill(cal.color);
|
||||
item->setIcon(QIcon(pix));
|
||||
|
||||
// Restore checked state
|
||||
if (checkedStates.contains(cal.id)) {
|
||||
item->setCheckState(checkedStates[cal.id]);
|
||||
} else {
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
mCalendarList->blockSignals(false);
|
||||
}
|
||||
|
||||
// 2. Populate Shared Calendars (not owned by us)
|
||||
{
|
||||
// Save current check states and subscription states
|
||||
QMap<QString, Qt::CheckState> sharedCheckedStates;
|
||||
QMap<QString, bool> sharedSubscribedStates;
|
||||
for (int i = 0; i < mSharedCalendarList->count(); ++i) {
|
||||
QListWidgetItem* item = mSharedCalendarList->item(i);
|
||||
QString calId = item->data(Qt::UserRole).toString();
|
||||
sharedCheckedStates[calId] = item->checkState();
|
||||
sharedSubscribedStates[calId] = item->data(Qt::UserRole + 1).toBool();
|
||||
}
|
||||
|
||||
mSharedCalendarList->blockSignals(true);
|
||||
mSharedCalendarList->clear();
|
||||
if (rsGxsCalendar) {
|
||||
std::list<RsGroupMetaData> calendars;
|
||||
if (rsGxsCalendar->getCalendarsSummaries(calendars)) {
|
||||
for (const auto& meta : calendars) {
|
||||
QString calId = QString::fromStdString(meta.mGroupId.toStdString());
|
||||
|
||||
// Filter out calendars owned by us
|
||||
bool ownedByUs = false;
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == calId && c.owner == "local") {
|
||||
ownedByUs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ownedByUs) continue;
|
||||
|
||||
QString calName = QString::fromUtf8(meta.mGroupName.c_str());
|
||||
|
||||
// Check subscription status locally (immediate)
|
||||
bool isSubscribedLocal = false;
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == calId) {
|
||||
isSubscribedLocal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QListWidgetItem* item = new QListWidgetItem(calName, mSharedCalendarList);
|
||||
item->setData(Qt::UserRole, calId);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
|
||||
// Render blue bullet for subscribed, grey for unsubscribed
|
||||
QPixmap pix(12, 12);
|
||||
pix.fill(isSubscribedLocal ? QColor("#4a90e2") : Qt::gray);
|
||||
item->setIcon(QIcon(pix));
|
||||
item->setData(Qt::UserRole + 1, isSubscribedLocal);
|
||||
|
||||
// Restore checked state if we have a saved state and subscription status did not change.
|
||||
// If subscription state changed, set checked state based on new subscription status.
|
||||
if (sharedCheckedStates.contains(calId)) {
|
||||
bool wasSubscribed = sharedSubscribedStates.value(calId, false);
|
||||
if (wasSubscribed != isSubscribedLocal) {
|
||||
item->setCheckState(isSubscribedLocal ? Qt::Checked : Qt::Unchecked);
|
||||
} else {
|
||||
item->setCheckState(sharedCheckedStates[calId]);
|
||||
}
|
||||
} else {
|
||||
item->setCheckState(isSubscribedLocal ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mSharedCalendarList->blockSignals(false);
|
||||
}
|
||||
|
||||
updateTaskList();
|
||||
}
|
||||
|
||||
void TasksWidget::updateTaskList() {
|
||||
mTaskTable->setRowCount(0);
|
||||
|
||||
const auto& tasks = CalendarData::instance()->getTasks();
|
||||
|
||||
// Get enabled calendar IDs
|
||||
QStringList enabledCalIds;
|
||||
for (int i = 0; i < mCalendarList->count(); ++i) {
|
||||
QListWidgetItem* item = mCalendarList->item(i);
|
||||
if (item->checkState() == Qt::Checked) {
|
||||
enabledCalIds.append(item->data(Qt::UserRole).toString());
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < mSharedCalendarList->count(); ++i) {
|
||||
QListWidgetItem* item = mSharedCalendarList->item(i);
|
||||
if (item->checkState() == Qt::Checked) {
|
||||
enabledCalIds.append(item->data(Qt::UserRole).toString());
|
||||
}
|
||||
}
|
||||
|
||||
int row = 0;
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
for (const auto& task : tasks) {
|
||||
if (!enabledCalIds.contains(task.calendarId)) continue;
|
||||
|
||||
// Apply filters
|
||||
if (mCurrentFilterMode == 1 && task.completed) continue; // Active Tasks
|
||||
if (mCurrentFilterMode == 2 && !task.completed) continue; // Completed Tasks
|
||||
if (mCurrentFilterMode == 3 && (task.completed || !task.hasDue || task.due >= now)) continue; // Overdue Tasks
|
||||
|
||||
// Search text filter
|
||||
if (!mSearchText.isEmpty() && !task.title.contains(mSearchText, Qt::CaseInsensitive) &&
|
||||
!task.description.contains(mSearchText, Qt::CaseInsensitive)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mTaskTable->insertRow(row);
|
||||
|
||||
// Checkbox column
|
||||
QTableWidgetItem* checkItem = new QTableWidgetItem();
|
||||
checkItem->setCheckState(task.completed ? Qt::Checked : Qt::Unchecked);
|
||||
checkItem->setData(Qt::UserRole, task.id);
|
||||
mTaskTable->setItem(row, 0, checkItem);
|
||||
|
||||
// Priority / Exclamation mark column
|
||||
QTableWidgetItem* priorityItem = new QTableWidgetItem(task.category == tr("Urgent") ? "!" : "");
|
||||
priorityItem->setTextAlignment(Qt::AlignCenter);
|
||||
mTaskTable->setItem(row, 1, priorityItem);
|
||||
|
||||
// Title column
|
||||
QTableWidgetItem* titleItem = new QTableWidgetItem(task.title);
|
||||
if (task.completed) {
|
||||
QFont font = titleItem->font();
|
||||
font.setStrikeOut(true);
|
||||
titleItem->setFont(font);
|
||||
titleItem->setForeground(QBrush(Qt::gray));
|
||||
}
|
||||
mTaskTable->setItem(row, 2, titleItem);
|
||||
|
||||
// Start Date column
|
||||
QString startStr = task.hasStart ? task.start.toString("yyyy-MM-dd hh:mm") : tr("None");
|
||||
mTaskTable->setItem(row, 3, new QTableWidgetItem(startStr));
|
||||
|
||||
// Due Date column
|
||||
QString dueStr = task.hasDue ? task.due.toString("yyyy-MM-dd hh:mm") : tr("None");
|
||||
QTableWidgetItem* dueItem = new QTableWidgetItem(dueStr);
|
||||
if (task.hasDue && !task.completed && task.due < now) {
|
||||
dueItem->setForeground(QBrush(Qt::red));
|
||||
}
|
||||
mTaskTable->setItem(row, 4, dueItem);
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onNewTask() {
|
||||
TaskDialog dlg("", this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onQuickTaskAdded() {
|
||||
QString title = mQuickTaskEdit->text().trimmed();
|
||||
if (title.isEmpty()) return;
|
||||
|
||||
CalendarTask task;
|
||||
task.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Choose the first enabled calendar
|
||||
QString calId = "personal";
|
||||
for (int i = 0; i < mCalendarList->count(); ++i) {
|
||||
QListWidgetItem* item = mCalendarList->item(i);
|
||||
if (item->checkState() == Qt::Checked) {
|
||||
calId = item->data(Qt::UserRole).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
task.calendarId = calId;
|
||||
task.title = title;
|
||||
task.location = "";
|
||||
task.category = "None";
|
||||
task.hasStart = false;
|
||||
task.hasDue = false;
|
||||
task.status = "Not started";
|
||||
task.percentComplete = 0;
|
||||
task.repeat = "Does not repeat";
|
||||
task.reminder = "No reminder";
|
||||
task.description = "";
|
||||
task.completed = false;
|
||||
|
||||
CalendarData::instance()->addTask(task);
|
||||
mQuickTaskEdit->clear();
|
||||
refreshData();
|
||||
}
|
||||
|
||||
void TasksWidget::onTaskDoubleClicked(int row, int col) {
|
||||
if (col == 0) return; // Ignore checkbox double clicks
|
||||
|
||||
QTableWidgetItem* checkItem = mTaskTable->item(row, 0);
|
||||
if (checkItem) {
|
||||
QString taskId = checkItem->data(Qt::UserRole).toString();
|
||||
TaskDialog dlg(taskId, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
refreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onTaskClicked(int row, int col) {
|
||||
if (col != 0) return; // Only trigger for Checkbox column
|
||||
|
||||
QTableWidgetItem* checkItem = mTaskTable->item(row, 0);
|
||||
if (checkItem) {
|
||||
QString taskId = checkItem->data(Qt::UserRole).toString();
|
||||
|
||||
// Find and toggle completion
|
||||
const auto& tasks = CalendarData::instance()->getTasks();
|
||||
for (auto t : tasks) {
|
||||
if (t.id == taskId) {
|
||||
t.completed = !t.completed;
|
||||
t.status = t.completed ? tr("Completed") : tr("Not started");
|
||||
t.percentComplete = t.completed ? 100 : 0;
|
||||
CalendarData::instance()->updateTask(t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onFilterSelected(QListWidgetItem* item) {
|
||||
int idx = mFilterList->row(item);
|
||||
if (idx != -1) {
|
||||
mCurrentFilterMode = idx;
|
||||
updateTaskList();
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onCalendarSelectionChanged(QListWidgetItem* item) {
|
||||
updateTaskList();
|
||||
}
|
||||
|
||||
void TasksWidget::onSharedCalendarSelectionChanged(QListWidgetItem* item) {
|
||||
updateTaskList();
|
||||
}
|
||||
|
||||
void TasksWidget::onSearchChanged(const QString& text) {
|
||||
mSearchText = text.trimmed();
|
||||
updateTaskList();
|
||||
}
|
||||
|
||||
void TasksWidget::onCalendarContextMenu(const QPoint& pos) {
|
||||
QListWidgetItem* item = mCalendarList->itemAt(pos);
|
||||
if (!item) return;
|
||||
|
||||
QString calId = item->data(Qt::UserRole).toString();
|
||||
QString calName = item->text();
|
||||
bool isChecked = item->checkState() == Qt::Checked;
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
QAction* toggleAct = menu.addAction(isChecked ? tr("Hide %1").arg(calName) : tr("Show %1").arg(calName));
|
||||
QAction* showOnlyAct = menu.addAction(tr("Show Only %1").arg(calName));
|
||||
QAction* showAllAct = menu.addAction(tr("Show All Calendars"));
|
||||
|
||||
// Check if selected calendar is a shared/network calendar
|
||||
bool isSharedCal = false;
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == calId) {
|
||||
isSharedCal = (c.owner != "local");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QAction* newAct = nullptr;
|
||||
QAction* deleteAct = nullptr;
|
||||
if (!isSharedCal) {
|
||||
menu.addSeparator();
|
||||
newAct = menu.addAction(tr("New Calendar..."));
|
||||
deleteAct = menu.addAction(tr("Delete Calendar..."));
|
||||
}
|
||||
menu.addSeparator();
|
||||
QAction* exportAct = menu.addAction(tr("Export Calendar..."));
|
||||
menu.addSeparator();
|
||||
QAction* propertiesAct = menu.addAction(tr("Properties"));
|
||||
|
||||
QAction* selectedAct = menu.exec(mCalendarList->mapToGlobal(pos));
|
||||
if (!selectedAct) return;
|
||||
|
||||
if (selectedAct == toggleAct) {
|
||||
item->setCheckState(isChecked ? Qt::Unchecked : Qt::Checked);
|
||||
} else if (selectedAct == showOnlyAct) {
|
||||
mCalendarList->blockSignals(true);
|
||||
mSharedCalendarList->blockSignals(true);
|
||||
for (int i = 0; i < mCalendarList->count(); ++i) {
|
||||
QListWidgetItem* it = mCalendarList->item(i);
|
||||
it->setCheckState(it == item ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
for (int i = 0; i < mSharedCalendarList->count(); ++i) {
|
||||
mSharedCalendarList->item(i)->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
mCalendarList->blockSignals(false);
|
||||
mSharedCalendarList->blockSignals(false);
|
||||
updateTaskList();
|
||||
} else if (selectedAct == showAllAct) {
|
||||
mCalendarList->blockSignals(true);
|
||||
for (int i = 0; i < mCalendarList->count(); ++i) {
|
||||
mCalendarList->item(i)->setCheckState(Qt::Checked);
|
||||
}
|
||||
mCalendarList->blockSignals(false);
|
||||
updateTaskList();
|
||||
} else if (selectedAct == newAct) {
|
||||
CalendarPropertiesDialog dlg("", this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
refreshData();
|
||||
}
|
||||
} else if (selectedAct == deleteAct) {
|
||||
if (QMessageBox::question(this, tr("Delete Calendar"),
|
||||
tr("Are you sure you want to delete calendar '%1'?\nThis will also delete all associated events and tasks.").arg(calName),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
|
||||
CalendarData::instance()->removeCalendar(calId);
|
||||
refreshData();
|
||||
}
|
||||
} else if (selectedAct == exportAct) {
|
||||
exportCalendar(calId, calName);
|
||||
} else if (selectedAct == propertiesAct) {
|
||||
CalendarPropertiesDialog dlg(calId, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
refreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::onSharedCalendarContextMenu(const QPoint& pos) {
|
||||
QListWidgetItem* item = mSharedCalendarList->itemAt(pos);
|
||||
if (!item) return;
|
||||
|
||||
QString calId = item->data(Qt::UserRole).toString();
|
||||
QString calName = item->text();
|
||||
|
||||
bool isSubscribed = false;
|
||||
const auto& cals = CalendarData::instance()->getCalendars();
|
||||
for (const auto& c : cals) {
|
||||
if (c.id == calId) {
|
||||
isSubscribed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QMenu menu(this);
|
||||
QAction* subAct = menu.addAction(isSubscribed ? tr("Unsubscribe") : tr("Subscribe"));
|
||||
QAction* selectedAct = menu.exec(mSharedCalendarList->mapToGlobal(pos));
|
||||
if (selectedAct == subAct) {
|
||||
CalendarData::instance()->subscribeToCalendar(calId, !isSubscribed, calName);
|
||||
}
|
||||
}
|
||||
|
||||
void TasksWidget::exportCalendar(const QString& calId, const QString& calName) {
|
||||
QString icsContent = CalendarData::instance()->exportCalendarToIcs(calId);
|
||||
|
||||
QString defaultFileName = QString("%1.ics").arg(calName);
|
||||
defaultFileName.replace(QRegExp("[\\\\/:*?\"<>|]"), "_");
|
||||
|
||||
QString selectedFilter;
|
||||
QString filePath = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
tr("Export Calendar"),
|
||||
defaultFileName,
|
||||
tr("iCalendar files (*.ics);;All Files (*)"),
|
||||
&selectedFilter
|
||||
);
|
||||
|
||||
if (!filePath.isEmpty()) {
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
tr("Export Error"),
|
||||
tr("Could not open file %1 for writing.").arg(filePath)
|
||||
);
|
||||
} else {
|
||||
QTextStream out(&file);
|
||||
out.setCodec("UTF-8");
|
||||
out << icsContent;
|
||||
file.close();
|
||||
|
||||
QMessageBox::information(
|
||||
this,
|
||||
tr("Export Calendar"),
|
||||
tr("Calendar '%1' exported successfully to %2!").arg(calName).arg(QDir::toNativeSeparators(filePath))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
retroshare-gui/src/gui/calendar/TasksWidget.h
Normal file
79
retroshare-gui/src/gui/calendar/TasksWidget.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* retroshare-gui/src/gui/msgs/TasksWidget.h *
|
||||
* *
|
||||
* Copyright (C) 2011 by Retroshare Team <retroshare.project@gmail.com> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero 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 Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef TASKSWIDGET_H
|
||||
#define TASKSWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QDate>
|
||||
#include "gui/calendar/CalendarData.h"
|
||||
#include "ui_TasksWidget.h"
|
||||
|
||||
class QListWidgetItem;
|
||||
class QComboBox;
|
||||
|
||||
class TasksWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TasksWidget(QWidget* parent = nullptr);
|
||||
~TasksWidget();
|
||||
|
||||
public slots:
|
||||
void refreshData();
|
||||
|
||||
private slots:
|
||||
void onNewTask();
|
||||
void onQuickTaskAdded();
|
||||
void onTaskDoubleClicked(int row, int col);
|
||||
void onTaskClicked(int row, int col);
|
||||
void onFilterSelected(QListWidgetItem* item);
|
||||
void onCalendarSelectionChanged(QListWidgetItem* item);
|
||||
void onSharedCalendarSelectionChanged(QListWidgetItem* item);
|
||||
void onSearchChanged(const QString& text);
|
||||
void onCalendarContextMenu(const QPoint& pos);
|
||||
void onSharedCalendarContextMenu(const QPoint& pos);
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
void updateTaskList();
|
||||
void exportCalendar(const QString& calId, const QString& calName);
|
||||
|
||||
int mCurrentFilterMode; // 0=All, 1=Active, 2=Completed, 3=Overdue
|
||||
QString mSearchText;
|
||||
int mCalendarListMode; // 0=My Calendars, 1=Shared Calendars
|
||||
bool mInitialLoadDone;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
// UI elements (loaded from UI file, kept as pointers for compatibility)
|
||||
QCalendarWidget* mSidebarCalendar;
|
||||
QListWidget* mFilterList;
|
||||
QListWidget* mCalendarList;
|
||||
QListWidget* mSharedCalendarList;
|
||||
|
||||
QLineEdit* mQuickTaskEdit;
|
||||
QLineEdit* mSearchEdit;
|
||||
QTableWidget* mTaskTable;
|
||||
|
||||
Ui::TasksWidget ui;
|
||||
};
|
||||
|
||||
#endif // TASKSWIDGET_H
|
||||
188
retroshare-gui/src/gui/calendar/TasksWidget.ui
Normal file
188
retroshare-gui/src/gui/calendar/TasksWidget.ui
Normal file
@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TasksWidget</class>
|
||||
<widget class="QWidget" name="TasksWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="mainLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="handleWidth">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="sidebar" native="true">
|
||||
<layout class="QVBoxLayout" name="sidebarLayout">
|
||||
<property name="spacing">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="newTaskBtn">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; background-color: #4a90e2; color: white; border-radius: 4px; padding: 6px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+ New Task</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCalendarWidget" name="sidebarCalendar">
|
||||
<property name="gridVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="horizontalHeaderFormat">
|
||||
<enum>QCalendarWidget::SingleLetterDayNames</enum>
|
||||
</property>
|
||||
<property name="verticalHeaderFormat">
|
||||
<enum>QCalendarWidget::NoVerticalHeader</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filterLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Filter Tasks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="filterList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="calendarsLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 14px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>My Calendars</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="calendarList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="sharedCalendarsLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight: bold; font-size: 14px; margin-top: 10px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Shared Calendars</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="sharedCalendarList">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mainArea" native="true">
|
||||
<layout class="QVBoxLayout" name="mainAreaLayout">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="topLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="quickTaskEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Click here to add a new task</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Search tasks...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="taskTable">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@ -26,6 +26,10 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "MessagesDialog.h"
|
||||
#ifdef RS_USE_CALENDAR
|
||||
#include "gui/calendar/CalendarWidget.h"
|
||||
#include "gui/calendar/TasksWidget.h"
|
||||
#endif
|
||||
|
||||
#include "gui/common/TagDefs.h"
|
||||
#include "gui/common/PeerDefs.h"
|
||||
@ -147,6 +151,10 @@ MessagesDialog::MessagesDialog(QWidget *parent)
|
||||
lockUpdate = 0;
|
||||
lastSelectedIndex = QModelIndex();
|
||||
mLastCurrentQuickViewRow = -1;
|
||||
#ifdef RS_USE_CALENDAR
|
||||
mCalendarWidget = nullptr;
|
||||
mTasksWidget = nullptr;
|
||||
#endif
|
||||
|
||||
msgWidget = new MessageWidget(true, this);
|
||||
ui.msgLayout->addWidget(msgWidget);
|
||||
@ -266,6 +274,34 @@ MessagesDialog::MessagesDialog(QWidget *parent)
|
||||
ui.tabWidget->hideCloseButton(0);
|
||||
ui.tabWidget->setHideTabBarWithOneTab(true);
|
||||
|
||||
int tagIndex = ui.msgsButtons_HL->indexOf(ui.tagButton);
|
||||
if (tagIndex == -1) {
|
||||
tagIndex = 0;
|
||||
}
|
||||
|
||||
#ifdef RS_USE_CALENDAR
|
||||
QToolButton *calendarBtn = new QToolButton(this);
|
||||
calendarBtn->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/svg/calendar-month.svg"));
|
||||
calendarBtn->setIconSize(QSize(24, 24));
|
||||
calendarBtn->setText(tr("Calendar"));
|
||||
calendarBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
calendarBtn->setAutoRaise(true);
|
||||
calendarBtn->setToolTip(tr("Show Calendar"));
|
||||
connect(calendarBtn, SIGNAL(clicked()), this, SLOT(showCalendarTab()));
|
||||
|
||||
QToolButton *tasksBtn = new QToolButton(this);
|
||||
tasksBtn->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/svg/calendar-today.svg"));
|
||||
tasksBtn->setIconSize(QSize(24, 24));
|
||||
tasksBtn->setText(tr("Tasks"));
|
||||
tasksBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
tasksBtn->setAutoRaise(true);
|
||||
tasksBtn->setToolTip(tr("Show Tasks"));
|
||||
connect(tasksBtn, SIGNAL(clicked()), this, SLOT(showTasksTab()));
|
||||
|
||||
ui.msgsButtons_HL->insertWidget(tagIndex, calendarBtn);
|
||||
ui.msgsButtons_HL->insertWidget(tagIndex + 1, tasksBtn);
|
||||
#endif
|
||||
|
||||
int H = misc::getFontSizeFactor("HelpButton").height();
|
||||
QString help_str = tr(
|
||||
"<h1><img width=\"%1\" src=\":/icons/help_64.png\"> Messages</h1>"
|
||||
@ -1575,8 +1611,16 @@ void MessagesDialog::emptyTrash()
|
||||
rsMail->MessageDelete(it->msgId);
|
||||
}
|
||||
|
||||
void MessagesDialog::tabChanged(int /*tab*/)
|
||||
void MessagesDialog::tabChanged(int tab)
|
||||
{
|
||||
QWidget *widget = ui.tabWidget->widget(tab);
|
||||
#ifdef RS_USE_CALENDAR
|
||||
if (widget == mCalendarWidget && mCalendarWidget) {
|
||||
mCalendarWidget->refreshData();
|
||||
} else if (widget == mTasksWidget && mTasksWidget) {
|
||||
mTasksWidget->refreshData();
|
||||
}
|
||||
#endif
|
||||
connectActions();
|
||||
updateInterface();
|
||||
}
|
||||
@ -1590,15 +1634,43 @@ void MessagesDialog::tabCloseRequested(int tab)
|
||||
QWidget *widget = ui.tabWidget->widget(tab);
|
||||
|
||||
if (widget) {
|
||||
#ifdef RS_USE_CALENDAR
|
||||
if (widget == mCalendarWidget) {
|
||||
mCalendarWidget = nullptr;
|
||||
} else if (widget == mTasksWidget) {
|
||||
mTasksWidget = nullptr;
|
||||
}
|
||||
#endif
|
||||
ui.tabWidget->removeTab(tab);
|
||||
widget->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RS_USE_CALENDAR
|
||||
void MessagesDialog::showCalendarTab()
|
||||
{
|
||||
if (!mCalendarWidget) {
|
||||
mCalendarWidget = new CalendarWidget(this);
|
||||
ui.tabWidget->addTab(mCalendarWidget, FilesDefs::getIconFromQtResourcePath(":/icons/svg/calendar-month.svg"), tr("Calendar"));
|
||||
}
|
||||
ui.tabWidget->setCurrentWidget(mCalendarWidget);
|
||||
}
|
||||
|
||||
void MessagesDialog::showTasksTab()
|
||||
{
|
||||
if (!mTasksWidget) {
|
||||
mTasksWidget = new TasksWidget(this);
|
||||
ui.tabWidget->addTab(mTasksWidget, FilesDefs::getIconFromQtResourcePath(":/icons/svg/calendar-today.svg"), tr("Tasks"));
|
||||
}
|
||||
ui.tabWidget->setCurrentWidget(mTasksWidget);
|
||||
}
|
||||
#endif
|
||||
|
||||
void MessagesDialog::closeTab(const std::string &msgId)
|
||||
{
|
||||
QList<MessageWidget*> msgWidgets;
|
||||
|
||||
for (int tab = 1; tab < ui.tabWidget->count(); ++tab) {
|
||||
for (int tab = 3; tab < ui.tabWidget->count(); ++tab) {
|
||||
MessageWidget *msgWidget = dynamic_cast<MessageWidget*>(ui.tabWidget->widget(tab));
|
||||
if (msgWidget && msgWidget->msgId() == msgId) {
|
||||
msgWidgets.append(msgWidget);
|
||||
@ -1626,7 +1698,7 @@ void MessagesDialog::connectActions()
|
||||
ui.actionReplyAll->disconnect();
|
||||
ui.actionForward->disconnect();
|
||||
|
||||
if (msgWidget) {
|
||||
if (msg) {
|
||||
// connect actions
|
||||
msg->connectAction(MessageWidget::ACTION_REPLY, ui.actionReply);
|
||||
msg->connectAction(MessageWidget::ACTION_REPLY_ALL, ui.actionReplyAll);
|
||||
|
||||
@ -36,6 +36,10 @@ class MessageWidget;
|
||||
class QTreeWidgetItem;
|
||||
class RsMessageModel;
|
||||
class MessageSortFilterProxyModel ;
|
||||
#ifdef RS_USE_CALENDAR
|
||||
class CalendarWidget;
|
||||
class TasksWidget;
|
||||
#endif
|
||||
|
||||
class MessagesDialog : public MainPage
|
||||
{
|
||||
@ -110,6 +114,10 @@ private slots:
|
||||
|
||||
void tabChanged(int tab);
|
||||
void tabCloseRequested(int tab);
|
||||
#ifdef RS_USE_CALENDAR
|
||||
void showCalendarTab();
|
||||
void showTasksTab();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);
|
||||
@ -152,6 +160,10 @@ private:
|
||||
//RSTreeWidgetItemCompareRole *mMessageCompareRole;
|
||||
|
||||
MessageWidget *msgWidget;
|
||||
#ifdef RS_USE_CALENDAR
|
||||
CalendarWidget *mCalendarWidget;
|
||||
TasksWidget *mTasksWidget;
|
||||
#endif
|
||||
RsMessageModel *mMessageModel;
|
||||
MessageSortFilterProxyModel *mMessageProxyModel;
|
||||
|
||||
|
||||
@ -111,6 +111,7 @@ CONFIG += gxschannels
|
||||
CONFIG += posted
|
||||
CONFIG += gxsgui
|
||||
CONFIG += gxscircles
|
||||
#CONFIG += gxscalendar
|
||||
|
||||
# Other Disabled Bits.
|
||||
#CONFIG += framecatcher
|
||||
@ -1498,6 +1499,30 @@ gxsgui {
|
||||
|
||||
}
|
||||
|
||||
gxscalendar {
|
||||
DEFINES += RS_USE_CALENDAR
|
||||
|
||||
HEADERS += \
|
||||
gui/calendar/CalendarData.h \
|
||||
gui/calendar/CalendarWidget.h \
|
||||
gui/calendar/TasksWidget.h \
|
||||
gui/calendar/CalendarPropertiesDialog.h \
|
||||
gui/calendar/EventDialog.h \
|
||||
gui/calendar/TaskDialog.h
|
||||
|
||||
FORMS += \
|
||||
gui/calendar/CalendarWidget.ui \
|
||||
gui/calendar/TasksWidget.ui
|
||||
|
||||
SOURCES += \
|
||||
gui/calendar/CalendarData.cpp \
|
||||
gui/calendar/CalendarWidget.cpp \
|
||||
gui/calendar/TasksWidget.cpp \
|
||||
gui/calendar/CalendarPropertiesDialog.cpp \
|
||||
gui/calendar/EventDialog.cpp \
|
||||
gui/calendar/TaskDialog.cpp
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
#Define qmake_info.h file so GUI can get wath was used to compil
|
||||
|
||||
Loading…
Reference in New Issue
Block a user