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);