Files
fmviewer3/project/fm_viewer/ui/fm_daytime.cpp
2026-02-21 17:11:31 +09:00

644 lines
20 KiB
C++

#include "fm_daytime.h"
#if (USE_DATE_TIME_LIST)
#include <QVBoxLayout>
#include <QPainter>
#include <QMouseEvent>
#include <QMenu>
#include <QMessageBox>
#include <QDir>
#include <QTimer>
#include "../rm_include.h"
#include "../data/rm_video_list.h"
#include "../data/rm_video_item_2ch.h"
#include "../core/rm_math.h"
#include "fm_colors.h"
#include "../data/an6000_decode.h"
#include "../core/rm_player.h"
FMTimeBarMinute::FMTimeBarMinute(QWidget *parent) : FMTimeBar(0,60,1,5,parent)
{
}
void FMTimeBarMinute::reset()
{
FMTimeBar::reset();
mFiles.clear();
mAreas.clear();
mStartBackup = -1;
mEndBackup = -1;
backups.clear();
}
int FMTimeBarMinute::findAreaFromTime(QDateTime* time)
{
for(int i=0;i<mAreas.size();i++) {
if(mAreas.at(i).second->startTime() == *time) {
return i;
}
}
return -1;
}
int FMTimeBarMinute::getTime(QPoint xy)
{
for(int i=0;i<mAreas.size();i++) {
if(mAreas.at(i).first.contains(xy)) {
return i;
}
}
return -1;
}
void FMTimeBarMinute::createAreaBox()
{
mAreas.clear();
const int w = size().width();
const int h = size().height();
const int barHeight = h / 2;
const int yo = ((h - barHeight) / 2) -4; // const int ymargin = -4;
const double aw = w - (mMarginLR * 2);
int lastX = -1;
for(int i=0;i<mFiles.size();i++) {
//qInfo() << mFiles.at(i).second->title() << mFiles.at(i).second->durationInMSecs() / 1000 << __FUNCTION__;
QSize s = mFiles.at(i).first;
int sx = (int)((((double)s.width()) / 3600.0) * aw);
sx = qMax(lastX,sx); // 이전 영역과 겹치지 않도록..
int ex = (int)((((double)s.height()) / 3600.0) * aw);
// 영역 초과하지 않도록 수정
ex = qMax(qMin(ex,w-(mMarginLR*2)),sx+5);
lastX = ex;
//qInfo() << ex << w-mMarginLR << __FUNCTION__;
QRect r = QRect(mMarginLR+sx,yo,ex-sx,barHeight);
//r.adjust(1,1,-1,-1);
mAreas.append(QPair<QRect,RMVideoItem*>(r,mFiles.at(i).second));
}
mFiles.clear(); // 필요없으니 제거
}
void FMTimeBarMinute::mousePressEvent(QMouseEvent* event)
{
Q_UNUSED(event)
if(mHoverTime >= 0) {
if(event->button() == Qt::LeftButton) {
mPressed = true;
mMenu = false;
update();
} else if (event->button() == Qt::RightButton) {
mMenu = true;
mPressed = false;
}
}
}
void FMTimeBarMinute::showMenu(QPoint pos)
{
QMenu contextMenu("", this);
QAction* aStart = new QAction(FM_WSTR(L"バックアップ開始時間"), this);
QPair<bool,int>ds = QPair<bool,int>(true,mHoverTime);
aStart->setData(QVariant::fromValue(ds));
connect(aStart, SIGNAL(triggered()), this, SLOT(onSetBackup()));
contextMenu.addAction(aStart);
if(mStartBackup > -1) {
aStart->setEnabled(false);
}
QAction* aEnd = new QAction(FM_WSTR(L"バックアップ終了時間"), this);
QPair<bool,int>de = QPair<bool,int>(false,mHoverTime);
aEnd->setData(QVariant::fromValue(de));
connect(aEnd, SIGNAL(triggered()), this, SLOT(onSetBackup()));
contextMenu.addAction(aEnd);
if(mEndBackup > -1) {
aEnd->setEnabled(false);
}
if(mStartBackup > -1 || mEndBackup > -1) {
// リセット)の方法を教えてください。
QAction* aCancel = new QAction(FM_WSTR(L"リセット"), this);
//QPair<bool,int>de = QPair<bool,int>(false,mHoverTime);
//aCancel->setData(QVariant::fromValue(de));
connect(aCancel, SIGNAL(triggered()), this, SLOT(onSetBackup()));
contextMenu.addAction(aCancel);
}
contextMenu.exec(mapToGlobal(pos));
}
void FMTimeBarMinute::onSetBackup()
{
QAction* a = qobject_cast<QAction*>(sender());
if(a != NULL) {
QString typeName = QString(a->data().typeName());
if( typeName == "QPair<bool,int>") {
QPair<bool,int> param = a->data().value<QPair<bool,int>>();
if (param.first) { // start
mStartBackup = param.second;
} else { // end
mEndBackup = param.second;
}
// 추가
if(mStartBackup > -1 && mStartBackup < mAreas.size() && mEndBackup > -1 && mEndBackup < mAreas.size()) {
backups.clear();
for(int i=mStartBackup;i<=mEndBackup;i++) {
backups.append(mAreas.at(i).second);
}
emit backupSelected(backups);
}
update();
} else {
mStartBackup = -1;
mEndBackup = -1;
update();
}
}
}
void FMTimeBarMinute::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event)
if(mMenu && event->button() == Qt::RightButton) {
mPressed = false;
if(mHoverTime >= 0) {
showMenu(event->pos());
}
return;
}
FMTimeBar::mouseReleaseEvent(event);
}
void FMTimeBarMinute::paintEvent(QPaintEvent * pe)
{
Q_UNUSED(pe)
static QPixmap dc = QPixmap(":/image/down_check.png");
QPainter painter(this);
drawGrid(painter);
for(int i=0;i<mAreas.size();i++) {
QRect r = mAreas.at(i).first;
if(i == mSelectedTime) {
painter.fillRect(r,QColor(bDisabled ? FM_DISABLED_COLOR2 : FM_SELECTED_COLOR));
}
else if (i == mHoverTime) {
painter.fillRect(r,QColor(bDisabled ? FM_DISABLED_COLOR2 : FM_HOVER_COLOR));
} else {
painter.fillRect(r,QColor(bDisabled ? FM_DISABLED_COLOR : FM_SILDER_COLOR));
}
painter.setPen(QPen(QColor(0xAA,0xAA,0xAA),1, Qt::SolidLine));
painter.drawRect(r);
if(mStartBackup == i || mEndBackup == i) {
QRect ar = QRectCenter(QSize(16,16),r);
ar.moveTop(0);
painter.drawPixmap(ar, dc); // this works
}
}
QRect out = rect();
out.adjust(0,0,-1,mMax > 30 ? -1 : 0);
painter.setPen(QPen(QColor(0x88,0x88,0x88),1, Qt::SolidLine));
painter.drawRect(out);
}
FMTimeBar::FMTimeBar(int min, int max, int unit, int labelUnit, QWidget *parent)
: mMin(min), mMax(max), mUnit(unit),mLabelUnit(labelUnit), QWidget(parent)
{
bDisabled = false;
mMarginLR = 12;
mPressed = false;
mHoverTime = -1;
mSelectedTime = -1;
// 이벤트 등록해야 포커스 아닌 상태에서도 동작 가능
QCoreApplication::instance()->installEventFilter(this);
}
void FMTimeBar::reset()
{
mPressed = false;
mHoverTime = -1;
mSelectedTime = -1;
mExists.clear();
}
void FMTimeBar::showEvent(QShowEvent* e)
{
// margin 최적화
int cw = (size().width() - (mMarginLR * 2)) / mMax;
mMarginLR = (size().width() - cw * mMax) / 2;
QWidget::showEvent(e);
}
bool FMTimeBar::eventFilter(QObject *watched, QEvent *event)
{
if (watched == this && event->type() == QEvent::MouseMove && !mPressed)
{
QMouseEvent* me = static_cast<QMouseEvent *>(event);
// 날짜가 변경된 경우에만 업데이트
int d = getTime(me->pos());
if(d != mHoverTime) {
mHoverTime = d;
update();
}
return false;
}
return QWidget::eventFilter(watched, event);
}
int FMTimeBar::getTime(QPoint xy)
{
QRect r = rect();
if(xy.x() < mMarginLR || xy.x() > r.size().width() - mMarginLR)
{
return -1;
}
const int barHeight = size().height() / 2;
const int barWidth = (size().width() - (mMarginLR * 2)) / mMax;
const int yo = (r.size().height() - barHeight) / 2;
if (xy.y() < yo || xy.y() > size().height() - yo) {
return -1;
}
return (xy.x() - mMarginLR) / barWidth;
}
void FMTimeBar::drawGrid(QPainter& p)
{
const int offset_x = mMarginLR;
const int w = (size().width() - (offset_x * 2)) / mMax;
const int h = size().height();
const int barHeight = h / 2;
const int y1 = (h - barHeight) / 2 - 4; // -4 = ymargin
p.setPen(QPen(QColor(0x66,0x66,0x66),1, Qt::SolidLine));
// 눈금만 그리기
for(int c=0;c<mMax+1;c++) {
const int x = c*w+mMarginLR;
p.drawLine(x, y1, x, y1+barHeight);
}
// 레이블 유닛 단위로 레이블 표시
QFont font = QFont("Arial");
font.setPixelSize(10);
p.setFont(font);
p.setPen(QPen(QColor(0xAA,0xAA,0xAA),1, Qt::SolidLine));
for(int c=0;c<mMax+1;c++) {
if(c % mLabelUnit == 0) {
QRect cr = QRect(c*w+mMarginLR,y1,w,barHeight);
const int tw = 50;
QRect tr = QRect(cr.left()-tw/2,y1+barHeight,tw,12);
p.drawText(tr,QString::number(c),QTextOption(Qt::AlignCenter));
}
}
}
void FMTimeBar::paintEvent(QPaintEvent *)
{
// 중간에 바가 존재하며 아래쪽에 3시간 단위로 레이블 추가
QPainter painter(this);
drawGrid(painter);
const int barHeight = size().height() / 2;
const int offset_x = mMarginLR;
const int cw = (size().width() - (offset_x * 2)) / mMax;
const int ch = size().height();
const int yo = (ch - barHeight) / 2;
const int ymargin = -4;
// 0~24 마지막 레이블 표시를 위해
for(int c=0;c<mMax+1;c++) {
QRect cr = QRect(c*cw+offset_x,yo+ymargin,cw,barHeight);
if(c < mMax) {
// 영상이 존재하는 시간 일 경우
if(mExists.contains(c))
{
cr.adjust(1,1,0,0);
if (c == mSelectedTime) {
painter.fillRect(cr, QColor(bDisabled ? FM_DISABLED_COLOR : FM_SELECTED_COLOR));
}
else if(c == mHoverTime) {
painter.fillRect(cr,QColor(bDisabled ? FM_DISABLED_COLOR : FM_HOVER_COLOR));
} else {
painter.fillRect(cr,QColor(FM_SILDER_COLOR));
}
}
}
}
QRect out = rect();
out.adjust(0,0,-1,mMax > 30 ? -1 : 0);
painter.setPen(QPen(QColor(0x88,0x88,0x88),1, Qt::SolidLine));
painter.drawRect(out);
}
void FMTimeBar::mousePressEvent(QMouseEvent* event)
{
Q_UNUSED(event)
if(mExists.contains(mHoverTime) && event->button() == Qt::LeftButton) {
mPressed = true;
update();
}
}
void FMTimeBar::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event)
if(mPressed && event->button() == Qt::LeftButton) {
mPressed = false;
if(mHoverTime >= 0) {
mSelectedTime = mHoverTime;
// 선택상태
emit timeSelected(mSelectedTime,NULL,true);
}
update();
}
}
FMDayTime::FMDayTime(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
ZERO_LAYOUT(layout);
mHours = new FMTimeBar(0,24,1,3,this);
mHours->setFixedHeight(55);
layout->addWidget(mHours);
mMinutes = new FMTimeBarMinute(this);
mMinutes->setFixedHeight(55);
layout->addWidget(mMinutes);
setFixedHeight(110);
connect(mHours,SIGNAL(timeSelected(int,QDateTime*,bool)),this,SLOT(onHourSelected(int,QDateTime*,bool)));
connect(mMinutes,SIGNAL(timeSelected(int,QDateTime*,bool)),this,SLOT(onMinuteSelected(int,QDateTime*,bool)));
connect(mMinutes,SIGNAL(backupSelected(QList<RMVideoItem*>&)),this,SLOT(onBackupSelected(QList<RMVideoItem*>&)));
connect(RMPlayer::instance(),SIGNAL(playEvent(PLAY_EVENT,RMVideoItem*)),SLOT(onPlayEvent(PLAY_EVENT,RMVideoItem*)));
}
void FMDayTime::getHour(QSet<int>& hours)
{
hours.clear();
for(int i=0;i<mDayList.size();i++) {
hours.insert(mDayList.at(i)->startTime().time().hour());
}
}
void FMDayTime::loadMinutes(QList<QPair<QSize,RMVideoItem*>>& files)
{
files.clear();
// 2024/09/24 ...(1) 파일등을 처리하기 위해 동일한 '초' 의 가장 최초 파일만 추가
QTime lastTime;
for(int i=0;i<mDayList.size();i++) {
RMVideoItem* item = mDayList.at(i);
QTime t = item->startTime().time();
t = QTime(t.hour(),t.minute(),t.second()); // msec 제거하고 비교하여 제거
if(lastTime.isValid() && t == lastTime) {
continue;
}
lastTime = t;
if(t.hour() == mHours->mSelectedTime) {
int s = t.minute() * 60 + t.second();
int e = s + mDayList.at(i)->durationInMSecs() / 1000;
files.append(QPair<QSize,RMVideoItem*>(QSize(s,e),item));
}
}
mMinutes->createAreaBox();
}
//void FMDayTime::onPlayEvent(PLAY_EVENT event,RMVideoItem* item)
//{
// if(event == PLAY_WILL_LOADED && item != NULL)
// {
// QDate pdate = item->startTime().date();
// // 리스트 또는 다음파일 이동에서 재생되었는지
// // '분' 항목에서 재생되었는지 확인 필요
// QDate cdate = QDate(mCalendar->date.year(),mCalendar->date.month(),mCalendar->mSelectedDay);
// if(cdate != pdate) {
// mCalendar->date.setDate(pdate.year(),pdate.month(),pdate.day());
// mCalendar->mSelectedDay = pdate.day();
// mCalendar->update(); // 다시 그리기
// // 년/월이 변경되었을 경우 달력을 다시 로딩
// if(mCalendar->date.year() != pdate.year() || mCalendar->date.month() != pdate.month())
// {
// onChangeMonth(); // 달력 업데이트
// }
// mCalendar->updateDayItems(); // 날짜리스트 다시 로딩
// emit mCalendar->dateSelected(&mCalendar->mDayItems,false);
// //qInfo() << "DATE CHANGED:" << mCalendar->date << pdate << __FUNCTION__;
// }
// }
//}
void FMDayTime::onPlayTime(QDateTime* playTime, bool moveList)
{
// 날짜는 변경되지 않고 시간만 변경되었을 경우 발생
if(playTime != NULL && *playTime == mLastPlayTime) {
//qInfo() << "LOOP SKIP!!!" << __FUNCTION__;
return;
}
selectHour(playTime->time().hour(),playTime,moveList);
mHours->update(); // 화면갱신해야함
}
void FMDayTime::onClearDate()
{
mDayList.clear();
mMinutes->reset(); // 일단 초기화
mHours->reset(); // "
mHours->update();
mMinutes->update();
}
void FMDayTime::onDateSelected(QList<RMVideoItem*>* dayItems,QDateTime* playTime, bool moveList)
{
// 현재시간과 동일할 경우 SKIP
if(playTime != NULL && *playTime == mLastPlayTime) {
return;
}
// 복사, 보관
mDayList.clear();
mDayList = *dayItems;
mDayList.detach();
mMinutes->reset(); // 일단 초기화
mHours->reset(); // "
// 시간 확인
getHour(mHours->mExists);
// 최초 시간 선택 -> 분데이터 로딩, 캘린더 UI 선택시에만 최초시간 선택
// playTime 이 존재할 경우 최초 시간 무시
if(!mDayList.isEmpty()) {
// 루프방지 확인을 위한 날짜를 지정
selectHour(mDayList.first()->startTime().time().hour(),playTime,moveList);
}
mHours->update();
//mMinutes->update();
}
void FMDayTime::selectHour(int hour,QDateTime* playTime,bool moveList)
{
if(mHours->mExists.contains(hour)){
if(playTime == NULL) {
mHours->mSelectedTime = hour;
} else {
mHours->mSelectedTime = playTime->time().hour();
}
onHourSelected(mHours->mSelectedTime,playTime,moveList);
}
}
void FMDayTime::onHourSelected(int hour,QDateTime* playTime,bool moveList)
{
Q_UNUSED(hour)
// 선택된 분 처리하고
mMinutes->reset();
loadMinutes(mMinutes->mFiles);
if(playTime!= NULL) {
// 분 AREA 확인
mMinutes->mSelectedTime = mMinutes->findAreaFromTime(playTime);
}
// 리스트 업데이트 해야함
// 달력에서 선택된 경우에만 최초 시간으로 업데이트 + 리스트로 이벤트 전송
// 시간등이 선택되었을 경우 해당 분의 파일로 이동
if(moveList){ // playTime == NULL) {
RMVideoItem* first = findFirstSelectedHourItem();
if(first != NULL) {
emit listMove(first);
}
}
mMinutes->update();
}
RMVideoItem* FMDayTime::findFirstSelectedHourItem()
{
for(int i=0;i<mDayList.size();i++) {
QTime t = mDayList.at(i)->startTime().time();
// 선택된 시간만 확인
if(t.hour() == mHours->mSelectedTime) {
return mDayList.at(i);
}
}
return NULL;
}
RMVideoItem* FMDayTime::findSelectedItem()
{
return mMinutes->mAreas.at(mMinutes->mSelectedTime).second;
// for(int i=0;i<mDayList.size();i++) {
// QTime t = mDayList.at(i)->startTime().time();
// // 선택된 시간만 확인
// if(t.hour() == mHours->mSelectedTime && t.minute() == mMinutes->mSelectedTime) {
// return mDayList.at(i);
// }
// }
// return NULL;
}
void FMDayTime::onEnable()
{
//qInfo() << mMinutes->bDisabled << __FUNCTION__;
mMinutes->bDisabled = false;
this->setEnabled(true);
QApplication::restoreOverrideCursor();
}
void FMDayTime::onPlayEvent(PLAY_EVENT event,RMVideoItem* item)
{
Q_UNUSED(item)
if(event == PLAY_DID_LOADED) {
QTimer::singleShot(200,Qt::PreciseTimer,this,SLOT(onEnable()));
}
}
void FMDayTime::onMinuteSelected(int minute, QDateTime* playTime,bool moveList)
{
Q_UNUSED(moveList)
// mMinutes->mSelectedTime
Q_UNUSED(minute)
if(playTime == NULL) {
RMVideoItem* selected = findSelectedItem();
if(selected != NULL) {
// 스스로 재생한 시간을 기록하여 LOOP 방지
mLastPlayTime = selected->startTime();
QApplication::setOverrideCursor(Qt::WaitCursor);
// qInfo() << mMinutes->bDisabled << __FUNCTION__;
mMinutes->bDisabled = true;
//mMinutes->update();
setEnabled(false);
//QTimer::singleShot(500,Qt::PreciseTimer,this,SLOT(onEnable()));
int old = RMVideoFileList::instance()->getPlayIndex();
emit RMVideoFileList::instance()->playItemFound(selected,old);
}
}
//qInfo() << selected->filePath << __FUNCTION__;
}
void FMDayTime::onBackupSelected(QList<RMVideoItem*>& lst)
{
QString start = lst.first()->startTime().toString("HH:mm:ss");
QString end = lst.last()->startTime().toString("HH:mm:ss");
QMessageBox msgBox(QMessageBox::NoIcon,
FM_WSTR(L"ファイルのコピー"),
FM_WSTR(L"ファイルのコピーを開始します。\nコピー範囲 :") + start + QString(" ~ ") + end,
QMessageBox::Yes | QMessageBox::No,
this);
msgBox.setStyleSheet("QLabel{min-width: 260px;}");
msgBox.setWindowFlags(Qt::WindowTitleHint | Qt::Dialog | Qt::CustomizeWindowHint);
msgBox.setButtonText(QMessageBox::Yes, "OK");
msgBox.setButtonText(QMessageBox::No, FM_WSTR(L"キャンセル"));
if (QMessageBox::Yes == msgBox.exec())
{
QString folder = RMApp::openFolder(RMSettings::instance()->lastBackupPath(),false,FM_WSTR(L"ファイルのコピーのパスを選択"));
if(QDir(folder).exists()) {
RMSettings::instance()->setLastBackupPath(folder);
QString copyFolder = "Copy_" + lst.first()->startTime().toString("yyyyMMdd_HHmmss");
folder = QDir::cleanPath(folder + QDir::separator() + copyFolder);
// 폴더 생성
if(!QDir(folder).exists()) {
QDir().mkdir(folder);
}
QApplication::setOverrideCursor(Qt::WaitCursor);
for(int i=0;i<lst.size();i++){
RMVideoItem* item = lst.at(i);
QFileInfo fi(item->filePath);
QString dest = QDir::cleanPath(folder + QDir::separator() + fi.fileName());
if(QFile(dest).exists()) {
QFile(dest).remove();
}
QFile::copy(lst.at(i)->filePath, dest);
if(is_encrypted_an6000(dest.toStdWString().c_str())) {
decrypt_an6000(dest.toStdWString().c_str());
}
}
QApplication::restoreOverrideCursor();
QMessageBox::information(this,FM_WSTR(L"ファイルのコピー"),FM_WSTR(L"完了しました。"),"OK");
}
}
}
#endif // #if (USE_DATE_TIME_LIST)