diff --git a/Main/Main.vcxproj b/Main/Main.vcxproj index d5c5c360..4e72a7b7 100644 --- a/Main/Main.vcxproj +++ b/Main/Main.vcxproj @@ -274,6 +274,7 @@ + diff --git a/Main/Main.vcxproj.filters b/Main/Main.vcxproj.filters index 84c492c1..91b7f2fb 100644 --- a/Main/Main.vcxproj.filters +++ b/Main/Main.vcxproj.filters @@ -431,6 +431,9 @@ Source Files\Image + + Source Files\Image + diff --git a/Main/res/resource.h b/Main/res/resource.h index df84785c..54c23c5a 100644 --- a/Main/res/resource.h +++ b/Main/res/resource.h @@ -54,6 +54,7 @@ #define IDR_DOS_PREVIEW_BASE 219 #define IDR_KRYOFLUX_ACCESS 220 #define IDR_CAPS 221 +#define IDR_TRACK_EDITOR 222 #define ID_HIDDEN 1018 #define ID_SYSTEM 1019 #define ID_CONNECTED 1020 diff --git a/Main/res/resource.rc b/Main/res/resource.rc index ae15dd4b..83fbf64a 100644 --- a/Main/res/resource.rc +++ b/Main/res/resource.rc @@ -156,6 +156,15 @@ BEGIN VK_F5, ID_REFRESH, VIRTKEY, NOINVERT END +IDR_TRACK_EDITOR ACCELERATORS DISCARDABLE +BEGIN + "+", ID_ZOOM_IN, ASCII, NOINVERT + "-", ID_ZOOM_OUT, ASCII, NOINVERT + "0", ID_ZOOM_FIT, VIRTKEY, CONTROL, NOINVERT + VK_ESCAPE, IDCANCEL, VIRTKEY, NOINVERT +END + + ///////////////////////////////////////////////////////////////////////////// // // Menu @@ -640,6 +649,19 @@ BEGIN END END +IDR_TRACK_EDITOR MENU DISCARDABLE +BEGIN + POPUP "Preview" + BEGIN + MENUITEM "Zoom in\t+", ID_ZOOM_IN + MENUITEM "Zoom out\t–", ID_ZOOM_OUT + MENUITEM "Zoom to fit\tCtrl+0", ID_ZOOM_FIT + MENUITEM SEPARATOR + MENUITEM "Close\tEsc", IDCANCEL + END +END + + ///////////////////////////////////////////////////////////////////////////// // // Bitmap @@ -1578,6 +1600,18 @@ BEGIN LTEXT "Static",ID_DOS,56,78,123,20 END +IDR_TRACK_EDITOR DIALOG DISCARDABLE 0, 0, 442, 149 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION +CAPTION "" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,332,128,50,14,NOT WS_VISIBLE | WS_DISABLED + PUSHBUTTON "Cancel",IDCANCEL,385,128,50,14 + LTEXT "",ID_TRACK,0,0,441,117,NOT WS_VISIBLE | WS_BORDER | NOT + WS_GROUP + CONTROL "Slider1",ID_ACCURACY,"msctls_trackbar32",TBS_BOTH | + TBS_NOTICKS | WS_BORDER,0,117,149,17 +END ///////////////////////////////////////////////////////////////////////////// // @@ -1859,6 +1893,11 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 120 END + + IDR_TRACK_EDITOR, DIALOG + BEGIN + BOTTOMMARGIN, 142 + END END #endif // APSTUDIO_INVOKED diff --git a/Main/src/CapsBase.cpp b/Main/src/CapsBase.cpp index 8c67e1c5..82fe2985 100644 --- a/Main/src/CapsBase.cpp +++ b/Main/src/CapsBase.cpp @@ -669,3 +669,10 @@ returnData: *outFdcStatuses++=currRev->fdcStatus; return ERROR_SUCCESS; } + std::unique_ptr CCapsBase::GetTrackDescription(TCylinder cyl,THead head) const{ + // returns specified Track general description, represented using neutral LogicalTimes; returns Null if such description not available + if (const PCInternalTrack pit=internalTracks[cyl][head]) + return std::unique_ptr( new CTrackReader(*pit) ); + else + return __super::GetTrackDescription(cyl,head); + } diff --git a/Main/src/CapsBase.h b/Main/src/CapsBase.h index 0357acce..b4104ed0 100644 --- a/Main/src/CapsBase.h +++ b/Main/src/CapsBase.h @@ -96,6 +96,7 @@ TStdWinError SetMediumTypeAndGeometry(PCFormat pFormat,PCSide sideMap,TSector firstSectorNumber) override; bool EditSettings(bool initialEditing) override; TStdWinError Reset() override; + std::unique_ptr GetTrackDescription(TCylinder cyl,THead head) const override; }; diff --git a/Main/src/Image.cpp b/Main/src/Image.cpp index 7e5a232b..04ac6466 100644 --- a/Main/src/Image.cpp +++ b/Main/src/Image.cpp @@ -651,6 +651,11 @@ return ERROR_NOT_SUPPORTED; // each Track by default must be explicitly formatted to be sure about its structure (but Images abstracting physical drives can override this setting) } + std::unique_ptr CImage::GetTrackDescription(TCylinder cyl,THead head) const{ + // returns specified Track general description, represented using neutral LogicalTimes; returns Null if such description not available + return nullptr; + } + TStdWinError CImage::SaveTrack(TCylinder cyl,THead head){ // saves the specified Track to the inserted Medium; returns Windows standard i/o error return ERROR_NOT_SUPPORTED; // individual Track saving is not supported for this kind of Image (OnSaveDocument must be called instead) diff --git a/Main/src/Image.h b/Main/src/Image.h index f14ad061..b7f3d646 100644 --- a/Main/src/Image.h +++ b/Main/src/Image.h @@ -312,6 +312,7 @@ //TFdcStatus ReadData(WORD nBytesToRead,LPBYTE buffer); TFdcStatus ReadDataFm(WORD nBytesToRead,LPBYTE buffer); TFdcStatus ReadDataMfm(WORD nBytesToRead,LPBYTE buffer); + void ShowModal(LPCTSTR caption) const; }; class CTrackReaderWriter:public CTrackReader{ diff --git a/Main/src/Image_TrackEditor.cpp b/Main/src/Image_TrackEditor.cpp new file mode 100644 index 00000000..8fcf05a4 --- /dev/null +++ b/Main/src/Image_TrackEditor.cpp @@ -0,0 +1,283 @@ +#include "stdafx.h" + + #define ZOOM_FACTOR_MAX 24 + + class CTrackEditor sealed:public Utils::CRideDialog{ + const LPCTSTR caption; + CMainWindow::CDynMenu menu; + HANDLE hAutoscrollTimer; + + class CTimeEditor sealed:public CScrollView{ + const Utils::CRidePen penIndex; + Utils::CTimeline timeline; + CImage::CTrackReader tr; + TLogTime scrollTime; + + void OnUpdate(CView *pSender,LPARAM lHint,CObject *pHint) override{ + // request to refresh the display of content + SetScrollSizes( + MM_TEXT, + CSize( Utils::LogicalUnitScaleFactor*timeline.GetUnitCount(), 0 ) + ); + } + + LRESULT WindowProc(UINT msg,WPARAM wParam,LPARAM lParam) override{ + // window procedure + switch (msg){ + case WM_MOUSEACTIVATE: + // preventing the focus from being stolen by the parent + return MA_ACTIVATE; + } + return __super::WindowProc( msg, wParam, lParam ); + } + + BOOL OnScroll(UINT nScrollCode,UINT nPos,BOOL bDoScroll=TRUE) override{ + // scrolls the View's content in given way + SCROLLINFO si={ sizeof(si) }; + // . horizontal ScrollBar + GetScrollInfo( SB_HORZ, &si, SIF_POS|SIF_TRACKPOS|SIF_RANGE|SIF_PAGE ); + switch (LOBYTE(nScrollCode)){ + case SB_LEFT : si.nPos=0; break; + case SB_RIGHT : si.nPos=INT_MAX; break; + case SB_LINELEFT : si.nPos-=m_lineDev.cx;break; + case SB_LINERIGHT : si.nPos+=m_lineDev.cx;break; + case SB_PAGELEFT : si.nPos-=m_pageDev.cx;break; + case SB_PAGERIGHT : si.nPos+=m_pageDev.cx;break; + case SB_THUMBPOSITION: // "thumb" released + case SB_THUMBTRACK : si.nPos=si.nTrackPos; break; + } + SetScrollTime( timeline.GetTime(si.nPos) ); + return TRUE; + } + + void OnPrepareDC(CDC *pDC,CPrintInfo *pInfo=nullptr) override{ + // + // . base + __super::OnPrepareDC(pDC,pInfo); + // . changing the viewport + CRect rc; + GetClientRect(&rc); + pDC->SetViewportOrg( -GetTimeline().GetUnitCount(scrollTime), rc.Height()/2 ); + // . scaling + Utils::ScaleLogicalUnit(*pDC); + } + + void OnDraw(CDC *pDC) override{ + // drawing the LogicalTimes + // . drawing the Timeline + const HDC dc=*pDC; + ::SetBkMode( dc, TRANSPARENT ); + TLogTime timeA,timeZ; // visible region + timeline.Draw( dc, Utils::CRideFont::StdBold, &timeA, &timeZ ); + // . drawing Index pulses + BYTE i=0; + while (itimeline.logTimeLength) t=timeline.logTimeLength; + timeline.zoomFactor=newZoomFactor; + OnUpdate( nullptr, 0, nullptr ); + SetScrollTime( timeline.GetTime( timeline.GetUnitCount(t)-focusUnitX ) ); + Invalidate(); + } + + void SetZoomFactorCenter(BYTE newZoomFactor){ + CRect rc; + GetClientRect(&rc); + SetZoomFactor( newZoomFactor, rc.Width()/(Utils::LogicalUnitScaleFactor*2) ); + } + + inline TLogTime GetScrollTime() const{ + return scrollTime; + } + + void SetScrollTime(TLogTime t){ + if (t<0) t=0; + else if (t>timeline.logTimeLength) t=timeline.logTimeLength; + SCROLLINFO si={ sizeof(si) }; + si.fMask=SIF_POS; + si.nPos=timeline.GetUnitCount(t); + SetScrollInfo( SB_HORZ, &si, TRUE ); + ScrollWindow( // "base" + timeline.GetUnitCount(scrollTime)-si.nPos,//*Utils::GetLogicalUnitScaleFactor(CClientDC(this)) + 0 + ); + scrollTime=t; + Invalidate(FALSE); + } + } timeEditor; + + #define AUTOSCROLL_HALF 64 + + BOOL OnInitDialog() override{ + // dialog initialization + // - base + __super::OnInitDialog(); + // - setting window Caption + SetWindowText(caption); + // - adding menu to this dialog (and extending its bottom to compensate for shrunk client area) + SetMenu(&menu); + CRect rc; + GetWindowRect(&rc); + rc.bottom+=::GetSystemMetrics(SM_CYMENU); + SetWindowPos( nullptr, 0,0, rc.Width(),rc.Height(), SWP_NOZORDER|SWP_NOMOVE ); + // - creating the TimeEditor + timeEditor.Create( nullptr, nullptr, WS_CHILD|WS_VISIBLE, MapDlgItemClientRect(ID_TRACK), this, 0 ); + timeEditor.OnInitialUpdate(); // because hasn't been called automatically + // - setting up the Accuracy slider + SendDlgItemMessage( ID_ACCURACY, TBM_SETRANGEMAX, FALSE, 2*AUTOSCROLL_HALF ); + SendDlgItemMessage( ID_ACCURACY, TBM_SETPOS, TRUE, AUTOSCROLL_HALF ); + SendDlgItemMessage( ID_ACCURACY, TBM_SETTHUMBLENGTH, TRUE, 50 ); + return TRUE; + } + + BOOL PreTranslateMessage(PMSG pMsg) override{ + // pre-processing the Message + return ::TranslateAccelerator( m_hWnd, menu.hAccel, pMsg ); + } + + #define AUTOSCROLL_TIMER_ID 0x100000 + + LRESULT WindowProc(UINT msg,WPARAM wParam,LPARAM lParam) override{ + // window procedure + switch (msg){ + case WM_TIMER: + // timer tick + wParam=TB_THUMBTRACK, lParam=(LPARAM)GetDlgItemHwnd(ID_ACCURACY); + //fallthrough + case WM_HSCROLL:{ + // the Accuracy slider has been used + if (lParam==0) // this control's native scrollbar + break; + int i; + switch (LOWORD(wParam)){ + case TB_THUMBPOSITION: + // "thumb" released + ::KillTimer( m_hWnd, AUTOSCROLL_TIMER_ID ); + hAutoscrollTimer=INVALID_HANDLE_VALUE; + i=HIWORD(wParam); + SendDlgItemMessage( ID_ACCURACY, TBM_SETPOS, TRUE, AUTOSCROLL_HALF ); + break; + case TB_THUMBTRACK: + // "thumb" dragged + if (hAutoscrollTimer==INVALID_HANDLE_VALUE) // timer not yet set + hAutoscrollTimer=(HANDLE)SetTimer( AUTOSCROLL_TIMER_ID, 50, nullptr ); + if (msg!=WM_TIMER) // waiting for the automatic scroll event to occur + return TRUE; + i=SendDlgItemMessage( ID_ACCURACY, TBM_GETPOS ); + break; + default: + // ignoring all other events that may occur for a slider + SendDlgItemMessage( ID_ACCURACY, TBM_SETPOS, TRUE, AUTOSCROLL_HALF ); + return TRUE; + } + timeEditor.SetScrollTime( timeEditor.GetScrollTime()+timeEditor.GetTimeline().GetTime(i-AUTOSCROLL_HALF) ); + break; + } + } + return __super::WindowProc( msg, wParam, lParam ); + } + + BOOL OnCmdMsg(UINT nID,int nCode,LPVOID pExtra,AFX_CMDHANDLERINFO *pHandlerInfo){ + // command processing + switch (nCode){ + case CN_UPDATE_COMMAND_UI:{ + // update + CCmdUI *const pCmdUi=(CCmdUI *)pExtra; + switch (nID){ + case ID_ZOOM_IN: + pCmdUi->Enable( timeEditor.GetTimeline().zoomFactor>0 ); + return TRUE; + case ID_ZOOM_OUT: + pCmdUi->Enable( timeEditor.GetTimeline().zoomFactor( logTimeLength, ((LONGLONG)PixelToTime(std::max(-org.x,0L)+rcClient.Width())+intervalBig-1)/intervalBig*intervalBig ); // rounding to whole multiples of IntervalBig + if (pOutVisibleEnd!=nullptr) + *pOutVisibleEnd=timeZ; // - drawing using a workaround to overcome the coordinate space limits const int nUnitsA=GetUnitCount(timeA); ::SetViewportOrgEx( dc, nUnitsA*LogicalUnitScaleFactor+org.x, org.y, nullptr ); @@ -431,7 +435,6 @@ namespace Utils{ ::MoveToEx( dc, 0,0, nullptr ); ::LineTo( dc, GetUnitCount(timeZ)-nUnitsA, 0 ); // . drawing secondary time marks on the timeline - const TLogTime tVisible=timeZ-timeA; if (const TLogTime intervalSmall=intervalBig/10) for( TLogTime t=timeA; tnUnits && zf given actual scroll position, the Point falls into a Sector, otherwise False CClientDC dc(this); OnPrepareDC(&dc); @@ -494,24 +494,24 @@ TSectorId bufferId[(TSector)-1]; WORD bufferLength[(TSector)-1]; TLogTime bufferStarts[(TSector)-1]; - const TSector nSectors=IMAGE->ScanTrack( d.quot, d.rem, bufferId, bufferLength, bufferStarts ); + const TSector nSectors=IMAGE->ScanTrack( rOutChs.cylinder=d.quot, rOutChs.head=d.rem, bufferId, bufferLength, bufferStarts ); TimesToPixels( nSectors, bufferStarts, bufferLength ); for( TSector s=0; s>zoomLengthFactor)){ // cursor over a Sector - rOutChs.cylinder=d.quot, rOutChs.head=d.rem; rOutChs.sectorId=bufferId[s]; rnOutSectorsToSkip=s; - return true; + return TCursorPos::SECTOR; } + return TCursorPos::TRACK; } - return false; + return TCursorPos::NONE; } afx_msg void CTrackMapView::OnMouseMove(UINT nFlags,CPoint point){ // cursor moved over this view TPhysicalAddress chs; BYTE nSectorsToSkip; TLogTime nanoseconds; - const bool cursorOverSector=GetPhysicalAddressAndNanosecondsFromPoint(point,chs,nSectorsToSkip,nanoseconds); + const bool cursorOverSector=GetPhysicalAddressAndNanosecondsFromPoint(point,chs,nSectorsToSkip,nanoseconds)==TCursorPos::SECTOR; TCHAR buf[80], *p=buf; *p='\0'; if (showTimed && nanoseconds>=0){ // cursor in timeline range @@ -545,21 +545,31 @@ afx_msg void CTrackMapView::OnLButtonUp(UINT nFlags,CPoint point){ // left mouse button released - if (app.IsInGodMode() && !IMAGE->IsWriteProtected()){ - TPhysicalAddress chs; BYTE nSectorsToSkip; TLogTime nanoseconds; - if (GetPhysicalAddressAndNanosecondsFromPoint(point,chs,nSectorsToSkip,nanoseconds)){ - // cursor over a Sector - WORD w; TFdcStatus sr; - IMAGE->GetSectorData( chs, nSectorsToSkip, false, &w, &sr ); - if (!sr.IsWithoutError()){ - if (Utils::QuestionYesNo(_T("Unformat this track?"),MB_DEFBUTTON1)) - if (const TStdWinError err=IMAGE->UnformatTrack( chs.cylinder, chs.head )) - return Utils::FatalError( _T("Can't unformat"), err ); - }else if (Utils::QuestionYesNo(_T("Make this sector unreadable?"),MB_DEFBUTTON1)) - if (const TStdWinError err=IMAGE->MarkSectorAsDirty( chs, nSectorsToSkip, &TFdcStatus::DeletedDam )) - return Utils::FatalError( _T("Can't make unreadable"), err ); - Invalidate(); - } + TPhysicalAddress chs; BYTE nSectorsToSkip; int nanoseconds; + switch (GetPhysicalAddressAndNanosecondsFromPoint(point,chs,nSectorsToSkip,nanoseconds)){ + case TCursorPos::TRACK: + // clicked on a Track + if (const auto tr=IMAGE->GetTrackDescription( chs.cylinder, chs.head )){ + TCHAR caption[80]; + ::wsprintf( caption, _T("Track %d (Cyl=%d, Head=%d)"), chs.GetTrackNumber(__getNumberOfFormattedSidesInImage__(IMAGE)), chs.cylinder, chs.head ); + tr->ShowModal(caption); + } + break; + case TCursorPos::SECTOR: + // clicked on a Sector + if (app.IsInGodMode() && !IMAGE->IsWriteProtected()){ + WORD w; TFdcStatus sr; + IMAGE->GetSectorData( chs, nSectorsToSkip, false, &w, &sr ); + if (!sr.IsWithoutError()){ + if (Utils::QuestionYesNo(_T("Unformat this track?"),MB_DEFBUTTON1)) + if (const TStdWinError err=IMAGE->UnformatTrack( chs.cylinder, chs.head )) + return Utils::FatalError( _T("Can't unformat"), err ); + }else if (Utils::QuestionYesNo(_T("Make this sector unreadable?"),MB_DEFBUTTON1)) + if (const TStdWinError err=IMAGE->MarkSectorAsDirty( chs, nSectorsToSkip, &TFdcStatus::DeletedDam )) + return Utils::FatalError( _T("Can't make unreadable"), err ); + Invalidate(); + } + break; } } diff --git a/Main/src/ViewTrackMap.h b/Main/src/ViewTrackMap.h index daa89f41..037f8f9b 100644 --- a/Main/src/ViewTrackMap.h +++ b/Main/src/ViewTrackMap.h @@ -48,7 +48,7 @@ void OnDraw(CDC *pDC) override; void PostNcDestroy() override; void TimesToPixels(TSector nSectors,PLogTime pInOutBuffer,PCWORD pInSectorLengths) const; - bool GetPhysicalAddressAndNanosecondsFromPoint(POINT point,TPhysicalAddress &rOutChs,BYTE &rnOutSectorsToSkip,TLogTime &rOutNanoseconds); + enum TCursorPos{ NONE, TRACK, SECTOR } GetPhysicalAddressAndNanosecondsFromPoint(POINT point,TPhysicalAddress &rOutChs,BYTE &rnOutSectorsToSkip,int &rOutNanoseconds); void ResetStatusBarMessage() const; void __updateLogicalDimensions__(); afx_msg int OnCreate(LPCREATESTRUCT lpcs);