diff --git a/Niv.csproj b/Niv.csproj index a947af5..a0570c1 100644 --- a/Niv.csproj +++ b/Niv.csproj @@ -85,6 +85,8 @@ + + @@ -187,6 +189,11 @@ - + + + + + + \ No newline at end of file diff --git a/exe/Niv.exe b/exe/Niv.exe index e217ee2..f5b5f23 100644 Binary files a/exe/Niv.exe and b/exe/Niv.exe differ diff --git a/prop/AssemblyInfo.cs b/prop/AssemblyInfo.cs index e9267bc..7b5e110 100644 --- a/prop/AssemblyInfo.cs +++ b/prop/AssemblyInfo.cs @@ -20,5 +20,5 @@ ResourceDictionaryLocation.SourceAssembly )] -[assembly: AssemblyVersion("0.3.2.0")] -[assembly: AssemblyFileVersion("0.3.2.0")] +[assembly: AssemblyVersion("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] diff --git a/res/theme-dark/icon-undelete.png b/res/theme-dark/icon-undelete.png new file mode 100644 index 0000000..4c7bb01 Binary files /dev/null and b/res/theme-dark/icon-undelete.png differ diff --git a/res/theme-light/icon-undelete.png b/res/theme-light/icon-undelete.png new file mode 100644 index 0000000..6da34a9 Binary files /dev/null and b/res/theme-light/icon-undelete.png differ diff --git a/src/Recycle.cs b/src/Recycle.cs new file mode 100644 index 0000000..218f596 --- /dev/null +++ b/src/Recycle.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Text; +using System.Windows; + +namespace Niv +{ + class Recycle + { + // Folder of recycle + private static string recyclePath = Environment.CurrentDirectory + @"\Recycle\"; + private DirectoryInfo di = new DirectoryInfo(recyclePath); + + // Id counter for every recycle files. + private int recycleId = 0; + + // List of deleted files + private List recycleInfos = new List(); + + // Get the image count in recycle list. + public int count + { + get + { + return recycleInfos.Count; + } + } + + public Recycle() + { + clean(); + } + + // Move a image here. + public void recieve(ImageInfo info, int index) + { + // Create the recycle folder if not exists. + di.Refresh(); + if (!di.Exists) di.Create(); + + // Create the info + RecycleImageInfo recycleInfo = new RecycleImageInfo(info, recycleId, index); + + // Move the file and rename it. + FileInfo fi = new FileInfo(info.filename); + string newName = recyclePath + recycleId + Path.GetExtension(info.filename); + recycleInfo.newFilename = newName; + fi.MoveTo(newName); + + // Record the info + recycleInfos.Add(recycleInfo); + + recycleId++; + } + + // Move back the last deleted image. + public RecycleImageInfo undeleteLast() + { + return undelete(recycleInfos.Count - 1); + } + + // Move a image out of here, return the original index. + public RecycleImageInfo undelete(int index) + { + RecycleImageInfo recycleInfo = recycleInfos[index]; + + // Move the file back. + FileInfo fi = new FileInfo(recycleInfo.newFilename); + fi.MoveTo(recycleInfo.originalInfo.filename); + + recycleInfos.Remove(recycleInfo); + + return recycleInfo; + } + + // Delete all the files in recycle and the recycle folder. + public void clean() + { + if (di.Exists) di.Delete(true); + } + + // EOC + } +} diff --git a/src/RecycleImageInfo.cs b/src/RecycleImageInfo.cs new file mode 100644 index 0000000..3d0ef9d --- /dev/null +++ b/src/RecycleImageInfo.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Niv +{ + class RecycleImageInfo + { + // The original ImageInfo, used to undelete it. + public ImageInfo originalInfo; + + // The id(filename) in recycle folder. + public int id; + + // The original index in the image list. Used to insert it in the original index. + public int originalIndex; + + // New filename + public string newFilename; + + public RecycleImageInfo(ImageInfo info, int id, int originalIndex) + { + this.originalInfo = info; + this.id = id; + this.originalIndex = originalIndex; + } + + // EOC + } +} diff --git a/src/Walker.cs b/src/Walker.cs index cfc2b98..c8e3179 100644 --- a/src/Walker.cs +++ b/src/Walker.cs @@ -151,6 +151,12 @@ public void removeCurrentImageInfo() if (currentIndex == imageInfos.Count) currentIndex = 0; } + public void insertImageInfo(int index, ImageInfo info) + { + imageInfos.Insert(index, info); + + } + // EOC } } diff --git a/src/com.jarvisniu/I18n.cs b/src/com.jarvisniu/I18n.cs index d04d97b..e6d3821 100644 --- a/src/com.jarvisniu/I18n.cs +++ b/src/com.jarvisniu/I18n.cs @@ -140,6 +140,10 @@ private static void loadLanguageData() langData["zh-CN"]["tooltip.delete"] = "删除"; langData["zh-TW"]["tooltip.delete"] = "刪除"; + langData["en-US"]["tooltip.undelete"] = "Undo Delete"; + langData["zh-CN"]["tooltip.undelete"] = "撤销删除"; + langData["zh-TW"]["tooltip.undelete"] = "撤銷刪除"; + langData["en-US"]["tooltip.prev-image"] = "Previous"; langData["zh-CN"]["tooltip.prev-image"] = "上一张"; langData["zh-TW"]["tooltip.prev-image"] = "上一張"; diff --git a/xaml/NivWindow.xaml b/xaml/NivWindow.xaml index 0aff07c..c02e40a 100644 --- a/xaml/NivWindow.xaml +++ b/xaml/NivWindow.xaml @@ -13,20 +13,23 @@ - - - - - + + + + + + + + - + diff --git a/xaml/NivWindow.xaml.cs b/xaml/NivWindow.xaml.cs index 2822255..d0ee0dd 100644 --- a/xaml/NivWindow.xaml.cs +++ b/xaml/NivWindow.xaml.cs @@ -28,14 +28,14 @@ public partial class NivWindow : Window Transformer transformer; Controller inputController; FolderWalker walker = new FolderWalker(); - // RecycleBin recycleBin = new RecycleBin(AppDomain.CurrentDomain.BaseDirectory); + Recycle recycle = new Recycle(); // delayed closing timers Timer timerClosePage; // layout config - static double WINDOW_MIN_WIDTH = 680; - static double WINDOW_MIN_HEIGHT = 462; + static double WINDOW_MIN_WIDTH = 540; + static double WINDOW_MIN_HEIGHT = 384; public static int SEPARATOR_HEIGHT = 2; public static int TOOLBAR_HEIGHT = 48; public static int MARGIN_SIZE = 50; @@ -51,7 +51,6 @@ public partial class NivWindow : Window private Dictionary visibleStates = new Dictionary(); private bool isMarginBottomExist = true; - private bool isSmoothButtonVisible = false; private bool isZoomButtonInFitMode = true; private bool isFullscreen = false; private bool isAutoHideToolbar = false; @@ -95,9 +94,10 @@ private void loadLanguage() btnRotateLeft.ToolTip = I18n._("tooltip.rotate-left"); btnRotateRight.ToolTip = I18n._("tooltip.rotate-right"); btnDelete.ToolTip = I18n._("tooltip.delete"); + btnUndelete.ToolTip = I18n._("tooltip.undelete"); btnPrevImage.ToolTip = I18n._("tooltip.prev-image"); btnNextImage.ToolTip = I18n._("tooltip.next-image"); - // Tips of smooth button and zoom button are dynamic in refreshSmoothButton() and refreshZoomButton(). + // Tips of undelete, smooth and zoom button are dynamic in refreshFoobarButton(). btnMenu.ToolTip = I18n._("menu"); btnCloseInfo.ToolTip = I18n._("close"); btnExit.ToolTip = I18n._("tooltip.exit-program"); @@ -130,6 +130,7 @@ private void initLayout() menu.Height = 0; visibleStates[btnExit] = false; + visibleStates[btnSmooth] = false; visibleStates[container] = false; visibleStates[toolbar] = true; visibleStates[info] = false; @@ -138,14 +139,17 @@ private void initLayout() // add animation effects to buttons buttonAnimator.apply(btnZoom).apply(btnPrevImage).apply(btnNextImage).apply(btnSmooth).apply(btnMenu).apply(btnExit) - .apply(btnDelete).apply(btnRotateLeft).apply(btnRotateRight).apply(menuAbout) + .apply(btnDelete).apply(btnUndelete).apply(btnRotateLeft).apply(btnRotateRight).apply(menuAbout) .apply(menuHelp).apply(menuSetting).apply(menuImageInfo).apply(btnCloseInfo); // Hide the toolbar buttons onWalkerCountChanged(); + // Hide undelete button + onRecycleCountChanged(); + // Set render to high quality of images - Image[] images = { imageRotateLeft, imageRotateRight, imageDelete, imagePrev, imageNext, + Image[] images = { imageRotateLeft, imageRotateRight, imageDelete, imageUndelete, imagePrev, imageNext, imageSmooth, imageZoom, imageMenu, imageCloseInfo, imageHelp, imageAbout, imageSetting, imageInfo, imageExit }; foreach (Image image in images) RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality); @@ -171,13 +175,14 @@ private void setButtonSmoothVisibility() if (s > AA_SCALE_THRESHHOLD && walker.count > 0) { animatorJar.fadeIn(btnSmooth); - isSmoothButtonVisible = true; + visibleStates[btnSmooth] = true; } else if (s <= AA_SCALE_THRESHHOLD) { animatorJar.fadeOut(btnSmooth); - isSmoothButtonVisible = false; + visibleStates[btnSmooth] = false; } + refreshSmoothButton(); } private void setTheme() @@ -225,6 +230,7 @@ private void setTheme() imageRotateLeft.Source = loadThemeBitmap("icon-rotate-left.png"); imageRotateRight.Source = loadThemeBitmap("icon-rotate-right.png"); imageDelete.Source = loadThemeBitmap("icon-delete.png"); + imageUndelete.Source = loadThemeBitmap("icon-undelete.png"); imagePrev.Source = loadThemeBitmap("icon-prev.png"); imageNext.Source = loadThemeBitmap("icon-next.png"); refreshSmoothButton(); @@ -263,7 +269,13 @@ private void bindToolbarButtonsEvents() // Delete-image button click btnDelete.MouseUp += (object sender, MouseButtonEventArgs e) => { - MessageBox.Show("TODO: 图片删除功能正在开发"); + delete(); + }; + + // Delete-image button click + btnUndelete.MouseUp += (object sender, MouseButtonEventArgs e) => + { + undeleteLast(); }; // Prev-image button click @@ -281,7 +293,7 @@ private void bindToolbarButtonsEvents() // Smooth switch button click btnSmooth.MouseUp += (object sender, MouseButtonEventArgs e) => { - if (isSmoothButtonVisible) setSmoothTo(!walker.currentImageInfo.smooth); + if (visibleStates[btnSmooth]) setSmoothTo(!walker.currentImageInfo.smooth); refreshSmoothButton(); }; @@ -358,6 +370,7 @@ private void bindContainerEvents() separator.SizeChanged += (object sender, SizeChangedEventArgs e) => { refreshProgressI(); + gridSwitch.Visibility = separator.ActualWidth < 480 ? Visibility.Hidden : Visibility.Visible; }; container.MouseLeftButtonDown += (object sender, MouseButtonEventArgs e) => @@ -495,7 +508,7 @@ private void loadFromWalker() // infos labelInfoFilename.Content = Path.GetFileName(info.filename); FileInfo fi = new FileInfo(info.filename); - labelInfoSize.Content = humanifyNumber(fi.Length) + "B"; + labelInfoSize.Content = suffixifyNumber(fi.Length) + "B"; labelInfoResolution.Content = transformer.bitmap.PixelWidth + " x " + transformer.bitmap.PixelHeight; labelInfoDate.Content = dateTimeToString(fi.LastWriteTime); this.Title = Path.GetFileName(info.filename) + " - " + I18n._("appName"); @@ -627,6 +640,9 @@ private void onWalkerCountChanged() btnRotateLeft.Visibility = Visibility.Hidden; btnRotateRight.Visibility = Visibility.Hidden; btnZoom.Visibility = Visibility.Hidden; + + image.Source = null; + ImageBehavior.SetAnimatedSource(image, null); } else { @@ -680,6 +696,7 @@ private string getFirstCommandLineFilename() } return ""; } + private void showMessage(string message) { MessageBox.Show(message); @@ -718,10 +735,19 @@ private void setImageRotationBackToZero() public void saveRotationToFile() { string filename = walker.currentImageInfo.filename; + double rotationAngle = walker.currentImageInfo.rotationAngle; double savedAngle = walker.currentImageInfo.savedRotationAngle; double angle = simplifyAngle(rotationAngle - savedAngle); - if (angle == 0) return; + if (angle == 0) + { + return; + } + else if (Path.GetExtension(filename) == ".gif") + { + // showMessage("不支持保存GIF图片"); + return; + } System.Drawing.Image imgSrc = System.Drawing.Image.FromFile(filename); if (angle == 90) @@ -828,13 +854,14 @@ private void setSmoothByImageResolution() bool isImageSmall = transformer.bitmap.PixelWidth < 257 && transformer.bitmap.PixelHeight < 257; setSmoothTo(!isImageSmall); } - + private void refreshSmoothButton() { if (walker.currentImageInfo != null) { imageSmooth.Source = loadThemeBitmap(walker.currentImageInfo.smooth ? "icon-smooth-off.png" : "icon-smooth-on.png"); - btnSmooth.ToolTip = I18n._(walker.currentImageInfo.smooth ? "tooltip.disable-smooth" : "tooltip.enable-smooth"); + btnSmooth.ToolTip = visibleStates[btnSmooth] ? + I18n._(walker.currentImageInfo.smooth ? "tooltip.disable-smooth" : "tooltip.enable-smooth") : null; } } @@ -874,9 +901,9 @@ private double simplifyAngle(double angle) return angle; } - private string humanifyNumber(double num) + // Convert a number into the form that has a suffix k, M, G etc. + private string suffixifyNumber(double num) { - // 科学计数法 string[] units = { "", "k", "M", "G", "T" }; int level = 0; @@ -1101,7 +1128,7 @@ private void hideMainMenu() animatorJar.heightTo(menu, 0); animatorJar.fadeOut(menu); } - + #endregion private void closeMenu(object sender, MouseButtonEventArgs e) @@ -1127,34 +1154,54 @@ private void nextImage() private void delete() { - if (walker.count == 0) return; + recycle.recieve(walker.currentImageInfo, walker.currentIndex); + walker.removeCurrentImageInfo(); + // showMessage("图片已删除至图片回收站。"); - FileInfo fi = new FileInfo(walker.currentImageInfo.filename); - if (fi.Exists) - { - //recycleBin.receive(filename); - showMessage("图片已删除至图片回收站。"); - showPage(); - } + if (walker.count > 0) loadFromWalker(); - walker.removeCurrentImageInfo(); + onWalkerCountChanged(); + onRecycleCountChanged(); + } - if (walker.count > 0) + private void undeleteLast() + { + if (recycle.count > 0) + { + RecycleImageInfo recycleInfo = recycle.undeleteLast(); + walker.insertImageInfo(recycleInfo.originalIndex, recycleInfo.originalInfo); + walker.currentIndex = recycleInfo.originalIndex; loadFromWalker(); + + onWalkerCountChanged(); + onRecycleCountChanged(); + } + } + + private void onRecycleCountChanged() + { + if (recycle.count > 0) + { + animatorJar.fadeIn(btnUndelete); + btnUndelete.ToolTip = I18n._("tooltip.undelete"); + } else - exit(); // TODO不能退出,因为可能只有一张而删错,应该显示一个打开文件的按钮 + { + animatorJar.fadeOut(btnUndelete); + btnUndelete.ToolTip = null; + } } // Press key "D" to show something for debugging. private void debug() { - MessageBox.Show(progress.Margin.Bottom.ToString()); + //MessageBox.Show(this.Height.ToString()); } private void exit() { setImageRotationBackToZero(); - //recycleBin.clean(); + recycle.clean(); aboutWindow.exit(); Application.Current.Shutdown(); }