Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: toggle "read only" attribute to modify read only file or lock modification to file #636

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1a7e54d
Implemented detecting readonly attribute.
soumyamahunt Aug 2, 2020
fd38453
Fixed compile.
soumyamahunt Aug 2, 2020
367f7fe
Fixed compile.
soumyamahunt Aug 2, 2020
8dc072f
Disabled "Save" when file is read-only.
soumyamahunt Aug 2, 2020
35b7935
Implemented changing read-only attribute.
soumyamahunt Aug 2, 2020
beedd60
Added notification for excemption.
soumyamahunt Aug 2, 2020
d1bc2b6
code-refinement.
soumyamahunt Aug 2, 2020
c63a88b
code-refinement.
soumyamahunt Aug 2, 2020
558d267
fixed theming.
soumyamahunt Aug 2, 2020
9351165
Made requested changes.
soumyamahunt Aug 2, 2020
cd58dda
Fixed read-only attribute not being detected when file opened for the…
soumyamahunt Aug 7, 2020
15988e7
Merge branch 'master' into edit-readonly
soumyamahunt Aug 7, 2020
2d9c8bc
Implemented detecting read only status change outside app.
soumyamahunt Jan 11, 2021
372fadb
Merge branch 'master' into edit-readonly
soumyamahunt Jan 11, 2021
496f141
Fixed not detecting read only attribute for some file types.
soumyamahunt Jan 13, 2021
3d666c3
Handle errors when handle can't be gotten.
soumyamahunt Jan 14, 2021
620d8ff
Fix delay in attribute detection in some cases.
soumyamahunt Jan 14, 2021
971cf59
Use dotNET FileAttributes enum.
soumyamahunt Jan 14, 2021
e916cf3
Code refactor.
soumyamahunt Jan 14, 2021
7f52ad9
code refactor.
soumyamahunt Jan 15, 2021
8eaff24
Fixed attribute couldn't be changed in some cases.
soumyamahunt Jan 15, 2021
025e34f
Added crash analytics for attribues feature.
soumyamahunt Jan 15, 2021
c7e2e77
Modified handle access.
soumyamahunt Jan 19, 2021
71c67bb
Use WinRT specific Expando properties setter for changing read only a…
soumyamahunt Jan 19, 2021
463e061
Merge branch 'master' into edit-readonly
soumyamahunt Feb 9, 2021
f353845
Fixed build failing for Production configuration.
soumyamahunt Feb 9, 2021
0b2b7e4
Solve conflicts.
soumyamahunt Mar 27, 2021
994d2b3
Resolve conflicts.
soumyamahunt Mar 29, 2021
a56b1c4
chore: code refactor
soumyamahunt May 1, 2021
4a2a190
chore: code refactor
soumyamahunt May 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ jobs:
msbuild $env:SOLUTION_NAME `
/p:Platform=$env:PLATFORM `
/p:Configuration=$env:CONFIGURATION `
/p:AllowUnsafeBlocks=true `
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE `
/p:AppxBundle=$env:APPX_BUNDLE `
/p:AppxPackageSigningEnabled=$env:APPX_PACKAGE_SIGNING_ENABLED `
Expand Down
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ steps:
solution: '$(solution)'
configuration: '$(buildConfiguration)'
msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)"
/p:AllowUnsafeBlocks=true
/p:AppxPackageDir="$(appxPackageDir)"
/p:AppxBundle=Always
/p:UapAppxPackageBuildMode=StoreUpload
Expand Down
3 changes: 3 additions & 0 deletions src/Notepads/Controls/TextEditor/ITextEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface ITextEditor
event EventHandler FileSaved;
event EventHandler FileReloaded;
event EventHandler FileRenamed;
event EventHandler FileAttributeChanged;

Guid Id { get; set; }

Expand All @@ -46,6 +47,8 @@ public interface ITextEditor

string EditingFilePath { get; }

bool IsReadOnly { get; set; }

StorageFile EditingFile { get; }

bool IsModified { get; }
Expand Down
45 changes: 43 additions & 2 deletions src/Notepads/Controls/TextEditor/TextEditor.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public sealed partial class TextEditor : ITextEditor, IDisposable
public event EventHandler FileSaved;
public event EventHandler FileReloaded;
public event EventHandler FileRenamed;
public event EventHandler FileAttributeChanged;

public Guid Id { get; set; }

Expand All @@ -73,6 +74,24 @@ public sealed partial class TextEditor : ITextEditor, IDisposable

public string EditingFilePath { get; private set; }

private System.IO.FileAttributes _fileAttributes;

public bool IsReadOnly
{
get => _fileAttributes.HasFlag(System.IO.FileAttributes.ReadOnly);
set
{
if (EditingFile == null) return;

EditingFile.SetFileAttributes(
value
? _fileAttributes | System.IO.FileAttributes.ReadOnly
: _fileAttributes & ~System.IO.FileAttributes.ReadOnly,
() => CheckAndUpdateAttributesInfo()
);
}
}

private StorageFile _editingFile;

public StorageFile EditingFile
Expand All @@ -85,7 +104,7 @@ private set
}
}

private void UpdateDocumentInfo()
private async void UpdateDocumentInfo()
{
if (EditingFile == null)
{
Expand All @@ -100,6 +119,8 @@ private void UpdateDocumentInfo()
FileType = FileTypeUtility.GetFileTypeByFileName(EditingFile.Name);
}

await CheckAndUpdateAttributesInfo();

// Hide content preview if current file type is not supported for previewing
if (!FileTypeUtility.IsPreviewSupported(FileType))
{
Expand All @@ -110,6 +131,20 @@ private void UpdateDocumentInfo()
}
}

private async Task CheckAndUpdateAttributesInfo()
{
if (EditingFile == null || FileModificationState == FileModificationState.RenamedMovedOrDeleted) return;

var fileAttributes = EditingFile.GetFileAttributes(!_isAttributeCheckedOnce);
_isAttributeCheckedOnce = true;
if (_fileAttributes == fileAttributes) return;

_fileAttributes = fileAttributes;
await Dispatcher.CallOnUIThreadAsync(
() => FileAttributeChanged?.Invoke(this, null)
);
}

private bool _isModified;

public bool IsModified
Expand Down Expand Up @@ -142,6 +177,8 @@ private set

private FileModificationState _fileModificationState;

private bool _isAttributeCheckedOnce = false;

private bool _isContentPreviewPanelOpened;

private readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView();
Expand Down Expand Up @@ -351,10 +388,12 @@ public TextEditorStateMetaData GetTextEditorStateMetaData()
return metaData;
}

private void TextEditor_Loaded(object sender, RoutedEventArgs e)
private async void TextEditor_Loaded(object sender, RoutedEventArgs e)
{
Loaded?.Invoke(this, e);

await CheckAndUpdateAttributesInfo();

StartCheckingFileStatusPeriodically();

// Insert "Legacy Windows Notepad" style date and time if document starts with ".LOG"
Expand Down Expand Up @@ -442,6 +481,8 @@ private async Task CheckAndUpdateFileStatus(CancellationToken cancellationToken)
FileModificationState = newState.Value;
});

await CheckAndUpdateAttributesInfo();

_fileStatusSemaphoreSlim.Release();
}

Expand Down
41 changes: 39 additions & 2 deletions src/Notepads/Core/NotepadsCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,36 @@ public class NotepadsCore : INotepadsCore
_dispatcher = dispatcher;
_extensionProvider = extensionProvider;

ThemeSettingsService.OnThemeChanged += ThemeSettingsService_OnThemeChanged;
ThemeSettingsService.OnAccentColorChanged += ThemeSettingsService_OnAccentColorChanged;
}

private async void ThemeSettingsService_OnThemeChanged(object sender, ElementTheme e)
{
await _dispatcher.CallOnUIThreadAsync(() =>
{
if (Sets.Items == null) return;
foreach (SetsViewItem item in Sets.Items)
{
var textEditor = item.Content as ITextEditor;
item.Icon.Foreground = textEditor != null && textEditor.IsReadOnly
? ThemeSettingsService.GetReadOnlyTabIconForegroundBrush()
: new SolidColorBrush(ThemeSettingsService.AppAccentColor);
}
});
}

private async void ThemeSettingsService_OnAccentColorChanged(object sender, Color color)
{
await _dispatcher.CallOnUIThreadAsync(() =>
{
if (Sets.Items == null) return;
foreach (SetsViewItem item in Sets.Items)
{
item.Icon.Foreground = new SolidColorBrush(color);
var textEditor = item.Content as ITextEditor;
item.Icon.Foreground = textEditor != null && textEditor.IsReadOnly
? ThemeSettingsService.GetReadOnlyTabIconForegroundBrush()
: new SolidColorBrush(color);
item.SelectionIndicatorForeground = new SolidColorBrush(color);
}
});
Expand Down Expand Up @@ -203,6 +222,7 @@ public void OpenTextEditors(ITextEditor[] editors, Guid? selectedEditorId = null
textEditor.LineEndingChanged += TextEditor_OnLineEndingChanged;
textEditor.EncodingChanged += TextEditor_OnEncodingChanged;
textEditor.FileRenamed += TextEditor_OnFileRenamed;
textEditor.FileAttributeChanged += TextEditor_OnFileAttributeChanged;

return textEditor;
}
Expand Down Expand Up @@ -241,6 +261,7 @@ public void DeleteTextEditor(ITextEditor textEditor)
textEditor.LineEndingChanged -= TextEditor_OnLineEndingChanged;
textEditor.EncodingChanged -= TextEditor_OnEncodingChanged;
textEditor.FileRenamed -= TextEditor_OnFileRenamed;
textEditor.FileAttributeChanged -= TextEditor_OnFileAttributeChanged;
textEditor.Dispose();
}

Expand Down Expand Up @@ -405,7 +426,9 @@ private SetsViewItem CreateTextEditorSetsViewItem(ITextEditor textEditor)
FontSize = 1.5,
Width = 3,
Height = 3,
Foreground = new SolidColorBrush(ThemeSettingsService.AppAccentColor),
Foreground = textEditor.IsReadOnly
? ThemeSettingsService.GetReadOnlyTabIconForegroundBrush()
: new SolidColorBrush(ThemeSettingsService.AppAccentColor)
};

var textEditorSetsViewItem = new SetsViewItem
Expand Down Expand Up @@ -570,6 +593,20 @@ private void TextEditor_OnFileRenamed(object sender, EventArgs e)
TextEditorRenamed?.Invoke(this, textEditor);
}

private void TextEditor_OnFileAttributeChanged(object sender, EventArgs e)
{
if (!(sender is ITextEditor textEditor)) return;

var item = GetTextEditorSetsViewItem(textEditor);
if (item == null) return;
item.Icon.Foreground = textEditor.IsReadOnly
? ThemeSettingsService.GetReadOnlyTabIconForegroundBrush()
: new SolidColorBrush(ThemeSettingsService.AppAccentColor);

TextEditorFileModificationStateChanged?.Invoke(this, textEditor);
TextEditorEditorModificationStateChanged?.Invoke(this, textEditor);
}

#region DragAndDrop

private async void Sets_DragOver(object sender, DragEventArgs args)
Expand Down
34 changes: 34 additions & 0 deletions src/Notepads/Core/TabContextFlyout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

public class TabContextFlyout : MenuFlyout
{
Expand All @@ -23,6 +24,7 @@ public class TabContextFlyout : MenuFlyout
private MenuFlyoutItem _copyFullPath;
private MenuFlyoutItem _openContainingFolder;
private MenuFlyoutItem _rename;
private MenuFlyoutItem _toggleReadOnly;

private string _filePath;
private string _containingFolderPath;
Expand All @@ -46,6 +48,7 @@ public TabContextFlyout(INotepadsCore notepadsCore, ITextEditor textEditor)
Items.Add(OpenContainingFolder);
Items.Add(new MenuFlyoutSeparator());
Items.Add(Rename);
Items.Add(ToggleReadOnly);

var style = new Style(typeof(MenuFlyoutPresenter));
style.Setters.Add(new Setter(Control.BorderThicknessProperty, 0));
Expand All @@ -69,12 +72,21 @@ private void TabContextFlyout_Opening(object sender, object e)
_filePath = _textEditor.EditingFile.Path;
_containingFolderPath = Path.GetDirectoryName(_filePath);
isFileReadonly = FileSystemUtility.IsFileReadOnly(_textEditor.EditingFile);
ToggleReadOnly.Visibility = Visibility.Visible;
}
else
{
ToggleReadOnly.Visibility = Visibility.Collapsed;
}

CloseOthers.IsEnabled = CloseRight.IsEnabled = _notepadsCore.GetNumberOfOpenedTextEditors() > 1;
CopyFullPath.IsEnabled = !string.IsNullOrEmpty(_filePath);
OpenContainingFolder.IsEnabled = !string.IsNullOrEmpty(_containingFolderPath);
Rename.IsEnabled = _textEditor.FileModificationState != FileModificationState.RenamedMovedOrDeleted && !isFileReadonly;
ToggleReadOnly.IsEnabled = _textEditor.FileModificationState == FileModificationState.Untouched;
ToggleReadOnly.Text = _textEditor.IsReadOnly
? _resourceLoader.GetString("Tab_ContextFlyout_ToggleReadOnlyOffButtonDisplayText")
: _resourceLoader.GetString("Tab_ContextFlyout_ToggleReadOnlyOnButtonDisplayText");

if (App.IsGameBarWidget)
{
Expand Down Expand Up @@ -272,6 +284,28 @@ private MenuFlyoutItem Rename
}
}

private MenuFlyoutItem ToggleReadOnly
{
get
{
if (_toggleReadOnly == null)
{
_toggleReadOnly = new MenuFlyoutItem() { Text = _resourceLoader.GetString("Tab_ContextFlyout_ToggleReadOnlyOnButtonDisplayText") };
_toggleReadOnly.Click += (sender, args) =>
{
if (!(this.Target is Notepads.Controls.SetsViewItem item)) return;

_textEditor.IsReadOnly = (_toggleReadOnly.Text == _resourceLoader.GetString("Tab_ContextFlyout_ToggleReadOnlyOnButtonDisplayText"));

item.Icon.Foreground= _textEditor.IsReadOnly
? ThemeSettingsService.GetReadOnlyTabIconForegroundBrush()
: new SolidColorBrush(ThemeSettingsService.AppAccentColor);
};
}
return _toggleReadOnly;
}
}

private void ExecuteOnAllTextEditors(Action<ITextEditor> action)
{
foreach (ITextEditor textEditor in _notepadsCore.GetAllTextEditors())
Expand Down
10 changes: 10 additions & 0 deletions src/Notepads/Notepads.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
Expand All @@ -48,6 +49,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Production|x86'">
Expand All @@ -61,6 +63,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
Expand All @@ -73,6 +76,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
Expand All @@ -85,6 +89,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Production|ARM64'">
Expand All @@ -97,6 +102,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
Expand All @@ -110,6 +116,7 @@
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<Optimize>false</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
Expand All @@ -121,6 +128,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Production|x64'">
Expand All @@ -133,6 +141,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
Expand Down Expand Up @@ -177,6 +186,7 @@
<Compile Include="Extensions\ScrollViewerExtensions.cs" />
<Compile Include="Utilities\FutureAccessListUtility.cs" />
<Compile Include="Utilities\LanguageUtility.cs" />
<Compile Include="Utilities\Win32FileSystemUtility.cs" />
<Compile Include="Views\Settings\AboutPage.xaml.cs">
<DependentUpon>AboutPage.xaml</DependentUpon>
</Compile>
Expand Down
10 changes: 10 additions & 0 deletions src/Notepads/Services/ThemeSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,16 @@ private static Brush GetAppBackgroundBrush(ElementTheme theme)
}
}

public static Brush GetReadOnlyTabIconForegroundBrush()
{
var darkModeBaseColor = Color.FromArgb(255, 123, 123, 123);
var lightModeBaseColor = Color.FromArgb(255, 146, 146, 146);

var baseColor = ThemeMode == ElementTheme.Light ? lightModeBaseColor : darkModeBaseColor;

return new SolidColorBrush(baseColor);
}

public static void ApplyThemeForTitleBarButtons(ApplicationViewTitleBar titleBar, ElementTheme theme)
{
if (theme == ElementTheme.Dark)
Expand Down
Loading