public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (parameter != null && (string)parameter == "pin") { return((bool?)value == true ? Resources.Str_Unpin : Resources.Str_Pin); } var pi = value as PowerItem; if (pi == null) { return(Resources.Err_CantGetTooltip); } if (!pi.IsNotPureControlPanelFlowItem) { return(Resources.Str_CplElement); } if (pi.IsSpecialObject) { var cmd = Util.GetOpenCommandForClass(pi.Argument); if (cmd == null) { return(pi.FriendlyName + Resources.Str_Library); } return(cmd.Item1); } try { return(PowerItemTree.GetResolvedArgument(pi)); } catch (IOException) { return(Resources.Err_CantGetTooltip); } }
//------------------------------------------ /// <summary> /// Starts or cancels asynchronous search and sets the data source for MFU list /// depending on text in search field /// </summary> private void SearchBoxTextChanged(object sender, TextChangedEventArgs e) { PowerItemTree.SearchTreeCancel(); //first always cancel the current search var q = SearchBox.Text.Trim().ToLowerInvariant(); //ignore spaces an case if (!String.IsNullOrWhiteSpace(q) && q.Length > 1) //we can probably start search { //Clear selection always before (re)starting search or preparing the WebSearch. //For the Search this will help P8 to handle Enter properly, because sometimes, //if you're REALLY fast, UI is repainted BEFORE Items collection is being actually //updated, along with SelectedItem, so you see and try launching item different //from one being actually launched. dataGrid.SelectedIndex = -1; dataGrid.SelectedItem = null; if (q[1] != ' ') //not web search { dataGrid.ItemsSource = _searchView; //switch MFU list to search results and kisk search invoker var t = PowerItemTree.SearchTreeCancel(); Util.ForkPool(() => PowerItemTree.SearchTree(q, _searchData, ExpandGroup, t), "Search root for " + q); } //else{} //web search - no actions from our side here, wait for enter } else //no search { dataGrid.ItemsSource = MfuItems; //show MFU list SearchMarker.Visibility = Visibility.Hidden; //Hide search marker if any } }
/// <summary> /// Removes item from user list. /// </summary> /// <param name="item">Item to remove. This method is called from UI, which converts /// the string paths (potentially not mapped to PowerItem originally) to new PowerItems /// when needed, so that's why there is no overload to remove string from the list.</param> public static void RemoveCustom(PowerItem item) { lock (UserList) { UserList.Remove(PowerItemTree.GetResolvedArgument(item)); _writeUserList = true; UpdateStartMfuSync(); } }
/// <summary> /// Safely invokes passed item and hides the butonstack - as it is done for the grid /// Used in other code as well, like in the search bar handlers, because they behave /// like invoking something from the grid. /// </summary> /// <param name="item">Grid's selected item (usually)</param> private void InvokeFromDataGrid(PowerItem item) { try { PowerItemTree.SearchTreeCancel(); //if search thread is really slow compared to this UI one, //the following call may execute before search was finished, and MFUList may receive //"process launched" event and update itself, which will cause InvalidOperationException item.Invoke(); } catch (Exception ex) { Util.DispatchCaughtException(ex); } Hide(); }
/// <summary> /// When not null value is passed as argument, tries to perform the command /// related to the argument passed via the system Verb engine. Works for /// non-folders.<br/> /// If RunAsAdmin verb is passed and this PowerItem instance is Folder, flag /// is converted to boolean true instructing ResolveItem() that, if available, /// Common start menu folder should be used instead of user one. This is not /// used for non-folder items and won't work for folders not under Start menu, /// even there's corresponding CSIDLs.<br/> /// If null value is passed, then it simply runs the PowerItem. Consult Invoke() /// xml-doc on how this can be performed. /// </summary> /// <param name="verb">One from <code>API.SEVerbs</code> constants, or null/empty /// string, which means "no command"</param> public void InvokeVerb(string verb) { //Shell namespaces and Power8 classes if (SpecialFolderId != API.Csidl.INVALID) { if (string.IsNullOrEmpty(Argument)) { Util.DisplaySpecialFolder(SpecialFolderId); return; } if (SpecialFolderId == API.Csidl.POWER8CLASS) { Util.InstanciateClass(Argument); return; } if (SpecialFolderId == API.Csidl.POWER8IMMERSIVE) { Util.TryInvokeAppUserModelId(Argument); return; } } //All the other stuff: FS-resolvable var psi = PowerItemTree.ResolveItem(this, IsFolder && verb == API.SEVerbs.RunAsAdmin); if (!string.IsNullOrEmpty(verb) && IsFile) //pass verb { psi.Verb = verb; } if (psi.Arguments.StartsWith("\\\\")) //network items may be only directly launched { psi.UseShellExecute = false; } try { Util.CreateProcess(startInfo: psi); } catch (Win32Exception w32E) //Any exception will be handled really out of here, but we { //shall report proper error if (w32E.NativeErrorCode == 0x483) //1155, e.g. when doing "runas" on "*.hlp" { psi.Verb = null; Util.CreateProcess(startInfo: psi); throw new InvalidProgramException(Resources.Err_StartAsAdminFailed + w32E.Message); } throw; } }
public static int MoveCustomListItem(PowerItem which, PowerItem where) { lock (LastList) { //1. Change order in UserList var argFrom = PowerItemTree.GetResolvedArgument(which); var argTo = PowerItemTree.GetResolvedArgument(@where); int idxFrom = UserList.IndexOf(argFrom); int idxTo = UserList.IndexOf(argTo); UserList.RemoveAt(idxFrom); UserList.Insert(idxTo + idxFrom > idxTo ? 1 : 0, argFrom); //2. Update LastList LastList.Clear(); GetMfuFromCustomData().ForEach(m => LastList.Add(m.Clone())); //3. Change order in MfuList - with respect to pinning StartMfu.Move(StartMfu.IndexOf(which), StartMfu.IndexOf(where)); return(StartMfu.IndexOf(which)); } }
public static void Add2Custom(PowerItem item, string fullPath = null) { var arg = item == null ? fullPath : PowerItemTree.GetResolvedArgument(item); var idx = UserList.IndexOf(arg); if (idx == UserList.Count - 1 && UserList.Count > 0) //Already in list and top item { return; } if (idx != -1) //Already in list, move to top { UserList.RemoveAt(idx); } UserList.Add(arg); //Including case where item not in list if (SettingsManager.Instance.MfuIsCustom && fullPath == null) { UpdateStartMfuSync(); //Refresh } }
/// <summary> /// Synchronous getter of an icon for PowerItem /// </summary> /// <param name="item">PowerItem we need icon extracted for</param> /// <param name="iconNeeded">type of icon needed - small or large</param> /// <returns>ImageContainer with ImageSources extracted. Can be null.</returns> public static ImageContainer GetImageContainerSync(PowerItem item, API.Shgfi iconNeeded) { Log.Raw("begin>>>>>>>>>>>>>>>", item.FriendlyName); //Checking if there's cached ImageContainer string resolvedArg, descr; try { resolvedArg = PowerItemTree.GetResolvedArgument(item); descr = GetObjectDescriptor(item, resolvedArg); } catch (IOException) { return(null); } lock (Cache) { var container = (ImageContainer)(Cache.ContainsKey(descr) ? Cache[descr] : null); Log.Fmt("arg<={0}, descr<={1}, container<={2}", resolvedArg, descr, (container != null ? "not " : "") + "null"); if (container == null) //No cached instance { container = new ImageContainer(resolvedArg, descr, item.SpecialFolderId); Cache.Add(descr, container); if (iconNeeded == API.Shgfi.SMALLICON) { container.ExtractSmall(); } else { container.ExtractLarge(); } } #if DEBUG Log.Raw("end<<<<<<<<<<<<<<", item.FriendlyName); #endif return(container); } }
/// <summary> /// Returns string, of a Path kind, that can be passed to a system, and will /// represent the passed PowerItem. Depending on Caller Name, may invoke /// automatic Link resolution for Link PowerItems. "Denamespaces" the /// passed ControlPanel item returning open command for it. /// </summary> /// <param name="item">The PowerItem which has to be located/properties for /// which have to be shown.</param> /// <param name="callerName">String, the name of clicked menu item, hendler /// of which is calling this method. Recognizes "AppOpenTargetContainer" and /// "AppShowTargetProperties".</param> /// <returns>Path to binary FS object that represents the passed PowerItem or /// the target of its link.</returns> private static string Args4PropsAndCont(PowerItem item, string callerName) { string arg = null; if (item.IsControlPanelChildItem) { var executor = Util.GetOpenCommandForClass(item.Argument); if (executor != null && File.Exists(executor.Item1)) { arg = executor.Item1; } } if (arg == null) { arg = PowerItemTree.GetResolvedArgument(item); } if (item.IsLink && (callerName == "AppOpenTargetContainer" || callerName == "AppShowTargetProperties")) { arg = item.ResolvedLink; } return(arg); }
/// <summary> /// Handles search event raised by Windows Serach threads. /// Works with UI thus invokes Main Dispatcher for real work. /// </summary> private void HandleSearch(object sender, PowerItemTree.WinSearchEventArgs args) { Util.Send(() => { if (args.SearchCompleted) { SearchMarker.Visibility = Visibility.Hidden; ExpandGroup(args.Root, args.Token); } else { SearchMarker.Visibility = Visibility.Visible; } }); }
//Same as above but inSync private static void GetRecentListForSync(PowerItem item) { string fsObject; try { fsObject = PowerItemTree.GetResolvedArgument(item); } catch (IOException) //No money no honey (no object to get JL for) { return; } IEnumerable <string> jl = null; var p8R = GetP8Recent(item.IsLink ? item.ResolvedLink : fsObject); //P8 internal JL if (Util.OsIs.SevenOrMore) //System JL { var recent = GetJumpList(fsObject, API.ADLT.RECENT); var frequent = GetJumpList(fsObject, API.ADLT.FREQUENT); if (recent != null && frequent != null) { jl = recent.Union(frequent); } else { jl = recent ?? frequent; } } if (jl != null && p8R != null) //merge everything { jl = jl.Union(p8R); } else { jl = jl ?? p8R; } if (jl == null) { return; //No jump list discovered -> nothing to do } jl = jl.Distinct() //No duplicates .Where(x => x.StartsWith("::") || File.Exists(x)) //No obsoletes .Take(25); //Not too many foreach (var arg in jl) { var local = arg; Util.Post(() => item.JumpList.Add(local.StartsWith("::") ? new PowerItem { Argument = local.Substring(2), Parent = item, SpecialFolderId = API.Csidl.POWER8JLITEM } : new PowerItem { Argument = local, Parent = item } )); } }
/// <summary> /// This is the part of FriendlyName getter. Executes heavy part of FriendlyName evaluation either /// synchronously or in async way, depending on how it's called. /// </summary> /// <returns>The PI's partially evaluated FriendlyName, which can be also empty string or even null. /// Assign the value returned to the FriendlyName. When use this method asynchronously, always Post() /// or Send() the assignment.</returns> private string TryExtractFriendlyNameAsync() { string fName = null; if (ResourceIdString != null) //Return based on resId { fName = Util.ResolveStringResource(ResourceIdString); if (!string.IsNullOrEmpty(fName)) { return(fName); } ResourceIdString = null; //Operation failed somewhere, resourceId is invalid } if (SpecialFolderId != API.Csidl.INVALID) //Resolve for a special folder, if any { if (SpecialFolderId == API.Csidl.POWER8JLITEM) //For jump list item <IShellLink> { if (Argument.StartsWith("/n,::")) //in part., for explorer shell NSs { fName = Util.ResolveLongPathOrDisplayName(Argument.Substring(3)); } if (string.IsNullOrEmpty(fName)) //Otherwise, set display name to link target { fName = Argument; } if (!string.IsNullOrEmpty(fName) && fName.Length > 60) { //finally, if it is too long, cut it from the middle fName = fName.Substring(0, 28) + "…" + fName.Substring(fName.Length - 28, 28); } } else //for all the other special folders { fName = Util.ResolveSpecialFolderName(SpecialFolderId); } if (!string.IsNullOrEmpty(fName)) { return(fName); } } //If no SpecialFolderId or resolving failed if (Parent == null) //main menu { //this basically should never happen since the moment when desktop.ini parsing is implemented return(Resources.Str_AllPrograms); } //For Recent list... if (IsMfuChild) //so it must have ARGUMENT... { //...for either link or exe if (IsLink || Argument.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) { var container = PowerItemTree.SearchStartMenuItemSyncFast(Argument); //yeah, "fast"... :( if (container != null) { fName = container.FriendlyName; } if (string.IsNullOrEmpty(fName) /*(still)*/ && !IsLink) {//get file version info table and extract data from there. Costly but provides valuable results. try { var ver = FileVersionInfo.GetVersionInfo(PowerItemTree.GetResolvedArgument(this)); if (!string.IsNullOrWhiteSpace(ver.FileDescription)) //try description, if not fallback to... { fName = ver.FileDescription; } else if (!string.IsNullOrWhiteSpace(ver.ProductName)) //...Product. This is specifically for... { fName = ver.ProductName; //...NFS.Run and the kind of. } } catch (IOException) //It is possible that item existing in the last shown list is not available... {} //anymore. This is a rare situation - so try-catch seems more logical than Exists() call. } } else if (IsControlPanelItemInFact) { PowerItemTree.CplDone.WaitOne(); //we don't want InvalidOperationExceptions accessing Items... var same = PowerItemTree.ControlPanelRoot.Items.FirstOrDefault( i => string.Equals(i.Argument, Argument, StringComparison.OrdinalIgnoreCase)); if (same != null) //if there's such item { fName = same.FriendlyName; //simply return it's FriendlyName } } } return(fName); }
//------------------------------------------ /// <summary> /// Update Control Panel menu button when ControlPanelByCategoryChanged event occured. /// </summary> void OnControlPanelByCategoryChanged(object sender, EventArgs e) { SpecialItems.Remove("ControlPanel"); PowerItemTree.RefreshControlPanelRoot(); ControlPanel.Item = GetSpecialItems("ControlPanel"); }
/// <summary> /// Handles specific key presses when focus is in Data grid: /// - Enter starts the selected PowerItem, if any; /// - Tab/Shift-Tab moves focus to correct controls so that grid doesn't /// hold focus inside. There are arrow buttons for that. /// </summary> private void DataGridPreviewKeyDown(object sender, KeyEventArgs e) { var pi = dataGrid.SelectedItem as PowerItem; switch (e.Key) { case Key.Enter: if (pi != null && !pi.AreItemsDisplayed) { e.Handled = true; InvokeFromDataGrid(pi); } break; case Key.Tab: e.Handled = true; //todo: check why CTRL is tested differently in other places if (pi != null && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))) { //CTRL+Tab >> put path to item to search bar var t = "\"" + PowerItemTree.GetResolvedTarget(pi) + "\" "; SearchBox.Text = t; SearchBox.Focus(); SearchBox.CaretIndex = t.Length; } else if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) { AllItemsMenuRoot.Focus(); //SHIFT+Tab >> focus backwards } else { SearchBox.Focus(); //Tab >> focus forwards } break; case Key.Up: case Key.Down: if (pi != null && pi.AreItemsDisplayed) { return; //Do not handle keys when JL is in focus } //Not in search view when we press Up/Down if ((System.Windows.Forms.Control.ModifierKeys & Keys.Control) > 0 && dataGrid.ItemsSource.Equals(MfuItems)) { //with CTRL if (dataGrid.SelectedIndex > -1 && SettingsManager.Instance.MfuIsCustom) { //...and we have something selected, and we're in custom MFU... var si = dataGrid.SelectedItem as PowerItem; //...and more than 1 in (un) pinned group if (si != null && MfuItems.Count(m => m.IsPinned == si.IsPinned) > 1) { //=> move item itself e.Handled = true; dataGrid.Focus(); //.Net 4.5 hack. Datagrid in .Net4.5 doesn't switch focus to new row int increment = (e.Key == Key.Up ? -1 : 1); int i = dataGrid.SelectedIndex; PowerItem target = null; do //search for nearest item with same pinning state { i += increment; if (i == -1) { i = MfuItems.Count - 1; } else if (i == MfuItems.Count) { i = 0; } if (MfuItems[i].IsPinned == si.IsPinned) { target = MfuItems[i]; } } while (target == null); MfuList.MoveCustomListItem(si, target); } } } else if ((System.Windows.Forms.Control.ModifierKeys & Keys.Control) == 0) //Not with CTRL, { // regardless of source => just move selection e.Handled = true; var idx = dataGrid.SelectedIndex + (e.Key == Key.Up ? -1 : 1); if (idx < 0) { idx = dataGrid.Items.Count - 1; } if (idx >= dataGrid.Items.Count) { idx = 0; } dataGrid.SelectedIndex = idx; } if (dataGrid.SelectedItem != null) //when in search view and no hits/in clear custom list { dataGrid.ScrollIntoView(dataGrid.SelectedItem); } break; case Key.P: if (pi != null && pi.IsMfuChild) { e.Handled = true; pi.IsPinned ^= true; PinInternal(pi); dataGrid.Focus(); } break; case Key.X: if (pi != null && dataGrid.ItemsSource.Equals(_searchView)) { e.Handled = true; ExpandCollapseGroup(pi); dataGrid.Focus(); } break; case Key.Right: case Key.Left: if (pi != null) { e.Handled = true; pi.AreItemsDisplayed ^= true; } break; } }