// rdlogmodel.cpp // // Data model for Rivendell logs // // (C) Copyright 2020-2025 Fred Gleason // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public // License along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // #include "rdapplication.h" #include "rdconf.h" #include "rdescape_string.h" #include "rdlog.h" #include "rdlog_line.h" #include "rdlogmodel.h" RDLogModel::RDLogModel(const QString &logname,bool read_only,QObject *parent) : QAbstractTableModel(parent) { d_log_name=logname; d_read_only=read_only; MakeModel(); } RDLogModel::RDLogModel(QObject *parent) : QAbstractTableModel(parent) { d_read_only=false; MakeModel(); } RDLogModel::~RDLogModel() { if(d_fms!=NULL) { delete d_fms; } if(d_bold_fms!=NULL) { delete d_bold_fms; } for(int i=0;ishowTwelveHourTime()) { d_size_hints[0]=QSize(40+d_bold_fms->horizontalAdvance("T00:00:00.0 AM"),0); } else { d_size_hints[0]=QSize(40+d_bold_fms->horizontalAdvance("T00:00:00.0"),0); } width=d_bold_fms->horizontalAdvance(tr("PLAY")); if(d_bold_fms->horizontalAdvance(tr("SEGUE"))>width) { width=d_bold_fms->horizontalAdvance(tr("SEGUE")); } if(d_bold_fms->horizontalAdvance(tr("STOP"))>width) { width=d_bold_fms->horizontalAdvance(tr("STOP")); } d_size_hints[1]=QSize(10+width,0); d_size_hints[2]=QSize(10+d_bold_fms->horizontalAdvance("000000"),0); width=0; sql=QString("select ")+ "`NAME` "+ // 00 "from `GROUPS`"; q=new RDSqlQuery(sql); while(q->next()) { if(d_bold_fms->horizontalAdvance(q->value(0).toString())>width) { width=d_bold_fms->horizontalAdvance(q->value(0).toString()); } } delete q; d_size_hints[3]=QSize(10+width,0); d_size_hints[4]=QSize(10+d_bold_fms->horizontalAdvance("8:88:88"),0); width=0; for(int i=0;ihorizontalAdvance(RDLogLine::sourceText(src))>width) { width=d_bold_fms->horizontalAdvance(RDLogLine::sourceText(src)); } } d_size_hints[10]=QSize(10+width,0); } int RDLogModel::columnCount(const QModelIndex &parent) const { return d_headers.size(); } int RDLogModel::rowCount(const QModelIndex &parent) const { if(d_read_only) { return lineCount(); } return 1+lineCount(); } QVariant RDLogModel::headerData(int section,Qt::Orientation orient,int role) const { if((orient==Qt::Horizontal)&&(role==Qt::DisplayRole)) { return d_headers.at(section); } return QVariant(); } QVariant RDLogModel::data(const QModelIndex &index,int role) const { QString str; RDLogLine *ll=NULL; int col=index.column(); int row=index.row(); if((ll=logLine(row))!=NULL) { switch((Qt::ItemDataRole)role) { case Qt::DisplayRole: return cellText(col,row,ll); case Qt::DecorationRole: return cellIcon(col,row,ll); case Qt::FontRole: return cellTextFont(col,row,ll); case Qt::ForegroundRole: return cellTextColor(col,row,ll); case Qt::BackgroundRole: return rowBackgroundColor(row,ll); case Qt::SizeHintRole: return d_size_hints.at(col); case Qt::TextAlignmentRole: return d_alignments.at(col); default: break; } } else { // End of log marker if(((Qt::ItemDataRole)role==Qt::DisplayRole)&&(index.column()==5)) { return tr("--- end of log ---"); } } return QVariant(); } bool RDLogModel::exists() { return RDLog::exists(d_log_name); } bool RDLogModel::exists(int line) { if((int)d_log_lines.size()>line) { return true; } return false; } bool RDLogModel::exists(const QTime &hard_time,int except_line) { for(int i=0;itimeType()==RDLogLine::Hard)&& (logLine(i)->startTime(RDLogLine::Logged)==hard_time)&& (i!=except_line)) { return true; } } return false; } QString RDLogModel::logName() const { return d_log_name; } void RDLogModel::setLogName(QString logname) { // clear(); RDLog *log=new RDLog(logname); d_log_name=log->name(); // So we normalize the case delete log; } QString RDLogModel::serviceName() const { return d_service_name; } int RDLogModel::load(bool track_ptrs) { RDLogLine line; QString sql; RDSqlQuery *q; beginResetModel(); // // Get the service name // sql=QString("select `SERVICE` from `LOGS` where ")+ "`NAME`='"+RDEscapeString(d_log_name)+"'"; q=new RDSqlQuery(sql); if(q->next()) { d_service_name=q->value(0).toString(); } delete q; RDLog *log=new RDLog(d_log_name); d_max_id=log->nextId(); delete log; LoadLines(d_log_name,0,track_ptrs); endResetModel(); return d_log_lines.size(); } void RDLogModel::saveModified(RDConfig *config,bool update_tracks) { for(int i=0;ihasBeenModified()) { save(config,update_tracks, i); } } } void RDLogModel::save(RDConfig *config,bool update_tracks,int line) { QString sql; RDSqlQuery *q; if(d_log_name.isEmpty()) { return; } if(line<0) { if(exists()) { sql=QString("delete from `LOG_LINES` where ")+ "`LOG_NAME`='"+RDEscapeString(d_log_name)+"'"; RDSqlQuery::apply(sql); } if (d_log_lines.size() > 0) { QString values = ""; for(int i=0;iclearModified(); } RDLog *log=new RDLog(d_log_name); if(log->nextId()setNextId(nextId()); } if(update_tracks) { log->updateTracks(); } delete log; } int RDLogModel::append(const QString &logname,bool track_ptrs) { return LoadLines(logname,d_max_id,track_ptrs); } void RDLogModel::clear() { if(d_log_lines.size()>0) { beginResetModel(); for(int i=0;ishortDateString(now.date())+" - "+ rda->timeString(now.time())+"\n"; *report+=QString("Log: ")+d_log_name+"\n"; *report+=QString("Effective Airdate: ")+rda->shortDateString(date)+"\n"; *report+="\n"; // // Line Scan // for(int i=0;icartNumber()>0) { sql=QString("select ")+ "`TYPE`,"+ // 00 "`TITLE` "+ // 01 "from `CART` where "+ QString::asprintf("`NUMBER`=%d",logLine(i)->cartNumber()); q=new RDSqlQuery(sql); if(!q->first()) { *report+=QString(" ")+ rda->timeString(logLine(i)->startTime(RDLogLine::Logged))+ QString::asprintf(" - missing cart %06d",logLine(i)->cartNumber())+ "\n"; errs++; } else { if((RDCart::Type)q->value(0).toInt()==RDCart::Audio) { if(logLine(i)->startTime(RDLogLine::Logged).isNull()) { // // Handle events with no logged start time (e.g. manual inserts) // //TODO do we need to verify date here? sql=QString("select `CUT_NAME` from `CUTS` where ")+ QString::asprintf("(`CART_NUMBER`=%u)&&",logLine(i)->cartNumber())+ "((`START_DATETIME` is null)||"+ "(`START_DATETIME`<='"+date.toString("yyyy-MM-dd")+" 23:59:59'))&&"+ "((`END_DATETIME` is null)||"+ "(`END_DATETIME`>='"+date.toString("yyyy-MM-dd")+" 00:00:00'))&&"+ "("+RDDowCode(date.dayOfWeek())+"='Y')&&(LENGTH>0)"; } else { //TODO Do we need to verify date and logLine(i)->startTime? sql=QString("select `CUT_NAME` from `CUTS` where ")+ QString::asprintf("(`CART_NUMBER`=%u)&&",logLine(i)->cartNumber())+ "((`START_DATETIME` is null)||"+ "(`START_DATETIME`<='"+date.toString("yyyy-MM-dd")+" "+ logLine(i)->startTime(RDLogLine::Logged).toString("hh:mm:ss")+ "'))&&"+ "((`END_DATETIME` is null)||"+ "(`END_DATETIME`>='"+date.toString("yyyy-MM-dd")+" "+ logLine(i)->startTime(RDLogLine::Logged).toString("hh:mm:ss")+ "'))&&"+ "((`START_DAYPART` is null)||"+ "(`START_DAYPART`<='"+ logLine(i)->startTime(RDLogLine::Logged). toString("hh:mm:ss")+"'))&&"+ "((`END_DAYPART` is null)||"+ "(`END_DAYPART`>='"+logLine(i)->startTime(RDLogLine::Logged). toString("hh:mm:ss")+"'))&&"+ "("+RDDowCode(date.dayOfWeek())+"='Y')&&(LENGTH>0)"; } q1=new RDSqlQuery(sql); if(!q1->first()) { *report+=QString(" ")+ rda->timeString(logLine(i)->startTime(RDLogLine::Logged))+ QString::asprintf(" - cart %06d [",logLine(i)->cartNumber())+ q->value(1).toString()+"] "+QObject::tr("is not playable")+"\n"; errs++; } delete q1; } } delete q; } } *report+="\n"; if(errs==1) { *report+=QString::asprintf("%d validation exception found.\n\n",errs); } else { *report+=QString::asprintf("%d validation exceptions found.\n\n",errs); } return errs; } void RDLogModel::update(int line) { if(d_log_name.isEmpty()) { return; } if(d_log_lines[line]->cartNumber()>0) { QString sql=QString("select ")+ "`CART`.`TYPE`,"+ // 00 "`CART`.`GROUP_NAME`,"+ // 01 "`CART`.`TITLE`,"+ // 02 "`CART`.`ARTIST`,"+ // 03 "`CART`.`ALBUM`,"+ // 04 "`CART`.`YEAR`,"+ // 05 "`CART`.`LABEL`,"+ // 06 "`CART`.`CLIENT`,"+ // 07 "`CART`.`AGENCY`,"+ // 08 "`CART`.`USER_DEFINED`,"+ // 09 "`CART`.`FORCED_LENGTH`,"+ // 10 "`CART`.`CUT_QUANTITY`,"+ // 11 "`CART`.`LAST_CUT_PLAYED`,"+ // 12 "`CART`.`PLAY_ORDER`,"+ // 13 "`CART`.`ENFORCE_LENGTH`,"+ // 14 "`CART`.`PRESERVE_PITCH`,"+ // 15 "`CART`.`PUBLISHER`,"+ // 16 "`CART`.`COMPOSER`,"+ // 17 "`CART`.`USAGE_CODE`,"+ // 18 "`CART`.`AVERAGE_SEGUE_LENGTH`,"+ // 19 "`CART`.`VALIDITY`,"+ // 20 "`CART`.`NOTES`,"+ // 21 "`GROUPS`.`COLOR` "+ // 22 "from `CART` left join `GROUPS` "+ "on `CART`.`GROUP_NAME`=`GROUPS`.`NAME` where "+ QString::asprintf("`CART`.`NUMBER`=%u",d_log_lines[line]->cartNumber()); RDSqlQuery *q=new RDSqlQuery(sql); if(q->first()) { switch((RDCart::Type)q->value(0).toInt()) { case RDCart::Audio: d_log_lines[line]->setType(RDLogLine::Cart); break; case RDCart::Macro: d_log_lines[line]->setType(RDLogLine::Macro); break; default: break; } d_log_lines[line]-> setCartType((RDCart::Type)q->value(0).toInt()); // Cart Type d_log_lines[line]->setGroupName(q->value(1).toString()); // Group Name d_log_lines[line]->setTitle(q->value(2).toString()); // Title d_log_lines[line]->setArtist(q->value(3).toString()); // Artist d_log_lines[line]->setPublisher(q->value(16).toString()); // Publisher d_log_lines[line]->setComposer(q->value(17).toString()); // Composer d_log_lines[line]->setAlbum(q->value(4).toString()); // Album d_log_lines[line]->setYear(q->value(5).toDate()); // Year d_log_lines[line]->setLabel(q->value(6).toString()); // Label d_log_lines[line]->setClient(q->value(7).toString()); // Client d_log_lines[line]->setAgency(q->value(8).toString()); // Agency d_log_lines[line]->setUserDefined(q->value(9).toString()); // User Defined d_log_lines[line]->setUsageCode((RDCart::UsageCode)q->value(16).toInt()); d_log_lines[line]->setForcedLength(q->value(10).toUInt()); // Forced Length d_log_lines[line]->setAverageSegueLength(q->value(19).toUInt()); d_log_lines[line]->setCutQuantity(q->value(11).toUInt()); // Cut Quantity d_log_lines[line]->setLastCutPlayed(q->value(12).toUInt()); // Last Cut Played d_log_lines[line]-> setPlayOrder((RDCart::PlayOrder)q->value(13).toUInt()); // Play Order d_log_lines[line]-> setEnforceLength(RDBool(q->value(14).toString())); // Enforce Length d_log_lines[line]-> setPreservePitch(RDBool(q->value(15).toString())); // Preserve Pitch d_log_lines[line]->setValidity((RDCart::Validity)q->value(20).toInt()); d_log_lines[line]->setCartNotes(q->value(21).toString()); // Cart Notes d_log_lines[line]->setGroupColor(q->value(22).toString()); // Group Color } else { d_log_lines[line]->setValidity(RDCart::NeverValid); } delete q; } emitDataChanged(line); } int RDLogModel::lineCount() const { return d_log_lines.size(); } void RDLogModel::insert(int line,int num_lines,bool preserve_trans) { if(!preserve_trans) { if((line>0)&&(d_log_lines[line-1]!=NULL)) { d_log_lines[line-1]->setEndPoint(-1,RDLogLine::LogPointer); d_log_lines[line-1]->setSegueStartPoint(-1,RDLogLine::LogPointer); d_log_lines[line-1]->setSegueEndPoint(-1,RDLogLine::LogPointer); emitDataChanged(line-1); } if(line<(lineCount()-1)) { d_log_lines[line]->setStartPoint(-1,RDLogLine::LogPointer); d_log_lines[line]->setHasCustomTransition(false); emitDataChanged(line); } } if(linesetId(++d_max_id); } endInsertRows(); return; } if(line>=lineCount()) { beginInsertRows(QModelIndex(),lineCount(),lineCount()+num_lines-1); for(int i=0;isetId(++d_max_id); } endInsertRows(); return; } } void RDLogModel::remove(int line,int num_lines,bool preserve_trans) { if(!preserve_trans) { if(line>0) { d_log_lines[line-1]->setEndPoint(-1,RDLogLine::LogPointer); d_log_lines[line-1]->setSegueStartPoint(-1,RDLogLine::LogPointer); d_log_lines[line-1]->setSegueEndPoint(-1,RDLogLine::LogPointer); emitDataChanged(line-1); } if(line<((int)d_log_lines.size()-num_lines)) { d_log_lines[line+num_lines]->setStartPoint(-1,RDLogLine::LogPointer); d_log_lines[line+num_lines]->setHasCustomTransition(false); emitDataChanged(line+num_lines); } } beginRemoveRows(QModelIndex(),line,line+num_lines-1); for(int i=0;i=lineCount()) { to_line=lineCount()-1; dest_offset=0; } if(((destline=logLine(to_line+dest_offset))==NULL)|| (srcline=logLine(from_line+src_offset))==NULL) { remove(to_line+dest_offset,1); return; } *destline=*srcline; destline->clearTrackData(RDLogLine::AllTrans); remove(from_line+src_offset,1); } void RDLogModel::copy(int from_line,int to_line) { RDLogLine *srcline; RDLogLine *destline; insert(to_line,1); if(((destline=logLine(to_line))==NULL)|| (srcline=logLine(from_line))==NULL) { remove(to_line,1); return; } *destline=*srcline; destline->clearExternalData(); destline->clearTrackData(RDLogLine::AllTrans); destline->setSource(RDLogLine::Manual); } int RDLogModel::length(int from_line,int to_line,QTime *sched_time) { if(sched_time!=NULL) { *sched_time=QTime(); } if(to_line<0) { to_line=lineCount(); for(int i=from_line;itimeType()==RDLogLine::Hard) { to_line=i; i=lineCount(); if(sched_time!=NULL) { *sched_time=logLine(i)->startTime(RDLogLine::Logged); } } } } int len=0; for(int i=from_line;i=lineCount())||(logLine(i+1)->transType()!=RDLogLine::Segue)|| (logLine(i)->segueStartPoint()<0)) { len+=logLine(i)->forcedLength(); } else { len+=logLine(i)->segueStartPoint()-logLine(i)->startPoint(); } } return len; } int RDLogModel::lengthToStop(int from_line,QTime *sched_time) { int to_line=-1; for(int i=from_line;itransType()==RDLogLine::Stop) { to_line=i; } } if(to_line<0) { return -1; } return length(from_line,to_line,sched_time); } bool RDLogModel::blockLength(int *nominal_length,int *actual_length,int line) { *nominal_length=0; *actual_length=0; QTime start_time; int start_line=-1; QTime end_time; int end_line=-1; if((line<0)||(line>(lineCount()-1))) { *nominal_length=0; *actual_length=0; return false; } // // Find Block Start // for(int i=line;i>=0;i--) { if(logLine(i)->timeType()==RDLogLine::Hard) { start_time=logLine(i)->startTime(RDLogLine::Logged); start_line=i; i=-1; } } if(start_line<0) { return false; } // // Find Block End // for(int i=line+1;itimeType()==RDLogLine::Hard) { end_time=logLine(i)->startTime(RDLogLine::Logged); end_line=i; i=lineCount(); } } if(end_line<0) { return false; } // // Calculate Lengths // *nominal_length=start_time.msecsTo(end_time); for(int i=start_line;itransType()==RDLogLine::Segue))) { *actual_length+=logLine(i)->averageSegueLength(); } else { *actual_length+=logLine(i)->forcedLength(); } } return true; } QTime RDLogModel::blockStartTime(int line) const { int actual_length=0; QTime start_time(0,0,0); QTime return_time(0,0,0); int start_line=0; if((line<0)||(line>(lineCount()-1))) { actual_length=0; return return_time; } // // Find Block Start // for(int i=line;i>=0;i--) { if(logLine(i)->timeType()==RDLogLine::Hard) { start_time=logLine(i)->startTime(RDLogLine::Logged); start_line=i; i=-1; } } if(start_line == line) { return start_time; } // // Calculate Lengths // for(int i=start_line;itransType()==RDLogLine::Segue))) { if(logLine(i)->segueStartPoint(RDLogLine::LogPointer)<0) { actual_length+=100*(logLine(i)->averageSegueLength()/100); } else { if(logLine(i)->startPoint(RDLogLine::LogPointer)<0) { actual_length+=(logLine(i)->segueStartPoint(RDLogLine::LogPointer)- logLine(i)->startPoint(RDLogLine::CartPointer)); } else { actual_length+=(logLine(i)->segueStartPoint(RDLogLine::LogPointer)- logLine(i)->startPoint(RDLogLine::LogPointer)); } } } else { actual_length+=100*(logLine(i)->forcedLength()/100); } } return_time=start_time.addMSecs(actual_length); return return_time; } RDLogLine *RDLogModel::logLine(int line) const { if((line<0)||(line>=d_log_lines.size())) { return NULL; } return d_log_lines[line]; } void RDLogModel::setLogLine(int line,RDLogLine *ll) { int id=d_log_lines[line]->id(); *d_log_lines[line]=*ll; d_log_lines[line]->setId(id); } RDLogLine *RDLogModel::loglineById(int id, bool ignore_holdovers) const { int line = lineById(id, ignore_holdovers); if(line == -1) return NULL; else return d_log_lines[line]; } int RDLogModel::lineById(int id, bool ignore_holdovers) const { for(int i=0;iisHoldover()) { continue; } if(d_log_lines[i]->id()==id) { return i; } } return -1; } int RDLogModel::lineByStartHour(int hour,RDLogLine::StartTimeType type) const { for(int i=0;istartTime(type).isNull()&& (d_log_lines[i]->startTime(type).hour()==hour)) { return i; } } return -1; } int RDLogModel::lineByStartHour(int hour) const { int line=-1; if((line=lineByStartHour(hour,RDLogLine::Initial))<0) { if((line=lineByStartHour(hour,RDLogLine::Predicted))<0) { line=lineByStartHour(hour,RDLogLine::Imported); } } return line; } int RDLogModel::nextTimeStart(QTime after) { for(int i=0;itimeType()==RDLogLine::Hard)&& (d_log_lines[i]->startTime(RDLogLine::Logged)>after)) { return i; } } return -1; } RDLogLine::TransType RDLogModel::nextTransType(int line) { if(line<(lineCount()-1)) { return logLine(line+1)->transType(); } return RDLogLine::Stop; } void RDLogModel::removeCustomTransition(int line) { if((line<0)||(line>(lineCount()-1))) { return; } logLine(line)->setStartPoint(-1,RDLogLine::LogPointer); logLine(line)->setFadeupPoint(-1,RDLogLine::LogPointer); logLine(line)->setFadeupGain(0); logLine(line)->setDuckUpGain(0); logLine(line)->setHasCustomTransition(false); if(line<1) { return; } if(logLine(line-1)->type()!=RDLogLine::Track) { logLine(line-1)->setEndPoint(-1,RDLogLine::LogPointer); logLine(line-1)->setSegueStartPoint(-1,RDLogLine::LogPointer); logLine(line-1)->setSegueEndPoint(-1,RDLogLine::LogPointer); logLine(line-1)->setSegueGain(RD_FADE_DEPTH); logLine(line-1)->setFadedownPoint(-1,RDLogLine::LogPointer); logLine(line-1)->setFadedownGain(0); logLine(line-1)->setDuckDownGain(0); return; } if(line<2) { return; } logLine(line-2)->setEndPoint(-1,RDLogLine::LogPointer); logLine(line-2)->setSegueStartPoint(-1,RDLogLine::LogPointer); logLine(line-2)->setSegueEndPoint(-1,RDLogLine::LogPointer); logLine(line-2)->setSegueGain(RD_FADE_DEPTH); logLine(line-2)->setFadedownPoint(-1,RDLogLine::LogPointer); logLine(line-2)->setFadedownGain(0); logLine(line-2)->setDuckDownGain(0); } int RDLogModel::nextId() const { int id=-1; for(int i=0;iid()>id) { id=d_log_lines[i]->id(); } } return id+1; } int RDLogModel::nextLinkId() const { int id=-1; for(int i=0;ilinkId()>id) { id=d_log_lines[i]->linkId(); } } return id+1; } QString RDLogModel::xml() const { QString ret; ret+="\n"; for(int i=0;ixml(i); } ret+="\n"; return ret; } void RDLogModel::setTransition(int line,RDLogLine::TransType trans) { RDLogLine *ll=NULL; if((ll=logLine(line))!=NULL) { if(ll->transType()!=trans) { ll->setTransType(trans); emitDataChanged(line); } } } void RDLogModel::processNotification(RDNotification *notify) { RDLogLine *ll=NULL; if(notify->type()==RDNotification::CartType) { unsigned cartnum=notify->id().toUInt(); for(int i=0;itype()==RDLogLine::Cart)||(ll->type()==RDLogLine::Macro))&& (ll->cartNumber()==cartnum)) { ll->refreshCart(); emitDataChanged(i); } } } } void RDLogModel::setStartTimeStyle(RDLogModel::StartTimeStyle style) { if(d_start_time_style!=style) { d_start_time_style=style; emit dataChanged(createIndex(0,0),createIndex(lineCount(),0)); } } QString RDLogModel::StartTimeString(int line) const { RDLogLine *ll=logLine(line); if(ll!=NULL) { QString code="H"; switch(ll->timeType()) { case RDLogLine::Hard: if(ll->graceTime()<0) { code="S"; } return code+rda->tenthsTimeString(ll->startTime(RDLogLine::Logged)); break; default: if(d_start_time_style==RDLogModel::Estimated) { if(ll->startTime(RDLogLine::Predicted).isNull()) { return rda->tenthsTimeString(blockStartTime(line)); } else { return rda->tenthsTimeString(ll->startTime(RDLogLine::Predicted)); } } else { // Scheduled if(ll->startTime(RDLogLine::Logged).isNull()) { return QString(""); } else { return rda->tenthsTimeString(ll->startTime(RDLogLine::Logged)); } } break; } } return QString(); } int RDLogModel::LoadLines(const QString &logname,int id_offset,bool track_ptrs) { RDLogLine line; RDSqlQuery *q1; QString sql; RDSqlQuery *q; bool prev_custom=false; unsigned lines=0; unsigned start_line=d_log_lines.size(); // // Load the group color table // std::map group_colors; sql=QString("select ")+ "`NAME`,"+ // 00 "`COLOR` "+ // 01 "from `GROUPS`"; q=new RDSqlQuery(sql); while(q->next()) { group_colors[q->value(0).toString()]=QColor(q->value(1).toString()); } delete q; // // Load log lines // sql=QString("select ")+ "`LOG_LINES`.`LINE_ID`,"+ // 00 "`LOG_LINES`.`CART_NUMBER`,"+ // 01 "`LOG_LINES`.`START_TIME`,"+ // 02 "`LOG_LINES`.`TIME_TYPE`,"+ // 03 "`LOG_LINES`.`TRANS_TYPE`,"+ // 04 "`LOG_LINES`.`START_POINT`,"+ // 05 "`LOG_LINES`.`END_POINT`,"+ // 06 "`LOG_LINES`.`SEGUE_START_POINT`,"+ // 07 "`LOG_LINES`.`SEGUE_END_POINT`,"+ // 08 "`CART`.`TYPE`,"+ // 09 "`CART`.`GROUP_NAME`,"+ // 10 "`CART`.`TITLE`,"+ // 11 "`CART`.`ARTIST`,"+ // 12 "`CART`.`ALBUM`,"+ // 13 "`CART`.`YEAR`,"+ // 14 "`CART`.`LABEL`,"+ // 15 "`CART`.`CLIENT`,"+ // 16 "`CART`.`AGENCY`,"+ // 17 "`CART`.`USER_DEFINED`,"+ // 18 "`CART`.`CONDUCTOR`,"+ // 19 "`CART`.`SONG_ID`,"+ // 20 "`CART`.`FORCED_LENGTH`,"+ // 21 "`CART`.`CUT_QUANTITY`,"+ // 22 "`CART`.`LAST_CUT_PLAYED`,"+ // 23 "`CART`.`PLAY_ORDER`,"+ // 24 "`CART`.`ENFORCE_LENGTH`,"+ // 25 "`CART`.`PRESERVE_PITCH`,"+ // 26 "`LOG_LINES`.`TYPE`,"+ // 27 "`LOG_LINES`.`COMMENT`,"+ // 28 "`LOG_LINES`.`LABEL`,"+ // 29 "`LOG_LINES`.`GRACE_TIME`,"+ // 30 "`LOG_LINES`.`SOURCE`,"+ // 31 "`LOG_LINES`.`EXT_START_TIME`,"+ // 32 "`LOG_LINES`.`EXT_LENGTH`,"+ // 33 "`LOG_LINES`.`EXT_DATA`,"+ // 34 "`LOG_LINES`.`EXT_EVENT_ID`,"+ // 35 "`LOG_LINES`.`EXT_ANNC_TYPE`,"+ // 36 "`LOG_LINES`.`EXT_CART_NAME`,"+ // 37 "`CART`.`ASYNCRONOUS`,"+ // 38 "`LOG_LINES`.`FADEUP_POINT`,"+ // 39 "`LOG_LINES`.`FADEUP_GAIN`,"+ // 40 "`LOG_LINES`.`FADEDOWN_POINT`,"+ // 41 "`LOG_LINES`.`FADEDOWN_GAIN`,"+ // 42 "`LOG_LINES`.`SEGUE_GAIN`,"+ // 43 "`CART`.`PUBLISHER`,"+ // 44 "`CART`.`COMPOSER`,"+ // 45 "`CART`.`USAGE_CODE`,"+ // 46 "`CART`.`AVERAGE_SEGUE_LENGTH`,"+ // 47 "`LOG_LINES`.`LINK_EVENT_NAME`,"+ // 48 "`LOG_LINES`.`LINK_START_TIME`,"+ // 49 "`LOG_LINES`.`LINK_LENGTH`,"+ // 50 "`LOG_LINES`.`LINK_ID`,"+ // 51 "`LOG_LINES`.`LINK_EMBEDDED`,"+ // 52 "`LOG_LINES`.`ORIGIN_USER`,"+ // 53 "`LOG_LINES`.`ORIGIN_DATETIME`,"+ // 54 "`CART`.`VALIDITY`,"+ // 55 "`LOG_LINES`.`LINK_START_SLOP`,"+ // 56 "`LOG_LINES`.`LINK_END_SLOP`,"+ // 57 "`LOG_LINES`.`DUCK_UP_GAIN`,"+ // 58 "`LOG_LINES`.`DUCK_DOWN_GAIN`,"+ // 59 "`CART`.`START_DATETIME`,"+ // 60 "`CART`.`END_DATETIME`,"+ // 61 "`LOG_LINES`.`EVENT_LENGTH`,"+ // 62 "`CART`.`USE_EVENT_LENGTH`,"+ // 63 "`CART`.`NOTES` "+ // 64 "from `LOG_LINES` left join `CART` "+ "on `LOG_LINES`.`CART_NUMBER`=`CART`.`NUMBER` where "+ "`LOG_LINES`.`LOG_NAME`='"+RDEscapeString(logname)+"' "+ "order by `COUNT`"; q=new RDSqlQuery(sql); if(q->size()<=0) { delete q; return 0; } beginInsertRows(QModelIndex(),lineCount(),q->size()+lineCount()-1); for(int i=0;isize();i++) { lines++; line.clear(); q->next(); line.setType((RDLogLine::Type)q->value(27).toInt()); // Type line.setId(q->value(0).toInt()+id_offset); // Log Line ID if((q->value(0).toInt()+id_offset)>d_max_id) { d_max_id=q->value(0).toInt()+id_offset; } line.setStartTime(RDLogLine::Imported, QTime(0,0,0).addMSecs(q->value(2).toInt())); // Start Time line.setStartTime(RDLogLine::Logged, QTime(0,0,0).addMSecs(q->value(2).toInt())); line. setTimeType((RDLogLine::TimeType)q->value(3).toInt()); // Time Type line. setTransType((RDLogLine::TransType)q->value(4).toInt()); // Trans Type line.setMarkerComment(q->value(28).toString()); // Comment line.setMarkerLabel(q->value(29).toString()); // Label line.setGraceTime(q->value(30).toInt()); // Grace Time line.setUseEventLength(RDBool(q->value(63).toString())); // Use Event Length line.setEventLength(q->value(62).toInt()); // Event Length line.setSource((RDLogLine::Source)q->value(31).toUInt()); line.setLinkEventName(q->value(48).toString()); // Link Event Name line.setLinkStartTime(QTime(0,0,0).addMSecs(q->value(49).toInt())); // Link Start Time line.setLinkLength(q->value(50).toInt()); // Link Length line.setLinkStartSlop(q->value(56).toInt()); // Link Start Slop line.setLinkEndSlop(q->value(57).toInt()); // Link End Slop line.setLinkId(q->value(51).toInt()); // Link ID line.setLinkEmbedded(RDBool(q->value(52).toString())); // Link Embedded line.setOriginUser(q->value(53).toString()); // Origin User line.setOriginDateTime(q->value(54).toDateTime()); // Origin DateTime switch(line.type()) { case RDLogLine::Cart: line.setCartNumber(q->value(1).toUInt()); // Cart Number line.setStartPoint(q->value(5).toInt(),RDLogLine::LogPointer); line.setEndPoint(q->value(6).toInt(),RDLogLine::LogPointer); line.setSegueStartPoint(q->value(7).toInt(),RDLogLine::LogPointer); line.setSegueEndPoint(q->value(8).toInt(),RDLogLine::LogPointer); line.setCartType((RDCart::Type)q->value(9).toInt()); // Cart Type line.setGroupName(q->value(10).toString()); // Group Name line.setGroupColor(group_colors[q->value(10).toString()]); line.setTitle(q->value(11).toString()); // Title line.setArtist(q->value(12).toString()); // Artist line.setPublisher(q->value(44).toString()); // Publisher line.setComposer(q->value(45).toString()); // Composer line.setAlbum(q->value(13).toString()); // Album line.setYear(q->value(14).toDate()); // Year line.setLabel(q->value(15).toString()); // Label line.setClient(q->value(16).toString()); // Client line.setAgency(q->value(17).toString()); // Agency line.setUserDefined(q->value(18).toString()); // User Defined line.setCartNotes(q->value(64).toString()); // Cart Notes line.setConductor(q->value(19).toString()); // Conductor line.setSongId(q->value(20).toString()); // Song ID line.setUsageCode((RDCart::UsageCode)q->value(46).toInt()); line.setForcedLength(q->value(21).toUInt()); // Forced Length if(q->value(7).toInt()<0) { line.setAverageSegueLength(q->value(47).toInt()); } else { line. setAverageSegueLength(q->value(7).toInt()-q->value(5).toInt()); } line.setCutQuantity(q->value(22).toUInt()); // Cut Quantity line.setLastCutPlayed(q->value(23).toUInt()); // Last Cut Played line. setPlayOrder((RDCart::PlayOrder)q->value(24).toUInt()); // Play Ord line. setEnforceLength(RDBool(q->value(25).toString())); // Enforce Length line. setPreservePitch(RDBool(q->value(26).toString())); // Preserve Pitch if(!q->value(32).isNull()) { // Ext Start Time line.setExtStartTime(q->value(32).toTime()); } if(!q->value(33).isNull()) { // Ext Length line.setExtLength(q->value(33).toInt()); } if(!q->value(34).isNull()) { // Ext Data line.setExtData(q->value(34).toString()); } if(!q->value(35).isNull()) { // Ext Event ID line.setExtEventId(q->value(35).toString()); } if(!q->value(36).isNull()) { // Ext Annc. Type line.setExtAnncType(q->value(36).toString()); } if(!q->value(37).isNull()) { // Ext Cart Name line.setExtCartName(q->value(37).toString()); } if(!q->value(39).isNull()) { // FadeUp Point line.setFadeupPoint(q->value(39).toInt(),RDLogLine::LogPointer); } if(!q->value(40).isNull()) { // FadeUp Gain line.setFadeupGain(q->value(40).toInt()); } if(!q->value(41).isNull()) { // FadeDown Point line.setFadedownPoint(q->value(41).toInt(),RDLogLine::LogPointer); } if(!q->value(42).isNull()) { // FadeDown Gain line.setFadedownGain(q->value(42).toInt()); } if(!q->value(43).isNull()) { // Segue Gain line.setSegueGain(q->value(43).toInt()); } if(!q->value(58).isNull()) { // Duck Up Gain line.setDuckUpGain(q->value(58).toInt()); } if(!q->value(59).isNull()) { // Duck Down Gain line.setDuckDownGain(q->value(59).toInt()); } if(!q->value(60).isNull()) { // Start Datetime line.setStartDatetime(q->value(60).toDateTime()); } if(!q->value(61).isNull()) { // End Datetime line.setEndDatetime(q->value(61).toDateTime()); } line.setValidity((RDCart::Validity)q->value(55).toInt()); // Validity break; case RDLogLine::Macro: line.setCartNumber(q->value(1).toUInt()); // Cart Number line.setCartType((RDCart::Type)q->value(9).toInt()); // Cart Type line.setGroupName(q->value(10).toString()); // Group Name line.setGroupColor(group_colors[q->value(10).toString()]); line.setTitle(q->value(11).toString()); // Title line.setArtist(q->value(12).toString()); // Artist line.setPublisher(q->value(44).toString()); // Publisher line.setComposer(q->value(45).toString()); // Composer line.setAlbum(q->value(13).toString()); // Album line.setYear(q->value(14).toDate()); // Year line.setLabel(q->value(15).toString()); // Label line.setClient(q->value(16).toString()); // Client line.setAgency(q->value(17).toString()); // Agency line.setUserDefined(q->value(18).toString()); // User Defined line.setCartNotes(q->value(64).toString()); // Cart Notes line.setForcedLength(q->value(21).toUInt()); // Forced Length line.setAverageSegueLength(q->value(21).toInt()); if(!q->value(32).isNull()) { // Ext Start Time line.setExtStartTime(q->value(32).toTime()); } if(!q->value(33).isNull()) { // Ext Length line.setExtLength(q->value(33).toInt()); } if(!q->value(34).isNull()) { // Ext Data line.setExtData(q->value(34).toString()); } if(!q->value(35).isNull()) { // Ext Event ID line.setExtEventId(q->value(35).toString()); } if(!q->value(36).isNull()) { // Ext Annc. Type line.setExtAnncType(q->value(36).toString()); } if(!q->value(37).isNull()) { // Ext Cart Name line.setExtCartName(q->value(37).toString()); } if(!q->value(38).isNull()) { // Asyncronous line.setAsyncronous(RDBool(q->value(38).toString())); } break; case RDLogLine::Marker: break; case RDLogLine::Track: break; case RDLogLine::Chain: sql=QString("select `DESCRIPTION` from `LOGS` where ")+ "`NAME`='"+RDEscapeString(line.markerLabel())+"'"; q1=new RDSqlQuery(sql); if(q1->first()) { line.setMarkerComment(q1->value(0).toString()); } delete q1; break; default: break; } line.setHasCustomTransition(prev_custom||(q->value(5).toInt()>=0)||\ (q->value(39).toInt()>=0)); if(line.type()==RDLogLine::Cart) { prev_custom=(q->value(6).toInt()>=0)||(q->value(7).toInt()>=0)|| (q->value(8).toInt()>=0)||(q->value(41).toInt()>=0); } else { prev_custom=false; } line.clearModified(); d_log_lines.push_back(new RDLogLine(line)); } endInsertRows(); delete q; if(track_ptrs) { // // Load default cart pointers for "representative" cuts. This is // really only useful when setting up a voice tracker. // for(int i=start_line;icartType()==RDCart::Audio) { sql=QString("select ")+ "`START_POINT`,"+ // 00 "`END_POINT`,"+ // 01 "`SEGUE_START_POINT`,"+ // 02 "`SEGUE_END_POINT`,"+ // 03 "`TALK_START_POINT`,"+ // 04 "`TALK_END_POINT`,"+ // 05 "`HOOK_START_POINT`,"+ // 06 "`HOOK_END_POINT`,"+ // 07 "`FADEUP_POINT`,"+ // 08 "`FADEDOWN_POINT`,"+ // 09 "`CUT_NAME`,"+ // 10 "`ORIGIN_NAME`,"+ // 11 "`ORIGIN_DATETIME`,"+ // 12 "`DESCRIPTION`,"+ // 13 "`ISRC`,"+ // 14 "`ISCI`,"+ // 15 "`RECORDING_MBID`,"+ // 16 "`RELEASE_MBID` "+ // 17 "from `CUTS` where "+ QString::asprintf("`CART_NUMBER`=%u ",ll->cartNumber())+ "order by `CUT_NAME`"; q=new RDSqlQuery(sql); if(q->first()) { ll->setStartPoint(q->value(0).toInt(),RDLogLine::CartPointer); ll->setEndPoint(q->value(1).toInt(),RDLogLine::CartPointer); ll->setSegueStartPoint(q->value(2).toInt(),RDLogLine::CartPointer); ll->setSegueEndPoint(q->value(3).toInt(),RDLogLine::CartPointer); ll->setTalkStartPoint(q->value(4).toInt()); ll->setTalkEndPoint(q->value(5).toInt()); ll->setHookStartPoint(q->value(6).toInt()); ll->setHookEndPoint(q->value(7).toInt()); ll->setFadeupPoint(q->value(8).toInt(),RDLogLine::CartPointer); ll->setFadedownPoint(q->value(9).toInt(),RDLogLine::CartPointer); ll->setCutNumber(RDCut::cutNumber(q->value(10).toString())); ll->setOriginUser(q->value(11).toString()); ll->setOriginDateTime(q->value(12).toDateTime()); ll->setDescription(q->value(13).toString()); ll->setIsrc(q->value(14).toString()); ll->setIsci(q->value(15).toString()); ll->setRecordingMbId(q->value(16).toString()); ll->setReleaseMbId(q->value(17).toString()); } delete q; } } } return lines; } void RDLogModel::InsertLines(QString values) { QString sql; RDSqlQuery *q; sql = QString("insert into LOG_LINES (")+ "`LOG_NAME`,"+ // 00 "`LINE_ID`,"+ // 01 "`COUNT`,"+ // 02 "`CART_NUMBER`,"+ // 03 "`START_TIME`,"+ // 04 "`TIME_TYPE`,"+ // 05 "`TRANS_TYPE`,"+ // 06 "`START_POINT`,"+ // 07 "`END_POINT`,"+ // 08 "`SEGUE_START_POINT`,"+ // 09 "`SEGUE_END_POINT`,"+ // 10 "`TYPE`,"+ // 11 "`COMMENT`,"+ // 12 "`LABEL`,"+ // 13 "`GRACE_TIME`,"+ // 14 "`SOURCE`,"+ // 15 "`EXT_START_TIME`,"+ // 16 "`EXT_LENGTH`,"+ // 17 "`EXT_DATA`,"+ // 18 "`EXT_EVENT_ID`,"+ // 19 "`EXT_ANNC_TYPE`,"+ // 20 "`EXT_CART_NAME`,"+ // 21 "`FADEUP_POINT`,"+ // 22 "`FADEUP_GAIN`,"+ // 23 "`FADEDOWN_POINT`,"+ // 24 "`FADEDOWN_GAIN`,"+ // 25 "`SEGUE_GAIN`,"+ // 26 "`LINK_EVENT_NAME`,"+ // 27 "`LINK_START_TIME`,"+ // 28 "`LINK_LENGTH`,"+ // 29 "`LINK_ID`,"+ // 30 "`LINK_EMBEDDED`,"+ // 31 "`ORIGIN_USER`,"+ // 32 "`ORIGIN_DATETIME`,"+ // 33 "`LINK_START_SLOP`,"+ // 34 "`LINK_END_SLOP`,"+ // 35 "`DUCK_UP_GAIN`,"+ // 36 "`DUCK_DOWN_GAIN`,"+ // 37 "`EVENT_LENGTH`) "+ // 38 "values "+values; q=new RDSqlQuery(sql); delete q; } void RDLogModel::InsertLineValues(QString *query, int line) { // one line to save query space RDLogLine *ll=d_log_lines[line]; QString sql=QString("(")+ "'"+RDEscapeString(d_log_name)+"',"+ QString::asprintf("%d,",ll->id())+ QString::asprintf("%d,",line)+ QString::asprintf("%u,",ll->cartNumber())+ QString::asprintf("%d,",QTime(0,0,0).msecsTo(ll->startTime(RDLogLine::Logged)))+ QString::asprintf("%d,",ll->timeType())+ QString::asprintf("%d,",ll->transType())+ QString::asprintf("%d,",ll->startPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->endPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->segueStartPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->segueEndPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->type())+ "'"+RDEscapeString(ll->markerComment())+"',"+ "'"+RDEscapeString(ll->markerLabel())+"',"+ QString::asprintf("%d,",ll->graceTime())+ QString::asprintf("%d,",ll->source())+ RDCheckDateTime(ll->extStartTime(),"hh:mm:ss")+","+ QString::asprintf("%d,",ll->extLength())+ "'"+RDEscapeString(ll->extData())+"',"+ "'"+RDEscapeString(ll->extEventId())+"',"+ "'"+RDEscapeString(ll->extAnncType())+"',"+ "'"+RDEscapeString(ll->extCartName())+"',"+ QString::asprintf("%d,",ll->fadeupPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->fadeupGain())+ QString::asprintf("%d,",ll->fadedownPoint(RDLogLine::LogPointer))+ QString::asprintf("%d,",ll->fadedownGain())+ QString::asprintf("%d,",ll->segueGain())+ "'"+RDEscapeString(ll->linkEventName())+"',"+ QString::asprintf("%d,",QTime(0,0,0).msecsTo(ll->linkStartTime()))+ QString::asprintf("%d,",ll->linkLength())+ QString::asprintf("%d,",ll->linkId())+ "'"+RDYesNo(ll->linkEmbedded())+"',"+ "'"+RDEscapeString(ll->originUser())+"',"+ RDCheckDateTime(ll->originDateTime(),"yyyy-MM-dd hh:mm:ss")+","+ QString::asprintf("%d,",ll->linkStartSlop())+ QString::asprintf("%d,",ll->linkEndSlop())+ QString::asprintf("%d,",ll->duckUpGain())+ QString::asprintf("%d,",ll->duckDownGain())+ QString::asprintf("%d)",ll->eventLength()); *query += sql; } void RDLogModel::SaveLine(int line) { QString values = ""; InsertLineValues(&values, line); InsertLines(values); } void RDLogModel::emitDataChanged(int row) { QModelIndex left=createIndex(row,0); QModelIndex right=createIndex(row,columnCount()); emit dataChanged(left,right); } void RDLogModel::emitAllDataChanged() { QModelIndex left=createIndex(0,0); QModelIndex right=createIndex(lineCount(),columnCount()); emit dataChanged(left,right); } QStringList RDLogModel::headerTexts() const { QStringList ret; ret.push_back(tr("Est. Time")); // 00 ret.push_back(tr("Trans")); // 01 ret.push_back(tr("Cart")); // 02 ret.push_back(tr("Group")); // 03 ret.push_back(tr("Length")); // 04 ret.push_back(tr("Title")); // 05 ret.push_back(tr("Artist")); // 06 ret.push_back(tr("Sch. Time")); // 07 ret.push_back(tr("Client")); // 08 ret.push_back(tr("Agency")); // 09 ret.push_back(tr("Label")); // 10 ret.push_back(tr("Source")); // 11 ret.push_back(tr("Ext Data")); // 12 ret.push_back(tr("Line ID")); // 13 ret.push_back(tr("Count")); // 14 return ret; } QList RDLogModel::columnAlignments() const { QList ret; int left=Qt::AlignLeft|Qt::AlignVCenter; int center=Qt::AlignCenter; int right=Qt::AlignRight|Qt::AlignVCenter; ret.push_back(right); // Estimated Time ret.push_back(center); // Trans ret.push_back(center); // Cart ret.push_back(center); // Group ret.push_back(right); // Length ret.push_back(left); // Title ret.push_back(left); // Artist ret.push_back(right); // Scheduled Time ret.push_back(left); // Client ret.push_back(left); // Agency ret.push_back(left); // Label ret.push_back(left); // Source ret.push_back(left); // Ext Data ret.push_back(right); // Line ID ret.push_back(right); // Count return ret; } QPixmap RDLogModel::cellIcon(int col,int row,RDLogLine *ll) const { if(col==0) { return rda->iconEngine()->typeIcon(ll->type(),ll->source()); } return QPixmap(); } QString RDLogModel::cellText(int col,int line,RDLogLine *ll) const { switch(col) { case 0: // Start Time return StartTimeString(line); case 1: // Transition return RDLogLine::transText(ll->transType()); case 2: // Cart Number return ll->cartNumberText(); case 3: // Group return ll->groupName(); case 4: // Length return ll->forcedLengthText(); case 5: // Title return ll->titleText(); case 6: // Artist return ll->artist(); case 7: // Scheduled Time return rda->timeString(ll->startTime(RDLogLine::Imported)); case 8: // Client return ll->client(); case 9: // Agency return ll->agency(); case 10: // Label return ll->markerLabel(); case 11: // Source return RDLogLine::sourceText(ll->source()); case 12: // Ext Data switch(ll->type()) { case RDLogLine::MusicLink: case RDLogLine::TrafficLink: return ll->linkSummaryText(); case RDLogLine::Cart: case RDLogLine::Marker: case RDLogLine::Macro: case RDLogLine::OpenBracket: case RDLogLine::CloseBracket: case RDLogLine::Chain: case RDLogLine::Track: case RDLogLine::UnknownType: return ll->extData(); } break; case 13: // Line ID return QString::asprintf("%d",ll->id()); case 14: // Count return QString::asprintf("%d",line); } return QString(); } QFont RDLogModel::cellTextFont(int col,int line,RDLogLine *ll) const { if(col==3) { return d_bold_font; } return d_font; } QColor RDLogModel::cellTextColor(int col,int line,RDLogLine *ll) const { switch(col) { case 0: if(ll->timeType()==RDLogLine::Hard) { return Qt::blue; } break; case 3: return ll->groupColor(); } return RDGetTextColor(rowBackgroundColor(line,ll)); } QColor RDLogModel::rowBackgroundColor(int line,RDLogLine *ll) const { return d_palette.color(QPalette::Base); } void RDLogModel::MakeModel() { d_fms=NULL; d_bold_fms=NULL; d_start_time_style=RDLogModel::Scheduled; d_max_id=0; QStringList headers=headerTexts(); QList alignments=columnAlignments(); if(headers.size()!=alignments.size()) { fprintf(stderr,"header/alignment size mismatch\n"); exit(1); } for(int i=0;i