// // Copyright 2014 Perforce Software Inc. // using Perforce.View; using Perforce.ViewModel; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using System.Xml; using System.Xml.Linq; using System.Linq; using System.Text; using Perforce.P4; using Perforce.Model; namespace Perforce.Helper { public static class UIHelper { #region BUSY_INDICATOR_LOGIC /// <summary> /// A value indicating whether the UI is currently busy /// </summary> private static bool IsBusy; /// <summary> /// Sets the busystate as busy. /// </summary> public static void SetBusyState() { SetBusyState(true); } /// <summary> /// Sets the busystate to busy or not busy. /// </summary> /// <param name="busy">if set to <c>true</c> the application is now busy.</param> private static void SetBusyState(bool busy) { if (busy != IsBusy) { IsBusy = busy; Mouse.OverrideCursor = busy ? Cursors.Wait : null; if (IsBusy) { new DispatcherTimer(TimeSpan.FromSeconds(0), DispatcherPriority.ApplicationIdle, dispatcherTimer_Tick, System.Windows.Application.Current.Dispatcher); } } } /// <summary> /// Handles the Tick event of the dispatcherTimer control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> private static void dispatcherTimer_Tick(object sender, EventArgs e) { var dispatcherTimer = sender as DispatcherTimer; if (dispatcherTimer != null) { SetBusyState(false); dispatcherTimer.Stop(); } } #endregion public static SidebarSelector GetCurrentSelector() { var main = App.Current.MainWindow as MainWindow; return main.Selectors.FindSelected(); } public static SidebarSelector GetSelector(SELECTOR_TYPE type) { var main = App.Current.MainWindow as MainWindow; return main.Selectors.GetSelector(type); } public static void GoToPath(string originalPath, SELECTOR_TYPE type = SELECTOR_TYPE.NONE) { if (originalPath.StartsWith("p4://")) originalPath = originalPath.Substring(3); var path = Utility.DecodePath(originalPath); var helper = Utility.GetPerforceHelper(); if (helper.PathExists(path)) { var main = App.Current.MainWindow as MainWindow; var selector = main.Selectors.GetSelector(SELECTOR_TYPE.SERVER); GridHelper.GenerateGrid(selector, path); main.Selectors.DeselectAll(); selector.Model.IsSelected = true; main.ViewPane.Content = selector.SidbebarView; main.Breadcrumbs.Model.UpdatePath(); } else { ShowTimedMessage("Error: path does not exist", 1d); } } public static void NavTo(NavItem location) { var main = App.Current.MainWindow as MainWindow; var selector = main.Selectors.GetSelector(location.Selector); if (!string.IsNullOrEmpty(location.Path)) { GridHelper.GenerateGrid(selector, location.Path); } main.Selectors.DeselectAll(); selector.Model.IsSelected = true; main.ViewPane.Content = selector.SidbebarView; main.Breadcrumbs.Model.UpdatePath(); main.Tools.Model.RefreshNavButtons(); } public static void DisplayFavoriteFolder(FavoriteFolderItem fav) { DeselectAllSelectors(); var visualSelector = GetSelector(fav.Selector); visualSelector.Model.IsSelected = true; visualSelector.Model.IsRefreshable = false; var favoritesSelector = GetSelector(SELECTOR_TYPE.FAVORITE); (favoritesSelector.Model as FavoritesViewModel).Item = fav; favoritesSelector.Model.IsSelected = true; GridHelper.InitializeGrid(favoritesSelector, path: fav.FolderPath, header: fav.FolderName); var main = App.Current.MainWindow as MainWindow; main.Tools.Model.RefreshAddButtons(); main.ViewPane.Content = favoritesSelector.SidbebarView; } public static void ClearSelectedFavorites() { var favoritesSelector = GetSelector(SELECTOR_TYPE.FAVORITE); GridHelper.ClearGrid(favoritesSelector.SidbebarView.ListingGrid); favoritesSelector.Model.IsRefreshable = false; favoritesSelector.Model.IsSelected = false; } public static void SetSyncInterval() { var syncWorker = Utility.GetSyncBackgroundWorker(); var main = App.Current.MainWindow as MainWindow; var form = new SyncIntervalDialog(syncWorker.Interval); var dialog = main.DialogManager.CreateCustomContentDialog(form, Technewlogic.WpfDialogManagement.DialogMode.OkCancel); dialog.OkText = "Set"; dialog.Ok = () => { OnSyncIntervalSet(form); }; dialog.VerticalDialogAlignment = VerticalAlignment.Top; dialog.Show(); } private static void OnSyncIntervalSet(SyncIntervalDialog form) { var syncWorker = Utility.GetSyncBackgroundWorker(); int newInterval = -1; bool success = int.TryParse(form.SyncIntervalText.Text, out newInterval); if (success && newInterval > 0 && newInterval != syncWorker.Interval) { syncWorker.Interval = newInterval; } } public static void RefreshFavorites() { var main = App.Current.MainWindow as MainWindow; main.Selectors.Model.Refresh("FavoriteFolders"); } public static void RefreshFavoriteTags() { var main = App.Current.MainWindow as MainWindow; main.Selectors.Model.Refresh("FavoriteTags"); } public static void RefreshSelector(SELECTOR_TYPE type) { var selector = GetSelector(type); //if (selector != null && selector.Model != null) selector.Model.Refresh(); if (selector != null && selector.SidbebarView != null && selector.SidbebarView.ListingGrid != null) { var grid = selector.SidbebarView.ListingGrid; foreach (var child in grid.Children) { if (child is Refreshable) { (child as Refreshable).Refresh(sync: true); } } if (selector.Model.IsSelected) { for (var i = grid.Children.Count; i > 0; i--) { var child = grid.Children[i-1]; if (child is ColumnDisplay) { (child as ColumnDisplay).Focus(); break; } else if (child is ListingItemInfo) { (child as ListingItemInfo).Focus(); break; } } UIHelper.ScrollToRight(); } } } public static void RefreshSelectorAsync(SELECTOR_TYPE type) { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { RefreshSelector(type); })); } public static void RefreshCurrentColumn() { var selector = GetCurrentSelector(); if (selector != null) { if (selector.Model != null) { if (selector.Model.CurrentColumn != null) { selector.Model.CurrentColumn.Refresh(); } } } } public static void RefreshAllSelectors() { RefreshSelector(SELECTOR_TYPE.WORKSPACE); RefreshSelector(SELECTOR_TYPE.SERVER); RefreshSelector(SELECTOR_TYPE.PENDING); RefreshSelector(SELECTOR_TYPE.RECENT); RefreshSelector(SELECTOR_TYPE.TRASH); RefreshSelector(SELECTOR_TYPE.FAVORITE); } public static void RefreshAllSelectorsAsync() { RefreshSelectorAsync(SELECTOR_TYPE.WORKSPACE); RefreshSelectorAsync(SELECTOR_TYPE.SERVER); RefreshSelectorAsync(SELECTOR_TYPE.PENDING); RefreshSelectorAsync(SELECTOR_TYPE.RECENT); RefreshSelectorAsync(SELECTOR_TYPE.TRASH); RefreshSelectorAsync(SELECTOR_TYPE.FAVORITE); } public static void DeselectSelector(SELECTOR_TYPE type) { var selector = GetSelector(type); if (selector != null) selector.Model.IsSelected = false; } public static void DeselectAllSelectors() { DeselectSelector(SELECTOR_TYPE.WORKSPACE); DeselectSelector(SELECTOR_TYPE.SERVER); DeselectSelector(SELECTOR_TYPE.PENDING); DeselectSelector(SELECTOR_TYPE.RECENT); DeselectSelector(SELECTOR_TYPE.TRASH); DeselectSelector(SELECTOR_TYPE.FAVORITE); } public static void RefreshView() { var main = App.Current.MainWindow as MainWindow; var selector = GetCurrentSelector(); if (selector != null) { main.ViewPane.Content = selector.SidbebarView; } } public static void RefreshCurrent() { var selector = GetCurrentSelector(); if (selector != null) { selector.Model.Refresh(); } } /// <summary> /// Locates all children of a particular type /// </summary> /// <typeparam name="T">The datatype of the child in the search</typeparam> /// <param name="depObj">The parent dependency object</param> /// <returns></returns> public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject { if (depObj != null) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { DependencyObject child = VisualTreeHelper.GetChild(depObj, i); if (child != null && child is T) { yield return (T)child; } foreach (T childOfChild in FindVisualChildren<T>(child)) { yield return childOfChild; } } } } public static void ScrollToRight() { MainWindow main = App.Current.MainWindow as MainWindow; main.ViewPane.ScrollToRightEnd(); } public static void CriticalError(Exception e) { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { Utility.StopSyncWorker(); Utility.StopWatchdog(); System.IO.File.WriteAllText(@"error.txt", e.StackTrace); var message = string.Format("Error details:\n{0}\n", e.Message); var main = App.Current.MainWindow as MainWindow; // try to show the error in a modal dialog, otherwise just show a message box if (main != null && main.DialogManager != null) { var dialog = main.DialogManager.CreateMessageDialog(message, Technewlogic.WpfDialogManagement.DialogMode.OkCancel); dialog.OkText = "Restart"; dialog.CancelText = "Quit"; dialog.VerticalDialogAlignment = VerticalAlignment.Top; dialog.Ok = () => { System.Windows.Forms.Application.Restart(); System.Windows.Application.Current.Shutdown(); }; dialog.Cancel = () => { System.Windows.Application.Current.Shutdown(); }; dialog.Show(); } else { MessageBox.Show(e.Message); System.Windows.Application.Current.Shutdown(); } })); } public static void ShowMessage(string message, bool shutdown = false) { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { var main = App.Current.MainWindow as MainWindow; var dialog = main.DialogManager.CreateMessageDialog(message, Technewlogic.WpfDialogManagement.DialogMode.Ok); dialog.VerticalDialogAlignment = VerticalAlignment.Top; if (shutdown) { dialog.Ok = () => { App.Current.Shutdown(); }; } dialog.Show(); })); } public static void ShowTimedMessage(string message, double timeout = 3d) { App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { var main = App.Current.MainWindow as MainWindow; var form = new TimedMessageLabel(message, timeout); var dialog = main.DialogManager.CreateCustomContentDialog(form, Technewlogic.WpfDialogManagement.DialogMode.None); form.ParentDialog = dialog; dialog.Show(); })); } public static void OpenWithApplication(string filename, string extension) { var main = App.Current.MainWindow as MainWindow; var form = new ChooseApplicationForm(); var dialog = main.DialogManager.CreateCustomContentDialog(form, Technewlogic.WpfDialogManagement.DialogMode.OkCancel); form.ParentDialog = dialog; dialog.VerticalDialogAlignment = VerticalAlignment.Top; dialog.OkText = "Choose"; dialog.Ok = () => { OnOpenWith(form, filename, extension); }; dialog.CanOk = false; dialog.Cancel = OnOpenWithCancel; dialog.Show(); } private static void OnOpenWith(ChooseApplicationForm form, string filename, string extension) { var appPath = form.Executable.Text; if(!string.IsNullOrEmpty(appPath)) { // add association to settings var extHelper = Utility.GetExtensionHelper(); extHelper.SetMapping(extension, form.Executable.Text); extHelper.SaveMappings(); CommandHelper.OpenLocalFile(filename); } } private static void OnOpenWithCancel() { // cancelled choice } public static string GetFolderIcon(bool selected=false, bool mapped=false, bool inClientView=false, bool ignored=false, bool mixed=false) { StringBuilder builder = new StringBuilder("Folder"); if (ignored) { builder.Append("Ignored"); } else { if (mapped) { builder.Append("Mapped"); } else if(inClientView) { builder.Append("Mixed"); } else { builder.Append("Untracked"); } } //if (selected) builder.Append("Selected"); return GetIconURI(builder.ToString()); } public static string GetFileIcon(bool selected = false, FileMetaData metadata = null) { StringBuilder builder = new StringBuilder("File"); if(metadata != null) { var other = string.Empty; var action = metadata.Action; if (metadata.OtherActions != null && metadata.OtherActions.Count > 0) { other = "Other"; action = metadata.OtherActions[0]; } switch (action) { case FileAction.None: if (!metadata.IsMapped) { builder.Append("Untracked"); } break; case FileAction.Add: builder.Append("Add"); break; case FileAction.Edit: builder.Append("Edit"); break; case FileAction.Delete: builder.Append("Delete"); break; case FileAction.Move: builder.Append("Move"); break; case FileAction.MoveAdd: builder.Append("MoveAdd"); break; case FileAction.MoveDelete: builder.Append("MoveDelete"); break; } builder.Append(other); } return GetIconURI(builder.ToString()); } private static XElement _defaultIcons; public static string GetDefaultIcon(string extension) { string iconURI = Constants.DEFAULT_THUMBNAIL; if (_defaultIcons == null) { Assembly a = typeof(UIHelper).Assembly; System.IO.Stream s = a.GetManifestResourceStream("Perforce.Resources.DefaultIcons.DefaultIconMapping.xml"); _defaultIcons = XElement.Load(s); s.Close(); } if (!string.IsNullOrEmpty(extension)) { var selected = from cli in _defaultIcons.Elements("DefaultIcon") where cli.Attribute("Extension").Value == extension select cli.Attribute("Source").Value; var array = selected.ToArray(); if (array.Length == 1) { iconURI = array[0].ToString(); } } return iconURI; } private static XElement _mapping; public static string GetIconURI(string name) { string iconURI = null; if (_mapping == null) { Assembly a = typeof(UIHelper).Assembly; System.IO.Stream s = a.GetManifestResourceStream("Perforce.Resources.ResourceMap.xml"); _mapping = XElement.Load(s); s.Close(); } var selected = from cli in _mapping.Elements("Mapping") where cli.Attribute("Name").Value == name select cli.Attribute("Value").Value; var array = selected.ToArray(); if (array.Length == 1) { iconURI = array[0].ToString(); } return iconURI; } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 15071 | alan_petersen |
Populate -o //guest/perforce_software/piper/... //guest/alan_petersen/piper/.... |
||
//guest/perforce_software/piper/windows/R1.1/Perforce/Helper/UIHelper.cs | |||||
#2 | 13572 | alan_petersen | updating R1.1 | ||
#1 | 11256 | alan_petersen |
Populate //guest/perforce_software/piper/windows/R1.1/... from //guest/perforce_software/piper/windows/main/.... |
||
//guest/perforce_software/piper/windows/main/Perforce/Helper/UIHelper.cs | |||||
#1 | 11255 | alan_petersen | Rename/move file(s) | ||
//guest/perforce_software/piper/windows/Perforce/Helper/UIHelper.cs | |||||
#4 | 11032 | alan_petersen |
MEGA UPDATE: - fixed bug in folder rollback was failing due to deleted files - files needed to be re-added with the downgrade (-d) flag - put some checking in to stop/start the background workers when - folder versioning - can select 'show versions' on mapped folders - currently limites the number of changelists displayed to the last 50 - rollback button is disabled/hidden unless there are no files in the path (//path/to/folder/...) that are opened (uses p4 opened -a path) - various fixes for some strange null pointer exceptions - fixed folder items (edit all/revert all) - needs some testing to ensure that it is functioning correctly (e.g. when there are locked files in the path being checked out) - general code clean-up -- primarily using the Utility method to obtain the PerforceHelper, rather than having to cast all the time. - found some stability issues (at least when communicating with a local p4d running in the same virtual machine) so some additional error checking/handling was added - reconcile files action now provides feedback via a wait dialog: while reconcile is running, the dialog is displayed along with a wait indicator, when complete a message is displayed that the reconcile is finished - submit changes - submit now perfomed within a wait dialog: dialog displays message along with a wait indicator, results of the submit are displayed when complete - currently, the 'ok' button is disabled until the submit completes. - looking into providing a progress bar (using the feedback mechanism in the API) to provide some more interesting information (and to give users the sense that something is actually happening when a large changelist is begin submitted) - added copy function to PerforceHelper along with test case - implemented copy (ctrl-c) and paste (ctrl-v) in DESI - limitations: - currently only implemented for FILES (not folders) - to paste, one must select the destination FOLDER - next steps: - get working for folders - implement cut (ctrl-x) - permit paste into a column (more intuitive) - rebuilt using 'Any CPU' target -- not sure if this will fix the error seen by the Win7-64 user -- we may need to switch to separate 32- and 64-bit builds - fixed defect #78: delete and move/delete files are no longer displayed in the server view - fixed defect #76: tags now displayed in version history - added MemoryCache for repository reference -- the repository reference was getting stale and creating strange errors, so a reference is now stored in the MemoryCache with a SlidingExpiration of 2 minutes. - fixes #80: copy checks out file -- remnant of code from the rename logic was checking out the code - fixes #14: mail now opens outlook using office API - fixes #72: submit dialog now waits for results from submit, and if there is an error the error message is displayed, otherwise the changelist number is displayed - fixes #75: folder versioning changes -- restore version button now always displayed, but not connected to anything if the folder has opened items; -- opened items now ignored items marked for Add -- this could result in the folder being re-created if/when the user submits the added files - fixed bug in submit dialog in which changelist number was not displayed correctly - fixed bug in submission of partial changelists -- files moved to new changelist but original not updated, resulting in error - #79 - add file now works correctly, relying on the client path rather than the //dummy_depot_path #41: various changes and refactoring to support 'Recent' view - background sync causes immediate refresh of the recent list - ability to mark items as 'read' -- removed from the recent list and the view is refreshed #43 - udpates to logic to deal with transient threading issues (causing the NoSuchObject exceptions) and sync/refresh of workspace files to added files. #48: submit single file now working -- when submit file is selected, the file is moved to a shiny new changelist, and that changelist is presented in the UI - refactoring of submit pane logic to allow passing in a changelist ID incremental build with partially working toolbar buttons -- still working on the forward/back buttons (they aren't quite working right yet) - toolbar items are now functional: left/right nav, add files, create folder addresses #12: favorite folders -- added favorite folder functionality to the application - favorites are now stored in a workspace-specific property (client_name.favoriteFolders) in the favorites.json file in the user's configuration directory (this file is created if it does not exist) - favorites can be edited by double-clicking on the label, right-clicking the label and selecting 'edit', or clicking on the edit icon - favorites can be deleted by right-clicking on the label and selecting 'delete' update involved various refactoring so enable the hidden 'Favorites' selector and select the appropriate sidebar selector (Workspace or Server) when the favorite is selected - favorite tags implemented, satisfying ticket #18 - favorite tags are stored in a workspace-specific property (client_name.favoriteTags) in the favorites.json file in the user's configuration directory (this file is created if it does not exist) - favorite tags can be added by clicking on the '+' that appears when hovering over the Favorite Tags header in the sidebar - a popup appears with a textfield - ESC cancels the add and closes the popup - Enter or clicking on the add button adds the tag to the property (immediately saved) and updates the Favorite Tags sidebar list and closes the popup - changing focus (ie clicking somewhere else in the application) cancels the add and closes the popup - favorite tags can be selected/deselected with a single click - selected tags have a checkmark in the rightmost column - favorite tags can be deleted by right-clicking on the label and selecting 'delete' - list items in ColumnDisplay.xaml modified - FileItem model class modified with boolean method to determine if the file has a selected tag (matching is case insensitive) - FavoritesHelper caches selected tags (to avoid unnecessary IO and parsing) - FavoriteTagItem modified to - fixing copy/paste functionality: - copy/paste now uses filesystem copy and paste (rather than p4 copy) - CopyDirectory method added to Utility class to assist in copying of entire directory structures - copy enabled from either the server tab or the workspace tab... paste only allowed on the workspace tab - wait dialog implemented for the paste process -- should happen quickly if copy is on local filesystem, but if copy is from the server then copy may take a while if the file(s) being copied are large (they have to be downloaded first) - confirmation dialog when folders selected for the copy (giving user the option of skipping large directories if they were selected accidentally) - implementation of p4 sizes code to determine size/number of files in a given path - implementation of p4 print for a depot path -- by default saves to a temp directory, but in the case of copy/paste the print is performed directly to the target directory Addresses DEFECT #91 -- Desi crashing on viewing old versions - previous refactoring had introduced the VersionItem model class, but there were still some remnants of references to FileHistory around, causing issues (ie. crashing) - changelist cleanup had used the -f (force) flag, which hadn't appeared as an issue because the test cases currently run as an admin user, but that masked the fact that only admins can do 'p4 change -d -f'. Doh #94 DEFECT: Selecting tags causes error in Server view - issue was due to tag attributes not retrieved in HEX format, so hex decoding then failed (because they weren't hex!) - this is now fixed in the PerforceHelper - general code cleanup and optimization - helper methods in the utility class to retrieve common objects from the App context - start sync now first checks the preferences to ensure that sync is actually turned on - background processes now wait for initial workspace selection before starting up #36: DEFECT: Refresh color coding - modifications for #88 fix this too #52: DEFECT: Cannot move files from "My Pending Changes" to "Trash" - modified CommandHelper logic to revert files that are in any state other than 'None' when trying to delete #88: DEFECT: icon should be different for items not on server - logic to determine file status updated - icon sizes bumped up slightly (from 18px to 24px) to make differences more visible.. #96: DEFECT: Adding files double window - this was due to a bug in the AddFilesButton_Click method of ToolBar.xaml.cs... basically ShowDialog() was called twice! DOH!! #99: DEFECT: Refresh color coding - modified logic around refreshing after a paste, seems to have fixed this issue #106: DEFECT: Paste a file in a folder where the name exists and the application crashes - added CopyFileToDirectory method to utility class to check to see if the destination file already exists... if it does, then (Copy) is appended to the filename - if there is already a file named xxx (Copy), then a counter is appended: xxx (Copy 2) - this is repeated until it comes up with a filename that doesn't conflict #104: DEFECT: right click option to open file as read only does not work - Code in ContextMenuHelper's ViewWorkspaceFile method was not wired to the code that opens a file. Now it is! - no specific bugs fixed, just trying to get refresh to be a little more responsive and context menus working a little better - reworked DelegateCommand to permit parameter - reworked various menuitem commands to include item(s) selected as parameters - reworked command methods to get parameter, cast it appropriately to the item(s), perform the operation(s), and then refresh the items as well as associated UI components - reworked some methods in MainWindow to take item(s) as arguments (rather than just trying to get the currently selected item -- sometimes when you right-click on something it isn't fully selected) #107: Defect: Unsync from computer not working - modified code that syncs/unsyncs to refresh items after change -- should make for a more responsive interface - modified PerforceHelper code that sets the client to ensure that rmdir is set as an option - fixed Exit menu item to actually shut down the application (YAY) #49: Defect: Desi throws exception when internet connection is lost - modified the GetPerforceHelper utility method to check for a live server. If the server is unreachable, the user is presented with the option to relaunch the application or shutdown #97: DEFECT: Object error when using navigation buttons - code was checking Perforce server to determine if a path was a directory or file, but when the file was newly created it doesn't exist on the server so it returned false, which later caused a null pointer exception. The PerforceHelper IsDirectory code was modified to check, as a last restort, the client path to see if the file is a directory using System.IO.Directory.exists() - modification to MainWindow.cs to re-enable the filesystem watcher #73: Defect: Checkout and delete file .. appropriate change in status not done - code in filesystem watcher was not handing deletes of opened files correctly... opened files need to be reverted first, then they can be deleted using a p4 delete. Since the file no longer exists on the filesystem, these p4 commands are performed as server-only (-k option). When the file is an Add or MoveAdd, then the server-side revert is just performed. Otherwise, if the file is in any other state (other than none), a server-side revert is performed, then the server-side delete. #92: Defect: Option for unshelving missing - added a button to the file info screen for unshelving. Since unshelving may overwrite changes the user has made, a dialog is presented and the user must confirm the unshelve operation. - application now opens with window displaying the file/folder in question when given a command-line argument in the form p4://... |
||
#3 | 10804 | alan_petersen |
UPDATE: - some code cleanup - submit now catches errors more intelligently and displays them - connection failures in background processes now display dialog indicating connection failure - this stops the background process (this will avoid the endless dialog boxes seen in the Mac version!) - currently, dismissing the dialog exits the application -- this may change once I get the 'change connection' feature working |
||
#2 | 10794 | alan_petersen |
Various updates.... - made main exception caught (happens if application cannot initialize, for example) more explicit in the message box (was "test" :) - CTRL-F now moves focus to search box - fixed double-click warning message when item is already locked - better thumbnail handling - general cleanup |
||
#1 | 10761 | alan_petersen |
initial drop of Piper for Windows.... this version still has _many_ bugs (er... i mean "unintended features") but I will be updating it over the next week as more stability is added. |